diff --git a/pages/interop/reading-logs.mdx b/pages/interop/reading-logs.mdx
index 2a2bfcd5c..662dbd293 100644
--- a/pages/interop/reading-logs.mdx
+++ b/pages/interop/reading-logs.mdx
@@ -46,7 +46,7 @@ The process works through the [`CrossL2Inbox`](https://github.com/ethereum-optim
### Key components
-* **[Identifier](/interop/tutorials/relay-messages-cast#message-identifier)**: A struct containing information about the log, including `chainId`, `origin` (contract address), and other log metadata
+* **Identifier**: A struct containing information about the log, including `chainId`, `origin` (contract address), and other log metadata
* **[validateMessage](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L79)**: Function that verifies a log's authenticity before allowing its use
## Example: cross-chain attestation verification
diff --git a/pages/interop/tutorials.mdx b/pages/interop/tutorials.mdx
index c49f12fec..6537a9bfd 100644
--- a/pages/interop/tutorials.mdx
+++ b/pages/interop/tutorials.mdx
@@ -30,10 +30,6 @@ Documentation covering Interop related tutorials.
} />
- } />
-
- } />
-
} />
} />
diff --git a/pages/interop/tutorials/_meta.json b/pages/interop/tutorials/_meta.json
index 67816f137..c1bf92408 100644
--- a/pages/interop/tutorials/_meta.json
+++ b/pages/interop/tutorials/_meta.json
@@ -1,8 +1,8 @@
{
"message-passing": "Interop message passing",
- "deploy-superchain-erc20": "Deploying a SuperchainERC20",
+ "deploy-superchain-erc20": "Deploying a SuperchainERC20",
"transfer-superchainERC20": "Transferring a SuperchainERC20",
- "custom-superchain-erc20": "Custom SuperchainERC20 tokens",
+ "custom-superchain-erc20": "Custom SuperchainERC20 tokens",
"bridge-crosschain-eth": "Bridging native cross-chain ETH transfers",
"relay-messages-cast": "Relaying interop messages using `cast`",
"relay-messages-viem": "Relaying interop messages using `viem`",
diff --git a/pages/interop/tutorials/message-passing/_meta.json b/pages/interop/tutorials/message-passing/_meta.json
new file mode 100644
index 000000000..c8079b9f6
--- /dev/null
+++ b/pages/interop/tutorials/message-passing/_meta.json
@@ -0,0 +1,3 @@
+{
+ "relay-with-cast": "Manual relay transaction",
+}
diff --git a/pages/interop/tutorials/message-passing/relay-with-cast.mdx b/pages/interop/tutorials/message-passing/relay-with-cast.mdx
new file mode 100644
index 000000000..35933bf1b
--- /dev/null
+++ b/pages/interop/tutorials/message-passing/relay-with-cast.mdx
@@ -0,0 +1,220 @@
+---
+title: Manual relay transaction tutorial
+description: >-
+ Learn to relay transactions directly by sending the correct transaction.
+lang: en-US
+content_type: tutorial
+topic: interop-cast-manual-relay-tutorial
+personas:
+ - protocol-developer
+ - chain-operator
+ - app-developer
+categories:
+ - protocol
+ - interoperability
+ - cross-chain-messaging
+ - message-relaying
+ - cross-domain-messenger
+ - smart-contracts
+ - testnet
+ - superchain
+is_imported_content: 'false'
+---
+
+import { Callout } from 'nextra/components'
+import { Steps } from 'nextra/components'
+import { InteropCallout } from '@/components/WipCallout'
+import { AutorelayCallout } from '@/components/AutorelayCallout'
+
+# Manual relay transactions tutorial
+
+
+
+
+
+## Overview
+
+Learn to relay transactions directly by sending the correct transaction.
+
+
+ About this tutorial
+
+ **Prerequisite technical knowledge**
+
+ * Familiarity with blockchain concepts
+ * Familiarity with [Foundry](https://book.getfoundry.sh/getting-started/installation), especially `cast`
+
+ **What you'll learn**
+
+ * How to use `cast` to relay transactions with autorelay does not work
+
+ **Development environment**
+
+ * Unix-like operating system (Linux, macOS, or WSL for Windows)
+ * Node.js version 16 or higher
+ * Git for version control
+ * Supersim
+
+
+### What You'll Build
+
+* A script to relay messages without using [the JavaScript library](https://www.npmjs.com/package/@eth-optimism/viem)
+
+## Setup
+
+
+ ### Run Supersim
+
+ This exercise needs to be done on Supersim.
+ You cannot use the devnets because you cannot disable autorelay on them.
+
+ 1. Follow the [Installation Guide](/app-developers/tutorials/supersim/getting-started/installation).
+
+ 2. Run Supersim *without* `--interop.relay`.
+
+ ```sh
+ ./supersim
+ ```
+
+ ### Create the state for relaying messages
+
+ The results of this step are similar to what the [message passing tutorial](/interop/tutorials/message-passing) would produce if you did not have autorelay on.
+
+ Execute this script.
+
+ ```sh file=/public/tutorials/setup-for-manual-relay.sh#L1-L147 hash=a63d72f58a06ca7ca78fd1592efcf4a3
+ ```
+
+
+## Manually relay a message using `cast`
+
+Run this script:
+
+```sh
+./manual-relay/sendAndRelay.sh
+```
+
+### Explanation
+
+```sh
+#! /bin/sh
+PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
+URL_CHAIN_A=http://localhost:9545
+URL_CHAIN_B=http://localhost:9546
+GREETER_A_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
+GREETER_B_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
+CHAIN_ID_B=902
+```
+
+This is the configuration.
+The greeter addresses are the same because the nonce for the user address' nonce is the same.
+
+```sh
+cast send -q --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A $$"
+```
+
+Send a message from chain A to chain B. The `$$` is the process ID, so if you rerun the script you'll see that the information changes.
+
+```sh
+cast logs "SentMessage(uint256,address,uint256,address,bytes)" --rpc-url $URL_CHAIN_A | tail -14 > log-entry
+```
+
+Whenever `L2ToL2CrossDomainMessenger` sends a message to a different blockchain, it emits [a `SendMessage` event](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L83-L91).
+Here we look for those messages, but get only the last one.
+
+
+ Example `log-entry`
+
+ ```yaml
+ - address: 0x4200000000000000000000000000000000000023
+ blockHash: 0xcd0be97ffb41694faf3a172ac612a23f224afc1bfecd7cb737a7a464cf5d133e
+ blockNumber: 426
+ data: 0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064a41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001948656c6c6f2066726f6d20636861696e2041203131333030370000000000000000000000000000000000000000000000000000000000000000000000
+ logIndex: 0
+ removed: false
+ topics: [
+ 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320
+ 0x0000000000000000000000000000000000000000000000000000000000000386
+ 0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa3
+ 0x0000000000000000000000000000000000000000000000000000000000000000
+ ]
+ transactionHash: 0x1d6f2e5e2c8f3eb055e95741380ca36492f784b9782848b66b66c65c5937ff3a
+ transactionIndex: 0
+ ```
+
+
+```sh
+TOPICS=`cat log-entry | grep -A4 topics | awk '{print $1}' | tail -4 | sed 's/0x//'`
+TOPICS=`echo $TOPICS | sed 's/ //g'`
+```
+
+Consolidate the log topics into a single hex string.
+
+```sh
+ORIGIN=0x4200000000000000000000000000000000000023
+BLOCK_NUMBER=`cat log-entry | awk '/blockNumber/ {print $2}'`
+LOG_INDEX=`cat log-entry | awk '/logIndex/ {print $2}'`
+TIMESTAMP=`cast block $BLOCK_NUMBER --rpc-url $URL_CHAIN_A | awk '/timestamp/ {print $2}'`
+CHAIN_ID_A=`cast chain-id --rpc-url $URL_CHAIN_A`
+SENT_MESSAGE=`cat log-entry | awk '/data/ {print $2}'`
+```
+
+Read additional fields from the log entry.
+
+```sh
+LOG_ENTRY=0x`echo $TOPICS$SENT_MESSAGE | sed 's/0x//'`
+```
+
+Consolidate the entire log entry.
+
+```sh
+RPC_PARAMS=$(cat <
-The SuperchainERC20 standard is ready for production deployments.
-Please note that the OP Stack interoperability upgrade, required for crosschain messaging, is currently still in active development.
-
-
-# Relaying interop messages using `cast`
-
-This tutorial walks through how to form a [message identifier](https://specs.optimism.io/interop/messaging.html?utm_source=op-docs&utm_medium=docs#message-identifier) to relay a [L2ToL2CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) cross-chain call.
-
-We'll perform the SuperchainERC20 interop transfer in [Supersim first steps](/app-developers/tutorials/supersim/getting-started/first-steps##send-an-interoperable-superchainerc20-token-from-chain-901-to-902-l2-to-l2-message-passing) by manually relaying the message without the autorelayer.
-
-## Contracts used
-
-* [L2NativeSuperchainERC20](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/L2NativeSuperchainERC20.sol)
- * `0x420beeF000000000000000000000000000000001`
-* [L2ToL2CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/92ed64e171c6eb9c6a080c626640e8836f0653cc/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol)
- * `0x4200000000000000000000000000000000000023`
-
-## High level steps
-
-Sending an interop message using the `L2ToL2CrossDomainMessenger`:
-
-### On source chain (OPChainA 901)
-
-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.
-
-### On destination chain (OPChainB 902)
-
-3. Relay the message with `L2ToL2CrossDomainMessenger.relayMessage`
- * which then calls `L2NativeSuperchainERC20.relayERC20`
-
-## 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](https://specs.optimism.io/interop/messaging.html?utm_source=op-docs&utm_medium=docs#messaging-invariants) to confirm that the message is valid.
-
-```solidity
-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
-
-
- ### Start `supersim`
-
- ```sh
- supersim
- ```
-
- ### Mint tokens to transfer on chain 901
-
- Run the following command to mint 1000 `L2NativeSuperchainERC20` tokens to the recipient address:
-
- ```sh
- cast send 0x420beeF000000000000000000000000000000001 "mint(address _to, uint256 _amount)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1000 --rpc-url http://127.0.0.1:9545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
- ```
-
- ### Initiate the send transaction on chain 901
-
- Send the tokens from Chain 901 to Chain 902 using the following command:
-
- ```sh
- 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
- ```
-
- ### Get the log emitted by the `L2ToL2CrossDomainMessenger`
-
- 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
- ```
-
- Sample output:
-
- ```
- address: 0x4200000000000000000000000000000000000023
- blockHash: 0x311f8ccea3fc121aa3af18e0a87766ae56ed3f1d08cae91ec29f34a9919abcc0
- blockNumber: 14
- data: 0x0000000000000000000000004200000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000847cfd6dbc000000000000000000000000420beef000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000
- logIndex: 2
- removed: false
- topics: [
- 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. `0xREPLACE_WITH_CORRECT_BLOCKHASH` should be replaced with the `blockHash` from the output of the previous step.
-
- ```sh
- 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
-
- 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
- + 0000000000000000000000004200000000000000000000000000000000000028
- + 0000000000000000000000000000000000000000000000000000000000000000
- + 0000000000000000000000004200000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000847cfd6dbc000000000000000000000000420beef000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000
- ```
-
- Payload: ` 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3200000000000000000000000000000000000000000000000000000000000000386000000000000000000000000420000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000847cfd6dbc000000000000000000000000420beef000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000`
-
- ### Construct the access list for the message
-
- An access list must be passed along with the relay message transaction. There are two admin RPC methods that can be used to construct the access list: `admin_getAccessListByMsgHash` and `admin_getAccessListForIdentifier`.
-
- 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
- ```
-
- 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/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) with the access list.
-
- ```solidity
- // 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 the `SentMessage` log on the source chain
- * `bytes 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.
-
- ```sh
- cast send 0x4200000000000000000000000000000000000023 \
- "relayMessage((address, uint256, uint256, uint256, uint256), bytes)" \
- "(0x4200000000000000000000000000000000000023, 14, 2, 1743801675, 901)" \
- 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3200000000000000000000000000000000000000000000000000000000000000386000000000000000000000000420000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000000000000000000000000000004200000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000847cfd6dbc000000000000000000000000420beef000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000 \
- --access-list '[{"address":"0x4200000000000000000000000000000000000022","storageKeys":["0x010000000000000000000385000000000000000e0000000067f04d4b00000002", "0x03c6d2648cef120ce1d7ccf9f8d4042d6b25ff30a02e22d9ea2a47d2677ccb8d"]}]' \
- --rpc-url http://127.0.0.1:9546 \
- --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
- ```
-
- ### Check the balance on chain 902
-
- Verify that the balance of the L2NativeSuperchainERC20 on chain 902 has increased:
-
- ```sh
- 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](relay-messages-viem) - if you're using typescript, we have bindings available to make fetching identifiers and relaying messages easy
-
-## Next steps
-
-* Check out the collection of [Supersim tutorials](/app-developers/tutorials/supersim) for more resources on building with Interop.
-* Use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit) to deploy your token across the Superchain.
-* Review the [Superchain Interop Explainer](/interop/explainer) for answers to common questions about interoperability.
diff --git a/pages/interop/tutorials/relay-messages-viem.mdx b/pages/interop/tutorials/relay-messages-viem.mdx
deleted file mode 100644
index fd163af42..000000000
--- a/pages/interop/tutorials/relay-messages-viem.mdx
+++ /dev/null
@@ -1,289 +0,0 @@
----
-title: Relaying interop messages using `viem`
-description: Learn how to relay interop messages using `viem`.
-lang: en-US
-content_type: tutorial
-topic: relaying-interop-messages-using-viem
-personas:
- - protocol-developer
- - chain-operator
- - app-developer
-categories:
- - protocol
- - interoperability
- - cross-chain-messaging
- - javascript
- - message-relaying
- - cross-domain-messenger
- - viem
- - superchain
-is_imported_content: 'false'
----
-
-import { Callout } from 'nextra/components'
-import { Steps } from 'nextra/components'
-
-
-The SuperchainERC20 standard is ready for production deployments.
-Please note that the OP Stack interoperability upgrade, required for crosschain messaging, is currently still in active development.
-
-
-# Relaying interop messages using `viem`
-
-This tutorial walks through how to use [`viem`](https://viem.sh/) to send and relay interop messages using the `L2ToL2CrossDomainMessenger`.
-
-We'll perform the SuperchainERC20 interop transfer in [Supersim first steps](/app-developers/tutorials/supersim/getting-started/first-steps##send-an-interoperable-superchainerc20-token-from-chain-901-to-902-l2-to-l2-message-passing) and [Manually relaying interop messages with `cast`](relay-messages-cast), but use `viem` to relay the message without the autorelayer.
-
-
-If you'd like to skip ahead, the full code snippet can be found at the [end of the tutorial](#full-code-snippet).
-
-
-## Steps
-
-
-### Start `supersim`
-
-```sh
-supersim
-```
-
-### Install TypeScript packages
-```sh
-npm i viem @eth-optimism/viem
-```
-
-### Imports & Setup
-```ts
-import {
- http,
- encodeFunctionData,
- createWalletClient,
- parseAbi,
- defineChain,
- publicActions,
-} from "viem";
-import { privateKeyToAccount } from "viem/accounts";
-import {
- contracts,
- publicActionsL2,
- walletActionsL2,
- supersimL2A,
- supersimL2B,
-} from "@eth-optimism/viem";
-
-// SuperERC20 is in development so we manually define the address here
-const L2_NATIVE_SUPERCHAINERC20_ADDRESS = "0x420beeF000000000000000000000000000000001";
-const SUPERCHAIN_TOKEN_BRIDGE_ADDRESS = "0x4200000000000000000000000000000000000028";
-
-// Account for 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
-const account = privateKeyToAccount("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
-
-// Define chains
-// ... left out as we'll use the supersim chain definitions
-
-// Configure clients with optimism extension
-const opChainAClient = createWalletClient({
- transport: http(),
- chain: supersimL2A,
- account,
-}).extend(walletActionsL2())
- .extend(publicActionsL2())
- .extend(publicActions);
-
-const opChainBClient = createWalletClient({
- transport: http(),
- chain: supersimL2B,
- account,
-}).extend(walletActionsL2())
- .extend(publicActionsL2())
- .extend(publicActions);
-```
-
-### Mint and Bridge `L2NativeSuperchainERC20` from source chain
-```ts
-// #######
-// OP Chain A
-// #######
-
-// 1. Mint 1000 `L2NativeSuperchainERC20` token on chain A
-
-const mintTxHash = await opChainAClient.writeContract({
- address: L2_NATIVE_SUPERCHAINERC20_ADDRESS,
- abi: parseAbi(["function mint(address to, uint256 amount)"]),
- functionName: "mint",
- args: [account.address, 1000n],
-});
-
-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.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 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
-```ts
-
-// ##########
-// OP Chain B
-// ##########
-
-// 4. Relay the sent message
-
-console.log("Relaying message on OPChainB...");
-const relayTxHash = await opChainBClient.interop.relayCrossDomainMessage(relayMessageParams);
-
-const relayReceipt = await opChainBClient.waitForTransactionReceipt({ hash: relayTxHash });
-
-// 5. Ensure the message was relayed successfully
-
-const status = await opChainBClient.interop.getCrossDomainMessageStatus({ message: sentMessage })
-if (status != 'relayed') {
- throw new Error("failed to relay message!")
-}
-
-// 6. Check balance on OPChainB
-const balance = await opChainBClient.readContract({
- address: L2_NATIVE_SUPERCHAINERC20_ADDRESS,
- abi: parseAbi(["function balanceOf(address) view returns (uint256)"]),
- functionName: "balanceOf",
- args: [account.address],
-});
-
-console.log(`Balance on OPChainB: ${balance}`);
-```
-
-
-## Next steps
-* Check out the collection of [Supersim tutorials](/app-developers/tutorials/supersim) for more resources on building with Interop.
-* Use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit) to deploy your token across the Superchain.
-* Review the [Superchain Interop Explainer](/interop/explainer) for answers to common questions about interoperability.
-
-## Full code snippet
-
-
- Click to view
-
-```ts
-// Using viem to transfer L2NativeSuperchainERC20
-
-import {
- http,
- encodeFunctionData,
- createWalletClient,
- parseAbi,
- defineChain,
- publicActions,
-} from "viem";
-import { privateKeyToAccount } from "viem/accounts";
-import {
- contracts,
- publicActionsL2,
- walletActionsL2,
- supersimL2A,
- supersimL2B,
-} from "@eth-optimism/viem";
-
-// SuperERC20 is in development so we manually define the address here
-const L2_NATIVE_SUPERCHAINERC20_ADDRESS =
- "0x420beeF000000000000000000000000000000001";
-
-// account for 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
-const account = privateKeyToAccount("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
-
-// Define chains
-// ... left out as we'll use the supersim chain definitions
-
-// Configure op clients
-const opChainAClient = createWalletClient({
- transport: http(),
- chain: supersimL2A,
- account,
-}).extend(walletActionsL2())
- .extend(publicActionsL2())
- .extend(publicActions);
-
-const opChainBClient = createWalletClient({
- transport: http(),
- chain: supersimL2B,
- account,
-}).extend(walletActionsL2())
- .extend(publicActionsL2())
- .extend(publicActions);
-
-// #######
-// OP Chain A
-// #######
-
-// 1. Mint 1000 `L2NativeSuperchainERC20` token
-
-const mintTxHash = await opChainAClient.writeContract({
- address: L2_NATIVE_SUPERCHAINERC20_ADDRESS,
- abi: parseAbi(["function mint(address to, uint256 amount)"]),
- functionName: "mint",
- args: [account.address, 1000n],
-});
-
-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.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 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
-// ##########
-
-// 4. Relay the sent message
-
-console.log("Relaying message on OPChainB...");
-const relayTxHash = await opChainBClient.interop.relayCrossDomainMessage(relayMessageParams);
-
-const relayReceipt = await opChainBClient.waitForTransactionReceipt({ hash: relayTxHash });
-
-// 5. Ensure the message was relayed successfully
-
-const status = await opChainBClient.interop.getCrossDomainMessageStatus({ message: sentMessage })
-if (status != 'relayed') {
- throw new Error("failed to relay message!")
-}
-
-// 6. Check balance on OPChainB
-const balance = await opChainBClient.readContract({
- address: L2_NATIVE_SUPERCHAINERC20_ADDRESS,
- abi: parseAbi(["function balanceOf(address) view returns (uint256)"]),
- functionName: "balanceOf",
- args: [account.address],
-});
-
-console.log(`Balance on OPChainB: ${balance}`);
-```
-
diff --git a/public/tutorials/setup-for-manual-relay.sh b/public/tutorials/setup-for-manual-relay.sh
new file mode 100644
index 000000000..6ac523e0b
--- /dev/null
+++ b/public/tutorials/setup-for-manual-relay.sh
@@ -0,0 +1,152 @@
+#! /bin/sh
+
+rm -rf manual-relay
+mkdir manual-relay
+cd manual-relay
+
+PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+USER_ADDRESS=`cast wallet address --private-key $PRIVATE_KEY`
+URL_CHAIN_A=http://localhost:9545
+URL_CHAIN_B=http://localhost:9546
+
+forge init
+cat > src/Greeter.sol < src/GreetingSender.sol <> remappings.txt
+mkdir -p lib/node_modules/@eth-optimism/contracts-bedrock/interfaces/L2
+wget https://raw.githubusercontent.com/ethereum-optimism/optimism/refs/heads/develop/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol
+mv IL2ToL2CrossDomainMessenger.sol lib/node_modules/@eth-optimism/contracts-bedrock/interfaces/L2
+CHAIN_ID_B=`cast chain-id --rpc-url $URL_CHAIN_B`
+GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'`
+GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'`
+
+echo Setup done
+
+cat > sendAndRelay.sh < log-entry
+TOPICS=\`cat log-entry | grep -A4 topics | awk '{print \$1}' | tail -4 | sed 's/0x//'\`
+TOPICS=\`echo \$TOPICS | sed 's/ //g'\`
+
+ORIGIN=0x4200000000000000000000000000000000000023
+BLOCK_NUMBER=\`cat log-entry | awk '/blockNumber/ {print \$2}'\`
+LOG_INDEX=\`cat log-entry | awk '/logIndex/ {print \$2}'\`
+TIMESTAMP=\`cast block \$BLOCK_NUMBER --rpc-url \$URL_CHAIN_A | awk '/timestamp/ {print \$2}'\`
+CHAIN_ID_A=\`cast chain-id --rpc-url \$URL_CHAIN_A\`
+SENT_MESSAGE=\`cat log-entry | awk '/data/ {print \$2}'\`
+LOG_ENTRY=0x\`echo \$TOPICS\$SENT_MESSAGE | sed 's/0x//'\`
+
+RPC_PARAMS=\$(cat <