Skip to main content
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.

Overview

The lockbox is a smart contract that accepts deposits of the original ERC-20 and issues an equivalent amount of tokens that are Superchain interop compatible. Users can unwrap their Superchain interop token at any time by returning it to the contract, which burns the Superchain interop tokens and releases the corresponding original ERC-20 from the lockbox.
The code on the documentation site is sample code, not production code. This means that we ran it, and it works as advertised. However, it did not pass through the rigorous audit process that most Optimism code undergoes. You’re welcome to use it, but if you need it for production purposes you should get it audited first.

What you’ll do

Create a lockbox SuperchainERC20 contract to enable interoperability for an ERC20 contract without permission from the original ERC20 deployer.

Instructions

Some steps depend on whether you want to deploy on Supersim or on the development network.
1

Clone the starter kit

npx degit ethereum-optimism/starter-kit-superchain-erc20 lockbox-upgrade
cd lockbox-upgrade
2

Install dependencies

pnpm install
3

Generate the lockbox contract

pnpm run generate-lockbox
4

Deploy

pnpm foundry script scripts/DeployLockbox.s.sol --broadcast --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY
5

Install and run Supersim

If you are going to use Supersim, follow these instructions to install and run Supersim.
Make sure to run Supersim with autorelay on.
./supersim --interop.autorelay true
6

Setup the ERC-20 token on chain A

Download and run the setup script.
curl https://docs.optimism.io/tutorials/setup-for-erc20-upgrade.sh > setup-for-erc20-upgrade.sh
chmod +x setup-for-erc20-upgrade.sh
./setup-for-erc20-upgrade.sh
If you want to deploy to the development networks, provide setup-for-erc20-upgrade.sh with the private key of an address with ETH on both devnets.
./setup-for-erc20-upgrade.sh <private key>
7

Store the addresses

Execute the bottom two lines of the setup script output to store the ERC-20 address and the address of the beacon contract.
BEACON_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
export ERC20_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
8

