diff --git a/pages/interop/tutorials.mdx b/pages/interop/tutorials.mdx
index c49f12fec..9c5dfed80 100644
--- a/pages/interop/tutorials.mdx
+++ b/pages/interop/tutorials.mdx
@@ -34,8 +34,6 @@ Documentation covering Interop related tutorials.
} />
- } />
-
} />
} />
diff --git a/pages/interop/tutorials/_meta.json b/pages/interop/tutorials/_meta.json
index 67816f137..8cbb06ea7 100644
--- a/pages/interop/tutorials/_meta.json
+++ b/pages/interop/tutorials/_meta.json
@@ -6,7 +6,6 @@
"bridge-crosschain-eth": "Bridging native cross-chain ETH transfers",
"relay-messages-cast": "Relaying interop messages using `cast`",
"relay-messages-viem": "Relaying interop messages using `viem`",
- "contract-calls": "Making crosschain contract calls (ping pong)",
"event-reads": "Making crosschain event reads (tic-tac-toe)",
"event-contests": "Deploying crosschain event composability (contests)"
}
diff --git a/pages/interop/tutorials/contract-calls.mdx b/pages/interop/tutorials/contract-calls.mdx
deleted file mode 100644
index 8e721130d..000000000
--- a/pages/interop/tutorials/contract-calls.mdx
+++ /dev/null
@@ -1,205 +0,0 @@
----
-title: Making crosschain contract calls (ping pong)
-description: Learn how to make crosschain contract calls using ping pong.
-lang: en-US
-content_type: tutorial
-topic: making-crosschain-contract-calls-ping-pong
-personas:
- - protocol-developer
- - chain-operator
- - app-developer
-categories:
- - protocol
- - interoperability
- - cross-chain-messaging
- - superchain
-is_imported_content: 'false'
----
-
-import { Callout, Steps } from 'nextra/components'
-import { InteropCallout } from '@/components/WipCallout'
-
-
-
-# Making crosschain contract calls (ping pong)
-
-This guide walks through the `CrossChainPingPong.sol` contract, focusing on high level design and steps to integrating the `L2ToL2CrossChainMessenger` contract. For more info, view the [source code](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/pingpong/CrossChainPingPong.sol).
-
-## High level overview
-
-`CrossChainPingPong.sol` implements a cross-chain ping-pong game using the `L2ToL2CrossDomainMessenger`.
-
-* Players hit a **virtual ball** back and forth between allowed L2 chains. The game starts with a serve
-* from a designated start chain, and each hit increases the rally count. The contract tracks the last hitter's address, chain ID, and the current rally count.
-
-### Diagram
-
-```mermaid
-sequenceDiagram
- participant Chain1 as Chain 1
- participant Chain2 as Chain 2
-
- Note over Chain1: 🚀 Game Starts (starting chain)
- Note over Chain1: 🏓 Hit Ball
- Chain1->>Chain2: 📤 Send PingPongBall {rallyCount: 1, lastHitter: Chain1}
- Chain1-->>Chain1: Emit BallSent event
- activate Chain2
- Note over Chain2: 📥 Receive Ball
- Chain2-->>Chain2: Emit BallReceived event
-
- Note over Chain2: 🏓 Hit Ball
- Chain2->>Chain1: 📤 Send PingPongBall {rallyCount: 2, lastHitter: Chain2}
- Chain2-->>Chain2: Emit BallSent event
- deactivate Chain2
- activate Chain1
- Note over Chain1: 📥 Receive Ball
- Chain1-->>Chain1: Emit BallReceived event
-
- Note over Chain1,Chain2: Game continues...
-```
-
-### Flow
-
-
- ### Contract Deployment
-
- * Deployed on all participating chains
- * Utilizes CREATE2 with the same parameter, `_serverChainId`, resulting in the same address and initial state.
-
- ### Hit the Ball (Starting Move)
-
- * Call `hitBallTo` on the chain with the ball, specifying a destination chain.
- * Contract uses `L2ToL2CrossDomainMessenger` to send the ball data to the specified chain.
- * The reference to the ball is deleted from the serving chain.
-
- ### Receive on Destination Chain
-
- * `L2ToL2CrossDomainMessenger` on destination chain calls `receiveBall`.
- * Contract verifies the message sender and origin.
- * Ball data is stored, indicating its presence on this chain.
-
- ### Continue Game (Hit)
-
- * Any user on the chain currently holding the ball calls `hitBallTo` to send it to another chain.
- * Contract updates the `PingPongBall` data (increment rally count, update last hitter).
- * Process repeats from step 2.
-
-
-## Walkthrough
-
-Here's an explanation of the functions in the contract, with a focus on how it interacts with `L2ToL2CrossChainMessenger`.
-
-### Initialize contract state
-
-#### Constructor Setup
-
-```solidity
-constructor(uint256 _serverChainId) {
- if (block.chainid == _serverChainId) {
- ball = PingPongBall(1, block.chainid, msg.sender);
- }
-}
-```
-
-If the starting chain, initialize the ball allowing it to be hittable.
-
-#### Reliance on CREATE2 for cross chain consistency
-
-While not explicitly mentioned in the code, this contract's design implicitly assumes the use of CREATE2 for deployment. Here's why CREATE2 is crucial for this setup:
-
-* **Predictable Addresses**:
- CREATE2 enables deployment at the same address on all chains, crucial for cross-chain message verification:
- ```solidity
- if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender();
- ```
-
-* **Self-referential Messaging**:
- The contract sends messages to itself on other chains:
- ```solidity
- messenger.sendMessage(_toChainId, address(this), _message);
- ```
- This requires `address(this)` to be consistent across chains.
-
-* **Initialization State Considerations**:
-
- The starting chain id is part of the initcode, meaning a deployment with a differing value would result in a different address via CREATE2. This is a nice feature as there's an implicit agreement on the starting chain from the address.
-
- Without CREATE2, you would need to:
-
- * Manually track contract addresses for each chain.
- * Implement a more complex initialization process to register contract addresses across chains.
- * Potentially redesign the security model that relies on address matching.
-
-### Hit the ball
-
-`hitBallTo`: This function is used to hit the ball, when present, to another chain
-
-#### Hitting constraints
-
-```solidity
-function hitBallTo(uint256 _toChainId) public {
- if (ball.lastHitterAddress == address(0)) revert BallNotPresent();
- if (_toChainId == block.chainid) revert InvalidDestination();
- ...
-}
-```
-
-* The `ball` contract variable is populated on the chain, indicating its presence
-* The destination must be a different chain
-
-### Define receiving handler
-
-```solidity
-modifier onlyCrossDomainCallback() {
- if (msg.sender != address(messenger)) revert CallerNotL2ToL2CrossDomainMessenger();
- if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender();
-
- _;
-}
-
-function receiveBall(PingPongBall memory _ball) onlyCrossDomainCallback() external {
- // Hold reference to the ball
- ball = _ball;
-
- emit BallReceived(messenger.crossDomainMessageSource(), block.chainid, _ball);
-}
-```
-
-* The handler simply stores reference to the received ball
-* The handler can only be invocable by the cross chain messenger
-* Since the contract is self-referential, the cross chain sender must be the same contract address
-
-### Hit the ball cross-chain
-
-```solidity
-function hitBallTo(uint256 _toChainId) public {
- ...
-
- // Construct a new ball
- PingPongBall memory newBall = PingPongBall(ball.rallyCount + 1, block.chainid, msg.sender);
-
- // Delete current reference
- delete ball;
-
- // Send to the destination
- messenger.sendMessage(_toChainId, address(this), abi.encodeCall(this.receiveBall, (newBall)));
-
- emit BallSent(block.chainid, _toChainId, newBall);
-}
-```
-
-* Populate a new ball with updated properties
-* Delete reference to the current ball so it's no longer hittable
-* Invoke the contract on the destination chain matching the `receiveBall` handler defined in (2).
-
-## Takeaways
-
-This is just one of many patterns to use the `L2ToL2CrossDomainMessenger` in your contract to power cross chain calls. Key points to remember:
-
-* **Simple Message Passing**: This design sends simple messages between identical contracts on different chains. Each message contains only the essential game state (rally count, last hitter). More complex systems might involve multiple contracts, intermediary relayers.
-
-* **Cross Chain Sender Verification**: Always verify the sender of cross-chain messages. This includes checking both the immediate caller (the messenger) and the original sender on the source chain.
-
-* **Cross Chain Contract Coordination**: This design uses CREATE2 for consistent contract addresses across chains, simplifying cross-chain verification. Alternative approaches include:
- * Beacon proxy patterns for upgradeable contracts
- * Post-deployment setup where contract addresses are specified after deployment
diff --git a/pages/interop/tutorials/message-passing.mdx b/pages/interop/tutorials/message-passing.mdx
index 5214c4597..8d70fd00a 100644
--- a/pages/interop/tutorials/message-passing.mdx
+++ b/pages/interop/tutorials/message-passing.mdx
@@ -22,8 +22,7 @@ categories:
is_imported_content: 'false'
---
-import { Callout } from 'nextra/components'
-import { Steps } from 'nextra/components'
+import { Callout, Steps, Tabs } from 'nextra/components'
import { InteropCallout } from '@/components/WipCallout'
import { AutorelayCallout } from '@/components/AutorelayCallout'
@@ -36,66 +35,66 @@ import { AutorelayCallout } from '@/components/AutorelayCallout'
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
+
+ About this tutorial
-### What you'll learn
+ **Prerequisite technical knowledge**
-* 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
+ * Intermediate Solidity programming
+ * Basic TypeScript knowledge
+ * Understanding of smart contract development
+ * Familiarity with blockchain concepts
-
- This tutorial provides step-by-step instructions for implementing cross-chain messaging.
- For a conceptual overview,
- see the [message passing explainer](/interop/message-passing).
-
+ **What you'll learn**
-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.
+ * 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
-## Prerequisites
+ **Development environment**
-Before starting this tutorial, ensure your development environment meets the following requirements:
+ * Unix-like operating system (Linux, macOS, or WSL for Windows)
+ * Node.js version 16 or higher
+ * Git for version control
-### Technical knowledge
+ **Required tools**
-* Intermediate Solidity programming
-* Basic TypeScript knowledge
-* Understanding of smart contract development
-* Familiarity with blockchain concepts
+ The tutorial uses these primary tools:
-### Development environment
+ * Foundry: For smart contract development
+ * Supersim: For local blockchain simulation (optional)
+ * TypeScript: For offchain code (for relaying messages manually)
+ * Viem: For interactions with the chain from the offchain app
+
-* Unix-like operating system (Linux, macOS, or WSL for Windows)
-* Node.js version 16 or higher
-* Git for version control
+### What You'll Build
-### Required tools
+* A `Greeter` contract that stores and updates a greeting
+* A `GreetingSender` contract that sends cross-chain messages to update the greeting
+* A TypeScript application to relay messages between chains
-The tutorial uses these primary tools:
+
+ This tutorial provides step-by-step instructions for implementing cross-chain messaging.
+ For a conceptual overview,
+ see the [Message Passing Explainer](/interop/message-passing).
+
-* Foundry: For smart contract development
-* Supersim: For local blockchain simulation
-* TypeScript: For implementation
-* Viem: For blockchain interaction
+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.
## 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
+ * Foundry for smart contract development (required in all cases)
+ * Supersim for local blockchain simulation (optional)
### Verify your installation:
```sh
forge --version
- supersim --version
+ ./supersim --version
```
@@ -105,50 +104,75 @@ 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
-
- If you attempt to run these steps with the [devnet](/interop/tools/devnet), you *must* Send the executing message yourself, as explained [here](#implement-manual-message-relaying).
-
-
- 1. In the directory where Supersim is installed, start it with autorelay.
+ 1. If you are using [Supersim](/interop/tools/supersim), go to the directory where Supersim is installed and start it with autorelay.
```sh
./supersim --interop.autorelay
```
- Supersim creates three `anvil` blockchains:
+ If you are using [the devnets](/interop/tools/devnet), just skip this step.
+
+
+
+ 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) |
+
+ These are the three networks involved in the devnet:
+
+ | Role | ChainID | RPC URL |
+ | ------------ | --------: | -------------------------------------------------------------------------------- |
+ | L1 (Sepolia) | 11155111 | [https://eth-sepolia.public.blastapi.io](https://eth-sepolia.public.blastapi.io) |
+ | ChainA | 420120000 | [https://interop-alpha-0.optimism.io](https://interop-alpha-0.optimism.io) |
+ | ChainB | 420120001 | [https://interop-alpha-1.optimism.io](https://interop-alpha-1.optimism.io) |
+
+
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
- ```
+
+
+ Set these parameters for Supersim.
+
+ ```sh
+ PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+ USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
+ URL_CHAIN_A=http://127.0.0.1:9545
+ URL_CHAIN_B=http://127.0.0.1:9546
+ INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
+ ```
+
+
+
+ For Devnet, specify in `PRIVATE_KEY` the private key you used for the setup script and then these parameters.
+
+ ```sh
+ USER_ADDRESS=`cast wallet address --private-key $PRIVATE_KEY`
+ URL_CHAIN_A=https://interop-alpha-0.optimism.io
+ URL_CHAIN_B=https://interop-alpha-1.optimism.io
+ INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
+ ```
+
+
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_ADDRESS`.
```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
+ cast balance --ether $USER_ADDRESS --rpc-url $URL_CHAIN_A
+ cast balance --ether $USER_ADDRESS --rpc-url $URL_CHAIN_B
```
@@ -168,42 +192,42 @@ For development purposes, we'll first use autorelay mode to handle message execu
```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_ADDRESS` environment variable.
```sh
- GREETER_B_ADDR=`forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'`
+ GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'`
```
-
- Explanation
+
+ Explanation
- The command that deploys the contract is:
+ The command that deploys the contract is:
- ```sh
- forge create --rpc-url $RPC_B --private-key $PRIV_KEY Greeter --broadcast
- ```
+ ```sh
+ forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast
+ ```
- The command output gives us the deployer address, the address of the new contract, and the transaction hash:
+ 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
- ```
+ ```
+ 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.
+ 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}'
- ```
+ ```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.
+ 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=
- ```
-
+ ```sh
+ GREETER_B_ADDRESS=
+ ```
+
Sanity check
@@ -212,9 +236,9 @@ For development purposes, we'll first use autorelay mode to handle message execu
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
+ cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
+ cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "setGreeting(string)" Hello$$
+ cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
```
@@ -238,7 +262,7 @@ For development purposes, we'll first use autorelay mode to handle message execu
6. Create `src/GreetingSender.sol`.
- ```solidity file=/public/tutorials/GreetingSender.sol#L1-L28 hash=9ed77001810caf52bbaa94da8b0dc5c6
+ ```solidity file=/public/tutorials/GreetingSender.sol hash=75d197d1e1da112421785c2160f6a55a
```
@@ -257,7 +281,8 @@ For development purposes, we'll first use autorelay mode to handle message execu
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}'`
+ CHAIN_ID_B=`cast chain-id --rpc-url $URL_CHAIN_B`
+ GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'`
```
### Send a message
@@ -265,13 +290,13 @@ For development purposes, we'll first use autorelay mode to handle message execu
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
+ cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
+ cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A"
+ sleep 4
+ cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "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.
+ The `sleep` call is because it can take up to two seconds until the transaction is included in chain A, and then up to two seconds until the relay transaction is included in chain B.
## Sender information
@@ -279,7 +304,7 @@ For development purposes, we'll first use autorelay mode to handle message execu
Run this command to view the events to see who called `setGreeting`.
```sh
-cast logs --rpc-url $RPC_B 'SetGreeting(address,string)'
+cast logs --rpc-url $URL_CHAIN_B 'SetGreeting(address,string)'
```
The sender information is stored in the second event topic.
@@ -334,26 +359,26 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
}
```
-
- Explanation
+
+ Explanation
- ```solidity
- if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
- (address sender, uint256 chainId) =
- messenger.crossDomainMessageContext();
- emit CrossDomainSetGreeting(sender, chainId, _greeting);
- }
- ```
+ ```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).
-
+ 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}'`
+ GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'`
+ GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'`
```
### Verify you can see cross chain sender information
@@ -361,28 +386,32 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
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
+ cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
+ cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A, with a CrossDomainSetGreeting event"
+ sleep 4
+ cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
```
2. Read the log entries.
```sh
- cast logs --rpc-url $RPC_B 'CrossDomainSetGreeting(address,uint256,string)'
- echo $GREETER_A_ADDR
+ cast logs --rpc-url $URL_CHAIN_B 'CrossDomainSetGreeting(address,uint256,string)'
+ echo $GREETER_A_ADDRESS
echo 0x385 | cast --to-dec
+ echo 0x190a85c0 | 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.
+ See that the second topic (the first indexed log parameter) is the same as `$GREETER_A_ADDRESS`.
+ The third topic can be either `0x385=901`, which is the chain ID for supersim chain A, or `0x190a85c0=420120000`, which is the chain ID for devnet alpha 0.
## Implement manual message relaying
+If you are using the devnets, skip this section.
+You will not be able to see the effects anyway because you cannot disable autoforwarding on them.
+
### Set up
@@ -429,7 +458,7 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
We need them in the Node process that the shell creates.
```sh
- export GREETER_A_ADDR GREETER_B_ADDR PRIV_KEY
+ export GREETER_A_ADDRESS GREETER_B_ADDRESS PRIVATE_KEY
```
@@ -438,8 +467,8 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
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}`)
+ console.log(`Greeter A ${process.env.GREETER_A_ADDRESS}`)
+ console.log(`Greeter B ${process.env.GREETER_B_ADDRESS}`)
```
2. Run the program.
@@ -451,7 +480,7 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
### Send a greeting
- 1. Link the compiled versions of the onchain code, which include the ABI, to the source code.
+ 1. Link the compiled versions of the onchain code, which include the ABI, to the offchain code repository.
```sh
cd src
@@ -462,7 +491,7 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
2. Create or replace `src/app.mts` with this code.
- ```typescript file=/public/tutorials/app.mts#L1-L51 hash=8f6f776884b8e37ae613f7aea8cd6a3b
+ ```typescript file=/public/tutorials/app.mts hash=caec4e94c5ece6ff3841f2f5f3d17aae
```
3. Run the program, see that a greeting from chain A is relayed to chain B.
@@ -482,12 +511,12 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
```
2. In the window you used for your earlier tests, redeploy the contracts.
- Export the addresses so we'll have them in JavaScript
+ Export the addresses so we'll have them in the offchain.
```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}'`
+ export GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'`
+ export GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'`
cd ../offchain-code
```
@@ -511,35 +540,37 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
1. Replace `src/app.mts` with:
- ```typescript file=/public/tutorials/app_v2.mts hash=f0aef31ef2ce29590a37f18ca07e52a9
+ ```typescript file=/public/tutorials/app_v2.mts hash=5b31a6d32c1a033531a90c8a18807ad7
```
Explanation
- ```typescript file=/public/tutorials/app_v2.mts#L11-L15 hash=84fb4799b2fdd18785f691d602567145
+ ```typescript file=/public/tutorials/app_v2.mts#L9-L10 hash=2d062eb374989a8a40199a4d7dc8be6e
```
Import from the [`@eth-optimism/viem`](https://www.npmjs.com/package/@eth-optimism/viem) package.
- ```typescript file=/public/tutorials/app_v2.mts#L22-L28 hash=28c42407a1a01774f25ca78535d93c6e
+ ```typescript file=/public/tutorials/app_v2.mts#L17-L31 hash=cf5ce47bcbcd80327230a6da689688f8
```
- 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.
+ 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.
+ On wallet A we need the public actions, those that only read information.
+ On wallet B we need the wallet actions, the ones that require an account.
- ```typescript file=/public/tutorials/app_v2.mts#L59 hash=fc1aae397872717a3ed364930cdab9fc
+ ```typescript file=/public/tutorials/app_v2.mts#L54 hash=23aa6f24baeb5757130361f30c1b0e9c
```
To relay a message we need the information in the receipt.
Also, we need to wait until the transaction with the relayed message is actually part of a block.
- ```typescript file=/public/tutorials/app_v2.mts#L61-L63 hash=d45ebbc0d53dfbecbd8d357eb83e1b68
+ ```typescript file=/public/tutorials/app_v2.mts#L56-L59 hash=8cc99e67ee36474c81183108531cb295
```
A single transaction can send multiple messages.
But here we know we sent just one, so we look for the first one in the list.
- ```typescript file=/public/tutorials/app_v2.mts#L64-L70 hash=079ffd3e6fdf4eff3653ee7a74078183
+ ```typescript file=/public/tutorials/app_v2.mts#L60-L67 hash=b7ed7d70ba5ec84322beee5369c5bee5
```
Here we first send the relay message on chain B, and then wait for the receipt for it.
@@ -551,55 +582,6 @@ In this section we change `Greeter.sol` to emit a separate event in it receives
npm start
```
- ### Using devnet
-
- The same contracts are deployed on [the devnet](/interop/tools/devnet).
- You can relay messages in exactly the same way you'd do it on Supersim.
-
- | Contract | Network | Address |
- | ---------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
- | `Greeter` | [Devnet 1](/interop/tools/devnet#interop-devnet-1) | [`0x1A183FCf61053B7dcd2322BbE766f7E1946d3718`](https://sid.testnet.routescan.io/address/0x1A183FCf61053B7dcd2322BbE766f7E1946d3718) |
- | `GreetingSender` | [Devnet 0](/interop/tools/devnet#interop-devnet-1) | [`0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f`](https://sid.testnet.routescan.io/address/0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f/contract/420120000/readContract?chainid=420120000) |
-
- To modify the program to relay messages on devnet, follow these steps:
-
- 1. In `src/app.mts`, replace these lines to update the chains and contract addresses.
-
- | Line number | New content |
- | ----------: | -------------------------------------------------------------------------- |
- | 9 | `import { interopAlpha0, interopAlpha1 } from '@eth-optimism/viem/chains'` |
- | 23 | ` chain: interopAlpha0,` |
- | 31 | ` chain: interopAlpha1,` |
- | 39 | ` address: "0x1A183FCf61053B7dcd2322BbE766f7E1946d3718",` |
- | 45 | ` address: "0x9De9f84a4EB3616B44CF1d68cD1A9098Df6cB25f",` |
-
- 2. Set `PRIV_KEY` to the private key of an address that has [Sepolia ETH](https://cloud.google.com/application/web3/faucet/ethereum/sepolia).
-
- ```sh
- export PRIV_KEY=0x
- ```
-
- 3. Send ETH to the two L2 blockchains.
-
- ```sh
- cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIV_KEY --value 0.001ether 0x7385d89d38ab79984e7c84fab9ce5e6f4815468a
- cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIV_KEY --value 0.001ether 0x55f5c4653dbcde7d1254f9c690a5d761b315500c
- ```
-
- Wait a few minutes until you can see the ETH [on your explorer](https://sid.testnet.routescan.io/).
-
- 4. Rerun the test.
-
- ```sh
- npm start
- ```
-
- 5. 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:
diff --git a/public/tutorials/GreetingSender.sol b/public/tutorials/GreetingSender.sol
index f44dd9f3e..79257d4c3 100644
--- a/public/tutorials/GreetingSender.sol
+++ b/public/tutorials/GreetingSender.sol
@@ -1,8 +1,8 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
-import { Predeploys } from "lib/optimism/packages/contracts-bedrock/src/libraries/Predeploys.sol";
-import { IL2ToL2CrossDomainMessenger } from "lib/optimism/packages/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";
import { Greeter } from "src/Greeter.sol";
@@ -25,4 +25,4 @@ contract GreetingSender {
);
messenger.sendMessage(greeterChainId, greeterAddress, message);
}
-}
\ No newline at end of file
+}
diff --git a/public/tutorials/app.mts b/public/tutorials/app.mts
index b850cddc6..422d7246f 100644
--- a/public/tutorials/app.mts
+++ b/public/tutorials/app.mts
@@ -13,7 +13,7 @@ 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 account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const walletA = createWalletClient({
chain: supersimL2A,
@@ -28,13 +28,13 @@ const walletB = createWalletClient({
}).extend(publicActions)
const greeter = getContract({
- address: process.env.GREETER_B_ADDR as Address,
+ address: process.env.GREETER_B_ADDRESS as Address,
abi: greeterData.abi,
client: walletB
})
const greetingSender = getContract({
- address: process.env.GREETER_A_ADDR as Address,
+ address: process.env.GREETER_A_ADDRESS as Address,
abi: greetingSenderData.abi,
client: walletA
})
diff --git a/public/tutorials/app_v2.mts b/public/tutorials/app_v2.mts
index e3ddbf39c..0858019eb 100644
--- a/public/tutorials/app_v2.mts
+++ b/public/tutorials/app_v2.mts
@@ -6,50 +6,49 @@ import {
Address,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
-import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains'
-
+import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains'
import { walletActionsL2, publicActionsL2 } 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 account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
+
const walletA = createWalletClient({
chain: supersimL2A,
transport: http(),
account
}).extend(publicActions)
.extend(publicActionsL2())
- .extend(walletActionsL2())
+// .extend(walletActionsL2())
const walletB = createWalletClient({
chain: supersimL2B,
transport: http(),
account
}).extend(publicActions)
- .extend(publicActionsL2())
+// .extend(publicActionsL2())
.extend(walletActionsL2())
-
+
const greeter = getContract({
- address: process.env.GREETER_B_ADDR as Address,
+ address: process.env.GREETER_B_ADDRESS as Address,
abi: greeterData.abi,
client: walletB
})
-
+
const greetingSender = getContract({
- address: process.env.GREETER_A_ADDR as Address,
+ address: process.env.GREETER_A_ADDRESS 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})
@@ -62,10 +61,11 @@ const relayMessageParams = await walletA.interop.buildExecutingMessage({
log: sentMessage.log,
})
const relayMsgTxnHash = await walletB.interop.relayCrossDomainMessage(relayMessageParams)
-
+
const receiptRelay = await walletB.waitForTransactionReceipt({
hash: relayMsgTxnHash,
})
-
+
const greeting2 = await greeter.read.greet()
console.log(`Chain A Greeting: ${greeting2}`)
+
diff --git a/words.txt b/words.txt
index 8af644f62..7826325a1 100644
--- a/words.txt
+++ b/words.txt
@@ -95,8 +95,6 @@ Devnet
devnet
Devnets
devnets
-Devs
-
direnv
DISABLETXPOOLGOSSIP
disabletxpoolgossip
@@ -175,7 +173,6 @@ Inator
inator
INFLUXDBV
influxdbv
-initcode
IPCDISABLE
ipcdisable
ipcfile