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