diff --git a/pages/interop/tutorials/_meta.json b/pages/interop/tutorials/_meta.json
index 1b6e893cf..42e7e64bb 100644
--- a/pages/interop/tutorials/_meta.json
+++ b/pages/interop/tutorials/_meta.json
@@ -2,7 +2,7 @@
"message-passing": "Interop message passing",
"deploy-superchain-erc20": "Deploying a SuperchainERC20",
"transfer-superchainERC20": "Transferring a SuperchainERC20",
- "custom-superchain-erc20": "Custom SuperchainERC20 tokens",
+ "custom-superchain-erc20": "Custom SuperchainERC20 tokens",
"bridge-crosschain-eth": "Bridging native cross-chain ETH transfers",
"contract-calls": "Making crosschain contract calls (ping pong)",
"event-reads": "Making crosschain event reads (tic-tac-toe)",
diff --git a/pages/interop/tutorials/upgrade-to-superchain-erc20.mdx b/pages/interop/tutorials/upgrade-to-superchain-erc20.mdx
new file mode 100644
index 000000000..89232c9cd
--- /dev/null
+++ b/pages/interop/tutorials/upgrade-to-superchain-erc20.mdx
@@ -0,0 +1,58 @@
+---
+title: Upgrading ERC20 to SuperchainERC20
+lang: en-US
+description: Tutorial on how to take an existing ERC20 and upgrade it to SuperchainERC20.
+topic: Interoperability
+personas: [Developer]
+categories: [Tutorial, Interop]
+content_type: article
+---
+
+import { Callout, Steps, Card, Cards } 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.
+
+
+# Upgrading ERC20 to SuperchainERC20
+
+## Overview
+
+This guide explains how to upgrade an ERC20 to a [`SuperchainERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainERC20.sol) that can teleport across the [Superchain interop cluster](/interop/explainer#superchain-interop-cluster) using the [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) contract. For more information on how it works, [see the explainer](/interop/superchain-erc20).
+
+{/*
+
+I put this warning here when we don't have it on most pages because this tutorial
+has, IMHO, code that is a lot more likely to be used in production. It doesn't just
+show what is possible, it does the exact job needed.
+
+*/}
+
+There are several ways to upgrade an existing ERC20 for interop, depending on your circumstances:
+
+{/*
+
+* If you can upgrade the existing contract, but the address is not available on other chains? In that case, use a custom bridge
+
+upgrade-to-superchain-erc20/custom-bridge.
+
+*/}
+
+| When To Use | Action |
+| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
+| You can install a new ERC20 contract | [**Deploy New SuperchainERC20 contracts** directly](/interop/tutorials/deploy-superchain-erc20) |
+| Existing ERC20 contract cannot be upgraded | [**Implement Lockbox Solution** to bridge between tokens](/interop/tutorials/upgrade-to-superchain-erc20/lockbox) |
+| You can deploy to other chains using the same proxy address | [**Perform Contract Upgrade** while maintaining address](/interop/tutorials/upgrade-to-superchain-erc20/contract-upgrade) |
+
+
+ } />
+
+ } />
+
+
+## Next steps
+
+* Deploy a [SuperchainERC20](/interop/tutorials/deploy-superchain-erc20) to the Superchain
+* [Learn more about SuperchainERC20](/interop/superchain-erc20)
+* Build a [revolutionary app](/app-developers/get-started) that uses multiple blockchains within the Superchain
diff --git a/pages/interop/tutorials/upgrade-to-superchain-erc20/_meta.json b/pages/interop/tutorials/upgrade-to-superchain-erc20/_meta.json
index 4bada62e7..29d8c190c 100644
--- a/pages/interop/tutorials/upgrade-to-superchain-erc20/_meta.json
+++ b/pages/interop/tutorials/upgrade-to-superchain-erc20/_meta.json
@@ -1,3 +1,4 @@
{
- "custom-bridge": "Building a custom bridge"
+ "contract-upgrade": "Contract upgrade",
+ "lockbox": "Lockboxes for permissionless interop"
}
diff --git a/pages/interop/tutorials/upgrade-to-superchain-erc20/contract-upgrade.mdx b/pages/interop/tutorials/upgrade-to-superchain-erc20/contract-upgrade.mdx
new file mode 100644
index 000000000..5a7c65ccc
--- /dev/null
+++ b/pages/interop/tutorials/upgrade-to-superchain-erc20/contract-upgrade.mdx
@@ -0,0 +1,328 @@
+---
+title: Contract upgrade
+lang: en-US
+description: Tutorial on how to upgrade a proxied ERC20 contract for use with Superchain interop.
+topic: Interoperability
+personas: [Developer]
+categories: [Tutorial, Interop]
+content_type: article
+---
+
+import { Callout, Steps, Tabs } from 'nextra/components'
+
+
+ The SuperchainERC20 standard is ready for production deployments.
+ Please note that the OP Stack interoperability upgrade, required for cross-chain messaging, is currently still in active development.
+
+
+# Contract upgrade
+
+## Overview
+
+This guide explains how to upgrade an ERC20 to a [`SuperchainERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainERC20.sol) that can teleport across the [Superchain interop cluster](/interop/explainer#superchain-interop-cluster) when the original ERC20 contract was placed behind a proxy to enable future upgrades.
+
+
+ About this tutorial
+
+ **What you'll learn**
+
+ * How to upgrade an ERC20 token to enable Superchain interoperability when it was deployed with a proxy.
+
+ **Prerequisite knowledge**
+
+ * You should already know how to [deploy SuperchainERC20 tokens with custom code](/interop/tutorials/).
+
+
+
+ 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.
+
+
+{/*
+
+I put this warning here, when we don't have it on most pages, because this tutorial
+has code that is a lot more likely to be used in production. It doesn't just
+show what is possible, it does the exact job needed.
+
+*/}
+
+### What you'll do
+
+* Upgrade an existing ERC20 that uses [the proxy pattern](https://docs.openzeppelin.com/upgrades-plugins/proxies) to comply with interop requirements (with the proper authority).
+
+## How beacon proxies work
+
+```mermaid
+sequenceDiagram
+ Actor User
+ User->>BeaconProxy: transfer(
, )
+ BeaconProxy->>UpgradeableBeacon: What is the implementation address?
+ UpgradeableBeacon->>BeaconProxy: It is 0xBAD0...60A7
+ BeaconProxy->>0xBAD0...60A7: transfer(, )
+```
+
+A [beacon proxy](https://docs.openzeppelin.com/contracts/3.x/api/proxy#BeaconProxy) uses two contracts.
+The [`UpgradeableBeacon`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/beacon/UpgradeableBeacon.sol) contract holds the address of the implementation contract.
+The [`BeaconProxy`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/beacon/BeaconProxy.sol) contract is the one called for the functionality, the one that holds the storage.
+When a user (or another contract) calls `BeaconProxy`, it asks `UpgradeableBeacon` for the implementation address and then uses [`delegatecall`](https://www.evm.codes/?fork=cancun#f4) to call that contract.
+
+```mermaid
+sequenceDiagram
+ Actor User
+ Actor Owner
+ Participant BeaconProxy
+ Participant 0x600D...60A7
+ Owner->>UpgradeableBeacon: Your new implementation address is 0x600D...60A7
+ User->>BeaconProxy: transfer(, )
+ BeaconProxy->>UpgradeableBeacon: What is the implementation address?
+ UpgradeableBeacon->>BeaconProxy: It is 0x600D...60A7
+ BeaconProxy->>0x600D...60A7: transfer(, )
+```
+
+To upgrade the contract, an authorized address (typically the `Owner`) calls `UpgradeableBeacon` directly to specify the new implementation contract address.
+After that happens, all new calls are sent to the new implementation.
+
+## Instructions
+
+Some steps depend on whether you want to deploy on [Supersim](/interop/tools/supersim) or on the [development network](/interop/tools/devnet).
+
+
+ ### Install and run Supersim
+
+ If you are going to use Supersim, [follow these instructions](/app-developers/tutorials/supersim/getting-started/installation) to install and run Supersim.
+
+
+ Make sure to run Supersim with autorelay on.
+
+ ```sh
+ ./supersim --interop.autorelay true
+ ```
+
+
+ ### Setup the ERC20 token on chain A
+
+ Download and run the setup script.
+
+ ```sh
+ 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](/interop/tools/devnet), provide `setup-for-erc20-upgrade.sh` with the private key of an address with ETH on both devnets.
+
+ ```sh
+ ./setup-for-erc20-upgrade.sh
+ ```
+
+ ### Store the addresses
+
+ Execute the bottom two lines of the setup script output to store the ERC20 address and the address of the beacon contract.
+
+ ```sh
+ BEACON_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
+ export ERC20_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
+ ```
+
+ ### Specify environment variables
+
+ Specify these variables, which we use later:
+
+
+
+ Set these parameters for Supersim.
+
+ ```sh
+ PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+ USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
+ URL_CHAIN_A=http://127.0.0.1:9545
+ URL_CHAIN_B=http://127.0.0.1:9546
+ INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
+ ```
+
+
+
+ For Devnet, specify in `PRIVATE_KEY` the private key you used for the setup script and then these parameters.
+
+ ```sh
+ USER_ADDRESS=`cast wallet address --private-key $PRIVATE_KEY`
+ URL_CHAIN_A=https://interop-alpha-0.optimism.io
+ URL_CHAIN_B=https://interop-alpha-1.optimism.io
+ INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
+ ```
+
+
+
+ ### Create a Foundry project
+
+ We create a [Foundry](https://book.getfoundry.sh/) project and import the [OpenZeppelin](https://www.openzeppelin.com/solidity-contracts) contracts, which were used for the original ERC20 and proxy deployment.
+
+ ```sh
+ mkdir proxy-upgrade
+ cd proxy-upgrade
+ forge init
+ forge install OpenZeppelin/openzeppelin-contracts
+ forge install OpenZeppelin/openzeppelin-contracts-upgradeable
+ forge install ethereum-optimism/interop-lib
+ ```
+
+ ### Create and run the deployment script
+
+ 1. Create an `script/LabSetup.s.sol` file with this content:
+
+ ```solidity file=/public/tutorials/setup-for-erc20-upgrade.sh#L26-L66 hash=dd6605814c00636310d93706ab06f664 filename="script/LabSetup.s.sol"
+ ```
+
+ This is the same deployment script used for the original deployment on chain A.
+
+ 2. Run this command to deploy the same contracts on chain B.
+
+ ```sh
+ forge script script/LabSetup.s.sol --rpc-url $URL_CHAIN_B --broadcast --private-key $PRIVATE_KEY --tc LabSetup
+ ```
+
+ Scroll up and see the Logs section of the output:
+
+ ```
+ == Logs ==
+ Token address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
+ msg.sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
+ UpgradeableBeacon: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
+ Proxy: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
+ ```
+
+ Verify that the proxy address is the same as `$ERC20_ADDRESS`, and that the beacon address is the same as `$BEACON_ADDRESS`.
+
+
+ What to do when the values are not the same
+
+ This can happen when the nonce values of `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266` (or your address in the case of using devnet) on chain A and chain B are different.
+
+ You can see the nonce values using these commands:
+
+ ```sh
+ cast nonce $USER_ADDRESS --rpc-url $URL_CHAIN_A
+ cast nonce $USER_ADDRESS --rpc-url $URL_CHAIN_B
+ ```
+
+ The easiest solution is to send transactions to the chain with the lower nonce until the nonces are equal, and then deploy to both chains.
+
+ ```sh
+ forge script script/LabSetup.s.sol --rpc-url $URL_CHAIN_A --broadcast --private-key $PRIVATE_KEY --tc LabSetup
+ forge script script/LabSetup.s.sol --rpc-url $URL_CHAIN_B --broadcast --private-key $PRIVATE_KEY --tc LabSetup
+ ```
+
+ If you do this, remember to update `$ERC20_ADDRESS` and `$BEACON_ADDRESS`.
+
+ If the nonce on chain B is already higher than the nonce was on chain A when the original proxy contract was deployed this method is not available and you have to either create a special bridge or [use a lockbox](/interop/tutorials/upgrade-to-superchain-erc20/lockbox).
+
+
+ ### Deploy ERC7802 contracts
+
+ We need to replace the ERC20 contracts with contracts that:
+
+ * Support [ERC7802](https://eips.ethereum.org/EIPS/eip-7802) and [ERC165](https://eips.ethereum.org/EIPS/eip-165).
+ * Have the same storage layout as the ERC20 contracts they replace.
+
+
+ These contracts do *not* need to be deployed to the same address.
+ The address that needs to be the same is not the address of the ERC20 contract itself, but of the proxy.
+
+
+ 1. Create a file, `src/InteropToken.sol`:
+
+ ```solidity file=/public/tutorials/InteropToken.sol hash=007791836635608fdeb9c70c1b368f25 filename="src/InteropToken.sol"
+ ```
+
+
+ Detailed explanation
+
+ ```solidity file=/public/tutorials/InteropToken.sol#L1-L5 hash=36b9b9d0fb1ff680dc0eaa1c48b7c56b
+ ```
+
+ Most of the code is identical to the original `MyToken`.
+
+ ```solidity file=/public/tutorials/InteropToken.sol#L6-L7 hash=f06f3bd72be73dbd754008da7dd00d48
+ ```
+
+ These are the imports needed for ERC7802 support.
+ We need `IERC165` for documentation purposes, and `IERC7802` for the ERC7802 events.
+
+ ```solidity file=/public/tutorials/InteropToken.sol#L9 hash=ca402292e7551621669ef1a59b85d7ce
+ ```
+
+ We also implement [ERC165](https://eips.ethereum.org/EIPS/eip-165), but we don't need to import anything from there.
+
+ ```solidity file=/public/tutorials/InteropToken.sol#L10-L14 hash=37e9b49f50a8b70971ce5d0112bd934e
+ ```
+
+ This function is identical to the one in `MyToken`.
+
+ ```solidity file=/public/tutorials/InteropToken.sol#L16-L36 hash=448a7e21e094b3fd961f2b8ee15bc6c7
+ ```
+
+ Standard [ERC7802](https://eips.ethereum.org/EIPS/eip-7802) behavior.
+
+ ```solidity file=/public/tutorials/InteropToken.sol#L38-L42 hash=abb2093e9681984f25afa6f9d8b237a3
+ ```
+
+ Standard [ERC165](https://eips.ethereum.org/EIPS/eip-165) behavior.
+
+
+
+ Copying the original ERC20 token code with minimal differences is one method to keep the storage layout identical.
+ Alternatively, if you want to use a different contract, such as `SuperchainERC20`, you can modify the storage layout to match the old one using [the Solidity docs](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html).
+
+
+ 2. Deploy this contract on both chains, and store the addresses (which may or may not be the same).
+
+ ```sh
+ ERC7802_A=`forge create InteropToken --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A --broadcast | awk '/Deployed to:/ {print $3}'`
+ ERC7802_B=`forge create InteropToken --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_B --broadcast | awk '/Deployed to:/ {print $3}'`
+ ```
+
+ ### Update proxies
+
+ Notify the beacon contracts of the new implementation contracts.
+
+ ```sh
+ cast send $BEACON_ADDRESS --private-key $PRIVATE_KEY "upgradeTo(address)" $ERC7802_A --rpc-url $URL_CHAIN_A
+ cast send $BEACON_ADDRESS --private-key $PRIVATE_KEY "upgradeTo(address)" $ERC7802_B --rpc-url $URL_CHAIN_B
+ ```
+
+ ### Verification
+
+ 1. See your balance on chain A.
+
+ ```sh
+ cast call $ERC20_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei
+ ```
+
+ 2. See your balance on chain B.
+
+ ```sh
+ cast call $ERC20_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_B | cast from-wei
+ ```
+
+ 3. Transfer 0.1 token.
+
+ ```sh
+ AMOUNT=`echo 0.1 | cast to-wei`
+ cast send $INTEROP_BRIDGE --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY "sendERC20(address,address,uint256,uint256)" $ERC20_ADDRESS $USER_ADDRESS $AMOUNT `cast chain-id --rpc-url $URL_CHAIN_B`
+ ```
+
+ 4. See the new balances. Chain A should have 0.9 tokens, and Chain B should have 0.1 tokens.
+
+ ```sh
+ cast call $ERC20_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei
+ cast call $ERC20_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_B | cast from-wei
+ ```
+
+
+## Next steps
+
+* Deploy a [SuperchainERC20](/interop/tutorials/deploy-superchain-erc20) to the Superchain
+* [Learn more about SuperchainERC20](/interop/superchain-erc20)
+* Build a [revolutionary app](/app-developers/get-started) that uses multiple blockchains within the Superchain
diff --git a/pages/interop/tutorials/upgrade-to-superchain-erc20/custom-bridge.mdx b/pages/interop/tutorials/upgrade-to-superchain-erc20/custom-bridge.mdx
index 4262d38d6..30f07a954 100644
--- a/pages/interop/tutorials/upgrade-to-superchain-erc20/custom-bridge.mdx
+++ b/pages/interop/tutorials/upgrade-to-superchain-erc20/custom-bridge.mdx
@@ -212,7 +212,7 @@ Some steps depend on whether you want to deploy on [Supersim](/interop/tools/sup
1. Create a file, `src/InteropToken.sol`.
- ```solidity file=/public/tutorials/InteropToken.sol hash=5e728534c265028c94d60dcb6550699d filename="src/InteropToken.sol"
+ ```solidity file=/public/tutorials/InteropToken.sol hash=007791836635608fdeb9c70c1b368f25 filename="src/InteropToken.sol"
```
2. This `src/InteropToken.sol` is used for contract upgrades when the ERC20 contracts are at the same address.
diff --git a/pages/interop/tutorials/upgrade-to-superchain-erc20/lockbox.mdx b/pages/interop/tutorials/upgrade-to-superchain-erc20/lockbox.mdx
new file mode 100644
index 000000000..c99468504
--- /dev/null
+++ b/pages/interop/tutorials/upgrade-to-superchain-erc20/lockbox.mdx
@@ -0,0 +1,369 @@
+---
+title: Lockboxes for permissionless interop
+lang: en-US
+description: Tutorial on how to take permissionlessly create a lockbox contract to enable Superchain interoperability.
+topic: Interoperability
+personas: [Developer]
+categories: [Tutorial, Interop]
+content_type: article
+---
+
+import { Steps, Callout, Tabs } 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.
+
+
+# Lockboxes for permissionless interop
+
+## 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.
+
+
+ About this tutorial
+
+ **What you'll learn**
+
+ * How to permissionlessly create a lockbox contract to enable Superchain interoperability.
+
+ **Prerequisite knowledge**
+
+ * You should already know how to [deploy SuperchainERC20 tokens with custom code](/interop/tutorials/custom-superchain-erc20).
+
+
+
+ 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.
+
+
+{/*
+
+I put this warning here, when we don't have it on most pages, because this tutorial
+has code that is a lot more likely to be used in production. It doesn't just
+show what is possible, it does the exact job needed.
+
+*/}
+
+### 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](/interop/tools/supersim) or on the [development network](/interop/tools/devnet).
+
+
+ ### Install and run Supersim
+
+ If you are going to use Supersim, [follow these instructions](/app-developers/tutorials/supersim/getting-started/installation) to install and run Supersim.
+
+
+ Make sure to run Supersim with autorelay on.
+
+ ```sh
+ ./supersim --interop.autorelay true
+ ```
+
+
+ ### Setup the ERC-20 token on chain A
+
+ Download and run the setup script.
+
+ ```sh
+ 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](/interop/tools/devnet), provide `setup-for-erc20-upgrade.sh` with the private key of an address with ETH on both devnets.
+
+ ```sh
+ ./setup-for-erc20-upgrade.sh
+ ```
+
+ ### 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.
+
+ ```sh
+ BEACON_ADDRESS=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
+ export ERC20_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
+ ```
+
+ ### Specify environment variables
+
+ 1. Specify these variables, which we use later:
+
+
+
+ Set these parameters for Supersim.
+
+ ```sh
+ PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+ USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
+ URL_CHAIN_A=http://127.0.0.1:9545
+ URL_CHAIN_B=http://127.0.0.1:9546
+ ```
+
+
+
+ For Devnet, specify in `PRIVATE_KEY` the private key you used for the setup script and then these parameters.
+
+ ```sh
+ USER_ADDRESS=`cast wallet address --private-key $PRIVATE_KEY`
+ URL_CHAIN_A=https://interop-alpha-0.optimism.io
+ URL_CHAIN_B=https://interop-alpha-1.optimism.io
+ ```
+
+
+
+ 2. Regardless of whether you use Supersim or Devnet, specify these variables.
+
+ ```sh
+ 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`
+ ```
+
+ ### 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](/interop/tutorials/deploy-superchain-erc20), and install libraries, etc.
+
+ ```sh
+ 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:
+
+ ```json filename="packages/contracts/package.json"
+ {
+ "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`:
+
+ ```solidity filename="packages/contracts/scripts/LockboxDeployer.s.sol" file=/public/tutorials/LockboxDeployer.s.sol hash=534b543709be173d87508a53322d8c59
+ ```
+
+
+ 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.
+
+ ```solidity file=/public/tutorials/LockboxDeployer.s.sol#L46-L52 hash=302d02c3895f109e5e64d265b0473e6a
+ ```
+
+ 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).
+
+ ```solidity file=/public/tutorials/LockboxDeployer.s.sol#L54-L69 hash=c45855080dc554cece35ed87e2d68f68
+ ```
+
+ "Manually" calculate the address that [`CREATE2`](https://www.evm.codes/?fork=cancun#f5) will give us.\
+ If there is already a contract there, we have a problem.
+ Otherwise, deploy `LockboxSuperchainERC20`.
+
+ ```solidity file=/public/tutorials/LockboxDeployer.s.sol#L80-L84 hash=5d1f71b16a6f02d52a79b1a9e7588f87
+ ```
+
+ I modified this salt function to include a timestamp (obtained using `vm.unixTime()` in the constructor).
+ This is not necessary, but I consider it a developer experience improvement.
+ During development you redeploy slightly modified code a lot of times.
+ It is easier if you don't need to manually change the salt every time.
+
+
+ Remove this before deploying to production.
+ Otherwise, as new blockchains join the Interop cluster, you may not be able to deploy your contract at the same address.
+
+
+
+ ### Create and deploy the new contract
+
+ 1. Create this file in `packages/contracts/src/LockboxSuperchainERC20.sol`:
+
+ ```solidity filename="packages/contracts/src/LockboxSuperchainERC20.sol" file=/public/tutorials/LockboxSuperchainERC20.sol hash=d326f0e1c26904b844263274914951cf
+ ```
+
+
+ Explanation
+
+ ```solidity file=/public/tutorials/LockboxSuperchainERC20.sol#L11-L12 hash=45d211a19533f9b0dee310743b25459f
+ ```
+
+ The lockbox contract needs to know the contract for which it is a lockbox.
+ This requires not just the address, but also to know what chain has it.
+
+ ```solidity file=/public/tutorials/LockboxSuperchainERC20.sol#L47-L57 hash=20f6aa15d113dcaf992875184173cb47
+ ```
+
+ Users call this function to transfer original tokens to the contract and mint themselves an equivalent number of lockbox tokens.
+ This function has several tests to make sure it can be called.
+
+ * Check the chain ID.
+ Locking and redeeming tokens can only be done on the original token's chain.
+ * Use [`transferFrom`](https://ethereum.org/en/developers/tutorials/erc20-annotated-code/#transferFrom) to transfer the tokens to ourselves.
+ This call typically reverts when it fails, but it can also return `false`.
+ In that case, we revert.
+ There are two reasons it may fail.
+ * The user (in this case, the `LockboxSuperchainERC20` contract) does not have [the allowance](https://ethereum.org/en/developers/tutorials/erc20-annotated-code/#_approve) to spend that amount of tokens from the original owner (`msg.sender`).
+ * The original owner (`msg.sender`) does not have enough tokens to transfer.
+
+ If the tests are successful, mint the requested amount for `msg.sender`.
+
+ ```solidity file=/public/tutorials/LockboxSuperchainERC20.sol#L59-L67 hash=2e63a9cd1ac1114c3fb2110e28b60924
+ ```
+
+ Users call this function to redeem their existing lockbox tokens and replace them with the original tokens.
+ It also has multiple tests.
+
+ * Again, check chain ID.
+ * Try to `_burn` the amount of lockbox tokens.
+ [The solady `_burn` function](https://github.com/Vectorized/solady/blob/main/src/tokens/ERC20.sol#L539-L542), the one we inherit from `SuperchainERC20`, reverts if the user does not have enough tokens to burn.
+ * Transfer the amount of the original ERC-20 redeemed to
+ the caller.
+ This should never fail, because lockbox ERC-20 tokens are supposed to always be backed by an equal number of the original tokens.
+ However, if it does fail for some reason, revert.
+
+
+ 2. Deploy the contract.
+
+
+
+ ```sh
+ pnpm contracts:deploy:token
+ ```
+
+
+
+ To deploy to the [development networks](/interop/tools/devnet), follow the steps [in the tutorial](/interop/tutorials/deploy-superchain-erc20#prepare-for-deployment) before you deploy the contract.
+
+ Then, update `packages/contracts/.env` and deploy the token.
+
+ ```sh
+ echo DEPLOYER_PRIVATE_KEY=$PRIVATE_KEY > packages/contracts/.env
+ pnpm contracts:deploy:token
+ ```
+
+
+
+ 3. Get the new token address and store it in an environment variable.
+
+ ```sh
+ NEW_TOKEN_ADDRESS=`cat packages/contracts/broadcast/multi/LockboxDeployer.s.sol-latest/run.json | awk '/contractAddress/ {print $2}' | head -1 | sed 's/[",]//g'`
+ ```
+
+ ### Verification
+
+ 1. Check that the user has a single token of the original ERC-20.
+
+ ```sh
+ cast call $ERC20_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei
+ ```
+
+ 2. Lock a quarter token in the lockbox ERC-20 contract.
+ To do this we first need to give the lockbox ERC-20 contract an allowance and then call it.
+
+ ```sh
+ QUARTER_TOKEN=`echo 0.25 | cast to-wei`
+ cast send $ERC20_ADDRESS "approve(address,uint256)" $NEW_TOKEN_ADDRESS $QUARTER_TOKEN --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A
+ cast send $NEW_TOKEN_ADDRESS "lockAndMint(uint256)" $QUARTER_TOKEN --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A
+ ```
+
+ 3. See the balances of the user, both original and lockbox, and the balance of the lockbox contract itself.
+
+ ```sh
+ cast call $ERC20_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei
+ cast call $NEW_TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei
+ cast call $ERC20_ADDRESS "balanceOf(address)" $NEW_TOKEN_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei
+ ```
+
+ 4. Transfer 0.1 token to chain B.
+
+ ```sh
+ TENTH_TOKEN=`echo 0.1 | cast to-wei`
+ cast send $INTEROP_BRIDGE --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY "sendERC20(address,address,uint256,uint256)" $NEW_TOKEN_ADDRESS $USER_ADDRESS $TENTH_TOKEN `cast chain-id --rpc-url $URL_CHAIN_B`
+ ```
+
+ 5. Check the user's balances on both chains.
+
+ ```sh
+ cast call $NEW_TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_A | cast from-wei
+ cast call $NEW_TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_B | cast from-wei
+ ```
+
+ 6. Specify the configuration for another user.
+
+
+
+ ```sh
+ USER_ADDRESS_2=0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
+ PRIVATE_KEY_2=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
+ ```
+
+
+
+ Specify the private key (`PRIVATE_KEY_2`) and user address (`USER_ADDRESS_2`) of another user that has ETH on both devnets.
+
+
+
+ 7. Transfer new tokens to the new user (on chain B) and see that they were actually transferred.
+
+ ```sh
+ cast send $NEW_TOKEN_ADDRESS "transfer(address,uint256)" $USER_ADDRESS_2 $TENTH_TOKEN --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_B
+ cast call $NEW_TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url $URL_CHAIN_B | cast from-wei
+ cast call $NEW_TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS_2 --rpc-url $URL_CHAIN_B | cast from-wei
+ ```
+
+ 8. As the new user, transfer tokens back to chain A and redeem them.
+
+ ```sh
+ cast send $INTEROP_BRIDGE --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY_2 "sendERC20(address,address,uint256,uint256)" $NEW_TOKEN_ADDRESS $USER_ADDRESS_2 $TENTH_TOKEN `cast chain-id --rpc-url $URL_CHAIN_A`
+ cast send $NEW_TOKEN_ADDRESS --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY_2 "redeemAndBurn(uint256)" $TENTH_TOKEN
+ ```
+
+ 9. Check that the second user does not have any more of the new tokens, but does have the original token.
+
+ ```sh
+ cast call $NEW_TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS_2 --rpc-url $URL_CHAIN_A | cast from-wei
+ cast call $ERC20_ADDRESS "balanceOf(address)" $USER_ADDRESS_2 --rpc-url $URL_CHAIN_A | cast from-wei
+ ```
+
+
+## Next steps
+
+* Deploy a [SuperchainERC20](/interop/tutorials/deploy-superchain-erc20) to the Superchain
+* [Learn more about SuperchainERC20](/interop/superchain-erc20)
+* Build a [revolutionary app](/app-developers/get-started) that uses multiple blockchains within the Superchain
diff --git a/public/tutorials/InteropToken.sol b/public/tutorials/InteropToken.sol
index e48ed7311..5d34ae636 100644
--- a/public/tutorials/InteropToken.sol
+++ b/public/tutorials/InteropToken.sol
@@ -41,4 +41,3 @@ contract InteropToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, IE
|| _interfaceId == type(IERC165).interfaceId;
}
}
-
diff --git a/public/tutorials/LockboxDeployer.s.sol b/public/tutorials/LockboxDeployer.s.sol
new file mode 100644
index 000000000..1e16f6770
--- /dev/null
+++ b/public/tutorials/LockboxDeployer.s.sol
@@ -0,0 +1,85 @@
+// 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));
+ }
+}
diff --git a/public/tutorials/LockboxSuperchainERC20.sol b/public/tutorials/LockboxSuperchainERC20.sol
new file mode 100644
index 000000000..2bc96d825
--- /dev/null
+++ b/public/tutorials/LockboxSuperchainERC20.sol
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.25;
+
+import {SuperchainERC20} from "./SuperchainERC20.sol";
+import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
+
+contract LockboxSuperchainERC20 is SuperchainERC20 {
+ string private _name;
+ string private _symbol;
+ uint8 private immutable _decimals;
+ address immutable _originalTokenAddress;
+ uint256 immutable _originalChainId;
+
+ constructor(
+ string memory name_,
+ string memory symbol_,
+ uint8 decimals_,
+ address originalTokenAddress_,
+ uint256 originalChainId_) {
+ require(originalTokenAddress_ != address(0), "Invalid token address");
+ require(originalChainId_ != 0, "Invalid chain ID");
+ _name = name_;
+ _symbol = symbol_;
+ _decimals = decimals_;
+ _originalTokenAddress = originalTokenAddress_;
+ _originalChainId = originalChainId_;
+ }
+
+ 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 originalTokenAddress() public view returns (address) {
+ return _originalTokenAddress;
+ }
+
+ function originalChainId() public view returns (uint256) {
+ return _originalChainId;
+ }
+
+ function lockAndMint(uint256 amount_) external {
+ IERC20 originalToken = IERC20(_originalTokenAddress);
+
+ require(block.chainid == _originalChainId, "Wrong chain");
+ bool success = originalToken.transferFrom(msg.sender, address(this), amount_);
+
+ // Not necessariy if the ERC-20 contract reverts rather than reverting.
+ // However, the standard allows the ERC-20 contract to return false instead.
+ require(success, "No tokens to lock, no mint either");
+ _mint(msg.sender, amount_);
+ }
+
+ function redeemAndBurn(uint256 amount_) external {
+ IERC20 originalToken = IERC20(_originalTokenAddress);
+
+ require(block.chainid == _originalChainId, "Wrong chain");
+ _burn(msg.sender, amount_);
+
+ bool success = originalToken.transfer(msg.sender, amount_);
+ require(success, "Transfer failed, this should not happen");
+ }
+}
+
diff --git a/words.txt b/words.txt
index 367ff3cb5..6ae372fb9 100644
--- a/words.txt
+++ b/words.txt
@@ -322,6 +322,7 @@ productionize
productionized
Protip
Proxied
+proxied
Proxyd
proxyd
Pyth
@@ -378,6 +379,7 @@ smartcard
snapshotlog
Snapsync
snapsync
+solady
Solana
Soneium
soyboy