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 <