Specify environment variables

  1. Specify these variables, which we use later:
    1. Regardless of whether you use Supersim or Devnet, specify these variables.
    INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
    export ERC20_CHAINID=`cast chain-id --rpc-url $URL_CHAIN_A`
    ORIGINAL_TOKEN_NAME=`cast call $ERC20_ADDRESS "name()" --rpc-url $URL_CHAIN_A | cast to-ascii`
    export NEW_TOKEN_NAME="$ORIGINAL_TOKEN_NAME Lockbox"
    ORIGINAL_TOKEN_SYMBOL=`cast call $ERC20_ADDRESS "symbol()" --rpc-url $URL_CHAIN_A | cast to-ascii`
    export NEW_TOKEN_SYMBOL="$ORIGINAL_TOKEN_SYMBOL-L"
    export TOKEN_DECIMALS=`cast call $ERC20_ADDRESS "decimals()" --rpc-url $URL_CHAIN_A | cast to-dec`
    
    9

    Update the deployment utilities

    The new SuperchainERC20 variant is called LockboxSuperchainERC20, and it requires different constructor parameters. To be able to deploy it, we need to modify some of the deployment utilities.
    1. Download the SuperchainERC20 starter kit, and install libraries, etc.
      git clone https://github.com/ethereum-optimism/superchainerc20-starter.git
      cd superchainerc20-starter
      pnpm install
      pnpm init:env
      
    2. Replace packages/contracts/package.json with this code:
          {
            "name": "@superchainerc20-starter/contracts",
            "main": "index.js",
            "scripts": {
              "deploy:dev": "env-cmd -f .env cross-env-shell 'wait-port http://:8420/ready && forge script scripts/SuperchainERC20Deployer.s.sol --broadcast --private-key $DEPLOYER_PRIVATE_KEY'",
              "deploy:token": "env-cmd -f .env cross-env-shell 'forge script scripts/LockboxDeployer.s.sol --broadcast --private-key $DEPLOYER_PRIVATE_KEY'",
              "update:rpcs": "cd ../.. && ./scripts/fetch-superchain-rpc-urls.sh",
              "install": "forge install",
              "build": "forge build",
              "test": "forge test",
              "init:env": "cp .env.example .env"
            },
            "dependencies": {
              "viem": "^2.21.37"
            }
          }
      
    3. Create a new file, packages/contracts/scripts/LockboxDeployer.s.sol:
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.8.25;
      
        import {Script, console} from "forge-std/Script.sol";
        import {Vm} from "forge-std/Vm.sol";
        import {LockboxSuperchainERC20} from "../src/LockboxSuperchainERC20.sol";
      
          contract LockboxDeployer is Script {
            string deployConfig;
            uint256 timestamp;
      
            constructor() {
                string memory deployConfigPath = vm.envOr("DEPLOY_CONFIG_PATH", string("/configs/deploy-config.toml"));
                string memory filePath = string.concat(vm.projectRoot(), deployConfigPath);
                deployConfig = vm.readFile(filePath);
                timestamp = vm.unixTime();
            }
      
            /// @notice Modifier that wraps a function in broadcasting.
            modifier broadcast() {
                vm.startBroadcast(msg.sender);
                _;
                vm.stopBroadcast();
            }
      
            function setUp() public {}
      
            function run() public {
                string[] memory chainsToDeployTo = vm.parseTomlStringArray(deployConfig, ".deploy_config.chains");
      
                address deployedAddress;
      
                for (uint256 i = 0; i < chainsToDeployTo.length; i++) {
                    string memory chainToDeployTo = chainsToDeployTo[i];
      
                    console.log("Deploying to chain: ", chainToDeployTo);
      
                    vm.createSelectFork(chainToDeployTo);
                    address _deployedAddress = deployLockboxSuperchainERC20();
                    deployedAddress = _deployedAddress;
                }
      
                outputDeploymentResult(deployedAddress);
            }
      
            function deployLockboxSuperchainERC20() public broadcast returns (address addr_) {
                string memory name = vm.envString("NEW_TOKEN_NAME");
                string memory symbol = vm.envString("NEW_TOKEN_SYMBOL");
                uint256 decimals = vm.envUint("TOKEN_DECIMALS");
                require(decimals <= type(uint8).max, "decimals exceeds uint8 range");
                address originalTokenAddress = vm.envAddress("ERC20_ADDRESS");
                uint256 originalChainId = vm.envUint("ERC20_CHAINID");
      
                bytes memory initCode = abi.encodePacked(
                    type(LockboxSuperchainERC20).creationCode,
                        abi.encode(name, symbol, uint8(decimals), originalTokenAddress, originalChainId)
                );
                address preComputedAddress = vm.computeCreate2Address(_implSalt(), keccak256(initCode));
                if (preComputedAddress.code.length > 0) {
                    console.log(
                        "There is already a contract at %s", preComputedAddress, "on chain id: ", block.chainid
                    );
                    addr_ = preComputedAddress;
                } else {
                    addr_ = address(new LockboxSuperchainERC20{salt: _implSalt()}(
                        name, symbol, uint8(decimals), originalTokenAddress, originalChainId));
                    console.log("Deployed LockboxSuperchainERC20 at address: ", addr_, "on chain id: ", block.chainid);
                }
            }
      
            function outputDeploymentResult(address deployedAddress) public {
                console.log("Outputting deployment result");
      
                string memory obj = "result";
                string memory jsonOutput = vm.serializeAddress(obj, "deployedAddress", deployedAddress);
      
                vm.writeJson(jsonOutput, "deployment.json");
            }
      
            /// @notice The CREATE2 salt to be used when deploying the token.
            function _implSalt() internal view returns (bytes32) {
                string memory salt = vm.parseTomlString(deployConfig, ".deploy_config.salt");
                return keccak256(abi.encodePacked(salt, timestamp));
            }
          }
        
      

    Next steps

    I