The SuperchainERC20 standard is ready for production deployments. Please note that the OP Stack interoperability upgrade, required for crosschain messaging, is currently still in active development.
Transferring SuperchainERC20 tokens
This guide shows how to transfer SuperchainERC20
tokens between chains programmatically.
Note that this tutorial provides step-by-step instructions for transferring SuperchainERC20
tokens using code.
- For a detailed behind-the-scenes explanation, see the explainer.
- For a sample UI that bridges a
SuperchainERC20
token, see here (opens in a new tab).
Overview
Always verify your addresses and amounts before sending transactions. Cross-chain transfers cannot be reversed.
About this tutorial
What you'll learn
- How to send
SuperchainERC20
tokens on the blockchain and between blockchains - How to relay messages between chains
Technical knowledge
- Intermediate 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:
- Node: For running TypeScript code from the command line
- Viem: For blockchain interaction
What you'll build
- Commands to transfer
SuperchainERC20
tokens between chains - A TypeScript application to transfer
SuperchainERC20
tokens between chains
Directions
Preparation
-
If you are using Supersim, setup the SuperchainERC20 starter kit. The
pnpm dev
step also starts Supersim. -
Store the configuration in environment variables.
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 URL_CHAIN_A=http://127.0.0.1:9545 URL_CHAIN_B=http://127.0.0.1:9546 CHAIN_B_ID=`cast chain-id --rpc-url $URL_CHAIN_B` TOKEN_ADDRESS=`cat superchainerc20-starter/packages/contracts/broadcast/multi/SuperchainERC20Deployer.s.sol-latest/run.json | jq --raw-output .deployments[0].transactions[0].contractAddress`
-
Obtain tokens on chain A.
ONE=`echo 1 | cast to-wei` cast send $TOKEN_ADDRESS "mintTo(address,uint256)" $USER_ADDRESS $ONE --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A
Sanity check
Check that you have at least one token on chain A.
cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei
Transfer tokens using the command line
-
Specify configuration variables.
TENTH=`echo 0.1 | cast to-wei` INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
-
See your balance on both blockchains.
cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_B | cast from-wei
-
Call
SuperchainTokenBridge
(opens in a new tab) to transfer tokens.cast send $INTEROP_BRIDGE "sendERC20(address,address,uint256,uint256)" $TOKEN_ADDRESS $USER_ADDRESS $TENTH $CHAIN_B_ID --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A
-
See your balance on both blockchains.
cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_B | cast from-wei
Transfer tokens using TypeScript
We are going to use a Node (opens in a new tab) project, to be able to use @eth-optimism/viem
(opens in a new tab) to send the executing message.
We use TypeScript (opens in a new tab) to have type safety (opens in a new tab) combined with JavaScript functionality.
-
Export environment variables
export PRIVATE_KEY TOKEN_ADDRESS CHAIN_B_ID
-
Initialize a new Node project.
mkdir xfer-erc20 cd xfer-erc20 npm init -y npm install --save-dev -y viem tsx @types/node @eth-optimism/viem mkdir src
-
Create
src/xfer-erc20.mts
:import { createWalletClient, http, publicActions, getContract, } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { interopAlpha0, interopAlpha1, supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' import { walletActionsL2, publicActionsL2 } from '@eth-optimism/viem' const tokenAddress = process.env.TOKEN_ADDRESS const useSupersim = process.env.CHAIN_B_ID == "902" const balanceOf = { "constant": true, "inputs": [{ "name": "_owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "name": "balance", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" } const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) const wallet0 = createWalletClient({ chain: useSupersim ? supersimL2A : interopAlpha0, transport: http(), account }).extend(publicActions) // .extend(publicActionsL2()) .extend(walletActionsL2()) const wallet1 = createWalletClient({ chain: useSupersim ? supersimL2B : interopAlpha1, transport: http(), account }).extend(publicActions) .extend(publicActionsL2()) .extend(walletActionsL2()) const token0 = getContract({ address: tokenAddress, abi: [balanceOf], client: wallet0 }) const token1 = getContract({ address: tokenAddress, abi: [balanceOf], client: wallet1 }) const reportBalances = async () => { const balance0 = await token0.read.balanceOf([account.address]) const balance1 = await token1.read.balanceOf([account.address]) console.log(` Address: ${account.address} chain0: ${balance0.toString().padStart(20)} chain1: ${balance1.toString().padStart(20)} `) } console.log("Initial balances") await reportBalances() const sendTxnHash = await wallet0.interop.sendSuperchainERC20({ tokenAddress, to: account.address, amount: BigInt(1000000000), chainId: wallet1.chain.id }) console.log(`Send transaction: ${sendTxnHash}`) await wallet0.waitForTransactionReceipt({ hash: sendTxnHash }) console.log("Immediately after the transaction is processed") await reportBalances() await new Promise(resolve => setTimeout(resolve, 5000)); console.log("After waiting (hopefully, until the message is relayed)") await reportBalances()
Explanation of
xfer-erc20.mts
const sendTxnHash = await wallet0.interop.sendSuperchainERC20({ tokenAddress, to: account.address, amount: BigInt(1000000000), chainId: wallet1.chain.id })
Use
@eth-optimism/viem
'swalletActionsL2().sendSuperchainERC20
to send theSuperchainERC20
tokens. Internally, this function callsSuperchainTokenBridge.sendERC20
(opens in a new tab) to send the tokens. -
Run the TypeScript program, and see the change in your token balances.
pnpm tsx src/xfer-erc20.mts
Next steps
- Read the Superchain Interop Explainer or check out this Superchain interop design video walk-thru (opens in a new tab).
- Learn how this works.
- Use Supersim, a local dev environment that simulates Superchain interop for testing applications against a local version of the Superchain.