Skip to content

fix(interop): update outdated interop manual relay and viem tutorials #1569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions pages/interop/tutorials/bridge-crosschain-eth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ The tutorial uses these primary tools:

3. Place this in `src/transfer-eth.mts`:

```typescript file=<rootDir>/public/tutorials/transfer-eth.mts hash=f71ba1d6d6b4adf8cb3283792abac853
```typescript file=<rootDir>/public/tutorials/transfer-eth.mts hash=9f19c28046ed792e3f04aefd8fb4453e
```

<details>
Expand All @@ -214,25 +214,25 @@ The tutorial uses these primary tools:

Import all chain definitions from `@eth-optimism/viem`.

```typescript file=<rootDir>/public/tutorials/transfer-eth.mts#L29-L32 hash=e8c21357997ea12151305337eced7d71
```typescript file=<rootDir>/public/tutorials/transfer-eth.mts#L29-L32 hash=2c2dd55bc8c8122fe2991ab38cc9c1ac
```

If the address we use is `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`, one of the prefunded addresses on `anvil`, assume we're using Supersim.
Otherwise, use Interop devnet.

```typescript file=<rootDir>/public/tutorials/transfer-eth.mts#L78-L80 hash=4933c70a9078c2369ef90bfe163f5fd7
```typescript file=<rootDir>/public/tutorials/transfer-eth.mts#L78-L80 hash=c5c2f87d5f6bb564376016ac62712501
```

To relay a message we need the information in the receipt.
Also, we need to wait until the transaction with the relayed message is actually part of a block.

```typescript file=<rootDir>/public/tutorials/transfer-eth.mts#L87-L89 hash=573f22b2b21415ff51c59c713fda07d1
```typescript file=<rootDir>/public/tutorials/transfer-eth.mts#L86-L89 hash=db8b76c8ca3304dd485f4a33a1dc8580
```

A single transaction can send multiple messages.
But here we know we sent just one, so we look for the first one in the list.

```typescript file=<rootDir>/public/tutorials/transfer-eth.mts#L90-L96 hash=4b26775b46c116262af4c7299d6f1127
```typescript file=<rootDir>/public/tutorials/transfer-eth.mts#L90-L96 hash=d650e8c2b31d75d82ba4c5e4519c028d
```

This is how you use `@eth-optimism/viem` to create an executing message.
Expand Down
129 changes: 104 additions & 25 deletions pages/interop/tutorials/relay-messages-cast.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Sending an interop message using the `L2ToL2CrossDomainMessenger`:

### On source chain (OPChainA 901)

1. Invoke `L2NativeSuperchainERC20.sentERC20` to bridge funds
1. Invoke `L2NativeSuperchainERC20.sendERC20` to bridge funds
* this leverages `L2ToL2CrossDomainMessenger.sendMessage` to make the cross chain call
2. Retrieve the log identifier and the message payload for the `SentMessage` event.

Expand Down Expand Up @@ -98,34 +98,47 @@ struct Identifier {

### Get the log emitted by the `L2ToL2CrossDomainMessenger`

The token contract calls the [L2ToL2CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/92ed64e171c6eb9c6a080c626640e8836f0653cc/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol), which emits a message (log) that can be relayed on the destination chain.
The token contract calls the [L2ToL2CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol), which emits a message (log) that can be relayed on the destination chain.

```sh
$ cast logs --address 0x4200000000000000000000000000000000000023 --rpc-url http://127.0.0.1:9545
cast logs --address 0x4200000000000000000000000000000000000023 --rpc-url http://127.0.0.1:9545
```

Sample output:

```
address: 0x4200000000000000000000000000000000000023
blockHash: 0x3905831f1b109ce787d180c1ed977ebf0ff1a6334424a0ae8f3731b035e3f708
blockNumber: 4
data: 0x000000000000000000000000420beef00000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064d9f50046000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000
logIndex: 1
blockHash: 0x311f8ccea3fc121aa3af18e0a87766ae56ed3f1d08cae91ec29f34a9919abcc0
blockNumber: 14
data: 0x0000000000000000000000004200000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000847cfd6dbc000000000000000000000000420beef000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000
logIndex: 2
removed: false
topics: [
0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320
0x0000000000000000000000000000000000000000000000000000000000000386
0x000000000000000000000000420beef000000000000000000000000000000001
0x0000000000000000000000000000000000000000000000000000000000000000
0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320
0x0000000000000000000000000000000000000000000000000000000000000386
0x0000000000000000000000004200000000000000000000000000000000000028
0x0000000000000000000000000000000000000000000000000000000000000000
]
...
transactionHash: 0x746a3e8a3a0ed0787367c3476269fa3050a2f9113637b563a4579fbc03efe5c4
transactionIndex: 0
```

### 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.

```sh
$ cast block 0xREPLACE_WITH_CORRECT_BLOCKHASH --rpc-url http://127.0.0.1:9545
...
timestamp 1728507703
...
cast block 0xREPLACE_WITH_CORRECT_BLOCKHASH --rpc-url http://127.0.0.1:9545
```

Sample output:

```
// (truncated for brevity)

