> ## Documentation Index
> Fetch the complete documentation index at: https://l2docs.whitechain.io/llms.txt
> Use this file to discover all available pages before exploring further.

# OP Stack canonical bridge

> Move assets between Ethereum and Whitechain with the OP Stack canonical bridge: deposits, withdrawals, contract addresses, and alternative bridges.

<Note>
  Available on Testnet today. Mainnet contract addresses and final timing parameters are published at Mainnet launch.
</Note>

## Overview

Whitechain settles on Ethereum via the OP Stack canonical bridge. Assets and arbitrary messages move between L1 (Ethereum) and L2 (Whitechain) through Optimism-standard bridge contracts. Deposits arrive on L2 in about 2 minutes. Withdrawals back to L1 complete after a mandatory dispute-resolution period: minimum 7 days on mainnet, about 1 hour on testnet (see [Timing parameters](#timing-parameters)).

## Ethereum to Whitechain

### Deposits (L1 to L2)

A deposit takes about 2 minutes. You initiate the deposit on L1 and the op-node automatically finalizes it on L2. No further action is required on L2 once the deposit is confirmed.

| Asset                  | L1 method                                          | Notes                                                                                                                                                                                                                                                            |
| ---------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ETH (native)           | Not supported                                      | Whitechain runs in Custom Gas Token (CGT) mode. `depositETH` reverts with `OptimismPortal_NotAllowedOnCGTMode()`.                                                                                                                                                |
| WBT (native gas token) | Not supported                                      | There is no L1-to-L2 gas-token deposit path for native WBT, on testnet or mainnet. Use the [Portal bridge](/build/bridge/portal-bridge) to move WBT between Ethereum and Whitechain, or claim testnet WBT from the [faucet](/learn/get-started/get-testnet-wbt). |
| ERC20 tokens           | `approve`, then `L1StandardBridge.depositERC20`    | The L2 token must already be deployed. See [For token issuers](#for-token-issuers).                                                                                                                                                                              |
| NFTs (ERC721)          | `approve`, then `L1Erc721BridgeProxy.bridgeERC721` | Only L1-origin NFTs are supported.                                                                                                                                                                                                                               |
| Arbitrary messages     | `L1CrossDomainMessenger.sendMessage`               | Has replay support if the L2 call runs out of gas. `OptimismPortal.depositTransaction` has no replay; do not call it directly.                                                                                                                                   |

Example `depositERC20` parameters:

| Field          | Example value                             | Note                         |
| -------------- | ----------------------------------------- | ---------------------------- |
| `_l1Token`     | address of the L1 token                   | must have a paired L2 token  |
| `_l2Token`     | address of the L2 `OptimismMintableERC20` | deployed via the factory     |
| `_amount`      | `10000000`                                | 10 tokens at 6 decimals      |
| `_minGasLimit` | `200000`                                  | minimum gas for L2 execution |
| `_extraData`   | `0x`                                      | optional, empty by default   |

After \~2 minutes, verify the balance with `balanceOf` on the L2 token contract.

### Withdrawals (L2 to L1)

A withdrawal takes about an hour on testnet. On mainnet it takes minimum 7 days. The wait for a qualifying output proposal adds to that minimum, and so does re-proving against a new game if the initial dispute game is successfully challenged (see [Timing parameters](#timing-parameters) for exact figures). The flow has six steps:

1. **Initiate on L2** call `L2StandardBridge.bridgeERC20` for ERC20 tokens, `L2ERC721Bridge.bridgeERC721` for L1-origin NFTs, or `L2CrossDomainMessenger.sendMessage` for arbitrary messages. Tokens are burned for L1-origin tokens and escrowed for L2-native tokens. L2-native NFTs cannot be withdrawn (see [When NOT to use the canonical bridge](#when-not-to-use-the-canonical-bridge)). The canonical bridge does not handle native WBT withdrawals; use the [Portal bridge](/build/bridge/portal-bridge) for that.
2. **Wait for an output proposal** the op-proposer publishes an output root on L1 via the `DisputeGameFactory` at a fixed interval (every 30 minutes on testnet). Your withdrawal can only be proven against a dispute game whose `l2BlockNumber` is greater than or equal to the L2 block of your withdrawal transaction. To find a suitable game and its `gameIndex`, monitor the create transactions on the `DisputeGameFactory` contract in the explorer.
3. **Prove the withdrawal on L1** call `OptimismPortal.proveWithdrawalTransaction` with the `gameIndex` and a Merkle proof from L2 storage. Tools such as viem OP Stack actions or `op-withdrawer` compute the proof for you (see [Programmatic bridging](#programmatic-bridging)).
4. **Resolve the dispute game** the game must resolve as `DEFENDER_WINS` before you can finalize. If it still has `IN_PROGRESS` status after its challenge clock expires, anyone can call `resolveClaim` on the game contract to resolve it. This step is permissionless and is typically handled by an off-chain service. If the game instead resolves as `CHALLENGER_WINS`, your proof is invalidated but your funds are not lost. Re-prove against a different, honest game; the delay countdown restarts from the new prove transaction.
5. **Wait for the dispute-resolution delays** two independent delays must elapse before finalization is allowed. `proofMaturityDelaySeconds` is the minimum time since your prove transaction. `disputeGameFinalityDelaySeconds` is the minimum time since the game resolved in favor of the defender. See [Timing parameters](#timing-parameters) for current values on each network.
6. **Finalize on L1** call `OptimismPortal.finalizeWithdrawalTransaction` to release the funds to L1.

<Warning>
  The dispute-resolution delay on mainnet (minimum 7 days) is enforced by the protocol and cannot be skipped. Plan treasury operations and time-sensitive flows accordingly. Testnet delays are intentionally shortened and do not reflect mainnet behavior. For urgent exits, see [When NOT to use the canonical bridge](#when-not-to-use-the-canonical-bridge).
</Warning>

### Timing parameters

| Parameter                            | Testnet                                                      | Mainnet (planned)                              |
| ------------------------------------ | ------------------------------------------------------------ | ---------------------------------------------- |
| Deposit (L1 to L2)                   | \~2 minutes                                                  | \~2 minutes                                    |
| Proposer interval (new dispute game) | 30 minutes                                                   | TBD, confirmed once mainnet parameters are set |
| `proofMaturityDelaySeconds`          | 15 minutes                                                   | 7 days                                         |
| `disputeGameFinalityDelaySeconds`    | 5 minutes                                                    | TBD, confirmed once mainnet parameters are set |
| Full withdrawal cycle                | \~1 hour typical, up to \~3 hours (observed; see note below) | minimum 7 days                                 |

The full testnet cycle is longer than the two delays combined (15 + 5 minutes) because of discrete service intervals. A withdrawal can only be proven once a dispute game covers its L2 block. If the latest game predates your transaction, you wait for the next one: an observed wait of up to \~45 minutes within a measured 64-minute cycle. In the worst case, when batcher and proposer intervals align unfavorably across stages, the full cycle reaches about 3 hours.

Testnet delays are intentionally shortened to allow end-to-end testing. Do not assume testnet timings apply to mainnet. Mainnet values follow the OP Stack default consensus parameters (see [specs.optimism.io](https://specs.optimism.io), section Configurability, Consensus parameters).

### Programmatic bridging

Common paths for interacting with the bridge contracts directly:

| Tool                                                                              | What it does                                                                                                                                                                                                                                                                                       |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [viem OP Stack actions](https://viem.sh/op-stack)                                 | Recommended. The `viem/op-stack` module extends a client with L1 and L2 actions (`depositTransaction`, `proveWithdrawal`, `finalizeWithdrawal`, and the `buildProveWithdrawal` and `getWithdrawals` helpers) that build the proofs for you. See [Use viem with Whitechain](/build/dapps/use-viem). |
| [op-withdrawer](https://github.com/base/op-enclave/tree/v0.0.1-rc6/op-withdrawer) | CLI that runs the prove and finalize steps for L2 to L1 withdrawals. It is a release-candidate build (`v0.0.1-rc6`) from `base/op-enclave`; treat it as pre-release and pin the version.                                                                                                           |
| Direct contract calls                                                             | Any Ethereum-compatible library (viem, ethers, web3.js) can call the bridge contracts. See [Contract addresses](#contract-addresses).                                                                                                                                                              |

The `@eth-optimism/sdk` package is deprecated and is no longer maintained. Use viem OP Stack actions instead.

Withdrawals still require the prove and finalize steps described above, regardless of which tool you use. No tool triggers them automatically. Proof generation additionally requires an archive-capable L2 RPC endpoint, since `eth_getProof` needs historical state access beyond the default pruning window. Without this step, funds remain locked in the L1 bridge escrow.

## Contract addresses

Contract addresses below are for testnet. Mainnet addresses will be added once mainnet is live.

### Testnet

L2 addresses below fall in the `0x4200...` predeploy range. They are identical across every OP Stack environment: devnet, testnet, and mainnet. L1 addresses are specific to this testnet deployment and will change if the deployment is ever redone. When verifying addresses, focus on the L1 side; the `0x4200...` L2 predeploys are fixed by the protocol and never change.

| Contract                            | L1                                                                                                                              | L2                                           |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `L1StandardBridge`                  | [`0x0c50be539ab5d72d226038928f2eb25100899ded`](https://sepolia.etherscan.io/address/0x0c50be539ab5d72d226038928f2eb25100899ded) | n/a                                          |
| `OptimismPortal`                    | [`0xff9b597b0781457ae6aa7256ca5ed5839bf7d0c3`](https://sepolia.etherscan.io/address/0xff9b597b0781457ae6aa7256ca5ed5839bf7d0c3) | n/a                                          |
| `DisputeGameFactoryProxy`           | [`0xfaa2faa8912c069c01abc169c33713c79027c833`](https://sepolia.etherscan.io/address/0xfaa2faa8912c069c01abc169c33713c79027c833) | n/a                                          |
| `L1CrossDomainMessengerProxy`       | [`0x547967f45d19dc4ca0a056ba525afe99bda18a88`](https://sepolia.etherscan.io/address/0x547967f45d19dc4ca0a056ba525afe99bda18a88) | n/a                                          |
| `L1Erc721BridgeProxy`               | [`0x1e419fc5575bbd6d352582e9d8073918d79f0c5b`](https://sepolia.etherscan.io/address/0x1e419fc5575bbd6d352582e9d8073918d79f0c5b) | n/a                                          |
| `SystemConfigProxy`                 | [`0x9328ea869949f33c57b7b680b6edb58769e2181c`](https://sepolia.etherscan.io/address/0x9328ea869949f33c57b7b680b6edb58769e2181c) | n/a                                          |
| `OptimismMintableERC20FactoryProxy` | [`0x5898f6c319842a379799d634c6ed2da960a42b9b`](https://sepolia.etherscan.io/address/0x5898f6c319842a379799d634c6ed2da960a42b9b) | n/a                                          |
| `L2StandardBridge`                  | n/a                                                                                                                             | `0x4200000000000000000000000000000000000010` |
| `L2CrossDomainMessenger`            | n/a                                                                                                                             | `0x4200000000000000000000000000000000000007` |
| `OptimismMintableERC20Factory`      | n/a                                                                                                                             | `0x4200000000000000000000000000000000000012` |
| `L2ERC721Bridge`                    | n/a                                                                                                                             | `0x4200000000000000000000000000000000000014` |
| `OptimismMintableERC721Factory`     | n/a                                                                                                                             | `0x4200000000000000000000000000000000000017` |

`OptimismPortal2` is the current implementation contract behind the `OptimismPortal` address above; use the name `OptimismPortal` when referring to it.

## When NOT to use the canonical bridge

The canonical bridge is the safest and most trust-minimized path between L1 and L2, but it does not fit every use case.

| Scenario                                   | Why the canonical bridge does not work                                                                                              | Alternative                                                                                                                                                           |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Need to exit to L1 quickly (mainnet)       | The dispute-resolution period (minimum 7 days on mainnet) is mandatory and cannot be skipped                                        | The [Portal bridge](/build/bridge/portal-bridge) for a faster, centralized transfer. On mainnet there is no fast canonical exit yet; plan for the full dispute period |
| Bridging USDC                              | Whitechain already has a canonical USDC.e representation; a second wrapped pairing created via the factory would fragment liquidity | Use the [Portal bridge](/build/bridge/portal-bridge) for USDC.e                                                                                                       |
| Token has transfer fees or rebasing logic  | The standard bridge does not account for balance changes during transfer                                                            | A custom bridge contract designed for the token                                                                                                                       |
| Token has a blocklist or pausable transfer | Finalization may revert permanently, leaving funds stuck                                                                            | A custom bridge contract                                                                                                                                              |
| L2-native NFTs going to L1                 | The standard `L2ERC721Bridge` only supports L1-origin NFTs                                                                          | A custom NFT bridge                                                                                                                                                   |
| Very large amounts with time-sensitivity   | Funds are locked for the entire dispute-resolution period                                                                           | Stage the transfer in advance, or use a third-party bridge                                                                                                            |

## For token issuers

To make an L1 ERC20 bridgeable to Whitechain, deploy an `OptimismMintableERC20` on L2 that mirrors the L1 token. Use the `OptimismMintableERC20Factory.createOptimismMintableERC20WithDecimals` function with the L1 token address, the name and symbol you want on L2, and the decimals.

Before creating a new pairing, confirm the token doesn't already have an official Whitechain representation (see [When NOT to use the canonical bridge](#when-not-to-use-the-canonical-bridge)). A second wrapped version of an already-represented token fragments liquidity.

Example `createOptimismMintableERC20WithDecimals` parameters:

| Field          | Example value                    |
| -------------- | -------------------------------- |
| `_remoteToken` | address of the original L1 token |
| `_name`        | Example Token                    |
| `_symbol`      | EXT                              |
| `_decimals`    | 18                               |

The resulting L2 token grants mint and burn rights exclusively to the `L2StandardBridge`.

Verify the pairing before sending real value: `L1StandardBridge.deposits(l1Token, l2Token)` must return a non-zero value, or query the factory to confirm the L2 contract was created.

Tokens with transfer fees, rebasing logic, blocklists, or pausable transfers are **not compatible** with the standard bridge. Use a custom bridge solution for those cases.

## Disclaimer

* Always test with a small amount before bridging large balances.
* Verify every contract address against this page or the official Whitechain Explorer before submitting a transaction.
* Withdrawals are non-reversible once finalized on L1.

## Related

* [Quick end-to-end test on testnet](/build/bridge/bridge-e2e-testnet)
* [Portal bridge](/build/bridge/portal-bridge)
* [Faucet](/learn/get-started/get-testnet-wbt)
* [Network reference](/learn/network/reference)
* [Use viem with Whitechain](/build/dapps/use-viem)
