Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 125 additions & 161 deletions pages/interop/tutorials/custom-superchain-erc20.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

<Callout>
The SuperchainERC20 standard is ready for production deployments.
Expand All @@ -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:

<Steps>

### 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) {}
<details>
<summary>About this tutorial</summary>

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
</details>

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) {}
<Steps>
### 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 address<sup>1</sup> |
| 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.

</Steps>
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= <<<private key goes here>>>
```

## Prerequisites
### Blockchain-specific setup

Before starting this tutorial, ensure your development environment meets the following requirements:
<Tabs items={['Supersim', 'Devnets']}>
<Tabs.Tab>
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
```
</Tabs.Tab>

* Understanding of smart contract development
* Familiarity with blockchain concepts
* Familiarity with [standard SuperchainERC20 deployments](./deploy-superchain-erc20).
<Tabs.Tab>
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
```

<Steps>
### 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 |
| ---------------- | --------------------------------------------- | ---------------------- |
| salt<sup>1</sup> | A unique identifier | Carthage |
| chains | The chains to deploy the contract<sup>2</sup> | \["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.
</Tabs.Tab>
</Tabs>

### Create the custom contract

Expand Down Expand Up @@ -246,49 +189,70 @@ The tutorial uses these primary tools:
pnpm contracts:deploy:token
```

<details>
<summary>Sanity check</summary>
### 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`
```
<Tabs items={['Supersim', 'Devnets']}>
<Tabs.Tab>
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
```
</Tabs.Tab>

```sh
RPC_DEV0=https://interop-alpha-0.optimism.io
RPC_DEV1=https://interop-alpha-1.optimism.io
```
<Tabs.Tab>
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
```
</Tabs.Tab>
</Tabs>

```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
```
</details>
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
```
</Steps>

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.