Skip to content

Commit b5a88cf

Browse files
committed
First version of tutorial
1 parent 0c2cf26 commit b5a88cf

File tree

3 files changed

+279
-76
lines changed

3 files changed

+279
-76
lines changed

pages/stack/interop/tutorials/_meta.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"message-passing": "Interop message passing",
3-
"transfer-superchainERC20": "Transferring a SuperchainERC20",
3+
"transfer-superchainERC20": "Transferring SuperchainERC20 tokens",
44
"deploy-superchain-erc20": "Issuing new assets with SuperchainERC20",
55
"bridge-crosschain-eth": "Bridging native cross-chain ETH transfers",
66
"relay-messages-cast": "Relaying interop messages using `cast`",
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Transferring a SuperchainERC20
2+
title: Transferring SuperchainERC20 tokens
33
lang: en-US
44
description: Learn how to transfer a SuperchainERC20 between chains using L2ToL2CrossDomainMessenger.
55
---
@@ -13,106 +13,202 @@ Please note that the OP Stack interoperability upgrade, required for crosschain
1313

1414
# Transferring a SuperchainERC20
1515

16-
This guide provides an overview of transferring `SuperchainERC20` tokens between chains.
16+
This guide shows how to transfer `SuperchainERC20` tokens between chains programatically.
17+
18+
<Callout>
19+
This tutorial provides step-by-step instructions for transferring `SuperchainERC20` tokens using code.
20+
[See here](../superchain-erc20#how-it-works) for a detailed explanation of how this works.
21+
</Callout>
1722

1823
## Overview
1924

20-
Transferring SuperchainERC20 tokens between chains involves two main phases:
25+
**Warning:** Always verify your addresses and amounts before sending transactions.
26+
Cross-chain transfers cannot be reversed.
2127

22-
1. **Source Chain Operations**
23-
* Mint tokens if needed
24-
* Initiate the transfer using the bridge
25-
2. **Destination Chain Operations**
26-
* Relay the transfer message
27-
* Verify the transfer completion
28+
### What you'll build
2829

29-
<Callout type="warning">
30-
Always verify your addresses and amounts before sending transactions. Cross-chain transfers cannot be reversed.
31-
</Callout>
30+
* A TypeScript application to transfer `SuperchainERC20` tokens between chains
31+
32+
### What you'll learn
33+
34+
* How to send `SuperchainERC20` tokens on the blockchain and between blockchains
35+
* How to relay messages between chains
36+
37+
## Prerequisites
38+
39+
Before starting this tutorial, ensure your development environment meets the following requirements:
40+
41+
### Technical knowledge
42+
43+
* Intermediate TypeScript knowledge
44+
* Understanding of smart contract development
45+
* Familiarity with blockchain concepts
46+
47+
### Development environment
3248

33-
## How it works
34-
35-
This diagram illustrates the process of a SuperchainERC20 token transfer between chains.
36-
Through the `L2ToL2CrossDomainMessenger` contract, tokens are burned on the source chain and a transfer message is emitted.
37-
This message must then be relayed to the destination chain, where an equivalent amount of tokens will be minted to the specified recipient address - ensuring secure cross-chain transfers while maintaining the total token supply across all chains.
38-
39-
```mermaid
40-
sequenceDiagram
41-
actor User
42-
participant SourceChain
43-
participant Bridge as L2ToL2CrossDomainMessenger
44-
participant DestChain
45-
46-
Note over User,DestChain: Step 1: Prepare Tokens
47-
User->>SourceChain: Check token balance
48-
alt
49-
User->>SourceChain: Mint or acquire tokens
50-
end
51-
52-
Note over User,DestChain: Step 2: Initiate Transfer
53-
User->>SourceChain: Approve bridge contract
54-
User->>Bridge: Call sendERC20
55-
Bridge->>SourceChain: Burn tokens
56-
Bridge-->>Bridge: Emit transfer message
57-
58-
Note over User,DestChain: Step 3: Complete Transfer
59-
User->>Bridge: Get message details
60-
User->>Bridge: Relay message on destination
61-
Bridge->>DestChain: Mint tokens to recipient
62-
63-
Note over User,DestChain: Step 4: Verify
64-
User->>DestChain: Check token balance
65-
```
49+
* Unix-like operating system (Linux, macOS, or WSL for Windows)
50+
* Node.js version 16 or higher
51+
* Git for version control
52+
53+
### Required tools
54+
55+
The tutorial uses these primary tools:
56+
57+
* Foundry: For issuing transactions
58+
* TypeScript: For implementation
59+
* Node: For running TypeScript code from the command line
60+
* Viem: For blockchain interaction
61+
62+
63+
## Directions
6664

6765
<Steps>
68-
### Prepare your tokens
6966

70-
Ensure you have tokens on the source chain using one of these methods:
67+
### Preparation
7168

72-
* Use existing tokens you already own
73-
* Mint new tokens using the [SuperchainERC20 contract](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/L2NativeSuperchainERC20.sol) if you have minting permissions
74-
* Acquire tokens through a supported exchange or transfer
69+
You need onchain `SuperchainERC20` tokens.
70+
You can [deploy your own token](./deploy-superchain-erc20), but in this tutorial we will use [`CustomSuperchainToken`](https://sid.testnet.routescan.io/address/0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8), existing `SuperchainERC20` token on the [Interop devnet](../tools/devnet).
7571

76-
### Initiate the transfer
72+
1. Create environment variables for the RPC endpoints for the blockchains and the token address.
7773

78-
To start the transfer:
74+
```sh
75+
RPC_DEV0=https://interop-alpha-0.optimism.io
76+
RPC_DEV1=https://interop-alpha-1.optimism.io
77+
TOKEN_ADDRESS=0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8
78+
```
7979

80-
1. Choose the destination chain where you want to receive the tokens
81-
2. Specify the recipient address and the amount to transfer
82-
3. Call the bridge contract, which will:
83-
* Lock or burn your tokens on the source chain
84-
* Emit a message that will be used to mint tokens on the destination chain
80+
1. Set `PRIVATE_KEY` to the private key of an address that has [Sepolia ETH](https://cloud.google.com/application/web3/faucet/ethereum/sepolia).
8581

86-
### Complete the transfer
82+
```sh
83+
export PRIVATE_KEY=0x<private key here>
84+
MY_ADDRESS=`cast wallet address $PRIVATE_KEY`
85+
```
8786

88-
To finalize the transfer on the destination chain:
87+
1. Send ETH to the two L2 blockchains.
8988

90-
1. Get the message details from the source chain event
91-
2. Use the `L2ToL2CrossDomainMessenger` contract to relay the message
92-
3. The message relay will trigger the minting of tokens on the destination chain
89+
```sh
90+
cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIVATE_KEY --value 0.02ether 0x7385d89d38ab79984e7c84fab9ce5e6f4815468a
91+
cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIVATE_KEY --value 0.02ether 0x55f5c4653dbcde7d1254f9c690a5d761b315500c
92+
```
9393

94-
<Callout type="info">
95-
The transfer isn't complete until the message is successfully relayed on the destination chain. See the [technical reference guide](/stack/interop/tutorials/relay-messages-viem) for specific relay instructions.
96-
</Callout>
94+
1. Wait a few minutes until you can see the ETH [on the block explorer](https://sid.testnet.routescan.io/) for your address.
9795

98-
### Verify completion
96+
<details>
97+
<summary>Sanity check</summary>
9998

100-
After relaying the message:
99+
```sh
100+
cast balance --ether $MY_ADDRESS --rpc-url $RPC_DEV0
101+
cast balance --ether $MY_ADDRESS --rpc-url $RPC_DEV1
102+
```
103+
</details>
101104

102-
1. Check your token balance on the destination chain
103-
2. Confirm the transferred amount matches what you sent
104-
3. The tokens should now be available for use on the destination chain
105-
</Steps>
105+
1. Obtain tokens on Interop devnet 0.
106+
When using `CustomSuperchainToken` there are two ways to do this:
107+
108+
- Use the [block explorer](https://sid.testnet.routescan.io/address/0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8/contract/420120000/writeContract?chainid=420120000) and a browser wallet to run the [faucet](https://sid.testnet.routescan.io/address/0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8/contract/420120000/writeContract?chainid=420120000#F6) function.
109+
110+
- Use `cast` to call the `faucet` function.
111+
112+
```sh
113+
cast send --rpc-url $RPC_DEV0 --private-key $PRIVATE_KEY $TOKEN_ADDRESS "faucet()"
114+
```
115+
116+
<details>
117+
118+
<summary>Sanity check</summary>
106119

107-
For detailed technical instructions including contract addresses, specific commands, and message relaying details, refer to our [technical reference guide](/stack/interop/tutorials/relay-messages-viem).
120+
Run this command to check your token balance.
108121

109-
## Alternative methods
122+
```sh
123+
cast call --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "balanceOf(address)" $MY_ADDRESS | cast --from-wei
124+
```
110125

111-
You can also use:
126+
</details>
112127

113-
* [viem bindings/actions](/stack/interop/tutorials/relay-messages-viem) for TypeScript integration
128+
### Transfer tokens using TypeScript
129+
130+
We are going to use a [Node](https://nodejs.org/en) project, to be able to use [`@eth-optimism/viem`](https://www.npmjs.com/package/@eth-optimism/viem) to send the executing message.
131+
We use [TypeScript](https://www.typescriptlang.org/) to have type safety combined with JavaScript functionality.
132+
133+
1. Initialize a new Node project.
134+
135+
```sh
136+
mkdir xfer-erc20
137+
cd xfer-erc20
138+
npm init -y
139+
npm install --save-dev -y viem tsx @types/node @eth-optimism/viem
140+
mkdir src
141+
```
142+
143+
1. Edit `package.json` to add the `start` script.
144+
145+
```json
146+
{
147+
"name": "xfer-erc20",
148+
"version": "1.0.0",
149+
"main": "index.js",
150+
"scripts": {
151+
"test": "echo \"Error: no test specified\" && exit 1",
152+
"start": "tsx src/xfer-erc20.mts"
153+
},
154+
"keywords": [],
155+
"author": "",
156+
"license": "ISC",
157+
"type": "commonjs",
158+
"description": "",
159+
"devDependencies": {
160+
"@eth-optimism/viem": "^0.3.2",
161+
"@types/node": "^22.13.4",
162+
"tsx": "^4.19.3",
163+
"viem": "^2.23.3"
164+
}
165+
}
166+
```
167+
168+
1. Create `src/xfer-erc20.mts`:
169+
170+
```solidity file=<rootDir>/public/tutorials/xfer-erc20.mts hash=19a948eeb482046afb1a55ccc5019599
171+
```
172+
173+
<details>
174+
175+
<summary>Explanation</summary>
176+
177+
```solidity file=<rootDir>/public/tutorials/xfer-erc20.mts#L79-L84 hash=85f317d0cbe2b59e303e36a3e6154c62
178+
```
179+
180+
Use `@eth-optimism/viem`'s `walletActionsL2().sendSuperchainERC20` to send the `SuperchainERC20` tokens.
181+
Internally, this function calls [`SuperchainTokenBridge.sendERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol#L52-L78) to send the tokens.
182+
183+
```typescript file=<rootDir>/public/tutorials/xfer-erc20.mts#L88-L90 hash=cab6e961b558f4f5a7b877062b1cfa45
184+
```
185+
186+
To relay a message we need the information in the receipt.
187+
Also, we need to wait until the transaction with the relayed message is actually part of a block.
188+
189+
```typescript file=<rootDir>/public/tutorials/xfer-erc20.mts#L92-L94 hash=1da0981adb2fbd38cccf1b0602158418
190+
```
191+
192+
A single transaction can send multiple messages.
193+
But here we know we sent just one, so we look for the first one in the list.
194+
195+
```typescript file=<rootDir>/public/tutorials/xfer-erc20.mts#L96-L99 hash=b5ad9f0c44aee84742cd20c348fdb156
196+
```
197+
198+
This is how you use `@eth-optimism/viem` to create an executing message.
199+
200+
</details>
201+
202+
1. Run the TypeScript program, and see the change in your `CustomSuperchainToken` balances.
203+
204+
```sh
205+
npm start
206+
```
207+
208+
</Steps>
114209

115210
## Next steps
116211

117212
* Read the [Superchain Interop Explainer](/stack/interop/explainer#faqs) or check out this [Superchain interop design video walk-thru](https://www.youtube.com/watch?v=FKc5RgjtGes).
213+
* Learn [how this works](/stack/interop/superchain-erc20).
118214
* Use [Supersim](/app-developers/tools/supersim), a local dev environment that simulates Superchain interop for testing applications against a local version of the Superchain.

public/tutorials/xfer-erc20.mts

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import {
2+
createWalletClient,
3+
http,
4+
publicActions,
5+
getContract,
6+
Address,
7+
} from 'viem'
8+
import { privateKeyToAccount } from 'viem/accounts'
9+
import { interopAlpha0, interopAlpha1 } from '@eth-optimism/viem/chains'
10+
11+
import {
12+
walletActionsL2,
13+
publicActionsL2,
14+
createInteropSentL2ToL2Messages,
15+
} from '@eth-optimism/viem'
16+
17+
const tokenAddress = "0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8"
18+
const balanceOf = {
19+
"constant": true,
20+
"inputs": [{
21+
"name": "_owner",
22+
"type": "address"
23+
}],
24+
"name": "balanceOf",
25+
"outputs": [{
26+
"name": "balance",
27+
"type": "uint256"
28+
}],
29+
"payable": false,
30+
"stateMutability": "view",
31+
"type": "function"
32+
}
33+
34+
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
35+
36+
const wallet0 = createWalletClient({
37+
chain: interopAlpha0,
38+
transport: http(),
39+
account
40+
}).extend(publicActions)
41+
.extend(publicActionsL2())
42+
.extend(walletActionsL2())
43+
44+
const wallet1 = createWalletClient({
45+
chain: interopAlpha1,
46+
transport: http(),
47+
account
48+
}).extend(publicActions)
49+
.extend(publicActionsL2())
50+
.extend(walletActionsL2())
51+
52+
const token0 = getContract({
53+
address: tokenAddress,
54+
abi: [balanceOf],
55+
client: wallet0
56+
})
57+
58+
const token1 = getContract({
59+
address: tokenAddress,
60+
abi: [balanceOf],
61+
client: wallet1
62+
})
63+
64+
65+
const reportBalances = async () => {
66+
const balance0 = await token0.read.balanceOf([account.address])
67+
const balance1 = await token1.read.balanceOf([account.address])
68+
69+
console.log(`
70+
Address: ${account.address}
71+
chain0: ${balance0.toString().padStart(20)}
72+
chain1: ${balance1.toString().padStart(20)}
73+
74+
`)
75+
}
76+
77+
await reportBalances()
78+
79+
const sendTxnHash = await wallet0.interop.sendSuperchainERC20({
80+
tokenAddress,
81+
to: account.address,
82+
amount: 1000000000,
83+
chainId: wallet1.chain.id
84+
})
85+
86+
console.log(`Send transaction: https://sid.testnet.routescan.io/tx/${sendTxnHash}`)
87+
88+
const sendTxnReceipt = await wallet0.waitForTransactionReceipt({
89+
hash: sendTxnHash
90+
})
91+
92+
const sentMessage =
93+
(await createInteropSentL2ToL2Messages(wallet0, { receipt: sendTxnReceipt }))
94+
.sentMessages[0]
95+
96+
const relayTxnHash = await wallet1.interop.relayMessage({
97+
sentMessageId: sentMessage.id,
98+
sentMessagePayload: sentMessage.payload,
99+
})
100+
101+
const relayTxnReceipt = await wallet1.waitForTransactionReceipt({
102+
hash: relayTxnHash
103+
})
104+
105+
console.log(`Relay transaction: https://sid.testnet.routescan.io/tx/${relayTxnHash}`)
106+
107+
await reportBalances()

0 commit comments

Comments
 (0)