EVM (Ethereum)

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

FieldTypeDefaultDescription
slippageBpsnumber (0–1000)50Slippage tolerance in basis points (50 = 0.5%). Applied once to the winning quote's outputAmountBeforeSlippage to derive minOutputAmount
maxFeeBpsnumber (0–500)100Fail-fast guard — throws if the cheapest route's effective protocol fee exceeds this. Set to 500 to disable the guard

Inputs

FieldTypeRequiredDescription
sourceChainIdnumberYesSource chain ID (e.g. 1 for Ethereum, 42161 for Arbitrum)
destinationChainIdnumberYesDestination chain ID — must differ from sourceChainId
tokenAddressstring (address)YesSource-chain token. Use 0x0000000000000000000000000000000000000000 for native ETH (the underlying bridge picks the wrapped variant where needed)
outputTokenAddressstring | nullNoDestination-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
amountstring (bigint)YesAmount in the source token's smallest unit (wei for ETH, 6-decimal base units for USDC, etc.)
recipientstring (address)YesDestination-chain address that receives the output token
senderstring (address)YesSource-chain sender. Also receives any unspent native messaging-fee refund
jsonRpcUrlstring (URL)YesSource-chain JSON-RPC URL — used to read on-chain quotes from each candidate route

Outputs

FieldTypeDescription
calldatastringABI-encoded calldata for the source-chain bridge transaction
tostring (address)Source-chain contract to send the transaction to
valuestring (bigint)Native chain currency to attach. Always covers the cross-chain messaging fee; additionally covers the bridge amount when bridging native ETH
approvalAddressstring (address)Spender for the upstream ERC-20 approve. Equal to to for the current routes
estimatedFeeAmountstring (bigint)Protocol fee in source-token base units (does not include the native messaging fee — that is in value)
estimatedFeeBpsnumberEffective fee rate in basis points
estimatedFinalitySecnumberExpected destination-chain settlement time in seconds
minOutputAmountstring (bigint)Minimum amount delivered on destination after fee + slippage. The output of the route's pre-encoded minAmountOut
outputTokenstring (address)Destination-chain token actually delivered (resolved from same-asset routing when outputTokenAddress was omitted)
nativeMessagingFeestring (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:

TriggerMessage
Source and destination chain IDs matchsource and destination chains must differ
No route covers the requested pairNo bridge route from chain <src> to <dst> for <token>. Try specifying outputTokenAddress for a cross-asset route.
outputTokenAddress pinned and no route matches itNo bridge route delivers <token> on chain <dst> for input <token>
No route could resolve a same-asset destinationNo bridge route could resolve a same-asset destination for <token> on chain <dst>. Try specifying outputTokenAddress.
Cheapest route's fee exceeds maxFeeBpsCheapest 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_TRANSACTION

How 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.

On this page