Tutorial on how to take permissionlessly create a lockbox contract to enable Superchain interoperability.
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.
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.
Show About this tutorial
What you’ll learn
How to permissionlessly create a lockbox contract to enable Superchain interoperability.
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.
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.
Create a new file, packages/contracts/scripts/LockboxDeployer.s.sol:
Report incorrect code
Copy
Ask AI
// 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)); } }
Show Explanation of the modified functions
For the most part, this is the standard SuperchainERC20Deployer.s.sol that comes with the SuperchainERC20 starter kit.
Some functions are modified, as explained below.
Report incorrect code
Copy
Ask AI
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"); }
Get the majority of the configuration from the environment.
Mostly of it is derived from the configuration of the original ERC-20 token.Note that there is no owner here.
This SuperchainERC20 contract does not need an owner, because minting and burning are handled by the users themselves (by locking and unlocking the original tokens).