> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polygon.technology/llms.txt
> Use this file to discover all available pages before exploring further.

# Ethereum to PoS

> Deploy sender and receiver contracts to transfer arbitrary data from Ethereum to Polygon PoS using the state sync mechanism.

This guide walks through deploying a sender contract on Sepolia (Ethereum testnet) and a receiver contract on Amoy (Polygon testnet), then sending and verifying arbitrary data across the bridge. For background on how state sync works, see the [state sync architecture docs](/pos/architecture/bor/state-sync/).

<Warning title="Example code only">
  The contracts and scripts in this guide are simplified illustrations. Do not deploy them to production without a thorough security review and audit appropriate for your use case.
</Warning>

<Tip title="Bridging tokens">
  Looking to bridge a token to Polygon PoS using the official bridge? Check out the guide on [how to submit a request to get your token mapped](/pos/how-to/bridging/ethereum-polygon/submit-mapping-request/).
</Tip>

<Steps>
  ### Deploy sender contract

  The sender contract calls [`syncState`](https://github.com/0xPolygon/pos-contracts/blob/e999579e9dc898ab6e66ddcb49ee84c2543a9658/contracts/root/stateSyncer/StateSender.sol#L33) on the `StateSender` contract on Ethereum. This emits a `StateSynced` event that Heimdall validators pick up and relay to Bor.

  `StateSender` is deployed at:

  * `0x49E307Fa5a58ff1834E0F8a60eB2a9609E6A5F50` on Sepolia
  * `0x28e4F3a7f651294B9564800b2D01f35189A5bFbE` on Ethereum mainnet

  ```solidity title="Sender.sol" theme={null}
  pragma solidity ^0.8.0;

  interface IStateSender {
      function syncState(address receiver, bytes calldata data) external;
  }

  contract Sender {
      address public stateSenderContract;
      address public receiver;
      uint public states;

      constructor(address _stateSender, address _receiver) {
          stateSenderContract = _stateSender;
          receiver = _receiver;
      }

      function sendState(bytes calldata data) external {
          states += 1;
          IStateSender(stateSenderContract).syncState(receiver, data);
      }
  }
  ```

  Deploy `Sender.sol` to Sepolia, passing in the `StateSender` address and your receiver contract address. Note the deployed address and ABI.

  ### Deploy receiver contract

  The receiver contract is invoked by the `StateReceiver` system contract on Bor whenever a matching `StateSynced` event is processed.

  <Warning title="Verify the caller">
    Always check that `msg.sender` is the `StateReceiver` contract at `0x0000000000000000000000000000000000001001`. Without this check, any address on Polygon can call `onStateReceive` on your contract directly.
  </Warning>

  ```solidity title="Receiver.sol" theme={null}
  pragma solidity ^0.8.0;

  interface IStateReceiver {
      function onStateReceive(uint256 stateId, bytes calldata data) external;
  }

  contract Receiver is IStateReceiver {
      address constant STATE_RECEIVER = 0x0000000000000000000000000000000000001001;

      uint public lastStateId;
      bytes public lastData;

      function onStateReceive(uint256 stateId, bytes calldata data) external {
          require(msg.sender == STATE_RECEIVER, "unauthorized");
          lastStateId = stateId;
          lastData = data;
      }
  }
  ```

  `stateId` is a monotonically increasing counter that uniquely identifies each state sync event. Deploy `Receiver.sol` to Amoy and note the deployed address and ABI.

  ### Register your sender and receiver contracts

  Before state sync will trigger your receiver, your sender/receiver pair must be registered with the `StateSender` contract. This registration is what authorizes the pair: only events originating from a registered sender contract targeting a registered receiver will be relayed by Heimdall validators.

  <Note>
    Registration for new custom sender/receiver pairs is managed by the Polygon team. Contact the team on [Discord](https://discord.com/invite/0xPolygonCommunity) or submit a request via [the mapping form](https://docs.google.com/forms/d/e/1FAIpQLSeq8HTef2dYpRx35_WWYhyr4C146K9dfhyYJQcoD1RuTTVABg/viewform) to initiate the process.
  </Note>

  If you want to test end-to-end before your custom contracts are registered, you can use the already-registered example contracts referenced in the form.

  ### Send and verify data

  With contracts deployed and registered, use the following script to send arbitrary bytes from Sepolia and verify receipt on Amoy. State sync takes approximately 7 to 8 minutes to complete.

  ```javascript title="test.js" theme={null}
  const { Web3 } = require('web3');

  const main = new Web3('<SEPOLIA_RPC_URL>');
  const matic = new Web3('<AMOY_RPC_URL>');

  const privateKey = '0x...';
  main.eth.accounts.wallet.add(privateKey);

  const senderAddress = '<SENDER_CONTRACT_ADDRESS>';
  const senderABI = [/* paste ABI here */];
  const receiverAddress = '<RECEIVER_CONTRACT_ADDRESS>';
  const receiverABI = [/* paste ABI here */];

  const sender = new main.eth.Contract(senderABI, senderAddress);
  const receiver = new matic.eth.Contract(receiverABI, receiverAddress);

  async function sendData(message) {
      const data = matic.utils.asciiToHex(message);
      const tx = await sender.methods.sendState(data).send({
          from: main.eth.accounts.wallet[0].address,
          gas: 200000,
      });
      console.log('Sent from Ethereum:', tx.transactionHash);
  }

  async function checkReceiver() {
      const stateId = await receiver.methods.lastStateId().call();
      const rawData = await receiver.methods.lastData().call();
      const message = matic.utils.hexToAscii(rawData);
      console.log('Last state ID:', stateId);
      console.log('Received message:', message);
  }

  async function run() {
      await sendData('Hello from Ethereum!');
      console.log('Waiting ~8 minutes for state sync...');
      setTimeout(checkReceiver, 8 * 60 * 1000);
  }

  run();
  ```

  Successful output looks like:

  ```bash theme={null}
  Sent from Ethereum: 0x4f64ae4ab4d2b2d2dc82cdd9ddae73af026e5a9c46c086b13bd75e38009e5204
  Waiting ~8 minutes for state sync...
  Last state ID: 453
  Received message: Hello from Ethereum!
  ```
</Steps>
