OP Stack
Custom SuperchainERC20 tokens
💡

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 (opens in a new tab) contracts to create custom tokens that can then be bridged quickly and safely using the SuperchainTokenBridge (opens in a new tab) contract (once interop is operational). For more information on how it works, see the explainer.

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 (opens in a new tab). Here we will use the SuperchainERC20 Starter Kit.

What you'll do

What you'll learn

Prerequisites

Before starting this tutorial, ensure your development environment meets the following requirements:

Technical knowledge

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. Don't start the development environment (step 5).

  2. Follow the deployment preparations steps 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.

// 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);
    }
}
Explanation
    function faucet() external {
        _mint(msg.sender, 10**_decimals);
    }

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.

      import {CustomSuperchainToken} from "../src/CustomSuperchainToken.sol";
    • Update lines 52-54 to get the CustomSuperchainToken initialization code.

      bytes memory initCode = abi.encodePacked(
            type(CustomSuperchainToken).creationCode, abi.encode(ownerAddr_, name, symbol, uint8(decimals))
      );
    • Modify line 62 to deploy a CustomSuperchainToken contract.

        addr_ = address(new CustomSuperchainToken{salt: _implSalt()}(ownerAddr_, name, symbol, uint8(decimals)));
  2. Deploy the token contract.

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 (opens in a new tab).

    TOKEN_ADDRESS=0xF3Ce0794cB4Ef75A902e07e5D2b75E4D71495ee8
  2. Source the .env file to get the private key and the address to which it corresponds.

    . packages/contracts/.env
    MY_ADDRESS=`cast wallet address $DEPLOYER_PRIVATE_KEY`
  3. Set variables for the RPC URLs.

    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).

    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.

    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 (opens in a new tab). You can either use the SuperchainERC20 implementation (opens in a new tab), or write your own.

For more details see the explainer.

Next steps