Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Bridge Tokens

Move tokens between Ethereum and Taiko.

Most users should bridge through the official web UI. The programmatic flow below is for scripts and AI agents.

Bridge UI

NetworkURL
Mainnet (Taiko Alethia)bridge.taiko.xyz
Testnet (Taiko Hoodi)bridge.hoodi.taiko.xyz

The UI handles approvals, fee estimation, and relay monitoring. On testnet, the sidebar exposes a faucet for test tokens like HORSE — fund your wallet with Hoodi L1 ETH from a public Hoodi faucet first.

Programmatic bridging

Use the flow below only when scripting a bridge transfer or driving it from an agent.

Agent prompt

Paste this into your coding agent:

Reference https://docs.taiko.xyz/SKILL.md
 
Bridge 0.1 WETH from Ethereum L1 to Taiko L2.
Approve the ERC20Vault, then call sendToken with the BridgeTransferOp struct.

Manual mode

Contract addresses

Mainnet (Ethereum L1 ↔ Taiko Alethia L2)
ContractAddress
L1 Bridge0xd60247c6848B7Ca29eDdF63AA924E53dB6Ddd8EC
L1 ERC20Vault0x996282cA11E5DEb6B5D122CC3B9A1FcAAD4415Ab
L1 SignalService0x9e0a24964e5397B566c1ed39258e21aB5E35C77C
L2 Bridge0x1670000000000000000000000000000000000001
L2 ERC20Vault0x1670000000000000000000000000000000000002
L2 SignalService0x1670000000000000000000000000000000000005
Hoodi Testnet (Ethereum Hoodi L1 ↔ Taiko Hoodi L2)
ContractAddress
L1 Bridge0x6a4cf607DaC2C4784B7D934Bcb3AD7F2ED18Ed80
L1 ERC20Vault0x0857cd029937E7a119e492434c71CB9a9Bb59aB0
L1 SignalService0x4c70b7F5E153D497faFa0476575903F9299ed811
L2 Bridge0x1670130000000000000000000000000000000001
L2 ERC20Vault0x1670130000000000000000000000000000000002
L2 SignalService0x1670130000000000000000000000000000000005

Bridge ERC-20 tokens (L1 → L2)

The mainnet examples below use a generic 18-decimal ERC-20. Adjust parseUnits / --to-wei for tokens with different decimals (for example, USDC has 6 decimals). For the Hoodi testnet version, see Testnet example.

Approve the ERC20Vault

The ERC20Vault pulls tokens from your wallet. Approve it for the amount you want to bridge.

ethers.js
import { ethers } from "ethers";
 
const provider = new ethers.JsonRpcProvider("https://eth.llamarpc.com");
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
 
const ERC20_ABI = ["function approve(address spender, uint256 amount) returns (bool)"];
const token = new ethers.Contract(TOKEN_ADDRESS, ERC20_ABI, signer);
 
const L1_ERC20_VAULT = "0x996282cA11E5DEb6B5D122CC3B9A1FcAAD4415Ab";
const amount = ethers.parseUnits("100", 18);
 
const tx = await token.approve(L1_ERC20_VAULT, amount);
await tx.wait();

Call sendToken on the ERC20Vault

Send a BridgeTransferOp struct to initiate the bridge.

BridgeTransferOp fields:
FieldTypeDescription
destChainIduint64Destination chain ID (167000 for Alethia, 167013 for Hoodi)
destOwneraddressMessage owner on the destination chain (usually your own address)
toaddressToken recipient on L2
feeuint64Processing fee in wei paid to the relayer (set 0 to self-relay)
tokenaddressL1 token contract address
gasLimituint32Gas limit for L2 execution (140000 is a safe default)
amountuint256Token amount in smallest unit
ethers.js
const VAULT_ABI = [
  "function sendToken((uint64 destChainId, address destOwner, address to, uint64 fee, address token, uint32 gasLimit, uint256 amount) op) payable returns (tuple)"
];
 
const vault = new ethers.Contract(L1_ERC20_VAULT, VAULT_ABI, signer);
 
const op = {
  destChainId: 167000,
  destOwner: signer.address,
  to: signer.address,
  fee: 0,
  token: TOKEN_ADDRESS,
  gasLimit: 140000,
  amount: ethers.parseUnits("100", 18),
};
 
// fee = 0 means self-relay; otherwise pass msg.value ≥ fee to cover the relayer
const tx = await vault.sendToken(op, { value: 0 });
const receipt = await tx.wait();
console.log("Bridge tx:", receipt.hash);

Wait for relay

After the L1 transaction confirms, the bridge message is relayed to L2 — typically within a few minutes. Track progress in the Bridge UI or by watching the L2 SignalService.

Testnet example

Swap in the Hoodi L1 ERC20Vault, a Hoodi L1 RPC, and chain ID 167013:

cast send 0x0857cd029937E7a119e492434c71CB9a9Bb59aB0 \
  "sendToken((uint64,address,address,uint64,address,uint32,uint256))" \
  "(167013,$YOUR_ADDRESS,$YOUR_ADDRESS,0,$TOKEN_ADDRESS,140000,$(cast --to-wei 100))" \
  --rpc-url https://hoodi.ethpandaops.io \
  --private-key $PRIVATE_KEY

Notes

  • First bridge deploys a BridgedERC20. The first time a given L1 token is bridged to L2, Taiko deploys a BridgedERC20 contract for it. Subsequent bridges reuse the same contract.
  • Bridged USDC ≠ native USDC. Bridging L1 USDC through the ERC20Vault produces a BridgedERC20, not Taiko's native USDC (0x07d83526730c7438048D55A4fc0b850e2aaB6f0b). For native USDC use Circle CCTP.
  • No rebase tokens. Rebasing tokens (e.g., stETH) aren't supported. Use the wrapped version (e.g., wstETH).
  • Fee-on-transfer tokens. Supported, but the amount credited on L2 reflects the post-fee balance.
  • Self-relay. Setting fee to 0 skips the relayer — you finalize the message on L2 yourself via the Bridge UI or by calling processMessage on the L2 Bridge.
  • Common L2 tokens:
TokenL2 Address
WETH0xA51894664A773981C6C112C43ce576f315d5b1B6
USDC (native)0x07d83526730c7438048D55A4fc0b850e2aaB6f0b