Manually relaying interop messages with cast
and L2ToL2CrossDomainMessenger
This guide describes how to form a message identifier to relay a L2ToL2CrossDomainMessenger cross chain call.
We'll perform the SuperchainERC20 interop transfer in First steps again, this time manually relaying the message without the autorelayer.
- Overview
- Steps
- 1. Start
supersim
- 2. Mint tokens to transfer on chain 901
- 3. Initiate the send transaction on chain 901
- 4. Get the log emitted by the
L2ToL2CrossDomainMessenger
- 5. Get the block timestamp the log was emitted in
- 6. Prepare the identifier
- 7. Send the relayMessage transaction
- 8. Check the balance on chain 902
- 1. Start
- Alternatives
Overview
Contracts used
- L2NativeSuperchainERC20
0x420beeF000000000000000000000000000000001
- L2ToL2CrossDomainMessenger
0x4200000000000000000000000000000000000023
High level steps
Sending an interop message using the L2ToL2CrossDomainMessenger
:
On source chain (OPChainA 901)
- Invoke
L2NativeSuperchainERC20.sentERC20
to bridge funds- this leverages
L2ToL2CrossDomainMessenger.sendMessage
to make the cross chain call
- this leverages
- Retrieve the log identifier and the message payload for the
SentMessage
event.
On destination chain (OPChainB 902)
- Relay the message with
L2ToL2CrossDomainMessenger.relayMessage
- which then calls
L2NativeSuperchainERC20.relayERC20
- which then calls
Message identifier
A message identifier uniquely identifies a log emitted on a chain. The sequencer and smart contracts (CrossL2Inbox) use the identifier to perform invariant checks to confirm that the message is valid.
struct Identifier {
address origin; // Account (contract) that emits the log
uint256 blocknumber; // Block number in which the log was emitted
uint256 logIndex; // Index of the log in the array of all logs emitted in the block
uint256 timestamp; // Timestamp that the log was emitted
uint256 chainid; // Chain ID of the chain that emitted the log
}
Steps
1. Start supersim
supersim
2. Mint tokens to transfer on chain 901
Run the following command to mint 1000 L2NativeSuperchainERC20
tokens to the recipient address:
cast send 0x420beeF000000000000000000000000000000001 "mint(address _to, uint256 _amount)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1000 --rpc-url http://127.0.0.1:9545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
3. Initiate the send transaction on chain 901
Send the tokens from Chain 901 to Chain 902 using the following command:
cast send 0x4200000000000000000000000000000000000028 "sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId)" 0x420beeF000000000000000000000000000000001 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1000 902 --rpc-url http://127.0.0.1:9545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
4. Get the log emitted by the L2ToL2CrossDomainMessenger
The token contract calls the L2ToL2CrossDomainMessenger, which emits a message (log) that can be relayed on the destination chain.
$ cast logs --address 0x4200000000000000000000000000000000000023 --rpc-url http://127.0.0.1:9545
address: 0x4200000000000000000000000000000000000023
blockHash: 0x3905831f1b109ce787d180c1ed977ebf0ff1a6334424a0ae8f3731b035e3f708
blockNumber: 4
data: 0x000000000000000000000000420beef00000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064d9f50046000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000
logIndex: 1
topics: [
0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320
0x0000000000000000000000000000000000000000000000000000000000000386
0x000000000000000000000000420beef000000000000000000000000000000001
0x0000000000000000000000000000000000000000000000000000000000000000
]
...
5. Retrieve the block timestamp the log was emitted in
Since the message identifier requires the block timestamp, fetch the block info to get the timestamp.
$ cast block 0xREPLACE_WITH_CORRECT_BLOCKHASH --rpc-url http://127.0.0.1:9545
...
timestamp 1728507703
...
6. Prepare the message identifier & payload
Now we have all the information needed for the message (log) identifier.
Parameter | Value | Note |
---|---|---|
origin | 0x4200000000000000000000000000000000000023 | L2ToL2CrossDomainMessenger |
blocknumber | 4 | from step 4 |
logIndex | 1 | from step 4 |
timestamp | 1728507703 | from step 5 |
chainid | 901 | OPChainA chainID |
The message payload is the concatenation of the [...topics, data] in order.
0x + 382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320
+ 0000000000000000000000000000000000000000000000000000000000000386
+ 000000000000000000000000420beef000000000000000000000000000000001
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 000000000000000000000000420beef00000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064d9f50046000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000
Payload: 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3200000000000000000000000000000000000000000000000000000000000000386000000000000000000000000420beef0000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000420beef00000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064d9f50046000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000
7. Send the relayMessage transaction
Call relayMessage
on the L2ToL2CrossDomainMessenger
// L2ToL2CrossDomainMessenger.sol (truncated for brevity)
contract L2ToL2CrossDomainMessenger {
// ...
function relayMessage(
ICrossL2Inbox.Identifier calldata _id,
bytes calldata _sentMessage
) payable
// ...
}
relayMessage
parameters
ICrossL2Inbox.Identifier calldata _id
: identifier pointing to theSentMessage
log on the source chainbytes memory _sentMessage
: encoding of the log topics & data
Below is an example call, but make sure to replace them with the correct values you received in previous steps.
$ cast send 0x4200000000000000000000000000000000000023 --gas-limit 200000 \
"relayMessage((address, uint256, uint256, uint256, uint256), bytes)" \
"(0x4200000000000000000000000000000000000023, 4, 1, 1728507703, 901)" \
0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3200000000000000000000000000000000000000000000000000000000000000386000000000000000000000000420beef0000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000420beef00000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064d9f50046000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000 \
--rpc-url http://127.0.0.1:9546 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
9. Check the balance on chain 902
Verify that the balance of the L2NativeSuperchainERC20 on chain 902 has increased:
cast balance --erc20 0x420beeF000000000000000000000000000000001 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:9546
Alternatives
This is obviously very tedious to do by hand 😅. Here are some alternatives
- use
supersim --interop.autorelay
- this only works on supersim, but relayers for the testnet/prod environment will be available soon! - use
viem
bindings/actions - if you're using typescript, we have bindings available to make fetching identifiers and relaying messages easy