The mechanism to natively read Ethereum data from Matic EVM chain is that of "State Sync". In other words, this mechanism enables transfer of arbitrary data from Ethereum chain to Matic chain. The procedure that makes it possible is: Validators on the Heimdall layer are listening for a particular event —
StateSynced from a Sender contract, as soon as the event is picked, the
data that was passed in the event is written on the Receiver contract. Read more here.
The Sender and Receiver contract are required to be mapped on Ethereum —
StateSender.sol needs to be aware of each sender and receiver. If you'd like to get the mapping done, please request a mapping here.
In the following walkthrough, we'll be deploying a Sender contract on Ropsten (Ethereum testnet) and a Receiver contract on Testnetv3 (Matic's testnet) and then we'll be sending data from Sender and reading data on Receiver via web3 calls in a node script.
1. Deploy Sender Contract
The sole purpose of Sender contract is to be able to call
syncState function on the StateSender contract — which is Matic's state syncer contract - the StateSynced event of which Heimdall is listening to.
State syncer contract is deployed at:
0x22E1f5aa1BA9e60527250FFeb35e30Aa2913727fon Ropsten (Receiver on Testnetv3)
0xfB631F5A239A5B651120335239CC19aEbCb185e6on Ethereum Mainnet (Receiver on BetaV2)
To be able to call this function, let's first include it's interface in our contract:
Next, let's write our custom function that takes in the data we'd like to pass on to Matic and calls syncState
In the above function,
stateSenderContract is the address of the
StateSender on the network you'll be deploying
Sender on. (eg., we'll be using
0x22E1f5aa1BA9e60527250FFeb35e30Aa2913727f for Ropsten), and receiver is the contract that will receive the data we send from here.
It is recommended to use constructors to pass in variables, but for the purpose of this demo, we'll simply harcode these two addresses:
Following is how our
Sender.sol looks like:
We are using a simple
states counter to keep track of the number of states sent via the Sender contract.
Use Remix to deploy the contract and keep a note of the address and ABI.
Ours is deployed at
0xf428d353aBdb6fdFf9E5E713f1Ec403cD15d0975 on Ropsten, and here is the ABI: SenderABI.json
2. Deploy Receiver contract
Receiver contract is the one that is invoked by a Validator when the
StateSynced event is emitted. The Validator invokes the function
onStateReceiveon the receiver contract to submit the data. To implement it, we first import StateReceiver interface and write down our custom logic — to interpret the tranferred data inside onStateReceive.
The function simply assigns the last received State Id and data to variables. StateId is a simple unique reference to the transferred state (a simple counter).
Deploy your Receiver.sol on Matic's testnet and keep a note of the address and ABI
Ours is deployed at
0x83bB46B64b311c89bEF813A534291e155459579e on TestnetV3, and here is the ABI: ReceiverABI.json
3. Getting your Sender and Receiver Mapped
You can either use the already deployed addresses (mentioned above) for sender and receiver, or deploy your custom contracts and request a mapping:
4. Sending and Receiving data
Now that we have our contracts in place and mapping done, we'll be writing a simple node script to send arbitrary hex bytes, receive them on matic network and interpret the data!
4.1 Setup your script
We'll first initialise our web3 objects, wallet to make the transactions and contracts
We're using @maticnetwork/meta package for the RPCs, the package isn't a requirement to run the script.
main objects refer to the web3 object initialised with Matic's and Ropsten's RPC respectively.
receiver objects refer to the contract objects of Sender.sol and Receiver.sol that we deployed in Step 1 and 2.
4.2 Sending data
Next, let's setup our functions to create bytestring of the data and send it via Sender contract:
getData will convert an ascii string (eg.,
Hello World !) to a string of bytes (eg.,
0x48656c6c6f20576f726c642021); while the function
sendData takes in data (an ascii string), calls
getData and passes on the bytestring to sender contract
4.3 Receiving data
Next, we'll be checking for received data on
It should take ~4 minutes for the state sync to execute.
Add the following functions to check (a) number of sent states from Sender and (b) Last received state on
checkReceiver simply calls the variables we defined in the contract — which would be set as soon as the Validator calls
onStateReceive on the contract. The
getString function simply interprets the bytestring (converts it back to ascii)
Finally, we'll write up a method to execute our functions:
4.4 Putting it all together!
This is how our test script looks like:
4.5 Let's run the script
Successful execution of the above script provide an output as:
Here is the Gist with contracts code and the test node script: Gist.