timestamp 1743801675

// ...
```

### Prepare the message identifier & payload
Expand All @@ -144,17 +157,82 @@ struct Identifier {

```
0x + 382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320
+ 0000000000000000000000000000000000000000000000000000000000000386
+ 000000000000000000000000420beef000000000000000000000000000000001
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 000000000000000000000000420beef00000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064d9f50046000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000000000000000000000000000000000000000000386
+ 0000000000000000000000004200000000000000000000000000000000000028
+ 0000000000000000000000000000000000000000000000000000000000000000
+ 0000000000000000000000004200000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000847cfd6dbc000000000000000000000000420beef000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000
```

Payload: ` 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3200000000000000000000000000000000000000000000000000000000000000386000000000000000000000000420000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000847cfd6dbc000000000000000000000000420beef000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000`

### Construct the access list for the message

An access list must be passed along with the relay message tx. There are two admin RPC methods that can be used to construct the access list: `admin_getAccessListByMsgHash` and `admin_getAccessListForIdentifier` and.

a. To get the access list using the `admin_getAccessListByMsgHash` RPC method, call the method with the message hash.

1. Retrieve the message hash from the supersim logs

```sh
INFO [04-04|14:21:15.587] L2ToL2CrossChainMessenger#SentMessage sourceChainID=901 destinationChainID=902 nonce=0 sender=0x4200000000000000000000000000000000000028 target=0x4200000000000000000000000000000000000028 msgHash=0xccff97c17ef11d659d319cbc5780235ea03ef34b0fa34f40b208a9519f257379 txHash=0x746a3e8a3a0ed0787367c3476269fa3050a2f9113637b563a4579fbc03efe5c4
```

Payload: `0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3200000000000000000000000000000000000000000000000000000000000000386000000000000000000000000420beef0000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000420beef00000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064d9f50046000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000`
2. Call `admin_getAccessListByMsgHash` with the message hash.

```sh
cast rpc admin_getAccessListByMsgHash 0xccff97c17ef11d659d319cbc5780235ea03ef34b0fa34f40b208a9519f257379 --rpc-url http://localhost:8420
```

Sample output:

```
{
"accessList": [
{
"address": "0x4200000000000000000000000000000000000022",
"storageKeys": [
"0x010000000000000000000385000000000000000e0000000067f04d4b00000002",
"0x03c6d2648cef120ce1d7ccf9f8d4042d6b25ff30a02e22d9ea2a47d2677ccb8d"
]
}
]
}
```

b. To get the access list using the `admin_getAccessListForIdentifier` RPC method, call the method with the identifier and the message payload.

```sh
cast rpc admin_getAccessListForIdentifier \
'{
"origin": "0x4200000000000000000000000000000000000023",
"blockNumber": "14",
"logIndex": "2",
"timestamp": "1743801675",
"chainId": "901",
"payload": "0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3200000000000000000000000000000000000000000000000000000000000000386000000000000000000000000420000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000847cfd6dbc000000000000000000000000420beef000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000"
}' \
--rpc-url http://localhost:8420
```

Sample output:

```
{
"accessList": [
{
"address": "0x4200000000000000000000000000000000000022",
"storageKeys": [
"0x010000000000000000000385000000000000000e0000000067f04d4b00000002",
"0x03c6d2648cef120ce1d7ccf9f8d4042d6b25ff30a02e22d9ea2a47d2677ccb8d"
]
}
]
}
```

### Send the relayMessage transaction

Call `relayMessage` on the [L2ToL2CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/92ed64e171c6eb9c6a080c626640e8836f0653cc/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L126)
Call `relayMessage` on the [L2ToL2CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) with the access list.

```solidity
// L2ToL2CrossDomainMessenger.sol (truncated for brevity)
Expand All @@ -180,10 +258,11 @@ struct Identifier {
Below is an example call, but make sure to replace them with the correct values you received in previous steps.

```sh
$ cast send 0x4200000000000000000000000000000000000023 --gas-limit 200000 \
cast send 0x4200000000000000000000000000000000000023 \
"relayMessage((address, uint256, uint256, uint256, uint256), bytes)" \
"(0x4200000000000000000000000000000000000023, 4, 1, 1728507703, 901)" \
0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3200000000000000000000000000000000000000000000000000000000000000386000000000000000000000000420beef0000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000420beef00000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064d9f50046000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000 \
"(0x4200000000000000000000000000000000000023, 14, 2, 1743801675, 901)" \
0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3200000000000000000000000000000000000000000000000000000000000000386000000000000000000000000420000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000847cfd6dbc000000000000000000000000420beef000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000 \
--access-list '[{"address":"0x4200000000000000000000000000000000000022","storageKeys":["0x010000000000000000000385000000000000000e0000000067f04d4b00000002", "0x03c6d2648cef120ce1d7ccf9f8d4042d6b25ff30a02e22d9ea2a47d2677ccb8d"]}]' \
--rpc-url http://127.0.0.1:9546 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```
Expand Down
54 changes: 21 additions & 33 deletions pages/interop/tutorials/relay-messages-viem.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ import {
walletActionsL2,
supersimL2A,
supersimL2B,
createInteropSentL2ToL2Messages,
decodeRelayedL2ToL2Messages,
} from "@eth-optimism/viem";

// SuperERC20 is in development so we manually define the address here
Expand Down Expand Up @@ -121,21 +119,20 @@ await opChainAClient.waitForTransactionReceipt({ hash: mintTxHash });
// 2. Initiate sendERC20 tx to bridge funds to chain B

console.log("Initiating sendERC20 on OPChainA to OPChainB...");
const sendERC20TxHash = await opChainAClient.writeContract({
address: SUPERCHAIN_TOKEN_BRIDGE_ADDRESS,
abi: parseAbi([
"function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId)",
]),
functionName: "sendERC20",
args: [L2_NATIVE_SUPERCHAINERC20_ADDRESS, account.address, 1000n, BigInt(supersimL2B.id)],
const sendERC20TxHash = await opChainAClient.interop.sendSuperchainERC20({
tokenAddress: SUPERSIM_SUPERC20_ADDRESS,
to: testAccount.address,
amount: AMOUNT_TO_SEND,
chainId: supersimL2B.id,
});

const sendERC20Receipt = await opChainAClient.waitForTransactionReceipt({ hash: sendERC20TxHash });

// 3. Construct the interoperable log data from the sent message

const { sentMessages } = await createInteropSentL2ToL2Messages(opChainAClient, { receipt: sendERC20Receipt })
const sentMessages = await opChainAClient.interop.getCrossDomainMessages({ logs: sendERC20Receipt.logs })
const sentMessage = sentMessages[0] // We only sent 1 message
const relayMessageParams = await opChainAClient.interop.buildExecutingMessage({ log: sentMessage.log })
```

### Relay the sent message on the destination chain
Expand All @@ -148,17 +145,14 @@ const sentMessage = sentMessages[0] // We only sent 1 message
// 4. Relay the sent message

console.log("Relaying message on OPChainB...");
const relayTxHash = await opChainBClient.relayL2ToL2Message({
sentMessageId: sentMessage.id,
sentMessagePayload: sentMessage.payload,
});
const relayTxHash = await opChainBClient.interop.relayCrossDomainMessage(relayMessageParams);

const relayReceipt = await opChainBClient.waitForTransactionReceipt({ hash: relayTxHash });

// 5. Ensure the message was relayed successfully

const { successfulMessages, failedMessages } = decodeRelayedL2ToL2Messages({ receipt: relayReceipt });
if (successfulMessages.length != 1) {
const status = await opChainBClient.interop.getCrossDomainMessageStatus({ message: sentMessage })
if (status != 'relayed') {
throw new Error("failed to relay message!")
}

Expand Down Expand Up @@ -202,8 +196,6 @@ import {
walletActionsL2,
supersimL2A,
supersimL2B,
createInteropSentL2ToL2Messages,
decodeRelayedL2ToL2Messages,
} from "@eth-optimism/viem";

// SuperERC20 is in development so we manually define the address here
Expand Down Expand Up @@ -250,22 +242,21 @@ await opChainAClient.waitForTransactionReceipt({ hash: mintTxHash });

// 2. Initiate sendERC20 tx to bridge funds to chain B

console.log("Initiating sendERC20 on OPChainA...");
const sendERC20TxHash = await opChainAClient.writeContract({
address: SUPERCHAIN_TOKEN_BRIDGE_ADDRESS,
abi: parseAbi([
"function sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId)",
]),
functionName: "sendERC20",
args: [L2_NATIVE_SUPERCHAINERC20_ADDRESS, account.address, 1000n, BigInt(supersimL2B.id)],
console.log("Initiating sendERC20 on OPChainA to OPChainB...");
const sendERC20TxHash = await opChainAClient.interop.sendSuperchainERC20({
tokenAddress: SUPERSIM_SUPERC20_ADDRESS,
to: testAccount.address,
amount: AMOUNT_TO_SEND,
chainId: supersimL2B.id,
});

const sendERC20Receipt = await opChainAClient.waitForTransactionReceipt({ hash: sendERC20TxHash });

// 3. Construct the interoperable log data from the sent message

const { sentMessages } = await createInteropSentL2ToL2Messages(opChainAClient, { receipt: sendERC20Receipt })
const sentMessages = await opChainAClient.interop.getCrossDomainMessages({ logs: sendERC20Receipt.logs })
const sentMessage = sentMessages[0] // We only sent 1 message
const relayMessageParams = await opChainAClient.interop.buildExecutingMessage({ log: sentMessage.log })

// ##########
// OP Chain B
Expand All @@ -274,17 +265,14 @@ const sentMessage = sentMessages[0] // We only sent 1 message
// 4. Relay the sent message

console.log("Relaying message on OPChainB...");
const relayTxHash = await opChainBClient.relayL2ToL2Message({
sentMessageId: sentMessage.id,
sentMessagePayload: sentMessage.payload,
});
const relayTxHash = await opChainBClient.interop.relayCrossDomainMessage(relayMessageParams);

const relayReceipt = await opChainBClient.waitForTransactionReceipt({ hash: relayTxHash });

// 5. Ensure the message was relayed successfully

const { successfulMessages, failedMessages } = decodeRelayedL2ToL2Messages({ receipt: relayReceipt });
if (successfulMessages.length != 1) {
const status = await opChainBClient.interop.getCrossDomainMessageStatus({ message: sentMessage })
if (status != 'relayed') {
throw new Error("failed to relay message!")
}

Expand Down
Loading