EVM Cross-Chain Bridges
Bridge tokens across EVM chains in a single source-chain transaction. Fully decentralized — no API key, on-chain quotes only, auto-routed to the cheapest path.
EVM Cross-Chain Bridges
A single component bridges tokens across EVM chains in one source-chain transaction. Fully decentralized: every quote is read on-chain through your own JSON-RPC endpoint. No API key, no external HTTP service, no off-chain attestation server.
Under the hood the component fans out to multiple decentralized bridge routes in parallel (LayerZero / OFT pools, canonical AMM bridges, hToken-style bridges) and auto-selects the path with the lowest effective fee for the requested asset pair. The chosen route is opaque to the caller — you only see the resulting calldata, fee, and finality estimate.
Cross-chain quote calls run several RPC round-trips in parallel. Public RPC URLs enforce rate limits per IP address and can fail under concurrent load. Use a private endpoint (Alchemy, Infura, QuickNode, or self-hosted). See Connecting to any chain.
BRIDGE_EVM_TOKENS
BRIDGE_EVM_TOKENS Workflow Component
Build the source-chain transaction that bridges amount of tokenAddress from sourceChainId to destinationChainId. The component queries every supported route in parallel, picks the cheapest (ties broken by faster finality), applies your slippage, and returns ready-to-sign calldata.
Config
| Field | Type | Default | Description |
|---|---|---|---|
slippageBps | number (0–1000) | 50 | Slippage tolerance in basis points (50 = 0.5%). Applied once to the winning quote's outputAmountBeforeSlippage to derive minOutputAmount |
maxFeeBps | number (0–500) | 100 | Fail-fast guard — throws if the cheapest route's effective protocol fee exceeds this. Set to 500 to disable the guard |
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
sourceChainId | number | Yes | Source chain ID (e.g. 1 for Ethereum, 42161 for Arbitrum) |
destinationChainId | number | Yes | Destination chain ID — must differ from sourceChainId |
tokenAddress | string (address) | Yes | Source-chain token. Use 0x0000000000000000000000000000000000000000 for native ETH (the underlying bridge picks the wrapped variant where needed) |
outputTokenAddress | string | null | No | Destination-chain token. Omit (recommended) to bridge to the same asset on the destination chain — the component resolves the canonical counterpart per route (USDC → USDC, USDT → USDT, ETH → ETH). Pin a specific address only for explicit cross-asset routes |
amount | string (bigint) | Yes | Amount in the source token's smallest unit (wei for ETH, 6-decimal base units for USDC, etc.) |
recipient | string (address) | Yes | Destination-chain address that receives the output token |
sender | string (address) | Yes | Source-chain sender. Also receives any unspent native messaging-fee refund |
jsonRpcUrl | string (URL) | Yes | Source-chain JSON-RPC URL — used to read on-chain quotes from each candidate route |
Outputs
| Field | Type | Description |
|---|---|---|
calldata | string | ABI-encoded calldata for the source-chain bridge transaction |
to | string (address) | Source-chain contract to send the transaction to |
value | string (bigint) | Native chain currency to attach. Always covers the cross-chain messaging fee; additionally covers the bridge amount when bridging native ETH |
approvalAddress | string (address) | Spender for the upstream ERC-20 approve. Equal to to for the current routes |
estimatedFeeAmount | string (bigint) | Protocol fee in source-token base units (does not include the native messaging fee — that is in value) |
estimatedFeeBps | number | Effective fee rate in basis points |
estimatedFinalitySec | number | Expected destination-chain settlement time in seconds |
minOutputAmount | string (bigint) | Minimum amount delivered on destination after fee + slippage. The output of the route's pre-encoded minAmountOut |
outputToken | string (address) | Destination-chain token actually delivered (resolved from same-asset routing when outputTokenAddress was omitted) |
nativeMessagingFee | string (bigint) | Native chain currency paid as cross-chain messaging fee (included inside value) |
Required upstream step
Bridging an ERC-20 requires an approve(spender = approvalAddress, amount) call before broadcasting the bridge transaction. Use BUILD_EVM_ERC20_APPROVE_CALLDATA for that step.
Bridging native ETH does not need an approve — the bridge amount is attached via value.
Errors
The component throws a 400 ComponentException for the following cases:
| Trigger | Message |
|---|---|
| Source and destination chain IDs match | source and destination chains must differ |
| No route covers the requested pair | No bridge route from chain <src> to <dst> for <token>. Try specifying outputTokenAddress for a cross-asset route. |
outputTokenAddress pinned and no route matches it | No bridge route delivers <token> on chain <dst> for input <token> |
| No route could resolve a same-asset destination | No bridge route could resolve a same-asset destination for <token> on chain <dst>. Try specifying outputTokenAddress. |
Cheapest route's fee exceeds maxFeeBps | Cheapest bridge route quoted <feeBps>bps which exceeds maxFeeBps <maxFeeBps> |
SDK example — USDC from Arbitrum to Ethereum
import { WorkspaceClient, ComponentModule } from '@zafeguard/caller-sdk';
const workspace = new WorkspaceClient({ apiKey: process.env.ZAFEGUARD_API_KEY! });
const SENDER = '0xYourSourceAddress';
const RECIPIENT = '0xYourDestinationAddress';
const USDC_ARB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
// 1. Get the bridge route + calldata
const bridge = await workspace.call(ComponentModule.BRIDGE_EVM_TOKENS, {
sourceChainId: 42161, // Arbitrum One
destinationChainId: 1, // Ethereum
tokenAddress: USDC_ARB,
// outputTokenAddress omitted — auto-resolves to USDC on Ethereum
amount: '1000000', // 1 USDC
recipient: RECIPIENT,
sender: SENDER,
jsonRpcUrl: process.env.ARBITRUM_RPC_URL!,
}).promise();
console.log('Route fee:', bridge.estimatedFeeBps, 'bps');
console.log('Finality:', bridge.estimatedFinalitySec, 'seconds');
console.log('Min delivered:', bridge.minOutputAmount, 'USDC base units');
// 2. Approve the bridge router (USDC is an ERC-20 → approve is required)
const approve = await workspace.call(ComponentModule.BUILD_EVM_ERC20_APPROVE_CALLDATA, {
tokenAddress: USDC_ARB,
spender: bridge.approvalAddress,
amount: '1000000',
}).promise();
// 3. From here: BUILD_EVM_TRANSACTION → SIGN → BROADCAST → WAIT (twice — approve, then bridge)
// For native-ETH bridges you skip the approve transaction entirely.Native-ETH bridge
For native ETH, pass the zero-address sentinel and skip the approve step. The bridge amount is attached via value along with the messaging fee.
const bridge = await workspace.call(ComponentModule.BRIDGE_EVM_TOKENS, {
sourceChainId: 42161,
destinationChainId: 10, // Optimism
tokenAddress: '0x0000000000000000000000000000000000000000',
amount: '10000000000000000', // 0.01 ETH
recipient: RECIPIENT,
sender: SENDER,
jsonRpcUrl: process.env.ARBITRUM_RPC_URL!,
}).promise();
// bridge.value = bridge amount + native messaging fee
// No approve step — feed straight into BUILD_EVM_TRANSACTIONHow routing works
The component quotes several bridges in parallel and picks the cheapest route within your maxFeeBps. If you pinned outputTokenAddress, only quotes delivering that token are considered. Ties are broken by faster finalitySec. Slippage is applied once at the end to the winning quote.
Chain & asset coverage
The component covers the major EVM chains for the common liquidity assets:
- Assets: USDC, USDT, DAI, native ETH, plus selected route-specific stables (FRAX, gOHM, MATIC, METIS).
- Chains: Ethereum, Optimism, BNB Chain, Polygon, Sonic, Fantom, zkSync Era, Metis, Polygon zkEVM, Moonbeam, Mantle, Base, Arbitrum, Avalanche, Linea, Blast, Scroll, Unichain, Berachain, Gnosis — and others as the underlying routes add support.
If a pair isn't covered for same-asset routing, try outputTokenAddress to opt into a cross-asset route, or use a swap component upstream (see DeFi →) to convert into a supported source asset first.
Full bridge workflow (ERC-20)
BUILD_EVM_ERC20_APPROVE_CALLDATA (spender = bridge.approvalAddress)
│
▼
BUILD_EVM_TRANSACTION → SIGN_EVM_TRANSACTION → BROADCAST_EVM_TRANSACTION → WAIT_FOR_EVM_TRANSACTION
│
▼
BRIDGE_EVM_TOKENS
│
▼
BUILD_EVM_TRANSACTION → SIGN_EVM_TRANSACTION → BROADCAST_EVM_TRANSACTION → WAIT_FOR_EVM_TRANSACTION
│
▼ (decentralized message layer settles on destination)
Tokens land at `recipient` on the destination chain within `estimatedFinalitySec`.
For native-ETH bridges, drop the first APPROVE → BUILD → SIGN → BROADCAST → WAIT block — BRIDGE_EVM_TOKENS covers everything in a single source-chain transaction with value set appropriately.