diff --git a/pages/stack/interop/tutorials/_meta.json b/pages/stack/interop/tutorials/_meta.json index 183081d61..e5ef429e1 100644 --- a/pages/stack/interop/tutorials/_meta.json +++ b/pages/stack/interop/tutorials/_meta.json @@ -2,6 +2,8 @@ "message-passing": "Interop message passing", "deploy-superchain-erc20": "Issuing new assets with SuperchainERC20", "transfer-superchainERC20": "Transferring a SuperchainERC20", + "deploy-superchain-erc20": "Issuing new assets with SuperchainERC20", + "custom-superchain-erc20": "Custom SuperchainERC20 tokens", "bridge-crosschain-eth": "Bridging native cross-chain ETH transfers", "relay-messages-cast": "Relaying interop messages using `cast`", "relay-messages-viem": "Relaying interop messages using `viem`", diff --git a/pages/stack/interop/tutorials/custom-superchain-erc20.mdx b/pages/stack/interop/tutorials/custom-superchain-erc20.mdx new file mode 100644 index 000000000..9257b3877 --- /dev/null +++ b/pages/stack/interop/tutorials/custom-superchain-erc20.mdx @@ -0,0 +1,168 @@ +--- +title: Creating custom SuperchainERC20 tokens +lang: en-US +description: Create SuperchainERC20 tokens with custom behaviors +--- + +import { Callout } from 'nextra/components' +import { Steps } from 'nextra/components' + + + 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. + + +# Custom SuperchainERC20 tokens + +## Overview + +This guide explains how to modify the behavior of [`SuperchainERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainERC20.sol) contracts to create custom tokens that can then be bridged quickly and safely using the [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) contract (once interop is operational). +For more information on how it works, [see the explainer](/stack/interop/superchain-erc20). + +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 ERC-20 tokens on different 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. + +## Prerequisites + +Before starting this tutorial, ensure your development environment meets the following requirements: + +### Technical knowledge + +* Understanding of smart contract development +* Familiarity with blockchain concepts +* Familiarity with [standard SuperchainERC20 deployments](./deploy-superchain-erc20). + +### Development environment + +* Unix-like operating system (Linux, macOS, or WSL for Windows) +* Git for version control + +### Required tools + +The tutorial uses these primary tools: + +* Foundry: For sending transactions to blockchains. + +## Step by step explanation + + + ### Prepare for deployment + + 1. Follow the setup steps in the [SuperchainERC20 Starter Kit](/app-developers/starter-kit#setup). + Don't start the development environment (step 5). + + 2. Follow [the deployment preparations steps](./deploy-superchain-erc20#prepare-for-deployment) in the issuing new assets page. + Don't deploy the contracts yet. + + **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. + + ### Create the custom contract + + The easiest way to do this is to copy and modify the `L2NativeSuperchainERC20.sol` contract. + Use this code, for example, as `packages/contracts/src/CustomSuperchainToken.sol`. + + ```solidity file=/public/tutorials/CustomSuperchainToken.sol hash=4ad95b9203ce523351eba0501f8b972d + ``` + +
+ Explanation + + ```solidity file=/public/tutorials/CustomSuperchainToken.sol#L36-L38 hash=4e402ea88c9cd796500425172a6de16d + ``` + + This function lets users get tokens for themselves. + This token is for testing purposes, so it is useful for users to get their own tokens to run tests. +
+ + ### Deploy the new token + + 1. Edit `packages/contracts/scripts/SuperchainERC20Deployer.s.sol`: + + * Change line 6 to import the new token. + + ```solidity + import {CustomSuperchainToken} from "../src/CustomSuperchainToken.sol"; + ``` + + * Update lines 52-54 to get the `CustomSuperchainToken` initialization code. + + ```solidity + bytes memory initCode = abi.encodePacked( + type(CustomSuperchainToken).creationCode, abi.encode(ownerAddr_, name, symbol, uint8(decimals)) + ); + ``` + + * Modify line 62 to deploy a `CustomSuperchainToken` contract. + + ```solidity + addr_ = address(new CustomSuperchainToken{salt: _implSalt()}(ownerAddr_, name, symbol, uint8(decimals))); + ``` + + 2. Deploy the token contract. + + ```sh + pnpm contracts:deploy:token + ``` + +
+ Sanity check + + 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). + + ```sh + TOKEN_ADDRESS=0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8 + ``` + + 2. Source the `.env` file to get the private key and the address to which it corresponds. + + ```sh + . packages/contracts/.env + MY_ADDRESS=`cast wallet address $DEPLOYER_PRIVATE_KEY` + ``` + + 3. Set variables for the RPC URLs. + + ```sh + RPC_DEV0=https://interop-alpha-0.optimism.io + RPC_DEV1=https://interop-alpha-1.optimism.io + ``` + + 4. Get your current balance (it should be zero). + + ```sh + 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 $RPC_DEV0 $TOKEN_ADDRESS "faucet()" + cast call --rpc-url $RPC_DEV0 $TOKEN_ADDRESS "balanceOf(address)" $MY_ADDRESS | cast --from-wei + ``` +
+
+ +## How does this work? + +To allow for superchain interoperability, an ERC-20 token has to implement [ERC-7802](https://specs.optimism.io/interop/token-bridging.html#ierc7802). +You can either use [the `SuperchainERC20` implementation](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainERC20.sol#L26-L46), or write your own. + +For more details [see the explainer](../superchain-erc20). + +## Next steps + +* Use the [SuperchainERC20 Starter Kit](/app-developers/starter-kit) to deploy your token across the Superchain. +* Explore the [SuperchainERC20 specifications](https://specs.optimism.io/interop/token-bridging.html) for in-depth implementation details. +* Review the [Superchain Interop Explainer](../explainer) for answers to common questions about interoperability. diff --git a/public/tutorials/CustomSuperchainToken.sol b/public/tutorials/CustomSuperchainToken.sol new file mode 100644 index 000000000..20bd72f62 --- /dev/null +++ b/public/tutorials/CustomSuperchainToken.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {SuperchainERC20} from "./SuperchainERC20.sol"; +import {Ownable} from "@solady/auth/Ownable.sol"; + +contract CustomSuperchainToken is SuperchainERC20, Ownable { + string private _name; + string private _symbol; + uint8 private immutable _decimals; + + constructor(address owner_, string memory name_, string memory symbol_, uint8 decimals_) { + _name = name_; + _symbol = symbol_; + _decimals = decimals_; + + _initializeOwner(owner_); + } + + function name() public view virtual override returns (string memory) { + return _name; + } + + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } + + function mintTo(address to_, uint256 amount_) external onlyOwner { + _mint(to_, amount_); + } + + function faucet() external { + _mint(msg.sender, 10**_decimals); + } +}