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.
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 .
Overview
Always verify your addresses and amounts before sending transactions.
Cross-chain transfers cannot be reversed.
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
Clone the starter repository
npx degit ethereum-optimism/starter-kit-superchain-erc20 transfer-superchain-erc20
cd transfer-superchain-erc20
Preparation
If you are using Supersim, setup the SuperchainERC20 starter kit .
The pnpm dev
step also starts Supersim.
Store the configuration in environment variables.
Obtain tokens on chain A.
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
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 project, to be able to use @eth-optimism/viem
to send the executing message.
We use TypeScript to have type safety 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 ()
Show 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
’s walletActionsL2().sendSuperchainERC20
to send the SuperchainERC20
tokens.
Internally, this function calls SuperchainTokenBridge.sendERC20
to send the tokens.
Run the TypeScript program, and see the change in your token balances.
pnpm tsx src/xfer-erc20.mts
Next steps