ChainScore Labs
All Guides

Layer 2 Token Standards and Wrappers

LABS

Layer 2 Token Standards and Wrappers

Chainscore © 2025

Core Concepts

Foundational knowledge for understanding token standards and their implementations across Ethereum Layer 2 networks.

Canonical Bridging

The canonical bridge is the official, protocol-endorsed channel for moving assets between L1 and L2. It mints a native representation of the asset on the destination chain, maintaining a 1:1 peg backed by assets locked in the source chain's bridge contract. This is the most secure and decentralized bridging method, though it can be slower. Examples include the Optimism Gateway and Arbitrum Bridge.

Bridged Token Standards

These are token contracts deployed on an L2 that represent an asset from another chain. The ERC-20 standard is universal, but implementations vary. Some use custom interfaces (e.g., Optimism's OVM_ETH) while others adhere strictly to the mainnet spec. Understanding the specific standard is crucial for dApp and wallet compatibility, as misalignment can cause integration failures.

Third-Party Wrappers

Third-party wrappers are alternative, often faster bridges provided by protocols like Across, Hop, or Synapse. They issue their own liquid wrapper tokens (e.g., hETH, synthETH) on the destination chain. These wrappers rely on their own liquidity pools and validator sets, introducing different trust and security assumptions compared to canonical bridges, but offering improved speed and capital efficiency.

Native Gas Tokens

Each L2 has a native gas token used to pay for transaction execution. On Optimism and Arbitrum, this is bridged ETH. On zkSync Era and Starknet, it can be ETH or a custom token like STRK. The minting mechanism and underlying guarantee (canonical vs. third-party) of this gas token directly impacts network security and user experience for fee payments.

Token Mint/Burn Mechanics

This describes the on-chain logic for creating and destroying bridged tokens. In a lock-and-mint model, assets are locked on L1 and an equivalent amount is minted on L2. The reverse burn-and-mint process is used for withdrawals. These mechanics are defined in the bridge contract and are critical for understanding finality times, security guarantees, and the redemption process for users.

Standardization Efforts (ERC-7281)

ERC-7281 (xERC-20) is a proposed standard for cross-chain token representation. It aims to solve fragmentation by allowing a token issuer on L1 to delegate minting rights to specific bridges for their token on L2s. This creates a canonical, issuer-controlled representation per bridge, improving security and liquidity uniformity. It represents a shift from chain-centric to token-centric bridging models.

Layer 2 Bridging Architectures

How Bridges Connect Layers

Bridging architectures define the technical and trust models for moving assets between Ethereum's Layer 1 (L1) and its Layer 2 (L2) networks. The primary goal is to enable secure, fast, and cost-effective transfers while maintaining the underlying asset's value and properties. These systems are critical for liquidity flow and user onboarding.

Core Architecture Types

  • Native Mint & Burn: The canonical method used by Optimistic and ZK Rollups. Tokens are minted on L2 when deposited from L1 and burned on L2 to withdraw back to L1. This is managed by the L2's smart contracts and is considered the most secure.
  • Lock & Mint (Third-Party): Used by many general-purpose bridges like Across or Hop. Assets are locked in a smart contract on the origin chain, and a wrapped representation is minted on the destination chain by a bridge operator.
  • Liquidity Networks: Protocols like Connext or Hop utilize liquidity pools on both chains. A user's asset is swapped for a local asset on the destination chain via a liquidity provider, enabling near-instant transfers.

Example: Depositing to Arbitrum

When you bridge ETH from Ethereum to Arbitrum using its native bridge, you initiate a deposit transaction to the Arbitrum Inbox contract on L1. After the challenge period, your ETH is minted as "AETH" on the Arbitrum L2, where it can be used for transactions and gas fees.

Wrapper Contract Interaction Flow

Process overview for bridging and managing tokens via a canonical wrapper contract.

1

Approve Token Spending

Grant the wrapper contract permission to transfer your Layer 1 tokens.

Detailed Instructions

Before bridging, you must grant the wrapper contract an allowance to spend the specific amount of tokens you intend to deposit. This is a standard ERC-20 approval process. First, identify the official contract address for the canonical bridge on the L1 network (e.g., 0x1234... for Optimism). Then, call the approve function on your L1 token contract, specifying the wrapper contract as the spender and the deposit amount as the value. Always verify the contract address from the official bridge documentation to avoid scams.

  • Sub-step 1: Call token.approve(wrapperAddress, amount) on L1
  • Sub-step 2: Wait for the transaction to be confirmed on the L1 network
  • Sub-step 3: Verify the approval by checking the allowance: token.allowance(yourAddress, wrapperAddress)
solidity
// Example using ethers.js const tx = await L1TokenContract.approve(wrapperAddress, depositAmount); await tx.wait();

Tip: For a better UX, consider approving a very large amount (like type(uint256).max) once, to avoid repeated transactions for future deposits.

2

Deposit Tokens to the Wrapper

Initiate the bridge by locking tokens in the L1 wrapper contract.

Detailed Instructions

Execute the deposit function on the L1 wrapper contract. The standard function is often named depositTo or bridgeTo. You must specify the recipient address on the destination L2 and the deposit amount. The contract will lock your L1 tokens in its escrow and emit an event that the L2's message relayer will pick up. Ensure you have sufficient L1 ETH to pay for gas. The deposit will trigger the minting of a corresponding canonical bridged token on L2. Monitor the transaction receipt for the DepositInitiated event.

  • Sub-step 1: Call wrapper.depositTo(l2RecipientAddress, amount) on L1
  • Sub-step 2: Pay the L1 gas fee and confirm the transaction
  • Sub-step 3: Capture the transaction hash and look for the deposit event log
solidity
// Example deposit call await L1BridgeContract.depositTo( '0xRecipientOnL2', ethers.utils.parseEther('1.0') );

Tip: The deposit typically has a finalization period (e.g., 10-20 minutes). Use the official bridge UI to track the status.

3

Claim Bridged Tokens on L2

Complete the bridge by claiming the minted tokens on the destination layer.

Detailed Instructions

After the L1 deposit transaction is finalized and the state root is relayed, the funds become claimable on L2. This is often an automatic process handled by the network's infrastructure, but you may need to trigger a finalization. Interact with the L2's bridge contract (the counterpart to the L1 wrapper). Call the finalizeDeposit or claim function, providing the proof of the L1 transaction. The L2 contract will verify the proof and mint the canonical L2 token to your specified address. The token will adhere to the L2's native token standard (e.g., Optimism's OVM_ETH or Arbitrum's AETH).

  • Sub-step 1: Wait for the deposit's challenge period to pass (if applicable)
  • Sub-step 2: Call L2Bridge.finalizeDeposit(from, to, amount, data) with the transaction proof
  • Sub-step 3: Verify the token balance of the recipient address on L2
solidity
// Example using an SDK for proof generation const proof = await generateProof(l1TxHash); const tx = await L2BridgeContract.finalizeDeposit(l1Sender, l2Recipient, amount, proof);

Tip: Most user-facing UIs abstract this step. For developers, use the bridge's SDK to generate the necessary proof parameters.

4

Withdraw Tokens Back to L1

Initiate the process to bridge tokens from L2 back to their native L1 chain.

Detailed Instructions

The withdrawal process is the inverse of deposit. Start by calling the withdraw or initiateWithdrawal function on the L2 token bridge contract. This function will burn the L2 bridged tokens and emit a message for the L1. You must pay gas on L2. After the transaction is included in an L2 block, there is a mandatory challenge period (e.g., 7 days for Optimism) for fraud proofs. Only after this period can the withdrawal be finalized on L1. Save the L2 transaction hash, as you will need it to prove the withdrawal later.

  • Sub-step 1: Call L2TokenBridge.withdraw(yourAddress, amount) on L2
  • Sub-step 2: Pay the L2 gas fee and wait for transaction confirmation
  • Sub-step 3: Note the L2 block number and transaction hash for the proof
solidity
// Example withdrawal initiation await L2StandardBridge.withdraw( L2_TOKEN_ADDRESS, amount, 200000, // gasLimit "0x" // empty data );

Tip: The long challenge period is a security feature. Plan your liquidity needs accordingly.

5

Finalize Withdrawal on L1

Prove and complete the withdrawal on Layer 1 after the challenge period.

Detailed Instructions

Once the L2 challenge period has elapsed, you must prove the withdrawal on L1. This involves providing a Merkle proof that your withdrawal transaction was included in an L2 state root that has been posted to L1. Use the L2 transaction hash and block number to generate the proof via the bridge's SDK or RPC calls. Then, call the finalizeWithdrawal function on the L1 wrapper contract with this proof. Upon successful verification, the contract will release the originally locked tokens from escrow to your designated L1 address. This step requires an L1 gas payment.

  • Sub-step 1: Use the bridge portal to generate the withdrawal proof after the challenge period
  • Sub-step 2: Call L1Bridge.finalizeWithdrawal(l2BlockNumber, proof) with the generated data
  • Sub-step 3: Confirm the transaction and verify your L1 token balance increase
solidity
// Example finalization with proof const proof = await getWithdrawalProof(l2TxHash); await L1BridgeContract.finalizeWithdrawal( proof.l2BlockNumber, proof.l2MessageIndex, proof.l2TxNumberInBlock, proof.merkleProof, proof.withdrawalMessage );

Tip: Many bridges offer a "prove and finalize" transaction bundler to simplify this two-step process.

Token Standard Comparison Across L2s

Comparison of native token standards and canonical bridging implementations across major Layer 2 networks.

Feature / MetricArbitrum (ERC-20)Optimism (Standard Bridge)zkSync Era (Native ETH & ERC-20)Starknet (ERC-20 & ERC-721)

Bridging Model

Canonical Messaging (L1 <-> L2)

Canonical Messaging (OptimismPortal)

Native Account Abstraction & L1<->L2 Messaging

L1 <-> L2 Messaging via StarkGate

Withdrawal Time (L2->L1)

~1 week (Dispute Period)

~1 week (Fault Proof Period)

~24 hours (ZK Validity Proof)

Several hours (ZK Validity Proof)

Gas Fee Structure

L1 Data + L2 Execution

L1 Data + L2 Execution

L1 Pubdata + L2 Execution

L1 Data + L2 Execution

Token Minting

On L1, locked in bridge

On L1, locked in bridge

Can be minted natively on L2

Must be bridged from L1 or minted via contract

Contract Upgradeability

Proxy patterns via L1 governance

Upgradeable via L1 ProxyAdmin

Immutable core, upgradeable via system contracts

Upgradeable via L1 governance (Starknet OS)

Native Fee Token

ETH (wrapped for gas)

ETH

ETH (paymaster abstraction possible)

ETH (STRK for fee abstraction)

Cross-Rollup Messaging

Via Arbitrum's cross-chain protocol

Via Optimism's Bedrock & cross-domain messaging

Via native L1/L2 communication & 3rd party

Via Starknet Messaging (L1 <-> L2)

Security and Trust Considerations

Understanding the security models and trust assumptions of L2 token standards is critical for safe asset management and protocol integration.

Escrow Contract Security

Escrow contracts hold the canonical assets on the L1 while minting wrapped versions on the L2. Their security is paramount.

  • Audits must cover upgrade mechanisms and pausability.
  • Minter/validator key management must be decentralized or time-locked.
  • A compromise here can lead to the permanent loss of all bridged assets.
  • Example: A flawed Arbitrum bridge contract could lock all ETH deposited to Arbitrum One.

Prover & Fraud Proof Trust

Optimistic rollups rely on a fraud proof system where a single honest validator can challenge invalid state transitions.

  • Users must trust that at least one honest node is monitoring the chain.
  • The challenge period (e.g., 7 days) creates a withdrawal delay for security.
  • Zero-knowledge rollups replace this with cryptographic validity proofs, offering instant finality.
  • This defines the base layer security assumption for your wrapped assets.

Standard Implementation Risks

The specific implementation of a token standard (e.g., L2 ERC20 wrapper) introduces unique risks.

  • Reentrancy vulnerabilities in deposit/withdraw functions can be exploited.
  • Incorrect fee calculation logic may lock funds or enable theft.
  • Integration flaws with the L2's gas metering can cause failed transactions.
  • Always verify the contract address and audit status before interacting.

Upgradeability and Admin Keys

Many bridge and token contracts are upgradeable, controlled by a multi-sig or DAO.

  • This introduces trust in the admin entity not to act maliciously.
  • Transparent governance and timelocks on upgrades are essential.
  • A malicious upgrade could change token economics or mint unlimited supply.
  • Assess the governance model of standards like Optimism's OVM 2.0 or Arbitrum Nitro.

L1 Reorg & Finality Risks

Wrapped assets depend on the finality of the L1 blockchain. A deep reorg can invalidate L2 state.

  • Optimistic rollups require L1 block confirmations for full finality.
  • Zero-knowledge rollups post validity proofs to L1, but still rely on its consensus.
  • A 51% attack on Ethereum could theoretically compromise bridged asset records.
  • This is a systemic risk shared across all L2 solutions.

Oracle and Price Feed Reliance

Cross-chain messaging and some wrapper minting mechanisms depend on oracles for price or state data.

  • A manipulated price feed could enable undercollateralized minting on the L2.
  • Decentralized oracle networks (e.g., Chainlink) mitigate single points of failure.
  • Assess the oracle design for standards involving synthetic assets or collateralized wrappers.
  • This is crucial for liquidity pools using L2 tokens as collateral.

Implementing a Custom Wrapper

Process overview

1

Define the Wrapper Interface and Storage

Establish the core data structures and function signatures for your wrapper contract.

Detailed Instructions

Begin by defining the ERC-20 interface your wrapper will implement and the internal state variables to track wrapped assets. A typical wrapper needs to store the address of the canonical token on L1 or L2 and maintain a mapping of balances.

  • Sub-step 1: Import the IERC20 interface from OpenZeppelin or define the standard transfer, balanceOf, and approve functions.
  • Sub-step 2: Declare an immutable public variable for the canonical token address, set in the constructor.
  • Sub-step 3: Create a mapping(address => uint256) for user balances and a uint256 for total supply.
solidity
// SPDX-License-Identifier: MIT import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract CustomWrapper { IERC20 public immutable canonicalToken; mapping(address => uint256) private _balances; uint256 private _totalSupply; constructor(address _canonicalToken) { canonicalToken = IERC20(_canonicalToken); } }

Tip: Using immutable for the canonical token address saves gas and ensures the reference cannot be changed after deployment.

2

Implement the Deposit and Withdrawal Logic

Create the core functions for wrapping and unwrapping tokens, handling asset custody.

Detailed Instructions

The deposit function must transfer tokens from the user to the wrapper contract and mint wrapped tokens. The withdraw function burns wrapped tokens and returns the underlying assets. You must handle approval from the user for the deposit and ensure sufficient wrapper balance for withdrawal.

  • Sub-step 1: In deposit(uint256 amount), call canonicalToken.transferFrom(msg.sender, address(this), amount). On success, mint wrapper tokens to the user by updating _balances and _totalSupply.
  • Sub-step 2: In withdraw(uint256 amount), check that the user's wrapper balance is sufficient, then decrement _balances and _totalSupply. Transfer the canonical tokens back to the user.
  • Sub-step 3: Emit corresponding Deposit and Withdraw events for off-chain tracking.
solidity
event Deposit(address indexed user, uint256 amount); event Withdraw(address indexed user, uint256 amount); function deposit(uint256 amount) external { require(canonicalToken.transferFrom(msg.sender, address(this), amount), "Transfer failed"); _balances[msg.sender] += amount; _totalSupply += amount; emit Deposit(msg.sender, amount); } function withdraw(uint256 amount) external { require(_balances[msg.sender] >= amount, "Insufficient balance"); _balances[msg.sender] -= amount; _totalSupply -= amount; require(canonicalToken.transfer(msg.sender, amount), "Transfer failed"); emit Withdraw(msg.sender, amount); }

Tip: Use the require statement with the return value of transferFrom to safely handle non-compliant ERC-20 tokens that return false on failure.

3

Integrate with the Native Bridging System

Connect your wrapper to the L1/L2 bridge's messaging layer for cross-chain functionality.

Detailed Instructions

For a wrapper to facilitate cross-chain movement, it must interact with the canonical bridge's messaging contract. On Optimism, this is the L1StandardBridge; on Arbitrum, it's the L1GatewayRouter. Your wrapper's bridge function should lock/burn tokens and send a message to mint on the destination chain.

  • Sub-step 1: Import the interface for the bridge messenger (e.g., IL1StandardBridge for Optimism).
  • Sub-step 2: Create a bridgeToL1 function that burns the user's wrapped tokens and calls sendMessage on the messenger contract with the recipient and amount.
  • Sub-step 3: Implement a function, callable only by the bridge, to mint tokens upon receiving a valid cross-chain message. This uses a modifier like onlyFromCrossDomainSender.
solidity
// Example for an Optimism-style L2 wrapper import "@eth-optimism/contracts/L2/messaging/IL2StandardBridge.sol"; contract CustomWrapperL2 is CustomWrapper { IL2StandardBridge public immutable l2Bridge; constructor(address _l2Bridge, address _l1Token) CustomWrapper(_l1Token) { l2Bridge = IL2StandardBridge(_l2Bridge); } function bridgeToL1(uint256 amount, address recipient) external { _burn(msg.sender, amount); // Internal burn function l2Bridge.withdraw(address(canonicalToken), amount, 0, abi.encode(recipient)); } }

Tip: The bridge call often requires a gasLimit parameter (set to 0 for defaults) and encoded calldata for the recipient on the other side.

4

Add Access Control and Pause Mechanisms

Implement administrative controls for security, upgrades, and emergency response.

Detailed Instructions

Use OpenZeppelin's AccessControl to restrict sensitive functions like changing bridge addresses or pausing deposits. A pause mechanism is critical to halt operations if a vulnerability is discovered in the bridge or wrapper logic.

  • Sub-step 1: Import @openzeppelin/contracts/access/AccessControl.sol and @openzeppelin/contracts/security/Pausable.sol.
  • Sub-step 2: Inherit from AccessControl and Pausable. In the constructor, grant the DEFAULT_ADMIN_ROLE to the deployer and set up a PAUSER_ROLE.
  • Sub-step 3: Add the whenNotPaused modifier to deposit and bridge functions. Create pause() and unpause() functions restricted to the pauser role.
solidity
import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; contract SecureCustomWrapper is CustomWrapper, AccessControl, Pausable { bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); constructor(address _canonicalToken) CustomWrapper(_canonicalToken) { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(PAUSER_ROLE, msg.sender); } function deposit(uint256 amount) external whenNotPaused override { super.deposit(amount); } function pause() external onlyRole(PAUSER_ROLE) { _pause(); } function unpause() external onlyRole(PAUSER_ROLE) { _unpause(); } }

