Skip to content

contracts: revive SuperchainTokenBridge + add SuperchainERC20Factory (wrap/unwrap) - #22087

Draft
opsuperchain wants to merge 2 commits into
ethereum-optimism:developfrom
karlfloersch:feat/superchain-erc20-factory
Draft

contracts: revive SuperchainTokenBridge + add SuperchainERC20Factory (wrap/unwrap)#22087
opsuperchain wants to merge 2 commits into
ethereum-optimism:developfrom
karlfloersch:feat/superchain-erc20-factory

Conversation

@opsuperchain

@opsuperchain opsuperchain commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

This PR revives the SuperchainTokenBridge and adds a new SuperchainERC20Factory that lets any existing ERC20 be wrapped into a SuperchainERC20 and moved across the Superchain.

Commit 1: revive the SuperchainTokenBridge

SuperchainTokenBridge, SuperchainERC20, and IERC7802 were removed in #19999 (and #20141) as unused interop contracts. This commit restores them from history at their original semvers and predeploy slot (0x4200...0028), adapted to the predeploy machinery that has landed since (Predeploys.getAllRecords() registry, L2ContractsManager deploy/upgrade flow, gate assertions in L2Genesis). The bridge is gated behind interop exactly like SuperchainETHBridge/ETHLiquidity. The legacy OptimismSuperchainERC20 lane (beacon, factory, L2StandardBridgeInterop) is intentionally not revived; the new factory below replaces it.

Commit 2: SuperchainERC20Factory

A minimal wrap/unwrap factory, added as an interop-gated predeploy at 0x4200...0026 (the slot freed by the old OptimismSuperchainERC20Factory):

  • deploy(chainId, token, name, symbol, decimals) deploys a SuperchainWrappedERC20 via CREATE2 with salt = keccak256(chainId, token). The wrapped token's constructor takes no arguments (metadata is read back from the factory during construction), so its initcode is constant and the wrapped token address is identical on every chain, derived purely from the [chain_id, address] identity of the original token.
  • On the original token's chain, metadata is read from the token itself (falling back to caller-supplied values for tokens without the metadata extension). On remote chains the deployer supplies it — or, better, uses propagation:
  • propagate(token, toChainId) sends the canonical wrapped metadata from the token's home chain to the factory on another chain via the L2ToL2CrossDomainMessenger (the factory is the same predeploy address everywhere, mirroring SuperchainTokenBridge.sendERC20/relayERC20). relayDeploy on the destination requires the messenger, the factory itself as cross-domain sender, and message source chain == the token's claimed home chain, so remote chains get authentic metadata without trusting any caller.
  • wrap(token, amount) escrows original tokens in the factory and mints the wrapped token 1:1 (mints the actually-received amount, so fee-on-transfer tokens can't inflate supply beyond backing). Only possible on the token's home chain.
  • unwrap(token, amount) burns wrapped tokens and releases escrow.
  • The wrapped token is a standard SuperchainERC20: crosschainMint/crosschainBurn are gated to the SuperchainTokenBridge, so once deployed on two chains, sendERC20/relayERC20 move it between them. Escrow on the home chain always equals global wrapped supply.

Notes for reviewers

  • The revive commit restores the bridge byte-for-byte where possible; the only source adaptations are the solady import path (the versioned @solady-v0.0.245 remapping no longer exists; the vendored solady is v0.0.158) and registration in the new predeploy registry.
  • L2ContractsManager semver bumped 1.13.0 → 1.14.0.
  • Rebasing tokens are not supported by the wrap/unwrap escrow (negative rebases can strand late unwrappers), same caveat as any escrow wrapper.

Tests

  • Restored SuperchainTokenBridge and SuperchainERC20 unit tests.
  • New suites for the factory (deterministic addresses, home/remote metadata, propagate/relayDeploy auth + source-chain checks, wrap/unwrap, fee-on-transfer, escrow accounting) and the wrapped token (factory/bridge mint-burn gating, ERC7802 interface).

🤖 Generated with Claude Code

opsuperchain and others added 2 commits July 28, 2026 20:46
Restores the SuperchainTokenBridge predeploy (0x4200...0028), the
abstract SuperchainERC20, and the IERC7802 interface that were removed
in ethereum-optimism#19999 / ethereum-optimism#20141, byte-for-byte where possible (the bridge's
semver-lock initCodeHash matches the pre-deletion value).

Adaptations to the current tree:
- Registered in the Predeploys.getAllRecords() registry and the
  L2ContractsManager deploy/upgrade flow (interop-gated, like
  SuperchainETHBridge and ETHLiquidity), which replaced the old
  per-setter genesis wiring after the deletion.
- SuperchainERC20 imports the vendored solady (v0.0.158); the versioned
  @solady-v0.0.245 remapping no longer exists. IERC20Solady is restored
  without the Permit2 error that solady v0.0.158 predates.
- L2ContractsManager semver bumped to 1.14.0.

The legacy OptimismSuperchainERC20 lane (beacon, factory,
L2StandardBridgeInterop) is intentionally not revived.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a factory that wraps any existing ERC20 into a SuperchainERC20 so
it can move across the Superchain through the SuperchainTokenBridge.
New interop-gated predeploy at 0x4200...0026 (the slot freed by the old
OptimismSuperchainERC20Factory).

- deploy(chainId, token, name, symbol, decimals) CREATE2-deploys a
  SuperchainWrappedERC20 with salt = keccak256(chainId, token). The
  wrapped token's constructor takes no arguments and reads its
  parameters back from the factory, so the initcode is constant and the
  wrapped token address is identical on every chain, derived purely
  from the [chain_id, address] identity of the original token.
- On the original token's chain, metadata is read from the token itself
  (with a fallback to caller-supplied values for tokens without the
  metadata extension); on remote chains the deployer supplies it, or
  uses propagation:
- propagate(token, toChainId) sends the canonical wrapped metadata from
  the token's home chain to the factory on another chain via the
  L2ToL2CrossDomainMessenger (the factory is the same predeploy address
  on every chain). relayDeploy requires the messenger, the factory
  itself as cross-domain sender, and message source chain == the
  token's claimed home chain, so remote chains receive authentic
  metadata without trusting any caller.
- wrap(token, amount) escrows original tokens in the factory and mints
  wrapped tokens 1:1 against the actually-received amount (so
  fee-on-transfer tokens cannot inflate supply beyond backing);
  unwrap(token, amount) burns and releases escrow. Both are only
  possible on the token's home chain; everywhere else supply moves
  exclusively through the SuperchainTokenBridge, so home-chain escrow
  always equals global wrapped supply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@opsuperchain
opsuperchain force-pushed the feat/superchain-erc20-factory branch from 4532d34 to 24cc925 Compare July 28, 2026 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant