Generated from superchain-starter. See the original repository for a more detailed development guide.
Example Superchain app (contract + frontend) that uses interop to send ETH to multiple recipients on a different chain.

- Enables sending ETH to multiple recipients on a different chain
- Uses
L2ToL2CrossDomainMessenger
for cross-chain message passing - Leverages
SuperchainWETH
for cross-chain ETH transfers - Implements a two-step process:
- Bridges ETH to the destination chain using SuperchainWETH
- Distributes ETH to multiple recipients on the destination chain
- Includes safety checks for message verification and ETH transfer success
This contract sends two cross-chain messages through the L2ToL2CrossDomainMessenger:
- (Message 1) to send ETH using SuperchainWETH from source to destination chain - triggered by SuperchainWETH#sendETH
- (Message 2) to disperse the received ETH to the recipients on the destination chain - triggered by CrossChainMultisend#send
Message 2 depends on the success of Message 1, which is enforced by the CrossDomainMessageLib.requireMessageSuccess(_sendWethMsgHash)
check in the relay function. This ensures that ETH bridging is completed before distribution occurs.
The CrossChainMultisend contract is designed to be deployed at the same address on all chains. This allows the contract to:
- "Trust" that the send message was emitted as a side effect of a specific sequence of events
- Process cross-chain messages from itself on other chains
- Maintain consistent behavior across the Superchain
CrossDomainMessageLib.requireCrossDomainCallback();
...
function requireCrossDomainCallback() internal view {
requireCallerIsCrossDomainMessenger();
if (
IL2ToL2CrossDomainMessenger(PredeployAddresses.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageSender()
!= address(this)
) revert InvalidCrossDomainSender();
}
The above CrossDomainMessageLib.requireCrossDomainCallback()
performs two checks
- That the msg.sender is L2ToL2CrossDomainMessenger
- That the message being sent was originally emitted on the source chain by the same contract address
Without the second check, it will be possible for ANY address on the source chain to send the message. This is undesirable because now there is no guarantee that the message was generated as a result of someone calling CrossChainMultisend.send
The contract captures the msgHash from SuperchainWETH's sendETH call and passes it to the destination chain. This enables:
- Verification that the ETH bridge operation completed successfully
- Reliable cross-chain message dependency tracking
This is a pattern for composing cross domain messages. Functions that emit a cross domain message (such as SuperchainWeth.sendEth
) should return the message hash so that other contracts can consume / depend on it.
This "returning msgHash pattern" is also used in the CrossChainMultisend.sol
, making it possible for a different contract to compose on this.
function send(uint256 _destinationChainId, Send[] calldata _sends) public payable returns (bytes32)
The contract implements a pattern for handling dependent cross-chain messages:
- First message (ETH bridging) must complete successfully
- Second message (ETH distribution) verifies the first message's success, otherwise reverts
- Auto-relayer handles the dependency by waiting for the first message before processing the second
CrossDomainMessageLib.requireMessageSuccess(_sendWethMsgHash);
The above check calls the L2ToL2CrossDomainMessenger.successfulMessages mapping to check that the message corresponding to msgHash was correctly relayed already.
While you can revert using any custom error, it is recommended that such cases emit
error RequiredMessageNotSuccessful(bytes32 msgHash)
(which CrossDomainMessageLib.requireMessageSuccess
does under the hood)
This allows indexers / relayers to realize dependencies between messages, and recognize that a failed relayed message should be retried once the dependent message succeeds at some point in the future.
The contract leverages SuperchainWETH to handle cross-chain ETH transfers:
- Automatically wraps and unwraps ETH as needed
- Provides reliable message hashes for tracking transfers
- Maintains ETH value consistency across chains
The high level flow is:
function sendETH(address _to, uint256 _chainId) external payable returns (bytes32 msgHash_);
- converts ETH to SuperchainWETH
- sends SuperchainWETH (which follows SuperchainERC20 standard) using a crossdomain message
function relayETH(address _from, address _to, uint256 _amount) external;
- relays the SuperchainWETH to the destination chain
- converts the SuperchainWETH to ETH
Follow this guide to install Foundry
Click the "Use this template" button above on GitHub, or generate directly
git clone <your-new-repository-url>
cd superchain-starter-multisend
pnpm i
pnpm dev
This command will:
- Start a local Superchain network (1 L1 chain and 2 L2 chains) using supersim
- Launch the frontend development server at (http://localhost:5173)
- Deploy the smart contracts to your local network
Start building on the Superchain!
This contract is not production ready. For one, the contract does not consider the case when the dispersal fails on the destination chain
- if one of the recipient is a contract that has a reverting
receive()
handler relay(...)
call will revert, meaning none of the recipients will be able to receive the dispersal
One unimplemented mitigation is to add a withdrawal flow for any failed recipients such that one recipient's failure to receive doesn't prevent the others
- Interop recipes / guides: https://docs.optimism.io/app-developers/tutorials/interop
- Superchain Dev Console: https://console.optimism.io/
Want to see more? Here are more example crosschain apps for inspiration / patterns!
- β‘ Crosschain Flash Loan
- Dependent cross-chain messages (compose multiple cross-domain messages)
- Using SuperchainTokenBridge for cross-chain ERC20 transfers
- Multichain lending vaults using
L2ToL2CrossDomainMessenger
- πΈ Multisend
- How to set up cross-chain callbacks (contract calling itself on another chain)
- Using SuperchainWETH for cross-chain ETH transfers
- Dependent cross-chain messages (compose multiple cross-domain messages)
- πͺ SuperchainERC20
- Using ERC-7802 interface for SuperchainERC20 tokens
- How to upgrade existing ERC20s into SuperchainERC20
- Minting supply on only one chain
- Deterministic address deployment on all chains
- π CrossChainPingPong
- Simple example of passing state between multiple chains using cross domain messenger
- How to set up cross-chain callbacks (contract calling itself on another chain)
- πΉοΈ CrossChainTicTacToe
- Allows players to play each other from any chain without cross-chain calls, instead relying on cross-chain event reading
- Creating horizontally scalable apps with interop
Files are licensed under the MIT license.