diff --git a/pages/interop/tutorials/transfer-superchainERC20.mdx b/pages/interop/tutorials/transfer-superchainERC20.mdx
index 52542ac5a..81c6dd05b 100644
--- a/pages/interop/tutorials/transfer-superchainERC20.mdx
+++ b/pages/interop/tutorials/transfer-superchainERC20.mdx
@@ -21,7 +21,7 @@ categories:
is_imported_content: 'false'
---
-import { Callout, Steps } from 'nextra/components'
+import { Callout, Steps, Tabs } from 'nextra/components'
import { AutorelayCallout } from '@/components/AutorelayCallout'
@@ -45,110 +45,144 @@ Note that this tutorial provides step-by-step instructions for transferring `Sup
Cross-chain transfers cannot be reversed.
-### What you'll build
-
-* A TypeScript application to transfer `SuperchainERC20` tokens between chains
+
+ About this tutorial
-### What you'll learn
+ **What you'll learn**
-* How to send `SuperchainERC20` tokens on the blockchain and between blockchains
-* How to relay messages between chains
+ * How to send `SuperchainERC20` tokens on the blockchain and between blockchains
+ * How to relay messages between chains
-## Prerequisites
+ **Technical knowledge**
-Before starting this tutorial, ensure your development environment meets the following requirements:
+ * Intermediate TypeScript knowledge
+ * Understanding of smart contract development
+ * Familiarity with blockchain concepts
-### Technical knowledge
+ **Development environment**
-* Intermediate TypeScript knowledge
-* Understanding of smart contract development
-* Familiarity with blockchain concepts
+ * Unix-like operating system (Linux, macOS, or WSL for Windows)
+ * Node.js version 16 or higher
+ * Git for version control
-### Development environment
+ **Required tools**
-* Unix-like operating system (Linux, macOS, or WSL for Windows)
-* Node.js version 16 or higher
-* Git for version control
+ The tutorial uses these primary tools:
-### Required tools
+ * Node: For running TypeScript code from the command line
+ * Viem: For blockchain interaction
+
-The tutorial uses these primary tools:
+### What you'll build
-* Foundry: For issuing transactions
-* TypeScript: For implementation
-* Node: For running TypeScript code from the command line
-* Viem: For blockchain interaction
+* Commands to transfer `SuperchainERC20` tokens between chains
+* A TypeScript application to transfer `SuperchainERC20` tokens between chains
## Directions
### Preparation
- You need onchain `SuperchainERC20` tokens.
- 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](/interop/tools/devnet).
+ 1. If you are using Supersim, setup the [SuperchainERC20 starter kit](/app-developers/starter-kit#setup).
+ The `pnpm dev` step also starts Supersim.
- 1. Create environment variables for the RPC endpoints for the blockchains and the token address.
+ 2. Store the configuration in environment variables.
- ```sh
- RPC_DEV0=https://interop-alpha-0.optimism.io
- RPC_DEV1=https://interop-alpha-1.optimism.io
- TOKEN_ADDRESS=0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8
- ```
+
+
+ ```sh
+ 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`
+ ```
+
- 2. Set `PRIVATE_KEY` to the private key of an address that has [Sepolia ETH](https://cloud.google.com/application/web3/faucet/ethereum/sepolia).
+
+ 1. Set `PRIVATE_KEY` to the private key for an address that has ETH on the two devnets.
- ```sh
- export PRIVATE_KEY=0x
- MY_ADDRESS=`cast wallet address $PRIVATE_KEY`
- ```
+ 2. Run these commands to specify the rest of the environment variables.
- 3. Send ETH to the two L2 blockchains.
+ ```sh
+ USER_ADDRESS=`cast wallet address --private-key $PRIVATE_KEY`
+ URL_CHAIN_A=https://interop-alpha-0.optimism.io
+ URL_CHAIN_B=https://interop-alpha-1.optimism.io
+ CHAIN_B_ID=`cast chain-id --rpc-url $URL_CHAIN_B`
+ TOKEN_ADDRESS=0x0FAe7deDb9CfC2d8288d432B85998e6b263F3A72
+ ```
+
+
- ```sh
- cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIVATE_KEY --value 0.02ether 0x7385d89d38ab79984e7c84fab9ce5e6f4815468a
- cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIVATE_KEY --value 0.02ether 0x55f5c4653dbcde7d1254f9c690a5d761b315500c
- ```
+ 3. Obtain tokens on chain A.
- 4. Wait a few minutes until you can see the ETH [on the block explorer](https://sid.testnet.routescan.io/) for your address.
+
+
+ ```sh
+ 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
+ ```
+
+
+
+ ```sh
+ cast send $TOKEN_ADDRESS "faucet()" --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A
+ ```
+
+
Sanity check
- Check the ETH balance of your address on both blockchains.
+ Check that you have at least one token on chain A.
```sh
- cast balance --ether $MY_ADDRESS --rpc-url $RPC_DEV0
- cast balance --ether $MY_ADDRESS --rpc-url $RPC_DEV1
+ cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei
```
- 5. Obtain tokens on Interop devnet 0.
- When using `CustomSuperchainToken`, there are two ways to do this:
+ ### Transfer tokens using the command line
- * 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.
+ 1. Specify configuration variables.
- * Use `cast` to call the `faucet` function.
+ ```sh
+ TENTH=`echo 0.1 | cast to-wei`
+ INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
+ ```
- ```sh
- cast send --rpc-url $RPC_DEV0 --private-key $PRIVATE_KEY $TOKEN_ADDRESS "faucet()"
- ```
+ 2. See your balance on both blockchains.
-
- Sanity check
+ ```sh
+ 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
+ ```
- Run this command to check your token balance.
+ 3. Call [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) to transfer tokens.
- ```sh
- cast call --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "balanceOf(address)" $MY_ADDRESS | cast --from-wei
- ```
-
+ ```sh
+ 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
+ ```
+
+ 4. See your balance on both blockchains.
+
+ ```sh
+ 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](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.
We use [TypeScript](https://www.typescriptlang.org/) to have [type safety](https://en.wikipedia.org/wiki/Type_safety) combined with JavaScript functionality.
- 1. Initialize a new Node project.
+ 1. Export environment variables
+
+ ```sh
+ export PRIVATE_KEY TOKEN_ADDRESS CHAIN_B_ID
+ ```
+
+ 2. Initialize a new Node project.
```sh
mkdir xfer-erc20
@@ -158,69 +192,25 @@ The tutorial uses these primary tools:
mkdir src
```
- 2. Edit `package.json` to add the `start` script.
-
- ```json
- {
- "name": "xfer-erc20",
- "version": "1.0.0",
- "main": "index.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
- "start": "tsx src/xfer-erc20.mts"
- },
- "keywords": [],
- "author": "",
- "license": "ISC",
- "type": "module",
- "description": "",
- "devDependencies": {
- "@eth-optimism/viem": "^0.3.2",
- "@types/node": "^22.13.4",
- "tsx": "^4.19.3",
- "viem": "^2.23.3"
- }
- }
- ```
-
3. Create `src/xfer-erc20.mts`:
- ```typescript file=/public/tutorials/xfer-erc20.mts hash=26d412ead555cdd59c16676a4dcd91e8
+ ```typescript file=/public/tutorials/xfer-erc20.mts hash=2964a6dcafacb8b7dc1e115a54fb3b7c
```
Explanation of `xfer-erc20.mts`
- ```typescript file=/public/tutorials/xfer-erc20.mts#L79-L84 hash=5084a0cf4064dc7cfaf1cf0f88e1f2d1
+ ```typescript file=/public/tutorials/xfer-erc20.mts#L76-L81 hash=b144852a4fa9ae45e79ec6f124e48e79
```
Use `@eth-optimism/viem`'s `walletActionsL2().sendSuperchainERC20` to send the `SuperchainERC20` tokens.
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.
-
-
-
- ```typescript file=/public/tutorials/xfer-erc20.mts#L88-L90 hash=b353aebabd92c4af52858461d18fe8cd
- ```
-
- To relay a message, we need the information in the receipt.
- Also, we need to wait until the transaction with the relayed message is actually part of a block.
-
- ```typescript file=/public/tutorials/xfer-erc20.mts#L92-L94 hash=47d2387a65e4a5f5dbfa98868c2c5abc
- ```
-
- A single transaction can send multiple messages.
- But here we know we sent just one, so we look for the first one in the list.
-
- ```typescript file=/public/tutorials/xfer-erc20.mts#L96-L99 hash=4cf177987a894a8cb58ae5a3e9d731e8
- ```
-
- This is how you use `@eth-optimism/viem` to create an executing message.
- 4. Run the TypeScript program, and see the change in your `CustomSuperchainToken` balances.
+ 4. Run the TypeScript program, and see the change in your token balances.
```sh
- npm start
+ pnpm tsx src/xfer-erc20.mts
```
diff --git a/public/tutorials/xfer-erc20.mts b/public/tutorials/xfer-erc20.mts
index b27966a9c..a9b911505 100644
--- a/public/tutorials/xfer-erc20.mts
+++ b/public/tutorials/xfer-erc20.mts
@@ -3,14 +3,14 @@ import {
http,
publicActions,
getContract,
- Address,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
-import { interopAlpha0, interopAlpha1 } from '@eth-optimism/viem/chains'
-
+import { interopAlpha0, interopAlpha1, supersimL2A, supersimL2B } from '@eth-optimism/viem/chains'
import { walletActionsL2, publicActionsL2 } from '@eth-optimism/viem'
-const tokenAddress = "0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8"
+const tokenAddress = process.env.TOKEN_ADDRESS
+const useSupersim = process.env.CHAIN_B_ID == "902"
+
const balanceOf = {
"constant": true,
"inputs": [{
@@ -30,15 +30,15 @@ const balanceOf = {
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const wallet0 = createWalletClient({
- chain: interopAlpha0,
+ chain: useSupersim ? supersimL2A : interopAlpha0,
transport: http(),
account
}).extend(publicActions)
- .extend(publicActionsL2())
+// .extend(publicActionsL2())
.extend(walletActionsL2())
const wallet1 = createWalletClient({
- chain: interopAlpha1,
+ chain: useSupersim ? supersimL2B : interopAlpha1,
transport: http(),
account
}).extend(publicActions)
@@ -70,6 +70,7 @@ Address: ${account.address}
`)
}
+console.log("Initial balances")
await reportBalances()
const sendTxnHash = await wallet0.interop.sendSuperchainERC20({
@@ -79,26 +80,15 @@ const sendTxnHash = await wallet0.interop.sendSuperchainERC20({
chainId: wallet1.chain.id
})
-console.log(`Send transaction: https://sid.testnet.routescan.io/tx/${sendTxnHash}`)
-
-const sendTxnReceipt = await wallet0.waitForTransactionReceipt({
+console.log(`Send transaction: ${sendTxnHash}`)
+await wallet0.waitForTransactionReceipt({
hash: sendTxnHash
})
-const sentMessages = await wallet0.interop.getCrossDomainMessages({
- logs: sendTxnReceipt.logs
-})
-const sentMessage = sentMessages[0]
-const relayMessageParams = await wallet0.interop.buildExecutingMessage({
- log: sentMessage.log
-})
-
-const relayTxnHash = await wallet1.interop.relayCrossDomainMessage(relayMessageParams)
-
-const relayTxnReceipt = await wallet1.waitForTransactionReceipt({
- hash: relayTxnHash
-})
+console.log("Immediately after the transaction is processed")
+await reportBalances()
-console.log(`Relay transaction: https://sid.testnet.routescan.io/tx/${relayTxnHash}`)
+await new Promise(resolve => setTimeout(resolve, 5000));
+console.log("After waiting (hopefully, until the message is relayed)")
await reportBalances()
diff --git a/words.txt b/words.txt
index bec3d2011..7099c8818 100644
--- a/words.txt
+++ b/words.txt
@@ -79,6 +79,7 @@ Comprensive
COMPUTEPENDINGBLOCK
computependingblock
confs
+command
Consen
corsdomain
counterfactually