Tip: Consider implementing a timelock for the admin role for critical functions to increase decentralization and security.

5

Deploy and Verify on Testnet

Test the wrapper's integration end-to-end and verify the contract source code.

Detailed Instructions

Deploy your contract to a Layer 2 testnet like Sepolia/Optimism-Goerli or Sepolia/Arbitrum-Sepolia. You must test the full flow: deposit, bridge message initiation, and finalization on the counterparty chain.

  • Sub-step 1: Write and run Foundry or Hardhat tests that simulate a user depositing, bridging, and withdrawing. Mock the bridge messenger in unit tests.
  • Sub-step 2: Deploy using a script, passing the correct canonical token and bridge addresses for the testnet. For example, the Optimism Goerli L2 Standard Bridge address is 0x4200000000000000000000000000000000000010.
  • Sub-step 3: Verify the contract source code on the block explorer (e.g., Blockscout for Arbitrum, Optimism Explorer) using the --verify flag in your deployment script or the explorer's UI.
bash
# Example Foundry deploy command with verification forge create --rpc-url $OPTIMISM_GOERLI_RPC \ --constructor-args 0xDeadBeef... 0x4200000000000000000000000000000000000010 \ --private-key $DEPLOYER_KEY \ src/CustomWrapperL2.sol:CustomWrapperL2 \ --etherscan-api-key $OPTIMISM_ETHERSCAN_KEY \ --verify

Tip: After verification, interact with your live contract via the block explorer to perform a test deposit with a small amount of test tokens to confirm functionality.

SECTION-FAQ

Frequently Asked Questions

Ready to Start Building?

Let's bring your Web3 vision to life.

From concept to deployment, ChainScore helps you architect, build, and scale secure blockchain solutions.