For the complete documentation index, see llms.txt. This page is also available as Markdown.

Bridge()

For chains that support StarGate bridge

    function bridge(uint256 amount) external payable override nonReentrant returns (uint256) {

This function allows anyone to trigger a bridge transaction where the consolidated fees (typically in the finalTargetToken format) will be sent to the Ethereum blockchain.

Function Arguments

Name
Type
Description

amount

uint256

This value represents the amount of tokens to bridge over from the contract on the selected chain to Ethereum

bridge

uint256

This value represents the bridge fee in native gas token units

This function requires 2 values to successfully work:

  1. Amount

    This value represents the amount of finalTargetToken you would like to bridge over. When passing the value 0, it will try to bridge the entire available amount. How to identify the available amount?

    1. Visit the CarbonVortex contract (follow the address in the relevant chain)

    2. Read finalTargetToken to find the address of the token to bridge over (if the address is null, check targetToken address instead)

    3. Check the contract balance for the token address

  2. Bridge

    This value represents the bridge fee which is required in order to pay for the 2nd transaction to claim the bridged amount on the ethereum blockchain. How to identify the required bridge fee? Different chains have different bridge implementation. Follow this step closely to find the correct value to use

Overview

The prove/finalize script lives in the bancorprotocol/carbon-vortex-bridge repo. Clone it and install dependencies:

Completing a Celo → Ethereum Vortex Bridge Withdrawal

Operator guide for the canonical OP-stack Celo Vortex bridge (VortexOpStackBridge).

Overview

Celo is an OP-stack L2, and the Vortex bridges WETH to Ethereum over the canonical Standard Bridge. A canonical L2→L1 withdrawal has three on-chain steps and an unavoidable ~7-day delay:

(L2: Celo)            (L1: Ethereum)                         (L1: Ethereum)
  1. bridge()    ──►    2. prove        ── wait ~7 days ──►    3. finalize
  burns WETH,          submits a Merkle proof of the          releases WETH to
  queues message       withdrawal to OptimismPortal2          the vault
  • Step 1 is a single contract write on Celo (a Celoscan "Write").

  • Steps 2 & 3 happen on Ethereum L1 and are driven by a script in the repo,

  • scripts/withdraw.mjs (pnpm withdraw <tx>) — the proof construction is far

  • too complex to do by hand in a block explorer.

The bridge is permissionless to prove/finalize: any funded EOA can run steps 2 and 3, and the WETH still lands in the vault regardless of who submits them.


Setup

The prove/finalize script lives in the bancorprotocol/carbon-vortex-bridge repo. Clone it and install dependencies:

 git clone https://github.com/bancorprotocol/carbon-vortex-bridge
cd carbon-vortex-bridge
pnpm install

Requires Node ≥ 18 and pnpm. Nothing else — scripts/withdraw.mjs is a self-contained viem script (no Hardhat/Foundry needed for the withdrawal flow).


Prerequisites

  • A signing key for the L1 transactions, passed explicitly as PRIVATE_KEY on the command line

  • (see the commands below).

  • That EOA needs ETH on Ethereum mainnet for gas (prove ≈ 350k gas; finalize is similar or smaller).

  • For Step 1, just CELO for gas on Celo.


Step 1 – Initiate the bridge on Celo

  1. Open the VortexOpStackBridge proxy on Celoscan → Contract → Write as Proxy:

  2. Connect your wallet.

  3. Find the bridge function. It takes one argument, amount:

    1. payableAmount (CELO)leave 0 (this bridge does not take a native fee; sending any value reverts).

    2. amount0 bridges the entire WETH balance held by the Vortex; or pass a specific amount (wei).

  4. Click Write and sign. This burns the L2 WETH and queues the canonical withdrawal message.

6. Copy the transaction hash – you need it for steps 2 and 3.

The L2 token being withdrawn is the bridged WETH 0xD221812de1BD094f35587EE8E174B07B6167D9Af; on L1 it is released as canonical WETH 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 to the vault.


Step 2 – Prove the withdrawal on Ethereum

The withdrawal can only be proven once a dispute game covering its L2 block has been posted to L1 by Celo's proposer (OP-Succinct validity proofs). This usually takes tens of minutes to a few hours.

From the repo root, check status any time (read-only, no key):

When it reads ready-to-prove, run the same command with your key to send the prove tx:

The script is status-aware — with a key set, the same command takes the correct next action at each stage; without a key it only reports status:

Status it prints
What it does (with PRIVATE_KEY)

waiting-to-prove

No game covers the L2 block yet. Re-run later.

ready-to-prove

Builds the proof and send the prove tx, then waits for the receipt.

waiting-to-finalize

Proven – prints the remaining time in the last 7-day window.

ready-to-finalize

Sends the finalize tx (see Step 4).

finalized

Done – WETH is in the vault.

On ready-to-prove it submits proveWithdrawalTransaction on the L1 portal and reports:

The 7-day proof-maturity timer starts at the moment the prove tx confirms.


Step 3 — Wait ~7 days

Re-running the read-only command during this period prints the countdown:

Nothing to do but wait — you don't need to keep a process running, just re-run later.


Step 4 — Finalize on Ethereum

After the window opens, run the command with your key again:

It detects ready-to-finalize, submits finalizeWithdrawalTransaction, and the WETH is released to the vault 0x60917e542aDdd13bfd1a7f81cD654758052dAdC4:

That completes the bridge.


Script Configuration

scripts/withdraw.mjs reads these from the environment (pass them on the command line; nothing is loaded from .env):

Env var
Purpose
Default

PRIVATE_KEY

Signer key for the L1 prove/finalize tx (0x-prefixed). Omit to run read-only.

L1_RPC

Ethereum mainnet RPC (reads + sends txs)

CELO_RPC

Celo RPC (receipt + status)

CELO_ARCHIVE_RPC

Celo archive RPC for eth_getProof (prove step)

Passing no PRIVATE_KEY runs read-only — it just prints the current status without sending anything.


Reference addresses

Contract
Network
Address

VortexOpStackBridge (proxy)

Celo

0x753425e27aE996f0DB75C759C6E5EBa5c74D6aAF

Vortex

Celo

0xD9D89e8A0dfE549e5B424D5b511cB3b84A764857

WETH (withdraw token, L2)

Celo

0xD221812de1BD094f35587EE8E174B07B6167D9Af

L2 Standard Bridge (predeploy)

Celo

0x4200000000000000000000000000000000000010

L2ToL1MessagePasser (predeploy)

Celo

0x4200000000000000000000000000000000000016

OptimismPortal2

L1

0xc5c5D157928BDBD2ACf6d0777626b6C75a9EAEDC

DisputeGameFactory

L1

0xFbAC162162f4009Bb007C6DeBC36B1dAC10aF683

L1 Standard Bridge

L1

0x9C4955b92F34148dbcfDCD82e9c9eCe5CF2badfe

WETH (released to vault, L1)

L1

0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

Vault (final recipient)

L1

0x60917e542aDdd13bfd1a7f81cD654758052dAdC4


Troubleshooting

  • eth_getProof"Missing or invalid parameters" – forno does not serve eth_getProof. Use an

  • archive endpoint (the default celo.drpc.org, or rpc.ankr.com/celo). This is why the prove step uses

  • CELO_ARCHIVE_RPC separately from CELO_RPC.

  • "Dispute game not found" / stuck on waiting-to-prove – the proposer hasn't posted a game covering

  • your L2 block yet. Wait and re-run; this is normal for the first tens of minutes to a couple of hours.

  • Prove/finalize tx fails or never sends – confirm the signing EOA has mainnet ETH for gas, and

  • that you passed PRIVATE_KEY (without it the script is read-only by design).

  • Want to check status without spending gas – run the command with no PRIVATE_KEY; it stays

  • read-only and just prints the current status.


Doing it manually (raw contract calls)

To drive the flow yourself without the script — every L1/L2 function call, on which contract, and how to source each parameter — see the contract-level reference: celo-bridge-withdrawal-technical.md.

On Coti

  1. View the wETH token page on readProxy on cotiscan

  2. Find the read function called quoteGasPayment

  3. Input 1 to indicate Ethereum as the destination chain

  4. Click Read to discover the required gas cost

Once you have this information available, paste it on the Send native COTI field for the bridge function

On BNB Bridge is utilizing the "across" bridge infrastructure which knows how to calculate all required fees during execution.

  1. bridge fee value should be set to zero (0)

  2. Click "write" and initiate the briding process.

Thats it.

On Sei, Base, Tac, Fantom, Mantle, Linea, Blast, Telos and Iota: Bridge supports automatic "claim" and as such you are required to include the gas fee needed for the following transaction on the ethereum blockchain. In order for the transaction to go through successfully, you must identify the required gas fee by following this process:

  1. Connect wallet

  2. Attempt to bridge any amount of tokens (you want to see the transaction details on your wallet signed page DO NOT SIGN THE TRANSACTION)

  3. In the wallet confirmation popup, find the value under "Param #2" as outlined in the image (you can "Cancel" the wallet popup after coping the required value)

  4. Copy the indicated value and paste it into the bridge field of the transaction. Make sure you normalize the decimal correctly to the gas token of the chain. For example, on Base, the input value should be in "ETH" units i.e. 0.000666588492010473 and not as indicated in WEI.

Example

Last updated