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.
Show About this tutorial
Prerequisite technical knowledge
Intermediate Solidity programming
Basic TypeScript knowledge
Understanding of smart contract development
Familiarity with blockchain concepts
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
Development environment
Unix-like operating system (Linux, macOS, or WSL for Windows)
Node.js version 16 or higher
Git for version control
Required toolsThe tutorial uses these primary tools:
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
The command output gives us the deployer address, the address of the new contract, and the transaction hash:
Report incorrect code
Copy
Ask AI
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266Deployed to: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707Transaction hash: 0xf155d360ec70ee10fe0e02d99c16fa5d6dc2a0e79b005fec6cbf7925ff547dbf
The awk command looks for the line that has Deployed to: and writes the third word in that line, which is the address.
Report incorrect code
Copy
Ask AI
awk '/Deployed to:/ {print $3}'
Finally, in UNIX (including Linux and macOS) 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.
Report incorrect code
Copy
Ask AI
GREETER_B_ADDRESS=<the address>
Show 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.
Install the Optimism Solidity libraries into the project.
Report incorrect code
Copy
Ask AI
cd libnpm install @eth-optimism/contracts-bedrockcd ..echo @eth-optimism/=lib/node_modules/@eth-optimism/ >> remappings.txt
Create src/GreetingSender.sol.
Report incorrect code
Copy
Ask AI
//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/src/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); } }
Show Explanation
Report incorrect code
Copy
Ask AI
function setGreeting(string calldata greeting) public { bytes memory message = abi.encodeCall( Greeter.setGreeting, (greeting) ); messenger.sendMessage(greeterChainId, greeterAddress, message); }
This function encodes a call to setGreeting and sends it to a contract on another chain.
abi.encodeCall(Greeter.setGreeting, (greeting)) constructs the calldata 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 (as long as there is an executing message to relay it).
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.
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).
1
Modify the Greeter contract
Modify src/Greeter.sol to this code.
Report incorrect code
Copy
Ask AI
//SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; interface IL2ToL2CrossDomainMessenger { function crossDomainMessageContext() external view returns (address sender_, uint256 source_); } 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); } } }
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.