Swap from your app
Goal: a working swap button. Chord finds the route and builds the transaction; the user’s wallet signs it. Chord never holds funds or keys.
1. Resolve tokens and decimals
Section titled “1. Resolve tokens and decimals”const A = "https://api.getchord.xyz/v1";const { tokens } = await fetch(`${A}/tokens?chain=5042`).then((r) => r.json());const USDC = tokens.find((t) => t.symbol === "USDC"); // 0x3600…0000, 6 decimalsFor a token that is not on the curated list, GET /arc/tokens/search?q= returns its address and decimals from the chain. Never assume 18.
2. Quote on intent
Section titled “2. Quote on intent”Quote when the user has typed an amount and paused, not on every keystroke. Paint a first number with fast=1 and replace it with the full answer.
const params = new URLSearchParams({ chain: "5042", inputMint: USDC.address, outputMint: token.address, amount: (5n * 10n ** 6n).toString(), slippageBps: "100" });const [fast, full] = await Promise.all([ fetch(`${A}/quote?${params}&fast=1`).then((r) => r.json()), fetch(`${A}/quote?${params}`).then((r) => r.json()),]);const quote = full; // never build from `fast` (preliminary: true)if (quote.routePlan.length === 0) return showNoRoute(quote.noRouteReason); // INSUFFICIENT_LIQUIDITY → suggest a smaller sizeShow outputAmount (scaled by the output token’s decimals), priceImpactPct, feeBps, and otherAmountThreshold as the minimum received.
3. Approve the router when needed
Section titled “3. Approve the router when needed”For an ERC-20 input — and on Arc every input is one, since the native unit aliases to the USDC predeploy — the router must be allowed to pull inputAmount. The router address is transaction.to in the /swap answer (0x1b1fee89a381e595fe6536b99ba91f3e16e9601a on both chains). Read the allowance first and skip the approve when it is enough.
const allowance = await publicClient.readContract({ address: USDC.address, abi: erc20Abi, functionName: "allowance", args: [wallet, ROUTER] });if (allowance < BigInt(quote.inputAmount)) { await walletClient.writeContract({ address: USDC.address, abi: erc20Abi, functionName: "approve", args: [ROUTER, BigInt(quote.inputAmount)] });}4. Build
Section titled “4. Build”const { transaction } = await fetch(`${A}/swap`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ quoteResponse: quote, userPublicKey: wallet, chain: 5042 }),}).then((r) => r.json());Send the quote back unmodified. A changed body is 400 ROUTE_MISMATCH; a different slippageBps is 400 SLIPPAGE_MISMATCH — re-quote instead. Pass feeAccount if you are a referrer.
5. Simulate
Section titled “5. Simulate”const sim = await fetch(`${A}/swap/simulate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ chain: 5042, from: wallet, to: transaction.to, data: transaction.data, value: transaction.value }),}).then((r) => r.json());if (sim.ok === false && !/not approved/i.test(sim.revert)) return showRevert(sim.revert);An allowance revert right after the approve is expected (the approve may not be mined yet); proceed. Any other revert, stop.
6. Sign and send
Section titled “6. Sign and send”const hash = await walletClient.sendTransaction({ to: transaction.to, data: transaction.data, value: BigInt(transaction.value), gas: BigInt(transaction.gasLimit), chain: arc,});On Arc value is always 0. Wait for the receipt; the output lands in the user’s wallet. Explorer: https://explorer.arc.io/tx/${hash}.
Edge cases
Section titled “Edge cases”- Quote expired. A quote is good for about 30 seconds. If the user idled, quote again before
/swap. - Price moved past the threshold. The transaction reverts with insufficient output. Quote again; do not resubmit.
- Gateway dropped the send. Arc’s public gateway has been seen dropping one broadcast in six. Use a dedicated RPC for
sendRawTransactionand retry with the same nonce on a timeout. - Robinhood Chain. Same flow with
chain: 4663; native ETH is the zero address and is sent asvalue, WETH is wrapped for you. Stocks and USDG are 0 bps. - Contracts calling the router.
GET /swap/calldataquotes and encodes in one call with the output tomsg.sender.