From 5a7a4080124ece5ccc2d5f78fcf5f6aeeb548639 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Thu, 6 Feb 2025 11:50:28 -0600 Subject: [PATCH 01/19] First part working --- pages/stack/interop/message-passing.mdx | 5 + pages/stack/interop/tutorials/_meta.json | 1 + .../interop/tutorials/message-passing.mdx | 247 ++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 pages/stack/interop/tutorials/message-passing.mdx diff --git a/pages/stack/interop/message-passing.mdx b/pages/stack/interop/message-passing.mdx index 7d25f156e..33e0715af 100644 --- a/pages/stack/interop/message-passing.mdx +++ b/pages/stack/interop/message-passing.mdx @@ -12,6 +12,11 @@ import { InteropCallout } from '@/components/WipCallout' # Interop message passing overview + + This is an explanation of how interop works. + You can find a step by step tutorial [here](tutorials/message-passing). + + The low-level [`CrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol) contract handles basic message execution. It verifies whether an initiating message exists but does not check the message's destination, processing status, or other attributes. The [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract extends `CrossL2Inbox` by providing complete cross-domain messaging functionality. diff --git a/pages/stack/interop/tutorials/_meta.json b/pages/stack/interop/tutorials/_meta.json index 6d2bdacfa..88f1e5427 100644 --- a/pages/stack/interop/tutorials/_meta.json +++ b/pages/stack/interop/tutorials/_meta.json @@ -1,4 +1,5 @@ { + "message-passing": "Interop message passing", "transfer-superchainERC20": "How to transfer a SuperchainERC20", "deploy-superchain-erc20": "Deploy assets using SuperchainERC20" } diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx new file mode 100644 index 000000000..8a5de0469 --- /dev/null +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -0,0 +1,247 @@ +--- +title: Interop message passing tutorial +lang: en-US +description: Learn how to pass messages from one superchain blockchain to another +--- + +import { Callout } from 'nextra/components' +import { Steps } from 'nextra/components' +import { InteropCallout } from '@/components/WipCallout' + + + +# Interop message passing tutorial + + + This is a step-by-step tutorial. + You can find an explanation of how this works [here](../message-passing). + + +In this tutorial you learn how to use the [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract to pass a message from one interop blockchain to another. + +In this tutorial we will use several tools: + +- The [Supersim multichain development environment](../tools/supersim) +- [Foundry](https://book.getfoundry.sh/) +- [Viem](https://viem.sh/), running on top of [Node](https://nodejs.org/en) +- UNIX. + This tutorial is designed to run either on Linux or MacOS. + If you use Windows, you can use the [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install). + + +## Install tools + + + +### Foundry + +Download and install [Foundry]. +Part of Foundry is [`anvil`](https://book.getfoundry.sh/anvil/), which runs a local Ethereum node. + +### Supersim + +Download and install [Supersim](https://github.com/ethereum-optimism/supersim/releases). + + + +## Solidity message passing + +In this section you create contracts and transfer a message between them. +For now, we ignore the need for executing messages by turning on autorelay. +Look below for an explanation how to relay your own messages. + + + +### Run the chains + +1. In the directory where Supersim is installed, start it with autorelay. + + ```sh + ./supersim --interop.autorelay + ``` + + Supersim creates three `anvil` blockchains: + + | Role | ChainID | RPC URL | + | - | -: | - | + | L1 | 900 | http://127.0.0.1:8545 + | OPChainA | 901 | http://127.0.0.1:9545 + | OPChainB | 902 | http://127.0.0.1:9546 + +1. Store the configuration in environment variables. + + ```sh + USER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + PRIV_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + RPC_L1=http://localhost:8545 + RPC_A=http://localhost:9545 + RPC_B=http://localhost:9546 + ``` + +1. To verify that the chains are running, check the balance of `$ADDR`. + + ```sh + cast balance --ether $USER_ADDR --rpc-url $RPC_L1 + cast balance --ether $USER_ADDR --rpc-url $RPC_A + cast balance --ether $USER_ADDR --rpc-url $RPC_B + ``` + +### Create the contracts + +1. Create a new Foundry project. + + ```sh + mkdir onchain-code + cd onchain-code + forge init + ``` + +1. In `src/Greeter.sol` put this file. + This is a variation on [Hardhat's Greeter contract](https://github.com/matter-labs/hardhat-zksync/blob/main/examples/upgradable-example/contracts/Greeter.sol). + + ```solidity + //SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + + contract Greeter { + string greeting; + + event SetGreeting( + address sender, // msg.sender + string greeting + ); + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + emit SetGreeting(msg.sender, _greeting); + } + } + ``` + +1. Deploy the contract to chain B and set `GREETER_B_ADDR` to the address for the contract. + + ```sh + GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` + ``` + +
+ + Explanation + + The command that deploys the contract is: + + ```sh + forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast + ``` + + The command output gives us the deployer address, the address of the new contract, and the transaction hash: + + ``` + Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + Deployed to: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 + Transaction hash: 0xf155d360ec70ee10fe0e02d99c16fa5d6dc2a0e79b005fec6cbf7925ff547dbf + ``` + + The [`awk`](https://www.tutorialspoint.com/awk/index.htm) command looks for the line that has `Deployed to:` and writes the third word in that line, which is the address. + + ```sh + awk '/Deployed to:/ {print $3}' + ``` + + Finally, in UNIX (including Linux and MacOS) the when the command line includes backticks (`\``), the shell executes the code between the backticks and puts the output, in this case the contract address, in the command. + So we get. + + ```sh + GREETER_B_ADDR= + ``` + +
+ +1. Run these commands to verify the contract works. + The first and third commands read the greeting, and the second submit a transaction that changes it. + + ```sh + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + cast send --private-key $PRIV_KEY --rpc-url $RPC_B $GREETER_B_ADDR "setGreeting(string)" Hello + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + ``` + +1. Install the Optimism Solidity libraries into the project. + + ```sh + cd lib + npm add @eth-optimism/contracts-bedrock + cd .. + echo @eth-optimism/=lib/node_modules/@eth-optimism/ >> remappings.txt + ``` + +1. The [`@eth-optimism/contracts-bedrock`](https://www.npmjs.com/package/@eth-optimism/contracts-bedrock) library does not have the Interop Solidity code yet. + Run these commands to add it. + + ```sh + 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 + ``` + +1. Create `src/GreetingSender.sol`. + + ```solidity + import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; + import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMe> + + import { Greeter } from "src/Greeter.sol"; + + contract GreetingSender { + IL2ToL2CrossDomainMessenger public immutable messenger = + IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + address immutable greeterAddress; + uint256 immutable greeterChainId; + + constructor(address _greeterAddress, uint256 _greeterChainId) { + greeterAddress = _greeterAddress; + greeterChainId = _greeterChainId; + } + + function setGreeting(string calldata greeting) public { + bytes memory message = abi.encodeCall( + Greeter.setGreeting, + (greeting) + ); + messenger.sendMessage(greeterChainId, greeterAddress, message); + } + } + ``` + +1. Deploy `GreetingSender` to chain A. + + ```sh + GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` + ``` + +1. Attempt to send a greeting from chain A to chain B. + + ```sh + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A" + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + ``` + +
+ +## Sender information + + + + + +## Javascript message relaying + + + + \ No newline at end of file From a37042223cde635a6bd91beabb2ade965ee326d8 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Thu, 6 Feb 2025 11:56:49 -0600 Subject: [PATCH 02/19] Minor changes --- pages/stack/interop/tutorials/message-passing.mdx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index 8a5de0469..3c4ec15b8 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -224,13 +224,15 @@ Look below for an explanation how to relay your own messages. GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` ``` -1. Attempt to send a greeting from chain A to chain B. +### Send a message - ```sh - cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii - cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A" - cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii - ``` +Send a greeting from chain A to chain B. + +```sh +cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii +cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A" +cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii +``` From 4374551b2f3124bfccc8101302763b10b6a39c76 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Thu, 6 Feb 2025 14:22:56 -0600 Subject: [PATCH 03/19] Add sender info --- .../interop/tutorials/message-passing.mdx | 131 +++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index 3c4ec15b8..a9f9168a3 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -107,7 +107,7 @@ Look below for an explanation how to relay your own messages. string greeting; event SetGreeting( - address sender, // msg.sender + address indexed sender, // msg.sender string greeting ); @@ -192,7 +192,7 @@ Look below for an explanation how to relay your own messages. ```solidity import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; - import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMe> + import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; import { Greeter } from "src/Greeter.sol"; @@ -218,6 +218,31 @@ Look below for an explanation how to relay your own messages. } ``` +
+ + Explanation + + ```solidity + function setGreeting(string calldata greeting) public { + bytes memory message = abi.encodeCall( + Greeter.setGreeting, + (greeting) + ); + ``` + + The message is the [calldata](https://docs.soliditylang.org/en/latest/internals/layout_in_calldata.html) sent to the destination contract. + The easiest way to calldata is the use [`abi.encodeCall`](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#abi-encoding-and-decoding-functions). + + + ```solidity + messenger.sendMessage(greeterChainId, greeterAddress, message); + } + ``` + + Actually send the message using [`L2ToL2CrossDomainMessenger.sendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L128-L154). + +
+ 1. Deploy `GreetingSender` to chain A. ```sh @@ -231,19 +256,121 @@ Send a greeting from chain A to chain B. ```sh cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A" +sleep 2 cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii ``` +The `sleep` call is because the message is not relayed until the next chain B block, which can take up to two seconds. + ## Sender information +Run this command to view the events to see who called `setGreeting`. + +```sh +cast logs --rpc-url $RPC_B 'SetGreeting(address,string)' +``` + +The sender is the second topic. +However, when the greetings come from a separate blockchain, that value is just the address of the local `L2ToL2CrossDomainMessenger` contract, which is always `4200000000000000000000000000000000000023`. +That is not useful. + +In this section we change `Greeter.sol` to report + +### Modify the Greeter contract + +1. Modify `src/Greeter.sol` to this code. + + ```solidity + //SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + + import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; + import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; + + contract Greeter { + + IL2ToL2CrossDomainMessenger public immutable messenger = + IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + string greeting; + + event SetGreeting( + address indexed sender, // msg.sender + string greeting + ); + + event CrossDomainSetGreeting( + address indexed sender, // Sender on the other side + uint256 indexed chainId, // ChainID of the other side + string greeting + ); + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + emit SetGreeting(msg.sender, _greeting); + + if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) { + (address sender, uint256 chainId) = + messenger.crossDomainMessageContext(); + emit CrossDomainSetGreeting(sender, chainId, _greeting); + } + } + } + ``` + +
+ + Explanation + + If we see that we got a message from `L2ToL2CrossDomainMessenger`, we call [`L2ToL2CrossDomainMessenger.crossDomainMessageContext`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L118-L126). + +
+ +1. Redeploy the contracts. + Because the address of `Greeter` is immutable in `GreetingSender`, we need to redeploy both contracts. + + ```sh + GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` + GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` + ``` + +### Verify you can see cross chain sender information + +1. Set the greeting through `GreetingSender`. + + ```sh + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A, with an event" + sleep 2 + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + ``` + +1. Read the log entries. + + ```sh + cast logs --rpc-url $RPC_B 'CrossDomainSetGreeting(address,uint256,string)' + echo $GREETER_A_ADDR + echo 0x385 | cast --to-dec + ``` + + See that the second topic (the first indexed log parameter) is the same as `$GREETER_A_ADDR`. + The third topic is `0x385=901`, which is the chain ID for chain A. +
## Javascript message relaying +So far we relied on `--interop.autorelay` to send the executing messages to chain B. +But in real life we are not going to have this, we will need to handle it ourselves. + \ No newline at end of file From 93e3f07e8341a169798b99c19528d84fe872ba92 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Fri, 7 Feb 2025 17:52:38 -0600 Subject: [PATCH 04/19] All except for relaying messages --- .../interop/tutorials/message-passing.mdx | 149 +++++++++++++++++- 1 file changed, 146 insertions(+), 3 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index a9f9168a3..e70e98dca 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -23,7 +23,9 @@ In this tutorial we will use several tools: - The [Supersim multichain development environment](../tools/supersim) - [Foundry](https://book.getfoundry.sh/) -- [Viem](https://viem.sh/), running on top of [Node](https://nodejs.org/en) +- [Viem](https://viem.sh/) +- [Node](https://nodejs.org/en) +- [TypeScript](https://www.typescriptlang.org/) - UNIX. This tutorial is designed to run either on Linux or MacOS. If you use Windows, you can use the [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install). @@ -68,7 +70,7 @@ Look below for an explanation how to relay your own messages. | OPChainA | 901 | http://127.0.0.1:9545 | OPChainB | 902 | http://127.0.0.1:9546 -1. Store the configuration in environment variables. +1. In a separate shell, store the configuration in environment variables. ```sh USER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 @@ -191,6 +193,9 @@ Look below for an explanation how to relay your own messages. 1. Create `src/GreetingSender.sol`. ```solidity + //SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; @@ -369,8 +374,146 @@ In this section we change `Greeter.sol` to report ## Javascript message relaying So far we relied on `--interop.autorelay` to send the executing messages to chain B. -But in real life we are not going to have this, we will need to handle it ourselves. +But we only have it because we're using a development system. +In production we will not have this, we need to create our own executing messages. +### Set up + +We are going to use a [Node](https://nodejs.org/en) project, to be able to get executing messages from the command line. +We use [TypeScript](https://www.typescriptlang.org/) to have type safety combined with JavaScript functionality. + +1. Initialize a new Node project. + + ```sh + mkdir ../offchain-code + cd ../offchain-code + npm init -y + npm install --save-dev -y viem tsx @types/node @eth-optimism/viem + mkdir src + ``` + +1. Edit `package.json` to add the `start` script. + + ```json + { + "name": "offchain-code", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "tsx src/app.mts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "description": "", + "devDependencies": { + "@eth-optimism/viem": "^0.3.0", + "@types/node": "^22.13.1", + "tsx": "^4.19.2", + "viem": "^2.22.23" + } + } + ``` + +1. Export environment variables. + This is necessary because those variables are currently limited to the shell process. + We need them in the Node process that the shell creates. + + ```sh + export GREETER_A_ADDR GREETER_B_ADDR PRIV_KEY RPC_A RPC_B + ``` + +1. Create a simple `src/app.mts` file. + + ```typescript + console.log(`Chain A: ${process.env.RPC_A}\tGreeter ${process.env.GREETER_A_ADDR}`) + console.log(`Chain B: ${process.env.RPC_B}\tGreeter ${process.env.GREETER_B_ADDR}`) + ``` + +1. Run the program. + + ```sh + npm run start + ``` + +### Send a greeting + +1. Link the compiled versions of the code, which include the ABI, to the source code. + + ```sh + cd src + ln -s ../../onchain-code/out/Greeter.sol/Greeter.json . + ln -s ../../onchain-code/out/GreetingSender.sol/GreetingSender.json . + cd .. + ``` + +1. Replace `src/app.mts` with this code. + + ```typescript + import { + createWalletClient, + http, + defineChain, + publicActions, + getContract, + Address, + } from 'viem' + import { privateKeyToAccount } from 'viem/accounts' + import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' + + + import greeterData from './Greeter.json' + import greetingSenderData from './GreetingSender.json' + + const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`) + + const walletA = createWalletClient({ + chain: supersimL2A, + transport: http(), + account + }).extend(publicActions) + + const walletB = createWalletClient({ + chain: supersimL2B, + transport: http(), + account + }).extend(publicActions) + + const greeter = getContract({ + address: process.env.GREETER_B_ADDR as Address, + abi: greeterData.abi, + client: walletB + }) + + const greetingSender = getContract({ + address: process.env.GREETER_A_ADDR as Address, + abi: greetingSenderData.abi, + client: walletA + }) + const txnBHash = await greeter.write.setGreeting(["Greeting directly to chain B"]) + await walletB.waitForTransactionReceipt({hash: txnBHash}) + + const greeting1 = await greeter.read.greet() + console.log(`Chain B Greeting: ${greeting1}`) + + const txnAHash = await greetingSender.write.setGreeting(["Greeting through chain A"]) + await walletA.waitForTransactionReceipt({hash: txnAHash}) + + const greeting2 = await greeter.read.greet() + console.log(`Chain A Greeting: ${greeting2}`) + ``` + +3. Run the program, see that a greeting from chain A is related to chain B. + + ```sh + npm start + ``` + +### Rerun supersim + + \ No newline at end of file From d04403b65712f36476e14517902ed464729283f3 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Sat, 8 Feb 2025 08:36:42 -0600 Subject: [PATCH 05/19] Added rerunning supersim, this time without autorelay --- .../interop/tutorials/message-passing.mdx | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index e70e98dca..4af1ee432 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -50,7 +50,7 @@ Download and install [Supersim](https://github.com/ethereum-optimism/supersim/re In this section you create contracts and transfer a message between them. For now, we ignore the need for executing messages by turning on autorelay. -Look below for an explanation how to relay your own messages. +Look [below](#javascript-message-relaying) for an explanation how to relay your own messages. @@ -353,7 +353,7 @@ In this section we change `Greeter.sol` to report ```sh cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii - cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A, with an event" + cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A, with a CrossDomainSetGreeting event" sleep 2 cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii ``` @@ -515,5 +515,40 @@ We use [TypeScript](https://www.typescriptlang.org/) to have type safety combine ### Rerun supersim +Now we need to rerun Supersim *without* autorelay. + +1. In the windows that runs Supersim, stop it and restart with this command: + + ```sh + ./supersim + ``` + +1. In the window you used for your earlier tests, redeploy the contracts. + Export the addresses so we'll have them in JavaScript + + ```sh + cd ../onchain-code + export GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` + export GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` + cd ../offchain-code + ``` + +1. Rerun the JavaScript program. + + ```sh + npm start + ``` + + See that the transaction to chain B changes the greeting, but the transaction to chain A does not. + + ``` + > offchain-code@1.0.0 start + > tsx src/app.mts + + Chain B Greeting: Greeting directly to chain B + Chain A Greeting: Greeting directly to chain B + ``` + + \ No newline at end of file From 4f16241257666c4ab8ed3d5c2401f1b49dfe48e4 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Sat, 8 Feb 2025 17:40:21 -0600 Subject: [PATCH 06/19] ready for review --- .../interop/tutorials/message-passing.mdx | 207 ++++++++++++++++-- 1 file changed, 193 insertions(+), 14 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index 4af1ee432..6609b51e6 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -15,6 +15,8 @@ import { InteropCallout } from '@/components/WipCallout' This is a step-by-step tutorial. You can find an explanation of how this works [here](../message-passing). + + If you just want a working code example, [see here](#use-the-eth-optimismviem-library-to-relay-the-message). In this tutorial you learn how to use the [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract to pass a message from one interop blockchain to another. @@ -80,7 +82,11 @@ Look [below](#javascript-message-relaying) for an explanation how to relay your RPC_B=http://localhost:9546 ``` -1. To verify that the chains are running, check the balance of `$ADDR`. +
+ +Sanity check + +To verify that the chains are running, check the balance of `$ADDR`. ```sh cast balance --ether $USER_ADDR --rpc-url $RPC_L1 @@ -88,6 +94,8 @@ Look [below](#javascript-message-relaying) for an explanation how to relay your cast balance --ether $USER_ADDR --rpc-url $RPC_B ``` +
+ ### Create the contracts 1. Create a new Foundry project. @@ -163,14 +171,20 @@ Look [below](#javascript-message-relaying) for an explanation how to relay your -1. Run these commands to verify the contract works. - The first and third commands read the greeting, and the second submit a transaction that changes it. +
- ```sh - cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii - cast send --private-key $PRIV_KEY --rpc-url $RPC_B $GREETER_B_ADDR "setGreeting(string)" Hello - cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii - ``` + Sanity check + + Run these commands to verify the contract works. + The first and third commands read the greeting, and the second submit a transaction that changes it. + + ```sh + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + cast send --private-key $PRIV_KEY --rpc-url $RPC_B $GREETER_B_ADDR "setGreeting(string)" Hello + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + ``` + +
1. Install the Optimism Solidity libraries into the project. @@ -281,7 +295,7 @@ The sender is the second topic. However, when the greetings come from a separate blockchain, that value is just the address of the local `L2ToL2CrossDomainMessenger` contract, which is always `4200000000000000000000000000000000000023`. That is not useful. -In this section we change `Greeter.sol` to report +In this section we change `Greeter.sol` to emit a separate event in it receives a cross domain message, with the sender's identity (address and chain ID). @@ -335,6 +349,14 @@ In this section we change `Greeter.sol` to report Explanation + ```solidity + if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) { + (address sender, uint256 chainId) = + messenger.crossDomainMessageContext(); + emit CrossDomainSetGreeting(sender, chainId, _greeting); + } + ``` + If we see that we got a message from `L2ToL2CrossDomainMessenger`, we call [`L2ToL2CrossDomainMessenger.crossDomainMessageContext`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L118-L126). @@ -424,14 +446,14 @@ We use [TypeScript](https://www.typescriptlang.org/) to have type safety combine We need them in the Node process that the shell creates. ```sh - export GREETER_A_ADDR GREETER_B_ADDR PRIV_KEY RPC_A RPC_B + export GREETER_A_ADDR GREETER_B_ADDR PRIV_KEY ``` 1. Create a simple `src/app.mts` file. ```typescript - console.log(`Chain A: ${process.env.RPC_A}\tGreeter ${process.env.GREETER_A_ADDR}`) - console.log(`Chain B: ${process.env.RPC_B}\tGreeter ${process.env.GREETER_B_ADDR}`) + console.log(`Greeter A ${process.env.GREETER_A_ADDR}`) + console.log(`Greeter B ${process.env.GREETER_B_ADDR}`) ``` 1. Run the program. @@ -442,7 +464,7 @@ We use [TypeScript](https://www.typescriptlang.org/) to have type safety combine ### Send a greeting -1. Link the compiled versions of the code, which include the ABI, to the source code. +1. Link the compiled versions of the onchain code, which include the ABI, to the source code. ```sh cd src @@ -549,6 +571,163 @@ Now we need to rerun Supersim *without* autorelay. Chain A Greeting: Greeting directly to chain B ``` +### Use the `@eth-optimism/viem` library to relay the message + +1. Replace `src/app.mts` with: + + ```typescript + import { + createWalletClient, + http, + defineChain, + publicActions, + getContract, + Address, + } from 'viem' + import { privateKeyToAccount } from 'viem/accounts' + import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' + + import { + walletActionsL2, + publicActionsL2, + createInteropSentL2ToL2Messages, + } from '@eth-optimism/viem' + + import greeterData from './Greeter.json' + import greetingSenderData from './GreetingSender.json' + + const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`) + + const walletA = createWalletClient({ + chain: supersimL2A, + transport: http(), + account + }).extend(publicActions) + .extend(publicActionsL2()) + .extend(walletActionsL2()) + + const walletB = createWalletClient({ + chain: supersimL2B, + transport: http(), + account + }).extend(publicActions) + .extend(publicActionsL2()) + .extend(walletActionsL2()) + + const txnBHash = await greeter.write.setGreeting( + ["Greeting directly to chain B"]) + await walletB.waitForTransactionReceipt({hash: txnBHash}) + + const greeting1 = await greeter.read.greet() + console.log(`Chain B Greeting: ${greeting1}`) + + const txnAHash = await greetingSender.write.setGreeting( + ["Greeting through chain A"]) + const receiptA = await walletA.waitForTransactionReceipt({hash: txnAHash}) + + const sentMessage = + (await createInteropSentL2ToL2Messages(walletA, { receipt: receiptA })) + .sentMessages[0] + const relayMsgTxnHash = await walletB.interop.relayMessage({ + sentMessageId: sentMessage.id, + sentMessagePayload: sentMessage.payload, + }) + + const receiptRelay = await walletB.waitForTransactionReceipt( + {hash: relayMsgTxnHash}) + + const greeting2 = await greeter.read.greet() + console.log(`Chain A Greeting: ${greeting2}`) + ``` + +
+ + Explanation + + ```typescript + import { + walletActionsL2, + publicActionsL2, + createInteropSentL2ToL2Messages, + } from '@eth-optimism/viem' + ``` + + Import from the [`@eth-optimism/viem`](https://www.npmjs.com/package/@eth-optimism/viem) package. + + ```typescript + const walletA = createWalletClient({ + chain: supersimL2A, + transport: http(), + account + }).extend(publicActions) + .extend(publicActionsL2()) + .extend(walletActionsL2()) + + const walletB = createWalletClient({ + chain: supersimL2B, + transport: http(), + account + }).extend(publicActions) + .extend(publicActionsL2()) + .extend(walletActionsL2()) + ``` + + In addition to extending the wallets with [Viem public actions](https://viem.sh/docs/accounts/local#5-optional-extend-with-public-actions), extend with the OP-Stack actions, both the public ones and the ones that require an account. + + ```typescript + const receiptA = await walletA.waitForTransactionReceipt({hash: txnAHash}) + ``` + + 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 + const sentMessage = + (await createInteropSentL2ToL2Messages(walletA, { receipt: receiptA })) + .sentMessages[0] + ``` + + 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 + const relayMsgTxnHash = await walletB.interop.relayMessage({ + sentMessageId: sentMessage.id, + sentMessagePayload: sentMessage.payload, + }) + + const receiptRelay = await walletB.waitForTransactionReceipt( + {hash: relayMsgTxnHash}) + ``` + + Here we first send the relay message on chain B, and then wait for the receipt for it. + +
+ +1. Rerun the JavaScript program, and see that the message is relayed. + + ```sh + npm start + ``` + +### Debugging + +To see what messages were relayed by a specific transaction you can use this code: + +```typescript +import { decodeRelayedL2ToL2Messages } from '@eth-optimism/viem' + +const decodedRelays = decodeRelayedL2ToL2Messages( + {receipt: receiptRelay}) + +console.log(decodedRelays) +console.log(decodedRelays.successfulMessages[0].log) +``` + +
+## Next steps - \ No newline at end of file +* Review the [Superchain Interop Explainer](../explainer) for answers to common questions about interoperability. +* Read the [Message Passing Explainer](../message-passing) to understand what happens "under the hood". +* Write a revolutionary app that uses multiple blockchains within the Superchain. From dbd6e8a4f68f903dd22cfa88d78eb762ce1f960b Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Sat, 8 Feb 2025 17:48:22 -0600 Subject: [PATCH 07/19] coderabbit --- pages/stack/interop/tutorials/message-passing.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index 6609b51e6..b6d2f7b2d 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -29,7 +29,7 @@ In this tutorial we will use several tools: - [Node](https://nodejs.org/en) - [TypeScript](https://www.typescriptlang.org/) - UNIX. - This tutorial is designed to run either on Linux or MacOS. + This tutorial is designed to run either on Linux or macOS. If you use Windows, you can use the [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install). @@ -162,7 +162,7 @@ To verify that the chains are running, check the balance of `$ADDR`. awk '/Deployed to:/ {print $3}' ``` - Finally, in UNIX (including Linux and MacOS) the when the command line includes backticks (`\``), the shell executes the code between the backticks and puts the output, in this case the contract address, in the command. + Finally, in UNIX (including Linux and macOS) the when the command line includes backticks (`\``), the shell executes the code between the backticks and puts the output, in this case the contract address, in the command. So we get. ```sh @@ -539,7 +539,7 @@ We use [TypeScript](https://www.typescriptlang.org/) to have type safety combine Now we need to rerun Supersim *without* autorelay. -1. In the windows that runs Supersim, stop it and restart with this command: +1. In the window that runs Supersim, stop it and restart with this command: ```sh ./supersim From a3cfa58a6db4df2302df26be8251af1220acd725 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Mon, 10 Feb 2025 10:25:10 -0600 Subject: [PATCH 08/19] Update message-passing.mdx --- pages/stack/interop/tutorials/message-passing.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index b6d2f7b2d..82eb74d35 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -1,7 +1,7 @@ --- title: Interop message passing tutorial lang: en-US -description: Learn how to pass messages from one superchain blockchain to another +description: Learn how to pass messages from one chain in the Superchain to another. --- import { Callout } from 'nextra/components' From c5f4f4d8c0b14dd84cf3c080659c15a1578a2aa4 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Mon, 10 Feb 2025 10:27:42 -0600 Subject: [PATCH 09/19] Update message-passing.mdx --- pages/stack/interop/tutorials/message-passing.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index 82eb74d35..bd4393b1d 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -86,7 +86,7 @@ Look [below](#javascript-message-relaying) for an explanation how to relay your Sanity check -To verify that the chains are running, check the balance of `$ADDR`. +To verify that the chains are running, check the balance of `$USER_ADDR`. ```sh cast balance --ether $USER_ADDR --rpc-url $RPC_L1 From d658a4f1273cb5563bf392d3dd408e0e705cfb9c Mon Sep 17 00:00:00 2001 From: Blessing Krofegha Date: Mon, 10 Feb 2025 19:16:47 +0100 Subject: [PATCH 10/19] Update message-passing.mdx --- .../interop/tutorials/message-passing.mdx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index bd4393b1d..e81507b34 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -21,16 +21,14 @@ import { InteropCallout } from '@/components/WipCallout' In this tutorial you learn how to use the [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract to pass a message from one interop blockchain to another. -In this tutorial we will use several tools: - -- The [Supersim multichain development environment](../tools/supersim) -- [Foundry](https://book.getfoundry.sh/) -- [Viem](https://viem.sh/) -- [Node](https://nodejs.org/en) -- [TypeScript](https://www.typescriptlang.org/) -- UNIX. - This tutorial is designed to run either on Linux or macOS. - If you use Windows, you can use the [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/install). +## Prerequisites + +Before starting this tutorial, ensure you have: + +* Basic knowledge of Solidity and TypeScript +* Experience with smart contract development +* Familiarity with blockchain concepts +* A Unix-like environment (Linux, macOS, or WSL for Windows) ## Install tools From fd240e5afc86aacd5071c1868c2fbbdbf244231c Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Tue, 11 Feb 2025 13:50:59 -0600 Subject: [PATCH 11/19] @krofax suggestions Co-authored-by: Blessing Krofegha --- .../interop/tutorials/message-passing.mdx | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index e81507b34..804845cad 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -16,10 +16,10 @@ import { InteropCallout } from '@/components/WipCallout' This is a step-by-step tutorial. You can find an explanation of how this works [here](../message-passing). - If you just want a working code example, [see here](#use-the-eth-optimismviem-library-to-relay-the-message). + [See here](#use-the-eth-optimismviem-library-to-relay-the-message), if you want a working code example. -In this tutorial you learn how to use the [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract to pass a message from one interop blockchain to another. +In this tutorial, you will learn how to use the [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract to pass messages between interoperable blockchains. ## Prerequisites @@ -31,22 +31,21 @@ Before starting this tutorial, ensure you have: * A Unix-like environment (Linux, macOS, or WSL for Windows) -## Install tools +## Installing required tools ### Foundry -Download and install [Foundry]. -Part of Foundry is [`anvil`](https://book.getfoundry.sh/anvil/), which runs a local Ethereum node. +Install Foundry by following the [official installation guide](https://book.getfoundry.sh/getting-started/installation). ### Supersim -Download and install [Supersim](https://github.com/ethereum-optimism/supersim/releases). +Download the latest [Supersim release](https://github.com/ethereum-optimism/supersim/releases). -## Solidity message passing +## Implementing solidity message passing In this section you create contracts and transfer a message between them. For now, we ignore the need for executing messages by turning on autorelay. @@ -130,7 +129,7 @@ To verify that the chains are running, check the balance of `$USER_ADDR`. } ``` -1. Deploy the contract to chain B and set `GREETER_B_ADDR` to the address for the contract. +1. Deploy the `Greeter` contract to Chain B and store the resulting contract address in the `GREETER_B_ADDR` environment variable. ```sh GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` @@ -174,7 +173,7 @@ To verify that the chains are running, check the balance of `$USER_ADDR`. Sanity check Run these commands to verify the contract works. - The first and third commands read the greeting, and the second submit a transaction that changes it. + The first and third commands retrieve the current greeting, while the second command updates it. ```sh cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii @@ -289,9 +288,8 @@ Run this command to view the events to see who called `setGreeting`. cast logs --rpc-url $RPC_B 'SetGreeting(address,string)' ``` -The sender is the second topic. -However, when the greetings come from a separate blockchain, that value is just the address of the local `L2ToL2CrossDomainMessenger` contract, which is always `4200000000000000000000000000000000000023`. -That is not useful. +The sender information is stored in the second event topic. +However, for cross-chain messages, this value corresponds to the local `L2ToL2CrossDomainMessenger` contract address (`4200000000000000000000000000000000000023`), making it ineffective for identifying the original sender. In this section we change `Greeter.sol` to emit a separate event in it receives a cross domain message, with the sender's identity (address and chain ID). From 2e755ce0910b1a49909c0aceb057cd5df778b6ce Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Tue, 11 Feb 2025 13:52:17 -0600 Subject: [PATCH 12/19] Update pages/stack/interop/tutorials/message-passing.mdx Co-authored-by: Blessing Krofegha --- pages/stack/interop/tutorials/message-passing.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index 804845cad..d11f3d676 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -1,7 +1,7 @@ --- title: Interop message passing tutorial lang: en-US -description: Learn how to pass messages from one chain in the Superchain to another. +description: Learn how to pass messages between chains in the Superchain using the L2ToL2CrossDomainMessenger contract. --- import { Callout } from 'nextra/components' From 815f63dcbfaa4257419563c245333ebebab3c9d6 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Wed, 12 Feb 2025 11:24:51 -0600 Subject: [PATCH 13/19] Added a "using the devnet" section --- .../interop/tutorials/message-passing.mdx | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index d11f3d676..addb19851 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -610,6 +610,18 @@ Now we need to rerun Supersim *without* autorelay. .extend(publicActionsL2()) .extend(walletActionsL2()) + const greeter = getContract({ + address: process.env.GREETER_B_ADDR as Address, + abi: greeterData.abi, + client: walletB + }) + + const greetingSender = getContract({ + address: process.env.GREETER_A_ADDR as Address, + abi: greetingSenderData.abi, + client: walletA + }) + const txnBHash = await greeter.write.setGreeting( ["Greeting directly to chain B"]) await walletB.waitForTransactionReceipt({hash: txnBHash}) @@ -706,6 +718,69 @@ Now we need to rerun Supersim *without* autorelay. npm start ``` +### Using the devnet + +The same contracts are deployed on [the devnet](../tools/devnet). +You can relay messages in exactly the same way you'd do it on Supersim. + +| Contract | Network | Address +| - | - | - +| `Greeter` | [Devnet 1](../tools/devnet#interop-devnet-1) | [`0x1A183FCf61053B7dcd2322BbE766f7E1946d3718`](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718/contract/420120001/readContract?chainid=420120001) +| `GreetingSender` | [Devnet 0](../tools/devnet#interop-devnet-1) | [`0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f`](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f/contract/420120000/readContract?chainid=420120000) + +1. Set the address environment variables. + + ```sh + export GREETER_A_ADDR=0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f + export GREETER_B_ADDR=0x1A183FCf61053B7dcd2322BbE766f7E1946d3718 + ``` + +1. In `src/app.mts`, replace this line: + + ```typescript + import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' + ``` + + with + + ```typescript + import { chainConfig } from 'viem/op-stack' + + const supersimL2A = defineChain({ + ...chainConfig, + id: 420120000, + name: 'Interop devnet 0', + rpcUrls: { + default: { + http: ['https://interop-alpha-0.optimism.io'], + }, + }, + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + sourceId: 11155111, + testnet: true, + }) + + const supersimL2B = defineChain({ + ...chainConfig, + id: 420120001, + name: 'Interop devnet 1', + rpcUrls: { + default: { + http: ['https://interop-alpha-1.optimism.io'], + }, + }, + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + sourceId: 11155111, + testnet: true, + }) + ``` + +1. Rerun the test. + + ```sh + npm start + ``` + ### Debugging To see what messages were relayed by a specific transaction you can use this code: From 7387006526b483fc01d47ad2fcc522237740b157 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Wed, 12 Feb 2025 22:15:33 -0600 Subject: [PATCH 14/19] Apply suggestions from code review Co-authored-by: Blessing Krofegha --- .../interop/tutorials/message-passing.mdx | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index addb19851..998d19e2b 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -33,27 +33,17 @@ Before starting this tutorial, ensure you have: ## Installing required tools - - -### Foundry - -Install Foundry by following the [official installation guide](https://book.getfoundry.sh/getting-started/installation). - -### Supersim - -Download the latest [Supersim release](https://github.com/ethereum-optimism/supersim/releases). - - +Before starting this tutorial, refer to the [Installation Guide](/app-developers/tutorials/supersim/getting-started/installation) to set up **Foundry** and **Supersim** on your system. ## Implementing solidity message passing -In this section you create contracts and transfer a message between them. -For now, we ignore the need for executing messages by turning on autorelay. -Look [below](#javascript-message-relaying) for an explanation how to relay your own messages. +This section demonstrates how to deploy smart contracts for cross-chain messaging and send messages between them. +For now, we will ignore the need for executing messages by turning on autorelay. +Read more on [how to relay your own messages](#javascript-message-relaying). -### Run the chains +### Setting up test networks 1. In the directory where Supersim is installed, start it with autorelay. @@ -187,7 +177,7 @@ To verify that the chains are running, check the balance of `$USER_ADDR`. ```sh cd lib - npm add @eth-optimism/contracts-bedrock + npm install @eth-optimism/contracts-bedrock cd .. echo @eth-optimism/=lib/node_modules/@eth-optimism/ >> remappings.txt ``` From 1d9af5ae6ac92f43b032e1436c85687ac189e945 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Wed, 12 Feb 2025 23:53:47 -0600 Subject: [PATCH 15/19] with devnet support --- .../interop/tutorials/message-passing.mdx | 74 ++++++++----------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index 998d19e2b..e29a29bfd 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -35,7 +35,7 @@ Before starting this tutorial, ensure you have: Before starting this tutorial, refer to the [Installation Guide](/app-developers/tutorials/supersim/getting-started/installation) to set up **Foundry** and **Supersim** on your system. -## Implementing solidity message passing +## Implementing onchain message passing (in Solidity) This section demonstrates how to deploy smart contracts for cross-chain messaging and send messages between them. For now, we will ignore the need for executing messages by turning on autorelay. @@ -419,7 +419,7 @@ We use [TypeScript](https://www.typescriptlang.org/) to have type safety combine "type": "commonjs", "description": "", "devDependencies": { - "@eth-optimism/viem": "^0.3.0", + "@eth-optimism/viem": "^0.3.2", "@types/node": "^22.13.1", "tsx": "^4.19.2", "viem": "^2.22.23" @@ -435,6 +435,10 @@ We use [TypeScript](https://www.typescriptlang.org/) to have type safety combine export GREETER_A_ADDR GREETER_B_ADDR PRIV_KEY ``` +
+ +Sanity check + 1. Create a simple `src/app.mts` file. ```typescript @@ -448,6 +452,9 @@ We use [TypeScript](https://www.typescriptlang.org/) to have type safety combine npm run start ``` +
+ + ### Send a greeting 1. Link the compiled versions of the onchain code, which include the ABI, to the source code. @@ -459,7 +466,7 @@ We use [TypeScript](https://www.typescriptlang.org/) to have type safety combine cd .. ``` -1. Replace `src/app.mts` with this code. +1. Create or replace `src/app.mts` with this code. ```typescript import { @@ -715,54 +722,25 @@ You can relay messages in exactly the same way you'd do it on Supersim. | Contract | Network | Address | - | - | - -| `Greeter` | [Devnet 1](../tools/devnet#interop-devnet-1) | [`0x1A183FCf61053B7dcd2322BbE766f7E1946d3718`](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718/contract/420120001/readContract?chainid=420120001) +| `Greeter` | [Devnet 1](../tools/devnet#interop-devnet-1) | [`0x1A183FCf61053B7dcd2322BbE766f7E1946d3718`](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718) | `GreetingSender` | [Devnet 0](../tools/devnet#interop-devnet-1) | [`0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f`](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f/contract/420120000/readContract?chainid=420120000) -1. Set the address environment variables. +To modify the program to relay messagez on devnet, follow these steps: - ```sh - export GREETER_A_ADDR=0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f - export GREETER_B_ADDR=0x1A183FCf61053B7dcd2322BbE766f7E1946d3718 - ``` +1. In `src/app.mts`, replace these lines to update the chains and contract addresses. -1. In `src/app.mts`, replace this line: + | Line number | New content | + | -: | - | + | 10 | `import { interopAlpha0, interopAlpha1 } from '@eth-optimism/viem/chains'` + | 24 | ` chain: interopAlpha0,` + | 32 | ` chain: interopAlpha1,` + | 40 | ` address: "0x1A183FCf61053B7dcd2322BbE766f7E1946d3718",` + | 46 | ` address: "0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f",` - ```typescript - import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' - ``` - - with - - ```typescript - import { chainConfig } from 'viem/op-stack' - - const supersimL2A = defineChain({ - ...chainConfig, - id: 420120000, - name: 'Interop devnet 0', - rpcUrls: { - default: { - http: ['https://interop-alpha-0.optimism.io'], - }, - }, - nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, - sourceId: 11155111, - testnet: true, - }) +1. Set `PRIV_KEY` to the private key of an address that has Sepolia ETH on the two chains. - const supersimL2B = defineChain({ - ...chainConfig, - id: 420120001, - name: 'Interop devnet 1', - rpcUrls: { - default: { - http: ['https://interop-alpha-1.optimism.io'], - }, - }, - nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, - sourceId: 11155111, - testnet: true, - }) + ```sh + export PRIV_KEY=0x ``` 1. Rerun the test. @@ -771,6 +749,12 @@ You can relay messages in exactly the same way you'd do it on Supersim. npm start ``` +1. You can see the transactions in a block explorer. + + - The first transaction, which sets the greeting directly, [on the `Greeter` contract on interop1](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718). + - The second transaction, the initiation message for the cross chain greeting change, [on the `GreetingSender` contract on interop0](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f). + - The third transaction, the executing message for the cross chain greeting change, [on the `Greeter` contract on interop1 as an internal transaction](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718/internalTx). + ### Debugging To see what messages were relayed by a specific transaction you can use this code: From e32c9c8250002a553ad8844a7a50d2e3befb2c46 Mon Sep 17 00:00:00 2001 From: krofax Date: Thu, 13 Feb 2025 14:40:15 +0100 Subject: [PATCH 16/19] update steps --- pages/stack/interop/tutorials/message-passing.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index e29a29bfd..09877d3ea 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -59,7 +59,7 @@ Read more on [how to relay your own messages](#javascript-message-relaying). | OPChainA | 901 | http://127.0.0.1:9545 | OPChainB | 902 | http://127.0.0.1:9546 -1. In a separate shell, store the configuration in environment variables. +2. In a separate shell, store the configuration in environment variables. ```sh USER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 @@ -93,7 +93,7 @@ To verify that the chains are running, check the balance of `$USER_ADDR`. forge init ``` -1. In `src/Greeter.sol` put this file. +2. In `src/Greeter.sol` put this file. This is a variation on [Hardhat's Greeter contract](https://github.com/matter-labs/hardhat-zksync/blob/main/examples/upgradable-example/contracts/Greeter.sol). ```solidity @@ -119,7 +119,7 @@ To verify that the chains are running, check the balance of `$USER_ADDR`. } ``` -1. Deploy the `Greeter` contract to Chain B and store the resulting contract address in the `GREETER_B_ADDR` environment variable. +3. Deploy the `Greeter` contract to Chain B and store the resulting contract address in the `GREETER_B_ADDR` environment variable. ```sh GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` From c81b7e0e6de18b1c1d797926ee92b15a904ff59b Mon Sep 17 00:00:00 2001 From: krofax Date: Thu, 13 Feb 2025 17:19:37 +0100 Subject: [PATCH 17/19] updated tutorial --- pages/stack/interop/tutorials.mdx | 2 + .../interop/tutorials/message-passing.mdx | 214 +++++++----------- public/tutorials/Greeter.sol | 20 ++ public/tutorials/GreetingSender.sol | 28 +++ public/tutorials/app.mts | 51 +++++ 5 files changed, 178 insertions(+), 137 deletions(-) create mode 100644 public/tutorials/Greeter.sol create mode 100644 public/tutorials/GreetingSender.sol create mode 100644 public/tutorials/app.mts diff --git a/pages/stack/interop/tutorials.mdx b/pages/stack/interop/tutorials.mdx index 7d9fdddef..f2fd052c4 100644 --- a/pages/stack/interop/tutorials.mdx +++ b/pages/stack/interop/tutorials.mdx @@ -11,6 +11,8 @@ import { Card, Cards } from 'nextra/components' Documentation covering Interop related tutorials. + } /> + } /> } /> diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index 09877d3ea..6f6ae8d32 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -1,7 +1,7 @@ --- title: Interop message passing tutorial lang: en-US -description: Learn how to pass messages between chains in the Superchain using the L2ToL2CrossDomainMessenger contract. +description: Learn to implement cross-chain communication in the Superchain by building a message passing system using the L2ToL2CrossDomainMessenger contract. --- import { Callout } from 'nextra/components' @@ -12,34 +12,82 @@ import { InteropCallout } from '@/components/WipCallout' # Interop message passing tutorial - - This is a step-by-step tutorial. - You can find an explanation of how this works [here](../message-passing). +## Overview + +This tutorial demonstrates how to implement cross-chain communication within the Superchain ecosystem. You'll build a complete +message passing system that enables different chains to interact with each other using the `L2ToL2CrossDomainMessenger` contract. + +### What You'll Build +* A Greeter contract that stores and updates messages +* A GreetingSender contract that sends cross-chain messages +* A TypeScript application to relay messages between chains + +### What you'll learn - [See here](#use-the-eth-optimismviem-library-to-relay-the-message), if you want a working code example. +* How to deploy contracts across different chains +* How to implement cross-chain message passing +* How to handle sender verification across chains +* How to relay messages manually between chains + + + This tutorial provides step-by-step instructions for implementing cross-chain messaging. + For a conceptual overview, +see the [message passing explainer](../message-passing). In this tutorial, you will learn how to use the [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract to pass messages between interoperable blockchains. ## Prerequisites -Before starting this tutorial, ensure you have: +Before starting this tutorial, ensure your development environment meets the following requirements: -* Basic knowledge of Solidity and TypeScript -* Experience with smart contract development +### Technical knowledge + +* Intermediate Solidity programming +* Basic TypeScript knowledge +* Understanding of smart contract development * Familiarity with blockchain concepts -* A Unix-like environment (Linux, macOS, or WSL for Windows) +### Development environment + +* Unix-like operating system (Linux, macOS, or WSL for Windows) +* Node.js version 16 or higher +* Git for version control + +### Required tools + +The tutorial uses these primary tools: +* Foundry: For smart contract development +* Supersim: For local blockchain simulation +* TypeScript: For implementation +* Viem: For blockchain interaction + + +## Setting up your development environment + + + + ### Follow the [Installation Guide](/app-developers/tutorials/supersim/getting-started/installation) to install: + * Foundry for smart contract development + * Supersim for local blockchain simulation -## Installing required tools + ### Verify your installation: + ```sh + forge --version + supersim --version + ``` -Before starting this tutorial, refer to the [Installation Guide](/app-developers/tutorials/supersim/getting-started/installation) to set up **Foundry** and **Supersim** on your system. + ## Implementing onchain message passing (in Solidity) -This section demonstrates how to deploy smart contracts for cross-chain messaging and send messages between them. -For now, we will ignore the need for executing messages by turning on autorelay. -Read more on [how to relay your own messages](#javascript-message-relaying). +The implementation consists of three main components: + +1. **Greeter Contract**: Deployed on `Chain B`, receives and stores messages. +2. **GreetingSender Contract**: Deployed on `Chain A`, initiates cross-chain messages. +3. **Message relay system**: Ensures message delivery between chains. + +For development purposes, we'll first use autorelay mode to handle message execution automatically. Later sections cover [manual message relaying](#javascript-message-relaying) for production environments. @@ -48,7 +96,7 @@ Read more on [how to relay your own messages](#javascript-message-relaying). 1. In the directory where Supersim is installed, start it with autorelay. ```sh - ./supersim --interop.autorelay + supersim ``` Supersim creates three `anvil` blockchains: @@ -90,34 +138,13 @@ To verify that the chains are running, check the balance of `$USER_ADDR`. ```sh mkdir onchain-code cd onchain-code - forge init + forge init ``` - 2. In `src/Greeter.sol` put this file. This is a variation on [Hardhat's Greeter contract](https://github.com/matter-labs/hardhat-zksync/blob/main/examples/upgradable-example/contracts/Greeter.sol). - ```solidity - //SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - contract Greeter { - string greeting; - - event SetGreeting( - address indexed sender, // msg.sender - string greeting - ); - - function greet() public view returns (string memory) { - return greeting; - } - - function setGreeting(string memory _greeting) public { - greeting = _greeting; - emit SetGreeting(msg.sender, _greeting); - } - } - ``` +```solidity file=/public/tutorials/Greeter.sol#L1-L20 hash=b3c5550bcc2cc4272125388ef23a67e7 +``` 3. Deploy the `Greeter` contract to Chain B and store the resulting contract address in the `GREETER_B_ADDR` environment variable. @@ -193,60 +220,23 @@ To verify that the chains are running, check the balance of `$USER_ADDR`. 1. Create `src/GreetingSender.sol`. - ```solidity - //SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - - import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; - import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; - - import { Greeter } from "src/Greeter.sol"; - - contract GreetingSender { - IL2ToL2CrossDomainMessenger public immutable messenger = - IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - - address immutable greeterAddress; - uint256 immutable greeterChainId; - - constructor(address _greeterAddress, uint256 _greeterChainId) { - greeterAddress = _greeterAddress; - greeterChainId = _greeterChainId; - } - - function setGreeting(string calldata greeting) public { - bytes memory message = abi.encodeCall( - Greeter.setGreeting, - (greeting) - ); - messenger.sendMessage(greeterChainId, greeterAddress, message); - } - } + ```solidity file=/public/tutorials/GreetingSender.sol#L1-L28 hash=75d197d1e1da112421785c2160f6a55a ```
Explanation - ```solidity - function setGreeting(string calldata greeting) public { - bytes memory message = abi.encodeCall( - Greeter.setGreeting, - (greeting) - ); + ```solidity file=/public/tutorials/GreetingSender.sol#L21-L27 hash=6c27ebcf4916e5aa2325d30f99c65436 ``` - The message is the [calldata](https://docs.soliditylang.org/en/latest/internals/layout_in_calldata.html) sent to the destination contract. - The easiest way to calldata is the use [`abi.encodeCall`](https://docs.soliditylang.org/en/latest/units-and-global-variables.html#abi-encoding-and-decoding-functions). - - - ```solidity - messenger.sendMessage(greeterChainId, greeterAddress, message); - } - ``` + This function encodes a call to `setGreeting` and sends it to a contract on another chain. - Actually send the message using [`L2ToL2CrossDomainMessenger.sendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L128-L154). + * `abi.encodeCall(Greeter.setGreeting, (greeting))` constructs the [calldata](https://docs.soliditylang.org/en/latest/internals/layout_in_calldata.html) by encoding the function selector and parameters. + * The encoded message is then passed to `messenger.sendMessage`, which forwards it to the destination contract (`greeterAddress`) on the specified chain (`greeterChainId`). + This ensures that `setGreeting` is executed remotely with the provided `greeting` value. +
1. Deploy `GreetingSender` to chain A. @@ -379,7 +369,7 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
-## Javascript message relaying +## Implement manual message relaying So far we relied on `--interop.autorelay` to send the executing messages to chain B. But we only have it because we're using a development system. @@ -468,59 +458,9 @@ We use [TypeScript](https://www.typescriptlang.org/) to have type safety combine 1. Create or replace `src/app.mts` with this code. - ```typescript - import { - createWalletClient, - http, - defineChain, - publicActions, - getContract, - Address, - } from 'viem' - import { privateKeyToAccount } from 'viem/accounts' - import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' - - - import greeterData from './Greeter.json' - import greetingSenderData from './GreetingSender.json' - - const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`) - - const walletA = createWalletClient({ - chain: supersimL2A, - transport: http(), - account - }).extend(publicActions) - - const walletB = createWalletClient({ - chain: supersimL2B, - transport: http(), - account - }).extend(publicActions) - - const greeter = getContract({ - address: process.env.GREETER_B_ADDR as Address, - abi: greeterData.abi, - client: walletB - }) - - const greetingSender = getContract({ - address: process.env.GREETER_A_ADDR as Address, - abi: greetingSenderData.abi, - client: walletA - }) - const txnBHash = await greeter.write.setGreeting(["Greeting directly to chain B"]) - await walletB.waitForTransactionReceipt({hash: txnBHash}) - - const greeting1 = await greeter.read.greet() - console.log(`Chain B Greeting: ${greeting1}`) - - const txnAHash = await greetingSender.write.setGreeting(["Greeting through chain A"]) - await walletA.waitForTransactionReceipt({hash: txnAHash}) +```solidity file=/public/tutorials/app.mts#L1-L51 hash=8f6f776884b8e37ae613f7aea8cd6a3b +``` - const greeting2 = await greeter.read.greet() - console.log(`Chain A Greeting: ${greeting2}`) - ``` 3. Run the program, see that a greeting from chain A is related to chain B. @@ -564,7 +504,7 @@ Now we need to rerun Supersim *without* autorelay. Chain A Greeting: Greeting directly to chain B ``` -### Use the `@eth-optimism/viem` library to relay the message +### Add manual relaying logic 1. Replace `src/app.mts` with: diff --git a/public/tutorials/Greeter.sol b/public/tutorials/Greeter.sol new file mode 100644 index 000000000..2a4f16065 --- /dev/null +++ b/public/tutorials/Greeter.sol @@ -0,0 +1,20 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Greeter { + string greeting; + + event SetGreeting( + address indexed sender, // msg.sender + string greeting + ); + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + emit SetGreeting(msg.sender, _greeting); + } +} \ No newline at end of file diff --git a/public/tutorials/GreetingSender.sol b/public/tutorials/GreetingSender.sol new file mode 100644 index 000000000..fa6cf3233 --- /dev/null +++ b/public/tutorials/GreetingSender.sol @@ -0,0 +1,28 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; +import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; + +import { Greeter } from "src/Greeter.sol"; + +contract GreetingSender { + IL2ToL2CrossDomainMessenger public immutable messenger = + IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + address immutable greeterAddress; + uint256 immutable greeterChainId; + + constructor(address _greeterAddress, uint256 _greeterChainId) { + greeterAddress = _greeterAddress; + greeterChainId = _greeterChainId; + } + + function setGreeting(string calldata greeting) public { + bytes memory message = abi.encodeCall( + Greeter.setGreeting, + (greeting) + ); + messenger.sendMessage(greeterChainId, greeterAddress, message); + } +} \ No newline at end of file diff --git a/public/tutorials/app.mts b/public/tutorials/app.mts new file mode 100644 index 000000000..b850cddc6 --- /dev/null +++ b/public/tutorials/app.mts @@ -0,0 +1,51 @@ +import { + createWalletClient, + http, + defineChain, + publicActions, + getContract, + Address, +} from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' + + +import greeterData from './Greeter.json' +import greetingSenderData from './GreetingSender.json' + +const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`) + +const walletA = createWalletClient({ + chain: supersimL2A, + transport: http(), + account +}).extend(publicActions) + +const walletB = createWalletClient({ + chain: supersimL2B, + transport: http(), + account +}).extend(publicActions) + +const greeter = getContract({ + address: process.env.GREETER_B_ADDR as Address, + abi: greeterData.abi, + client: walletB +}) + +const greetingSender = getContract({ + address: process.env.GREETER_A_ADDR as Address, + abi: greetingSenderData.abi, + client: walletA +}) +const txnBHash = await greeter.write.setGreeting(["Greeting directly to chain B"]) +await walletB.waitForTransactionReceipt({hash: txnBHash}) + +const greeting1 = await greeter.read.greet() +console.log(`Chain B Greeting: ${greeting1}`) + +const txnAHash = await greetingSender.write.setGreeting(["Greeting through chain A"]) +await walletA.waitForTransactionReceipt({hash: txnAHash}) + +const greeting2 = await greeter.read.greet() +console.log(`Chain A Greeting: ${greeting2}`) \ No newline at end of file From a2abef83d44a5cc88a5e05e3ad531c9beb6b5286 Mon Sep 17 00:00:00 2001 From: krofax Date: Thu, 13 Feb 2025 17:48:03 +0100 Subject: [PATCH 18/19] fix explanation --- .../interop/tutorials/message-passing.mdx | 873 ++++++++---------- words.txt | 1 + 2 files changed, 377 insertions(+), 497 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index 6f6ae8d32..ee5a5b086 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -14,10 +14,11 @@ import { InteropCallout } from '@/components/WipCallout' ## Overview -This tutorial demonstrates how to implement cross-chain communication within the Superchain ecosystem. You'll build a complete +This tutorial demonstrates how to implement cross-chain communication within the Superchain ecosystem. You'll build a complete message passing system that enables different chains to interact with each other using the `L2ToL2CrossDomainMessenger` contract. ### What You'll Build + * A Greeter contract that stores and updates messages * A GreetingSender contract that sends cross-chain messages * A TypeScript application to relay messages between chains @@ -30,9 +31,9 @@ message passing system that enables different chains to interact with each other * How to relay messages manually between chains - This tutorial provides step-by-step instructions for implementing cross-chain messaging. - For a conceptual overview, -see the [message passing explainer](../message-passing). + This tutorial provides step-by-step instructions for implementing cross-chain messaging. + For a conceptual overview, + see the [message passing explainer](../message-passing). In this tutorial, you will learn how to use the [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract to pass messages between interoperable blockchains. @@ -43,221 +44,211 @@ Before starting this tutorial, ensure your development environment meets the fol ### Technical knowledge -* Intermediate Solidity programming -* Basic TypeScript knowledge -* Understanding of smart contract development -* Familiarity with blockchain concepts +* Intermediate Solidity programming +* Basic TypeScript knowledge +* Understanding of smart contract development +* Familiarity with blockchain concepts ### Development environment -* Unix-like operating system (Linux, macOS, or WSL for Windows) -* Node.js version 16 or higher -* Git for version control +* Unix-like operating system (Linux, macOS, or WSL for Windows) +* Node.js version 16 or higher +* Git for version control ### Required tools The tutorial uses these primary tools: -* Foundry: For smart contract development -* Supersim: For local blockchain simulation -* TypeScript: For implementation -* Viem: For blockchain interaction +* Foundry: For smart contract development +* Supersim: For local blockchain simulation +* TypeScript: For implementation +* Viem: For blockchain interaction ## Setting up your development environment + ### Follow the [Installation Guide](/app-developers/tutorials/supersim/getting-started/installation) to install: - ### Follow the [Installation Guide](/app-developers/tutorials/supersim/getting-started/installation) to install: - * Foundry for smart contract development - * Supersim for local blockchain simulation + * Foundry for smart contract development + * Supersim for local blockchain simulation - ### Verify your installation: - ```sh - forge --version - supersim --version - ``` + ### Verify your installation: + ```sh + forge --version + supersim --version + ``` ## Implementing onchain message passing (in Solidity) The implementation consists of three main components: -1. **Greeter Contract**: Deployed on `Chain B`, receives and stores messages. -2. **GreetingSender Contract**: Deployed on `Chain A`, initiates cross-chain messages. -3. **Message relay system**: Ensures message delivery between chains. +1. **Greeter Contract**: Deployed on `Chain B`, receives and stores messages. +2. **GreetingSender Contract**: Deployed on `Chain A`, initiates cross-chain messages. +3. **Message relay system**: Ensures message delivery between chains. For development purposes, we'll first use autorelay mode to handle message execution automatically. Later sections cover [manual message relaying](#javascript-message-relaying) for production environments. + ### Setting up test networks -### Setting up test networks - -1. In the directory where Supersim is installed, start it with autorelay. - - ```sh - supersim - ``` + 1. In the directory where Supersim is installed, start it with autorelay. - Supersim creates three `anvil` blockchains: + ```sh + supersim + ``` - | Role | ChainID | RPC URL | - | - | -: | - | - | L1 | 900 | http://127.0.0.1:8545 - | OPChainA | 901 | http://127.0.0.1:9545 - | OPChainB | 902 | http://127.0.0.1:9546 + Supersim creates three `anvil` blockchains: -2. In a separate shell, store the configuration in environment variables. + | Role | ChainID | RPC URL | + | -------* | ------: | ---------------------------------------------* | + | L1 | 900 | [http://127.0.0.1:8545](http://127.0.0.1:8545) | + | OPChainA | 901 | [http://127.0.0.1:9545](http://127.0.0.1:9545) | + | OPChainB | 902 | [http://127.0.0.1:9546](http://127.0.0.1:9546) | - ```sh - USER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - PRIV_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 - RPC_L1=http://localhost:8545 - RPC_A=http://localhost:9545 - RPC_B=http://localhost:9546 - ``` + 2. In a separate shell, store the configuration in environment variables. -
+ ```sh + USER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + PRIV_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + RPC_L1=http://localhost:8545 + RPC_A=http://localhost:9545 + RPC_B=http://localhost:9546 + ``` -Sanity check +
+ Sanity check -To verify that the chains are running, check the balance of `$USER_ADDR`. + To verify that the chains are running, check the balance of `$USER_ADDR`. ```sh cast balance --ether $USER_ADDR --rpc-url $RPC_L1 cast balance --ether $USER_ADDR --rpc-url $RPC_A cast balance --ether $USER_ADDR --rpc-url $RPC_B ``` +
-
- -### Create the contracts + ### Create the contracts -1. Create a new Foundry project. + 1. Create a new Foundry project. - ```sh - mkdir onchain-code - cd onchain-code - forge init - ``` -2. In `src/Greeter.sol` put this file. - This is a variation on [Hardhat's Greeter contract](https://github.com/matter-labs/hardhat-zksync/blob/main/examples/upgradable-example/contracts/Greeter.sol). + ```sh + mkdir onchain-code + cd onchain-code + forge init + ``` + 2. In `src/Greeter.sol` put this file. + This is a variation on [Hardhat's Greeter contract](https://github.com/matter-labs/hardhat-zksync/blob/main/examples/upgradable-example/contracts/Greeter.sol). -```solidity file=/public/tutorials/Greeter.sol#L1-L20 hash=b3c5550bcc2cc4272125388ef23a67e7 -``` + ```solidity file=/public/tutorials/Greeter.sol#L1-L20 hash=b3c5550bcc2cc4272125388ef23a67e7 + ``` -3. Deploy the `Greeter` contract to Chain B and store the resulting contract address in the `GREETER_B_ADDR` environment variable. + 3. Deploy the `Greeter` contract to Chain B and store the resulting contract address in the `GREETER_B_ADDR` environment variable. - ```sh - GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` - ``` - -
- - Explanation - - The command that deploys the contract is: - - ```sh - forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast - ``` - - The command output gives us the deployer address, the address of the new contract, and the transaction hash: - - ``` - Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - Deployed to: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 - Transaction hash: 0xf155d360ec70ee10fe0e02d99c16fa5d6dc2a0e79b005fec6cbf7925ff547dbf - ``` + ```sh + GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` + ``` - The [`awk`](https://www.tutorialspoint.com/awk/index.htm) command looks for the line that has `Deployed to:` and writes the third word in that line, which is the address. +
+ Explanation - ```sh - awk '/Deployed to:/ {print $3}' - ``` + The command that deploys the contract is: - Finally, in UNIX (including Linux and macOS) the when the command line includes backticks (`\``), the shell executes the code between the backticks and puts the output, in this case the contract address, in the command. - So we get. + ```sh + forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast + ``` - ```sh - GREETER_B_ADDR= - ``` + The command output gives us the deployer address, the address of the new contract, and the transaction hash: -
+ ``` + Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + Deployed to: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 + Transaction hash: 0xf155d360ec70ee10fe0e02d99c16fa5d6dc2a0e79b005fec6cbf7925ff547dbf + ``` -
+ The [`awk`](https://www.tutorialspoint.com/awk/index.htm) command looks for the line that has `Deployed to:` and writes the third word in that line, which is the address. - Sanity check + ```sh + awk '/Deployed to:/ {print $3}' + ``` - Run these commands to verify the contract works. - The first and third commands retrieve the current greeting, while the second command updates it. + Finally, in UNIX (including Linux and macOS) the when the command line includes backticks (\`\`\`), the shell executes the code between the backticks and puts the output, in this case the contract address, in the command. + So we get. - ```sh - cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii - cast send --private-key $PRIV_KEY --rpc-url $RPC_B $GREETER_B_ADDR "setGreeting(string)" Hello - cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii - ``` + ```sh + GREETER_B_ADDR= + ``` +
-
+
+ Sanity check -1. Install the Optimism Solidity libraries into the project. + Run these commands to verify the contract works. + The first and third commands retrieve the current greeting, while the second command updates it. - ```sh - cd lib - npm install @eth-optimism/contracts-bedrock - cd .. - echo @eth-optimism/=lib/node_modules/@eth-optimism/ >> remappings.txt - ``` + ```sh + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + cast send --private-key $PRIV_KEY --rpc-url $RPC_B $GREETER_B_ADDR "setGreeting(string)" Hello + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + ``` +
-1. The [`@eth-optimism/contracts-bedrock`](https://www.npmjs.com/package/@eth-optimism/contracts-bedrock) library does not have the Interop Solidity code yet. - Run these commands to add it. + 4. Install the Optimism Solidity libraries into the project. - ```sh - 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 - ``` + ```sh + cd lib + npm install @eth-optimism/contracts-bedrock + cd .. + echo @eth-optimism/=lib/node_modules/@eth-optimism/ >> remappings.txt + ``` -1. Create `src/GreetingSender.sol`. + 5. The [`@eth-optimism/contracts-bedrock`](https://www.npmjs.com/package/@eth-optimism/contracts-bedrock) library does not have the Interop Solidity code yet. + Run these commands to add it. - ```solidity file=/public/tutorials/GreetingSender.sol#L1-L28 hash=75d197d1e1da112421785c2160f6a55a - ``` + ```sh + 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 + ``` -
+ 6. Create `src/GreetingSender.sol`. - Explanation + ```solidity file=/public/tutorials/GreetingSender.sol#L1-L28 hash=75d197d1e1da112421785c2160f6a55a + ``` - ```solidity file=/public/tutorials/GreetingSender.sol#L21-L27 hash=6c27ebcf4916e5aa2325d30f99c65436 - ``` +
+ Explanation - This function encodes a call to `setGreeting` and sends it to a contract on another chain. + ```solidity file=/public/tutorials/GreetingSender.sol#L21-L27 hash=6c27ebcf4916e5aa2325d30f99c65436 + ``` - * `abi.encodeCall(Greeter.setGreeting, (greeting))` constructs the [calldata](https://docs.soliditylang.org/en/latest/internals/layout_in_calldata.html) by encoding the function selector and parameters. - * The encoded message is then passed to `messenger.sendMessage`, which forwards it to the destination contract (`greeterAddress`) on the specified chain (`greeterChainId`). - This ensures that `setGreeting` is executed remotely with the provided `greeting` value. + This function encodes a call to `setGreeting` and sends it to a contract on another chain. - -
+ * `abi.encodeCall(Greeter.setGreeting, (greeting))` constructs the [calldata](https://docs.soliditylang.org/en/latest/internals/layout_in_calldata.html) by encoding the function selector and parameters. -1. Deploy `GreetingSender` to chain A. + * The encoded message is then passed to `messenger.sendMessage`, which forwards it to the destination contract (`greeterAddress`) on the specified chain (`greeterChainId`). + This ensures that `setGreeting` is executed remotely with the provided `greeting` value. +
- ```sh - GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` - ``` + 7. Deploy `GreetingSender` to chain A. -### Send a message + ```sh + GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` + ``` -Send a greeting from chain A to chain B. + ### Send a message -```sh -cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii -cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A" -sleep 2 -cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii -``` + Send a greeting from chain A to chain B. -The `sleep` call is because the message is not relayed until the next chain B block, which can take up to two seconds. + ```sh + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A" + sleep 2 + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + ``` + The `sleep` call is because the message is not relayed until the next chain B block, which can take up to two seconds.
## Sender information @@ -271,102 +262,98 @@ cast logs --rpc-url $RPC_B 'SetGreeting(address,string)' The sender information is stored in the second event topic. However, for cross-chain messages, this value corresponds to the local `L2ToL2CrossDomainMessenger` contract address (`4200000000000000000000000000000000000023`), making it ineffective for identifying the original sender. -In this section we change `Greeter.sol` to emit a separate event in it receives a cross domain message, with the sender's identity (address and chain ID). +In this section we change `Greeter.sol` to emit a separate event in it receives a cross domain message, with the sender's identity (address and chain ID). + ### Modify the Greeter contract -### Modify the Greeter contract + 1. Modify `src/Greeter.sol` to this code. -1. Modify `src/Greeter.sol` to this code. + ```solidity + //SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; - ```solidity - //SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; + import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; + import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; - import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; - import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol"; + contract Greeter { - contract Greeter { + IL2ToL2CrossDomainMessenger public immutable messenger = + IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - IL2ToL2CrossDomainMessenger public immutable messenger = - IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + string greeting; - string greeting; + event SetGreeting( + address indexed sender, // msg.sender + string greeting + ); - event SetGreeting( - address indexed sender, // msg.sender - string greeting - ); + event CrossDomainSetGreeting( + address indexed sender, // Sender on the other side + uint256 indexed chainId, // ChainID of the other side + string greeting + ); - event CrossDomainSetGreeting( - address indexed sender, // Sender on the other side - uint256 indexed chainId, // ChainID of the other side - string greeting - ); + function greet() public view returns (string memory) { + return greeting; + } - function greet() public view returns (string memory) { - return greeting; - } + function setGreeting(string memory _greeting) public { + greeting = _greeting; + emit SetGreeting(msg.sender, _greeting); - function setGreeting(string memory _greeting) public { - greeting = _greeting; - emit SetGreeting(msg.sender, _greeting); + if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) { + (address sender, uint256 chainId) = + messenger.crossDomainMessageContext(); + emit CrossDomainSetGreeting(sender, chainId, _greeting); + } + } + } + ``` - if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) { - (address sender, uint256 chainId) = - messenger.crossDomainMessageContext(); - emit CrossDomainSetGreeting(sender, chainId, _greeting); - } - } - } - ``` +
+ Explanation -
+ ```solidity + if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) { + (address sender, uint256 chainId) = + messenger.crossDomainMessageContext(); + emit CrossDomainSetGreeting(sender, chainId, _greeting); + } + ``` - Explanation + If we see that we got a message from `L2ToL2CrossDomainMessenger`, we call [`L2ToL2CrossDomainMessenger.crossDomainMessageContext`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L118-L126). +
- ```solidity - if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) { - (address sender, uint256 chainId) = - messenger.crossDomainMessageContext(); - emit CrossDomainSetGreeting(sender, chainId, _greeting); - } - ``` + 2. Redeploy the contracts. + Because the address of `Greeter` is immutable in `GreetingSender`, we need to redeploy both contracts. - If we see that we got a message from `L2ToL2CrossDomainMessenger`, we call [`L2ToL2CrossDomainMessenger.crossDomainMessageContext`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L118-L126). + ```sh + GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` + GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` + ``` -
+ ### Verify you can see cross chain sender information -1. Redeploy the contracts. - Because the address of `Greeter` is immutable in `GreetingSender`, we need to redeploy both contracts. + 1. Set the greeting through `GreetingSender`. - ```sh - GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` - GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` - ``` + ```sh + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A, with a CrossDomainSetGreeting event" + sleep 2 + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + ``` -### Verify you can see cross chain sender information + 2. Read the log entries. -1. Set the greeting through `GreetingSender`. - - ```sh - cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii - cast send --private-key $PRIV_KEY --rpc-url $RPC_A $GREETER_A_ADDR "setGreeting(string)" "Hello from chain A, with a CrossDomainSetGreeting event" - sleep 2 - cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii - ``` - -1. Read the log entries. - - ```sh - cast logs --rpc-url $RPC_B 'CrossDomainSetGreeting(address,uint256,string)' - echo $GREETER_A_ADDR - echo 0x385 | cast --to-dec - ``` - - See that the second topic (the first indexed log parameter) is the same as `$GREETER_A_ADDR`. - The third topic is `0x385=901`, which is the chain ID for chain A. + ```sh + cast logs --rpc-url $RPC_B 'CrossDomainSetGreeting(address,uint256,string)' + echo $GREETER_A_ADDR + echo 0x385 | cast --to-dec + ``` + See that the second topic (the first indexed log parameter) is the same as `$GREETER_A_ADDR`. + The third topic is `0x385=901`, which is the chain ID for chain A.
## Implement manual message relaying @@ -376,339 +363,231 @@ But we only have it because we're using a development system. In production we will not have this, we need to create our own executing messages. + ### Set up + + We are going to use a [Node](https://nodejs.org/en) project, to be able to get executing messages from the command line. + We use [TypeScript](https://www.typescriptlang.org/) to have type safety combined with JavaScript functionality. + + 1. Initialize a new Node project. + + ```sh + mkdir ../offchain-code + cd ../offchain-code + npm init -y + npm install --save-dev -y viem tsx @types/node @eth-optimism/viem + mkdir src + ``` + + 2. Edit `package.json` to add the `start` script. + + ```json + { + "name": "offchain-code", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "tsx src/app.mts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "description": "", + "devDependencies": { + "@eth-optimism/viem": "^0.3.2", + "@types/node": "^22.13.1", + "tsx": "^4.19.2", + "viem": "^2.22.23" + } + } + ``` + + 3. Export environment variables. + This is necessary because those variables are currently limited to the shell process. + We need them in the Node process that the shell creates. + + ```sh + export GREETER_A_ADDR GREETER_B_ADDR PRIV_KEY + ``` + +
+ Sanity check -### Set up + 1. Create a simple `src/app.mts` file. -We are going to use a [Node](https://nodejs.org/en) project, to be able to get executing messages from the command line. -We use [TypeScript](https://www.typescriptlang.org/) to have type safety combined with JavaScript functionality. + ```typescript + console.log(`Greeter A ${process.env.GREETER_A_ADDR}`) + console.log(`Greeter B ${process.env.GREETER_B_ADDR}`) + ``` -1. Initialize a new Node project. + 2. Run the program. - ```sh - mkdir ../offchain-code - cd ../offchain-code - npm init -y - npm install --save-dev -y viem tsx @types/node @eth-optimism/viem - mkdir src - ``` + ```sh + npm run start + ``` +
-1. Edit `package.json` to add the `start` script. - - ```json - { - "name": "offchain-code", - "version": "1.0.0", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "tsx src/app.mts" - }, - "keywords": [], - "author": "", - "license": "ISC", - "type": "commonjs", - "description": "", - "devDependencies": { - "@eth-optimism/viem": "^0.3.2", - "@types/node": "^22.13.1", - "tsx": "^4.19.2", - "viem": "^2.22.23" - } - } - ``` + ### Send a greeting -1. Export environment variables. - This is necessary because those variables are currently limited to the shell process. - We need them in the Node process that the shell creates. + 1. Link the compiled versions of the onchain code, which include the ABI, to the source code. - ```sh - export GREETER_A_ADDR GREETER_B_ADDR PRIV_KEY - ``` + ```sh + cd src + ln -s ../../onchain-code/out/Greeter.sol/Greeter.json . + ln -s ../../onchain-code/out/GreetingSender.sol/GreetingSender.json . + cd .. + ``` -
+ 2. Create or replace `src/app.mts` with this code. -Sanity check + ```solidity file=/public/tutorials/app.mts#L1-L51 hash=8f6f776884b8e37ae613f7aea8cd6a3b + ``` -1. Create a simple `src/app.mts` file. + 3. Run the program, see that a greeting from chain A is related to chain B. - ```typescript - console.log(`Greeter A ${process.env.GREETER_A_ADDR}`) - console.log(`Greeter B ${process.env.GREETER_B_ADDR}`) - ``` + ```sh + npm start + ``` -1. Run the program. + ### Rerun supersim - ```sh - npm run start - ``` + Now we need to rerun Supersim *without* autorelay. -
+ 1. In the window that runs Supersim, stop it and restart with this command: + ```sh + ./supersim + ``` -### Send a greeting + 2. In the window you used for your earlier tests, redeploy the contracts. + Export the addresses so we'll have them in JavaScript -1. Link the compiled versions of the onchain code, which include the ABI, to the source code. + ```sh + cd ../onchain-code + export GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` + export GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` + cd ../offchain-code + ``` - ```sh - cd src - ln -s ../../onchain-code/out/Greeter.sol/Greeter.json . - ln -s ../../onchain-code/out/GreetingSender.sol/GreetingSender.json . - cd .. - ``` + 3. Rerun the JavaScript program. -1. Create or replace `src/app.mts` with this code. + ```sh + npm start + ``` -```solidity file=/public/tutorials/app.mts#L1-L51 hash=8f6f776884b8e37ae613f7aea8cd6a3b -``` + See that the transaction to chain B changes the greeting, but the transaction to chain A does not. + ``` + > offchain-code@1.0.0 start + > tsx src/app.mts -3. Run the program, see that a greeting from chain A is related to chain B. + Chain B Greeting: Greeting directly to chain B + Chain A Greeting: Greeting directly to chain B + ``` - ```sh - npm start - ``` + ### Add manual relaying logic -### Rerun supersim + 1. Replace `src/app.mts` with: -Now we need to rerun Supersim *without* autorelay. + ```js file=/public/tutorials/app.mts#L1-L51 hash=8f6f776884b8e37ae613f7aea8cd6a3b + ``` -1. In the window that runs Supersim, stop it and restart with this command: +
+ Explanation - ```sh - ./supersim - ``` + 1. **Import Required Libraries** + * Imports functions from `viem` for wallet creation, HTTP transport, and contract interactions. + * Imports `@eth-optimism/viem` utilities for handling OP-Stack-specific actions and interoperability. + * Loads ABI definitions from `Greeter.json` and `GreetingSender.json` for contract interactions. -1. In the window you used for your earlier tests, redeploy the contracts. - Export the addresses so we'll have them in JavaScript + 2. **Initialize Wallet Clients** + * Uses `privateKeyToAccount` to generate an account from an environment variable. + * Creates `walletA` for chain `supersimL2A` and `walletB` for chain `supersimL2B`, extending them with Viem’s public and OP-Stack-specific actions. - ```sh - cd ../onchain-code - export GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` - export GREETER_A_ADDR=`forge create --rpc-url $RPC_A --private-key $PRIV_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDR 902 | awk '/Deployed to:/ {print $3}'` - cd ../offchain-code - ``` + 3. **Get Contract Instances** + * Retrieves contract instances for `greeter` on `walletB` and `greetingSender` on `walletA` using `getContract`. + * The addresses are taken from environment variables, and the clients are set to the respective wallets. -1. Rerun the JavaScript program. + 4. **Direct Greeting on Chain B** + * Calls `setGreeting` on `greeter` to store a greeting directly on chain B. + * Waits for the transaction to be confirmed using `waitForTransactionReceipt`. + * Reads and logs the greeting stored on chain B. - ```sh - npm start - ``` + 5. **Cross-Chain Greeting via Chain A** + * Calls `setGreeting` on `greetingSender` to send a greeting through chain A. + * Waits for the transaction receipt on chain A. - See that the transaction to chain B changes the greeting, but the transaction to chain A does not. + 6. **Retrieve and Relay the Cross-Chain Message** + * Extracts the message from the transaction receipt using `createInteropSentL2ToL2Messages`. + * Relays the message to chain B using `walletB.interop.relayMessage`. + * Waits for confirmation of the relay transaction. - ``` - > offchain-code@1.0.0 start - > tsx src/app.mts - - Chain B Greeting: Greeting directly to chain B - Chain A Greeting: Greeting directly to chain B - ``` - -### Add manual relaying logic - -1. Replace `src/app.mts` with: - - ```typescript - import { - createWalletClient, - http, - defineChain, - publicActions, - getContract, - Address, - } from 'viem' - import { privateKeyToAccount } from 'viem/accounts' - import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' - - import { - walletActionsL2, - publicActionsL2, - createInteropSentL2ToL2Messages, - } from '@eth-optimism/viem' - - import greeterData from './Greeter.json' - import greetingSenderData from './GreetingSender.json' - - const account = privateKeyToAccount(process.env.PRIV_KEY as `0x${string}`) - - const walletA = createWalletClient({ - chain: supersimL2A, - transport: http(), - account - }).extend(publicActions) - .extend(publicActionsL2()) - .extend(walletActionsL2()) - - const walletB = createWalletClient({ - chain: supersimL2B, - transport: http(), - account - }).extend(publicActions) - .extend(publicActionsL2()) - .extend(walletActionsL2()) - - const greeter = getContract({ - address: process.env.GREETER_B_ADDR as Address, - abi: greeterData.abi, - client: walletB - }) - - const greetingSender = getContract({ - address: process.env.GREETER_A_ADDR as Address, - abi: greetingSenderData.abi, - client: walletA - }) - - const txnBHash = await greeter.write.setGreeting( - ["Greeting directly to chain B"]) - await walletB.waitForTransactionReceipt({hash: txnBHash}) - - const greeting1 = await greeter.read.greet() - console.log(`Chain B Greeting: ${greeting1}`) - - const txnAHash = await greetingSender.write.setGreeting( - ["Greeting through chain A"]) - const receiptA = await walletA.waitForTransactionReceipt({hash: txnAHash}) - - const sentMessage = - (await createInteropSentL2ToL2Messages(walletA, { receipt: receiptA })) - .sentMessages[0] - const relayMsgTxnHash = await walletB.interop.relayMessage({ - sentMessageId: sentMessage.id, - sentMessagePayload: sentMessage.payload, - }) - - const receiptRelay = await walletB.waitForTransactionReceipt( - {hash: relayMsgTxnHash}) - - const greeting2 = await greeter.read.greet() - console.log(`Chain A Greeting: ${greeting2}`) - ``` + 7. **Verify the Updated Greeting on Chain B** + * Reads the greeting from `greeter` after the relay process. + * Logs the updated greeting, showing that it was successfully relayed from chain A to chain B. -
- - Explanation +
- ```typescript - import { - walletActionsL2, - publicActionsL2, - createInteropSentL2ToL2Messages, - } from '@eth-optimism/viem' - ``` + 2. Rerun the JavaScript program, and see that the message is relayed. - Import from the [`@eth-optimism/viem`](https://www.npmjs.com/package/@eth-optimism/viem) package. - - ```typescript - const walletA = createWalletClient({ - chain: supersimL2A, - transport: http(), - account - }).extend(publicActions) - .extend(publicActionsL2()) - .extend(walletActionsL2()) - - const walletB = createWalletClient({ - chain: supersimL2B, - transport: http(), - account - }).extend(publicActions) - .extend(publicActionsL2()) - .extend(walletActionsL2()) - ``` + ```sh + npm start + ``` - In addition to extending the wallets with [Viem public actions](https://viem.sh/docs/accounts/local#5-optional-extend-with-public-actions), extend with the OP-Stack actions, both the public ones and the ones that require an account. + ### Using devnet - ```typescript - const receiptA = await walletA.waitForTransactionReceipt({hash: txnAHash}) - ``` - - 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 - const sentMessage = - (await createInteropSentL2ToL2Messages(walletA, { receipt: receiptA })) - .sentMessages[0] - ``` - - 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 - const relayMsgTxnHash = await walletB.interop.relayMessage({ - sentMessageId: sentMessage.id, - sentMessagePayload: sentMessage.payload, - }) - - const receiptRelay = await walletB.waitForTransactionReceipt( - {hash: relayMsgTxnHash}) - ``` - - Here we first send the relay message on chain B, and then wait for the receipt for it. - -
- -1. Rerun the JavaScript program, and see that the message is relayed. + The same contracts are deployed on [the devnet](../tools/devnet). + You can relay messages in exactly the same way you'd do it on Supersim. - ```sh - npm start - ``` + | Contract | Network | Address | + | ---------------* | -------------------------------------------* | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------* | + | `Greeter` | [Devnet 1](../tools/devnet#interop-devnet-1) | [`0x1A183FCf61053B7dcd2322BbE766f7E1946d3718`](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718) | + | `GreetingSender` | [Devnet 0](../tools/devnet#interop-devnet-1) | [`0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f`](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f/contract/420120000/readContract?chainid=420120000) | -### Using the devnet + To modify the program to relay messagez on devnet, follow these steps: -The same contracts are deployed on [the devnet](../tools/devnet). -You can relay messages in exactly the same way you'd do it on Supersim. + 1. In `src/app.mts`, replace these lines to update the chains and contract addresses. -| Contract | Network | Address -| - | - | - -| `Greeter` | [Devnet 1](../tools/devnet#interop-devnet-1) | [`0x1A183FCf61053B7dcd2322BbE766f7E1946d3718`](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718) -| `GreetingSender` | [Devnet 0](../tools/devnet#interop-devnet-1) | [`0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f`](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f/contract/420120000/readContract?chainid=420120000) + | Line number | New content | + | ----------: | -------------------------------------------------------------------------* | + | 10 | `import { interopAlpha0, interopAlpha1 } from '@eth-optimism/viem/chains'` | + | 24 | ` chain: interopAlpha0,` | + | 32 | ` chain: interopAlpha1,` | + | 40 | ` address: "0x1A183FCf61053B7dcd2322BbE766f7E1946d3718",` | + | 46 | ` address: "0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f",` | -To modify the program to relay messagez on devnet, follow these steps: + 2. Set `PRIV_KEY` to the private key of an address that has Sepolia ETH on the two chains. -1. In `src/app.mts`, replace these lines to update the chains and contract addresses. + ```sh + export PRIV_KEY=0x + ``` - | Line number | New content | - | -: | - | - | 10 | `import { interopAlpha0, interopAlpha1 } from '@eth-optimism/viem/chains'` - | 24 | ` chain: interopAlpha0,` - | 32 | ` chain: interopAlpha1,` - | 40 | ` address: "0x1A183FCf61053B7dcd2322BbE766f7E1946d3718",` - | 46 | ` address: "0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f",` + 3. Rerun the test. -1. Set `PRIV_KEY` to the private key of an address that has Sepolia ETH on the two chains. + ```sh + npm start + ``` - ```sh - export PRIV_KEY=0x - ``` + 4. You can see the transactions in a block explorer. -1. Rerun the test. + * The first transaction, which sets the greeting directly, [on the `Greeter` contract on interop1](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718). + * The second transaction, the initiation message for the cross chain greeting change, [on the `GreetingSender` contract on interop0](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f). + * The third transaction, the executing message for the cross chain greeting change, [on the `Greeter` contract on interop1 as an internal transaction](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718/internalTx). - ```sh - npm start - ``` + ### Debugging -1. You can see the transactions in a block explorer. + To see what messages were relayed by a specific transaction you can use this code: - - The first transaction, which sets the greeting directly, [on the `Greeter` contract on interop1](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718). - - The second transaction, the initiation message for the cross chain greeting change, [on the `GreetingSender` contract on interop0](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f). - - The third transaction, the executing message for the cross chain greeting change, [on the `Greeter` contract on interop1 as an internal transaction](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718/internalTx). + ```typescript + import { decodeRelayedL2ToL2Messages } from '@eth-optimism/viem' -### Debugging - -To see what messages were relayed by a specific transaction you can use this code: - -```typescript -import { decodeRelayedL2ToL2Messages } from '@eth-optimism/viem' - -const decodedRelays = decodeRelayedL2ToL2Messages( - {receipt: receiptRelay}) - -console.log(decodedRelays) -console.log(decodedRelays.successfulMessages[0].log) -``` + const decodedRelays = decodeRelayedL2ToL2Messages( + {receipt: receiptRelay}) + console.log(decodedRelays) + console.log(decodedRelays.successfulMessages[0].log) + ```
## Next steps diff --git a/words.txt b/words.txt index 23d54c507..76ee91283 100644 --- a/words.txt +++ b/words.txt @@ -20,6 +20,7 @@ Ankr Apeworx Arweave authrpc +autorelay autorelayer basefee Betanet From bb094fdf0406b32958ea24406b28297871eff9d7 Mon Sep 17 00:00:00 2001 From: krofax Date: Thu, 13 Feb 2025 17:48:50 +0100 Subject: [PATCH 19/19] fix lint issues --- .../interop/tutorials/message-passing.mdx | 92 ++++++++++--------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx index ee5a5b086..007326985 100644 --- a/pages/stack/interop/tutorials/message-passing.mdx +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -101,11 +101,11 @@ For development purposes, we'll first use autorelay mode to handle message execu Supersim creates three `anvil` blockchains: - | Role | ChainID | RPC URL | - | -------* | ------: | ---------------------------------------------* | - | L1 | 900 | [http://127.0.0.1:8545](http://127.0.0.1:8545) | - | OPChainA | 901 | [http://127.0.0.1:9545](http://127.0.0.1:9545) | - | OPChainB | 902 | [http://127.0.0.1:9546](http://127.0.0.1:9546) | + \| Role | ChainID | RPC URL | + \| -------\* | ------: | ---------------------------------------------\* | + \| L1 | 900 | [http://127.0.0.1:8545](http://127.0.0.1:8545) | + \| OPChainA | 901 | [http://127.0.0.1:9545](http://127.0.0.1:9545) | + \| OPChainB | 902 | [http://127.0.0.1:9546](http://127.0.0.1:9546) | 2. In a separate shell, store the configuration in environment variables. @@ -493,41 +493,47 @@ In production we will not have this, we need to create our own executing message ```js file=/public/tutorials/app.mts#L1-L51 hash=8f6f776884b8e37ae613f7aea8cd6a3b ``` -
- Explanation +
+ Explanation - 1. **Import Required Libraries** - * Imports functions from `viem` for wallet creation, HTTP transport, and contract interactions. - * Imports `@eth-optimism/viem` utilities for handling OP-Stack-specific actions and interoperability. - * Loads ABI definitions from `Greeter.json` and `GreetingSender.json` for contract interactions. + 1. **Import Required Libraries** - 2. **Initialize Wallet Clients** - * Uses `privateKeyToAccount` to generate an account from an environment variable. - * Creates `walletA` for chain `supersimL2A` and `walletB` for chain `supersimL2B`, extending them with Viem’s public and OP-Stack-specific actions. + * Imports functions from `viem` for wallet creation, HTTP transport, and contract interactions. + * Imports `@eth-optimism/viem` utilities for handling OP-Stack-specific actions and interoperability. + * Loads ABI definitions from `Greeter.json` and `GreetingSender.json` for contract interactions. - 3. **Get Contract Instances** - * Retrieves contract instances for `greeter` on `walletB` and `greetingSender` on `walletA` using `getContract`. - * The addresses are taken from environment variables, and the clients are set to the respective wallets. + 2. **Initialize Wallet Clients** - 4. **Direct Greeting on Chain B** - * Calls `setGreeting` on `greeter` to store a greeting directly on chain B. - * Waits for the transaction to be confirmed using `waitForTransactionReceipt`. - * Reads and logs the greeting stored on chain B. + * Uses `privateKeyToAccount` to generate an account from an environment variable. + * Creates `walletA` for chain `supersimL2A` and `walletB` for chain `supersimL2B`, extending them with Viem's public and OP-Stack-specific actions. - 5. **Cross-Chain Greeting via Chain A** - * Calls `setGreeting` on `greetingSender` to send a greeting through chain A. - * Waits for the transaction receipt on chain A. + 3. **Get Contract Instances** - 6. **Retrieve and Relay the Cross-Chain Message** - * Extracts the message from the transaction receipt using `createInteropSentL2ToL2Messages`. - * Relays the message to chain B using `walletB.interop.relayMessage`. - * Waits for confirmation of the relay transaction. + * Retrieves contract instances for `greeter` on `walletB` and `greetingSender` on `walletA` using `getContract`. + * The addresses are taken from environment variables, and the clients are set to the respective wallets. - 7. **Verify the Updated Greeting on Chain B** - * Reads the greeting from `greeter` after the relay process. - * Logs the updated greeting, showing that it was successfully relayed from chain A to chain B. + 4. **Direct Greeting on Chain B** -
+ * Calls `setGreeting` on `greeter` to store a greeting directly on chain B. + * Waits for the transaction to be confirmed using `waitForTransactionReceipt`. + * Reads and logs the greeting stored on chain B. + + 5. **Cross-Chain Greeting via Chain A** + + * Calls `setGreeting` on `greetingSender` to send a greeting through chain A. + * Waits for the transaction receipt on chain A. + + 6. **Retrieve and Relay the Cross-Chain Message** + + * Extracts the message from the transaction receipt using `createInteropSentL2ToL2Messages`. + * Relays the message to chain B using `walletB.interop.relayMessage`. + * Waits for confirmation of the relay transaction. + + 7. **Verify the Updated Greeting on Chain B** + + * Reads the greeting from `greeter` after the relay process. + * Logs the updated greeting, showing that it was successfully relayed from chain A to chain B. +
2. Rerun the JavaScript program, and see that the message is relayed. @@ -540,22 +546,22 @@ In production we will not have this, we need to create our own executing message The same contracts are deployed on [the devnet](../tools/devnet). You can relay messages in exactly the same way you'd do it on Supersim. - | Contract | Network | Address | - | ---------------* | -------------------------------------------* | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------* | - | `Greeter` | [Devnet 1](../tools/devnet#interop-devnet-1) | [`0x1A183FCf61053B7dcd2322BbE766f7E1946d3718`](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718) | - | `GreetingSender` | [Devnet 0](../tools/devnet#interop-devnet-1) | [`0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f`](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f/contract/420120000/readContract?chainid=420120000) | + \| Contract | Network | Address | + \| ---------------\* | -------------------------------------------\* | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\* | + \| `Greeter` | [Devnet 1](../tools/devnet#interop-devnet-1) | [`0x1A183FCf61053B7dcd2322BbE766f7E1946d3718`](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718) | + \| `GreetingSender` | [Devnet 0](../tools/devnet#interop-devnet-1) | [`0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f`](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f/contract/420120000/readContract?chainid=420120000) | To modify the program to relay messagez on devnet, follow these steps: 1. In `src/app.mts`, replace these lines to update the chains and contract addresses. - | Line number | New content | - | ----------: | -------------------------------------------------------------------------* | - | 10 | `import { interopAlpha0, interopAlpha1 } from '@eth-optimism/viem/chains'` | - | 24 | ` chain: interopAlpha0,` | - | 32 | ` chain: interopAlpha1,` | - | 40 | ` address: "0x1A183FCf61053B7dcd2322BbE766f7E1946d3718",` | - | 46 | ` address: "0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f",` | + \| Line number | New content | + \| ----------: | -------------------------------------------------------------------------\* | + \| 10 | `import { interopAlpha0, interopAlpha1 } from '@eth-optimism/viem/chains'` | + \| 24 | ` chain: interopAlpha0,` | + \| 32 | ` chain: interopAlpha1,` | + \| 40 | ` address: "0x1A183FCf61053B7dcd2322BbE766f7E1946d3718",` | + \| 46 | ` address: "0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f",` | 2. Set `PRIV_KEY` to the private key of an address that has Sepolia ETH on the two chains.