diff --git a/pages/stack/interop/message-passing.mdx b/pages/stack/interop/message-passing.mdx index 4a867a5fa..1ea684fd6 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.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/_meta.json b/pages/stack/interop/tutorials/_meta.json index b0ed35ba8..f69605cf5 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": "Transferring a SuperchainERC20", "deploy-superchain-erc20": "Issuing new assets with SuperchainERC20", "bridge-crosschain-eth": "Bridging native cross-chain ETH transfers", diff --git a/pages/stack/interop/tutorials/message-passing.mdx b/pages/stack/interop/tutorials/message-passing.mdx new file mode 100644 index 000000000..007326985 --- /dev/null +++ b/pages/stack/interop/tutorials/message-passing.mdx @@ -0,0 +1,603 @@ +--- +title: Interop message passing tutorial +lang: en-US +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' +import { Steps } from 'nextra/components' +import { InteropCallout } from '@/components/WipCallout' + + + +# Interop message passing tutorial + +## 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 + +* 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 your development environment meets the following requirements: + +### Technical knowledge + +* 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 + +### 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 + + ### 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. + +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 + + 1. In the directory where Supersim is installed, start it with autorelay. + + ```sh + supersim + ``` + + 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) | + + 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 + + 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 + + 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). + + ```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. + + ```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= + ``` +
+ +
+ Sanity check + + Run these commands to verify the contract works. + 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 + 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 + ``` +
+ + 4. Install the Optimism Solidity libraries into the project. + + ```sh + cd lib + npm install @eth-optimism/contracts-bedrock + cd .. + echo @eth-optimism/=lib/node_modules/@eth-optimism/ >> remappings.txt + ``` + + 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. + + ```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`. + + ```solidity file=/public/tutorials/GreetingSender.sol#L1-L28 hash=75d197d1e1da112421785c2160f6a55a + ``` + +
+ Explanation + + ```solidity file=/public/tutorials/GreetingSender.sol#L21-L27 hash=6c27ebcf4916e5aa2325d30f99c65436 + ``` + + 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. + + * 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. +
+ + 7. 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}'` + ``` + + ### Send a message + + 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 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). + + + ### 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 + + ```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). +
+ + 2. 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 a CrossDomainSetGreeting event" + sleep 2 + cast call --rpc-url $RPC_B $GREETER_B_ADDR "greet()" | cast --to-ascii + ``` + + 2. 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. +
+ +## 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. +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 + + 1. Create a simple `src/app.mts` file. + + ```typescript + console.log(`Greeter A ${process.env.GREETER_A_ADDR}`) + console.log(`Greeter B ${process.env.GREETER_B_ADDR}`) + ``` + + 2. Run the program. + + ```sh + npm run start + ``` +
+ + ### Send a greeting + + 1. Link the compiled versions of the onchain 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 .. + ``` + + 2. Create or replace `src/app.mts` with this code. + + ```solidity file=/public/tutorials/app.mts#L1-L51 hash=8f6f776884b8e37ae613f7aea8cd6a3b + ``` + + 3. Run the program, see that a greeting from chain A is related to chain B. + + ```sh + npm start + ``` + + ### Rerun supersim + + Now we need to rerun Supersim *without* autorelay. + + 1. In the window that runs Supersim, stop it and restart with this command: + + ```sh + ./supersim + ``` + + 2. 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 + ``` + + 3. 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 + ``` + + ### Add manual relaying logic + + 1. Replace `src/app.mts` with: + + ```js file=/public/tutorials/app.mts#L1-L51 hash=8f6f776884b8e37ae613f7aea8cd6a3b + ``` + +
+ 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. + + 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. + + 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. + + 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. + + ```sh + npm start + ``` + + ### Using 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) | + \| `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",` | + + 2. Set `PRIV_KEY` to the private key of an address that has Sepolia ETH on the two chains. + + ```sh + export PRIV_KEY=0x + ``` + + 3. Rerun the test. + + ```sh + npm start + ``` + + 4. 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: + + ```typescript + import { decodeRelayedL2ToL2Messages } from '@eth-optimism/viem' + + const decodedRelays = decodeRelayedL2ToL2Messages( + {receipt: receiptRelay}) + + console.log(decodedRelays) + console.log(decodedRelays.successfulMessages[0].log) + ``` +
+ +## Next steps + +* 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. 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 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