Move USDC between chains with swapWithExactOut, review the quote, and track progress.
A bridge is a swap whose source and destination are the same token, so you pass that token's address on each chain. This guide receives 10 USDC on Base, funded from USDC on Arbitrum and Optimism. It needs a client with a connected wallet; see Nexus SDK Setup.
Example
TypeScript
import { UserActionError, type IntentEvent, type IntentHookData, type NexusClient,} from "@avail-project/nexus-core";import { formatUnits, parseUnits } from "@avail-project/nexus-core/utils";declare const client: NexusClient;declare function confirmWithUser(summary: string): Promise<boolean>;const USDC = { arbitrum: { chainId: 42161, tokenAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, optimism: { chainId: 10, tokenAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85" }, base: { chainId: 8453, tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" },} as const;const baseUsdc = await client.getToken(USDC.base);async function review({ quote, execution, allow, deny }: IntentHookData) { if (!execution.possible) { console.warn("Cannot execute:", execution.cause, quote.executionWarnings); deny(); return; } const spend = quote.input .map((input) => `$${input.totalRequiredUsd} ${input.tokenSymbol} on chain ${input.chainId}`) .join(", "); const minimum = formatUnits(quote.output.minAmountRaw, baseUsdc.decimals); const ok = await confirmWithUser(`Spend ${spend}. Receive at least ${minimum} USDC on Base.`); if (ok) allow(); else deny();}function onEvent(event: IntentEvent) { if (event.type === "step") { console.log(event.step.type, event.state, event.errorDetails?.code); } else if (event.type === "status") { console.log(event.status, event.legs.map((leg) => leg.txExplorerUrl)); }}try { const result = await client.swapWithExactOut( { toChainId: USDC.base.chainId, toTokenAddress: USDC.base.tokenAddress, toAmountRaw: parseUnits("10", baseUsdc.decimals), sources: [USDC.arbitrum, USDC.optimism], }, { hooks: { onIntent: review }, onEvent }, ); console.log("Bridged", result.intentId, result.intentExplorerUrl);} catch (error) { if (error instanceof UserActionError) console.info("User declined"); else throw error;}
Choose sources
sources restricts which chains and tokens can fund the transfer. List the same token on each source chain to keep it a pure bridge. If you omit sources, the SDK can spend any token in the wallet and swap it into USDC.Amounts are raw bigint values. Read decimals from getToken() on each chain instead of assuming a value.
Review the quote
hooks.onIntent receives quote, execution, allow, deny, and refresh. The operation waits until you call allow() or deny(); deny() rejects the call with a UserActionError. Without the hook, the quote is accepted automatically.
Field
Meaning
execution.possible
false with cause: 'insufficient-balance' when the wallet cannot cover the quote
quote.input[]
Per-source amountRaw, totalRequiredRaw (including deposit fee), and USD values
quote.output.minAmountRaw
Minimum received after slippage
quote.fees
Fees in raw units and USD
quote.provider
Route provider: nexus-v2, mayan, or relay
Call await refresh() for a new quote before allowing a stale one, and render its return value. See Review quotes for details.
Track progress
onEvent receives quote, step, and status events. step events report each wallet prompt and submission as started, completed, or failed. status events report the intent's lifecycle (created, deposited, fulfilled, or expired) and per-source transaction links in legs.The promise resolves after fulfillment with intentId, intentExplorerUrl, and the transactions it sent.
A rejection after a step event with committed: true does not mean the transfer failed. Funds may already be deposited and still settle. Check the intent in listIntents or the explorer before retrying.