# Swap from your app

> Quote, approve, simulate, build, sign — the full non-custodial swap flow on Arc, with the edge cases that bite.

Source: https://getchord.xyz/docs/recipes/swap/

**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

```ts
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 decimals
```

For 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

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.

```ts
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 size
```

Show `outputAmount` (scaled by the output token's decimals), `priceImpactPct`, `feeBps`, and `otherAmountThreshold` as the minimum received.

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

```ts
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

```ts
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

```ts
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

```ts
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

- **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 `sendRawTransaction` and retry with the same nonce on a timeout.
- **Robinhood Chain.** Same flow with `chain: 4663`; native ETH is the zero address and is sent as `value`, WETH is wrapped for you. Stocks and USDG are 0 bps.
- **Contracts calling the router.** `GET /swap/calldata` quotes and encodes in one call with the output to `msg.sender`.
