diff --git a/pages/interop/tutorials/custom-superchain-erc20.mdx b/pages/interop/tutorials/custom-superchain-erc20.mdx index 0c8c25193..994f47df4 100644 --- a/pages/interop/tutorials/custom-superchain-erc20.mdx +++ b/pages/interop/tutorials/custom-superchain-erc20.mdx @@ -18,8 +18,7 @@ categories: is_imported_content: 'false' --- -import { Callout } from 'nextra/components' -import { Steps } from 'nextra/components' +import { Callout, Steps, Tabs } from 'nextra/components' The SuperchainERC20 standard is ready for production deployments. @@ -35,168 +34,112 @@ This guide explains how to upgrade an ERC20 to a [`SuperchainERC20`](https://git To ensure fungibility across chains, `SuperchainERC20` assets must have the same contract address on all chains. This requirement abstracts away the complexity of cross-chain validation. Achieving this requires deterministic deployment methods. There are [many ways to do this](https://github.com/Arachnid/deterministic-deployment-proxy). Here we will use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit). -### What you'll do - -* Use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit) to deploy tokens with your custom code. - -### What you'll learn - -* How to deploy custom ERC20 tokens across multiple chains at the same address so that they can be bridged with the [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) contract. - -### How does this work? - -To benefit from Superchain Interoperability, an ERC-20 token has to implement [ERC-7802](https://eips.ethereum.org/EIPS/eip-7802). You can either use the SuperchainERC20 implementation, or write your own. - -At a high level you will: - - - -### Create a basic ERC20 contract - -The following code implements a basic ERC20 contract: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -contract MyERC20 is ERC20, Ownable { - constructor(address owner, string memory name, string memory symbol) ERC20(name, symbol) Ownable(owner) {} - - function mint(address to, uint256 amount) public onlyOwner { - _mint(to, amount); - } -} - -``` - -### Add the new SuperchainERC20 interface - -The first step is simply to implement [`IERC7802`](https://eips.ethereum.org/EIPS/eip-7802) and `IERC165`. Note that we've renamed the contract at this point: - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; -import {IERC7802} from "@openzeppelin/contracts/token/ERC20/IERC7802.sol"; - -contract MySuperchainERC20 is ERC20, Ownable, IERC7802 { - error NotSuperchainERC20Bridge(); - - constructor(address owner, string memory name, string memory symbol) ERC20(name, symbol) Ownable(owner) {} +
+ About this tutorial - function mint(address to, uint256 amount) public onlyOwner { - _mint(to, amount); - } + **What you'll learn** + * How to deploy custom ERC20 tokens across multiple chains at the same address so that they can be bridged with the [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) contract. - function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) { - return _interfaceId == type(IERC7802).interfaceId || - _interfaceId == type(ERC20).interfaceId - || _interfaceId == type(ERC165).interfaceId; - } -} + **Prerequisite technical knowledge** -``` + * Understanding of smart contract development + * Familiarity with blockchain concepts + * Familiarity with [standard SuperchainERC20 deployments](/interop/tutorials/deploy-superchain-erc20). -### Implement burn and mint functions + **Development environment** -There are two functions we need to implement: `CrosschainMint` and `CrosschainBurn`. These two functions allow two chains to bridge a token by burning them on one chain and mint them on another. Read more about these functions in our [SuperchainERC20 docs](/interop/superchain-erc20). + * Unix-like operating system (Linux, macOS, or WSL for Windows) + * Git for version control +
-Here's what our contract looks like once we've implemented the functions: - - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +### What you'll do -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; -import {IERC7802} from "@openzeppelin/contracts/token/ERC20/IERC7802.sol"; +* Use the [SuperchainERC20 starter kit](/app-developers/starter-kit) to deploy tokens with your custom code. -contract MySuperchainERC20 is ERC20, Ownable, IERC7802 { - error NotSuperchainERC20Bridge(); +## Step by step - constructor(address owner, string memory name, string memory symbol) ERC20(name, symbol) Ownable(owner) {} + + ### General setup - function mint(address to, uint256 amount) public onlyOwner { - _mint(to, amount); - } + Follow the setup steps in the [SuperchainERC20 starter kit](/app-developers/starter-kit#setup), steps 1-4. - /// @notice Allows the SuperchainTokenBridge to mint tokens. - /// @param _to Address to mint tokens to. - /// @param _amount Amount of tokens to mint. - function crosschainMint(address _to, uint256 _amount) external { - if (msg.sender != 0x4200000000000000000000000000000000000028) revert NotSuperchainERC20Bridge(); + 1. Install [Foundry](https://book.getfoundry.sh/getting-started/installation). - _mint(_to, _amount); + 2. Run these commands: - emit CrosschainMint(_to, _amount, msg.sender); - } + ```sh + git clone https://github.com/ethereum-optimism/superchainerc20-starter.git + cd superchainerc20-starter + pnpm install + pnpm init:env + ``` - /// @notice Allows the SuperchainTokenBridge to burn tokens. - /// @param _from Address to burn tokens from. - /// @param _amount Amount of tokens to burn. - function crosschainBurn(address _from, uint256 _amount) external { - if (msg.sender != 0x4200000000000000000000000000000000000028) revert NotSuperchainERC20Bridge(); - - _burn(_from, _amount); + 3. Edit `packages/contracts/configs/deploy-config.toml`'s `[token]` section. - emit CrosschainBurn(_from, _amount, msg.sender); - } + | Parameter | Meaning | Example | + | -------------- | ------------------------ | ------------------------ | + | owner\_address | Owner of the token | Your address1 | + | name | Token name | Quick Transfer Token | + | symbol | Token symbol | QTT | + | decimals | Number of decimal places | 18 | - function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) { - return _interfaceId == type(IERC7802).interfaceId || - _interfaceId == type(ERC20).interfaceId - || _interfaceId == type(ERC165).interfaceId; - } -} -``` + (1) This should be an address you control (for which you know the private key), which has some ETH on the blockchains in question. + In the case of Supersim, the default has 10k ETH and you can use it. + For the devnets, [see here](/interop/tools/devnet#sending-eth-to-the-interop-devnets) to send ETH. - + 4. If you change `owner_address`, you need to also set the private key. + Edit `packages/contracts/.env` to set `DEPLOYER_PRIVATE_KEY` to the private key of an account that has ETH on both devnet blockchains. -For more details [see the explainer](/interop/superchain-erc20). + ```sh + DEPLOYER_PRIVATE_KEY= <<>> + ``` -## Prerequisites + ### Blockchain-specific setup -Before starting this tutorial, ensure your development environment meets the following requirements: + + + The [SuperchainERC20 Starter Kit](/app-developers/starter-kit) is already set up for [Supersim](/interop/tools/supersim). + All you need to do is start it. -### Technical knowledge + ```sh + ./supersim --interop.autorelay + ``` + -* Understanding of smart contract development -* Familiarity with blockchain concepts -* Familiarity with [standard SuperchainERC20 deployments](./deploy-superchain-erc20). + + The [SuperchainERC20 Starter Kit](/app-developers/starter-kit) is already set up for [Supersim](/interop/tools/supersim). + If you want to use it with a different set of blockchains, for example the [Devnets](/interop/tools/devnet), follow these steps. -### Development environment + 1. Edit `packages/contracts/foundry.toml` to add the RPC endpoints (add the bottom two rows). -* Unix-like operating system (Linux, macOS, or WSL for Windows) -* Git for version control - -### Required tools - -The tutorial uses these primary tools: + ```toml + [rpc_endpoints] + op_chain_a = "http://127.0.0.1:9545" + op_chain_b = "http://127.0.0.1:9546" + devnet0 = "https://interop-alpha-0.optimism.io" + devnet1 = "https://interop-alpha-1.optimism.io" + ``` -* Foundry: For sending transactions to blockchains. + You can import most RPC endpoints with this command, but it does not include the Interop devnet. -## Step by step explanation + ```sh + pnpm contracts:update:rpcs + ``` - - ### Prepare for deployment + 2. Edit `packages/contracts/configs/deploy-config.toml`'s `[deploy-config]` section. - 1. Follow the setup steps in the [SuperchainERC20 Starter Kit](/app-developers/starter-kit#setup). - Don't start the development environment (step 5). + | Parameter | Meaning | Example | + | ---------------- | --------------------------------------------- | ---------------------- | + | salt1 | A unique identifier | Carthage | + | chains | The chains to deploy the contract2 | \["devnet0","devnet1"] | - 2. Follow [the deployment preparations steps](./deploy-superchain-erc20#prepare-for-deployment) in the issuing new assets page. - Don't deploy the contracts yet. + (1) Make sure to specify a previously unused value for the salt, for example your address and a timestamp. + This is necessary because if the same constructor code is used with the same salt when using the deployment script, it gets the same address, which is a problem if you want a fresh deployment. - **Note:** Make sure to specify a previously unused value for the salt, for example your address and a timestamp. - This is necessary because if the same constructor code is used with the same salt when using the deployment script, it gets the same address, which is a problem if you want a fresh deployment. + (2) These names must correspond to the chain names in the `[rpc_endpoints]` section of `foundry.toml` you updated in the previous step. + + ### Create the custom contract @@ -246,49 +189,70 @@ The tutorial uses these primary tools: pnpm contracts:deploy:token ``` -
- Sanity check + ### Verify the installation + + 1. Set `TOKEN_ADDRESS` to the address where the token is deployed. + You can also play with a previously created token, which is at address [`0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8`](https://sid.testnet.routescan.io/address/0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8) on the devnets. + + ```sh + TOKEN_ADDRESS=<<< Your token address >>> + ``` - 1. Set `TOKEN_ADDRESS` to the address where the token is deployed. - You can also play with the token I created, which is at address [`0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8`](https://sid.testnet.routescan.io/address/0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8). + 2. Source the `.env` file to get the private key and the address to which it corresponds. - ```sh - TOKEN_ADDRESS=0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8 - ``` + ```sh + . packages/contracts/.env + USER_ADDRESS=`cast wallet address $DEPLOYER_PRIVATE_KEY` + ``` - 2. Source the `.env` file to get the private key and the address to which it corresponds. + 3. Set variables for the RPC URLs. - ```sh - . packages/contracts/.env - MY_ADDRESS=`cast wallet address $DEPLOYER_PRIVATE_KEY` - ``` + + + Set these parameters for Supersim. - 3. Set variables for the RPC URLs. + ```sh + URL_CHAIN_A=http://127.0.0.1:9545 + URL_CHAIN_B=http://127.0.0.1:9546 + ``` + - ```sh - RPC_DEV0=https://interop-alpha-0.optimism.io - RPC_DEV1=https://interop-alpha-1.optimism.io - ``` + + For Devnets, specify in `PRIVATE_KEY` the private key you used for the setup script and then these parameters. - 4. Get your current balance (it should be zero). + ```sh + URL_CHAIN_A=https://interop-alpha-0.optimism.io + URL_CHAIN_B=https://interop-alpha-1.optimism.io + ``` + + - ```sh - cast call --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "balanceOf(address)" $MY_ADDRESS | cast --from-wei - ``` + 4. Get your current balance (it should be zero). - 5. Call the faucet to get a token and check the balance again. + ```sh + cast call --rpc-url $URL_CHAIN_A $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS | cast --from-wei + ``` - ```sh - cast send --private-key $DEPLOYER_PRIVATE_KEY --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "faucet()" - cast call --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "balanceOf(address)" $MY_ADDRESS | cast --from-wei - ``` -
+ 5. Call the faucet to get a token and check the balance again. + + ```sh + cast send --private-key $DEPLOYER_PRIVATE_KEY --rpc-url $URL_CHAIN_A $TOKEN_ADDRESS "faucet()" + cast call --rpc-url $URL_CHAIN_A $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS | cast --from-wei + ``` + + 6. Repeat the same steps on Chain B. + + ```sh + cast call --rpc-url $URL_CHAIN_B $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS | cast --from-wei + cast send --private-key $DEPLOYER_PRIVATE_KEY --rpc-url $URL_CHAIN_B $TOKEN_ADDRESS "faucet()" + cast call --rpc-url $URL_CHAIN_B $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS | cast --from-wei + ```
For more details [see the explainer](/interop/superchain-erc20). ## Next steps + * Use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit) to deploy your token across the Superchain. * If you'd like a guided walkthrough, check out our [tutorial video](https://x.com/i/status/1866095114374045969) instead. * Review the [Superchain Interop Explainer](/interop/explainer) for answers to common questions about interoperability. -