Interop basics
This guide explains the basic process of using the InteropMessage interface from protocol version v29. With this interface, a message is sent from one chain (chain A) and then verified on another chain (chain B).
Note: This interface only broadcasts messages. It does not include features such as specifying a destination or replay protection, which are available in higher-level interfaces.
Preparation
Setup chains
Follow the instructions at: launch doc
This will help you set up the Era chain and the associated gateway. In this example, we will send and verify the message on Era. If you prefer to test on another chain (like era2), launch a new chain following the guide above and migrate it to the gateway.
Fund an Account
Give funds to an account on your local Era using the following commands:
zkstack dev rich-account
ACCOUNT="0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"
PRIVATE_KEY="0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"
Sending a Message
While messages are usually sent by contracts, you can send one directly using:
cast send -r http://localhost:3050 0x0000000000000000000000000000000000008008 "sendToL1(bytes)" "0x1234" --private-key $PRIVATE_KEY
Here, 0x...8008
is the L1Messenger, a special system contract. Remember the transaction hash, as we’ll use it in
commands below.
Next, retrieve the interop proof from the sequencer (replace the transaction hash with your own). If you keep getting ‘null’ answers, please wait a while, as the sequencer has to seal and commit the batch before this data can be returned.
curl --request POST \
--url http://localhost:3050/ \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "zks_getL2ToL1LogProof",
"params": [
"0xc9f09ef3cf6c31fe9f1376845a2900da7b68e6f31db1c10c90d965ec8a8a76d2", 0, "proof_based_gw"
]
}'
The command returns a proof, that will look like this:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"proof": [
"0x010f050000000000000000000000000000000000000000000000000000000000",
"0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43ba",
"0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0",
"0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111",
"0x199cc5812543ddceeddd0fc82807646a4899444240db2c0d2f20c3cceb5f51fa",
"0xe4733f281f18ba3ea8775dd62d2fcd84011c8c938f16ea5790fd29a03bf8db89",
"0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272",
"0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d",
"0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c",
"0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db",
"0x124b05ec272cecd7538fdafe53b6628d31188ffb6f345139aac3c3c1fd2e470f",
"0xc3be9cbd19304d84cca3d045e06b8db3acd68c304fc9cd4cbffe6d18036cb13f",
"0xfef7bd9f889811e59e4076a0174087135f080177302763019adaf531257e3a87",
"0xa707d1c62d8be699d34cb74804fdd7b4c568b6c1a821066f126c680d4b83e00b",
"0xf6e093070e0389d2e529d60fadb855fdded54976ec50ac709e3a36ceaa64c291",
"0xe4ed1ec13a28c40715db6399f6f99ce04e5f19d60ad3ff6831f098cb6cf75944",
"0x0000000000000000000000000000000000000000000000000000000000000015",
"0x156467afe10e8eb1dcac04cca213c53d4bade9d73edfe9410ea45c86ebd9804e",
"0xcc4c41edb0c2031348b292b768e9bac1ee8c92c09ef8a3277c2ece409c12d86a",
"0x183a40fea23b03351928919261dfc02e45b94b564e263d339a601b9740112ccc",
"0x4cd95f8962e2e3b5f525a0f4fdfbbf0667990c7159528a008057f3592bcb2c06",
"0x112038ecbdf21c5fc2ef97d9ec047402bba05d6ff2aa1b304dcfac974ebaa109",
"0x0000000000000000000000000000001900000000000000000000000000000003",
"0x00000000000000000000000000000000000000000000000000000000000001fa",
"0x0102000100000000000000000000000000000000000000000000000000000000",
"0xf84927dc03d95cc652990ba75874891ccc5a4d79a0e10a2ffdd238a34a39f828",
"0x2c21d37d09509f7ccb3d7d93c86ff4d7b29246113b0032dec9688c9e119d6872"
],
"id": 0,
"root": "0xeb6861fa2f2fd60c93dba1343bf85e2312811fd23549b3df2b8017102430f086"
}
}
getL2toL1LogProof can return different proof types depending on the interop mode specified. Currently, only “proof_based_gw” is supported.
The second argument (0) is the index of the message - if your transaction created multiple interop messages, you have to select the correct one. You can figure out the correct one, by looking at the event logs from the transaction. In our case, we published just 1 interop transation from this method, so we can keep it at 0th.
Receiving a Message
On the destination chain, a contract usually calls the L2_MESSAGE_VERIFICATION_ADDRESS
(at 0x...10009
) with the
proof. You can also call the method manually:
cast call 0x0000000000000000000000000000000000010009 \
"proveL2MessageInclusionShared(uint256,uint256,uint256,(uint16,address,bytes),bytes32[])" \
271 \
22 \
0 \
"(0,0x36615Cf349d7F6344891B1e7CA7C72883F5dc049,0x1234)" \
"["0x010f050000000000000000000000000000000000000000000000000000000000","0x72abee45b59e344af8a6e520241c4744aff26ed411f4c4b00f8af09adada43ba","0xc3d03eebfd83049991ea3d3e358b6712e7aa2e2e63dc2d4b438987cec28ac8d0","0xe3697c7f33c31a9b0f0aeb8542287d0d21e8c4cf82163d0c44c7a98aa11aa111","0x199cc5812543ddceeddd0fc82807646a4899444240db2c0d2f20c3cceb5f51fa","0xe4733f281f18ba3ea8775dd62d2fcd84011c8c938f16ea5790fd29a03bf8db89","0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272","0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d","0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c","0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db","0x124b05ec272cecd7538fdafe53b6628d31188ffb6f345139aac3c3c1fd2e470f","0xc3be9cbd19304d84cca3d045e06b8db3acd68c304fc9cd4cbffe6d18036cb13f","0xfef7bd9f889811e59e4076a0174087135f080177302763019adaf531257e3a87","0xa707d1c62d8be699d34cb74804fdd7b4c568b6c1a821066f126c680d4b83e00b","0xf6e093070e0389d2e529d60fadb855fdded54976ec50ac709e3a36ceaa64c291","0xe4ed1ec13a28c40715db6399f6f99ce04e5f19d60ad3ff6831f098cb6cf75944","0x0000000000000000000000000000000000000000000000000000000000000015","0x156467afe10e8eb1dcac04cca213c53d4bade9d73edfe9410ea45c86ebd9804e","0xcc4c41edb0c2031348b292b768e9bac1ee8c92c09ef8a3277c2ece409c12d86a","0x183a40fea23b03351928919261dfc02e45b94b564e263d339a601b9740112ccc","0x4cd95f8962e2e3b5f525a0f4fdfbbf0667990c7159528a008057f3592bcb2c06","0x112038ecbdf21c5fc2ef97d9ec047402bba05d6ff2aa1b304dcfac974ebaa109","0x0000000000000000000000000000003100000000000000000000000000000001","0x00000000000000000000000000000000000000000000000000000000000001fa","0x0101000100000000000000000000000000000000000000000000000000000000","0xf84927dc03d95cc652990ba75874891ccc5a4d79a0e10a2ffdd238a34a39f828"]" \
--rpc-url http://localhost:3050
A successful verification will return a “true” value (or 0x00...01
), confirming that the InteropMessage was correctly
received.
The parameters used in the above call are as follows:
- First argument (271): Chain ID of the source chain.
- Second argument (22): Batch number from the source chain.
- Third argument (0): Transaction index within the batch.
- Fourth argument: A tuple containing the index in the batch (again), the interop sender, and the interop payload.
- Fifth argument: The proof obtained from
zks_getL2ToL1LogProof
.
To determine the batch number and transaction index, fetch the receipt of your original transaction with:
cast receipt -r http://localhost:3050 0xc9f09ef3cf6c31fe9f1376845a2900da7b68e6f31db1c10c90d965ec8a8a76d2
Look for the following details in the receipt output:
blockNumber 43
...
l1BatchNumber "0x16" (which is 22 in decimal)
l1BatchTxIndex "0x0"
Debugging
If the proveL2MessageInclusionShared
method returns false (or 0x0
), consider the following checks:
1. Message Not Propagated Yet
-
Sender:
The message might not have been sent to the gateway if the transaction is still pending (messages are queued until execution). Verify using the batch number. For instance, using the batch number (0x16 == 22):curl --request POST \ --url localhost:3050 \ --header 'Content-Type: application/json' \ --data '{ "jsonrpc": "2.0", "id": 1, "method": "zks_getL1BatchDetails", "params": [22] }'
Look for the
executeTxHash
field. If it exists, the message was sent to the gateway. If it’s missing or zero, the sequencer has not finalized the batch. -
Receiver:
On the destination chain, check theInteropRootStorage
contract to see if the latest gateway batch has been fetched:cast call -r http://localhost:3050 0x0000000000000000000000000000000000010008 "interopRoots(uint256,uint256)" 506 49
Here, 506 is the gateway’s chain ID and 49 is the gateway’s block number. A return value of 0 indicates the block root was not fetched yet.
2. Message Propagated but Proof Failing
If you are certain the message was propagated, ensure the following:
- You are using the correct indexes when there are multiple transactions in a batch or multiple messages within a transaction.
- Use the helper tool available at: zksync tools
This tool helps you view and prove interop messages with the following commands:
cargo run show-interop-message --source-rpc http://localhost:3050 --source-tx $TX_ID
cargo run prove-interop-message --source-rpc http://localhost:3050 --source-tx $TX_ID --target-rpc http://localhost:3050
These steps should help you troubleshoot and verify that the interop messages are sent and received correctly.