Skip to main content

API reference

Both endpoints take a JSON body and the API key in a header. Base URL: https://trade.achswap.app/api/v1.

Tokens, decimals and amounts​

All amounts are whole-number strings in the token's smallest unit. For a 6-decimal token, "1500000" is 1.5 tokens. Send strings, not JSON numbers: numbers are only accepted up to 2^53, and token amounts often exceed that.

TokenAddressDecimalsAs input
USDC, native0x0000000000000000000000000000000000000000 (0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE means the same)18Sent as the transaction's value. No approval.
USDC, ERC-200x36000000000000000000000000000000000000006Approve the executor first.
EURC0xbEf5f6d51CB62b58e6A8f77868681825C6fe21c16Approve the executor first.
cirBTC0x171A4217b86A807A64eB94757Db6849fb4bDbAA08Approve the executor first.
Any other tokenits addressits ownApprove the executor first.

Native USDC and 0x3600… are one balance at two scales. 1 USDC is 1000000000000000000 as native and 1000000 as the ERC-20. Quoting the same value either way gives the same output.

  • USDC to USDC is refused with 400: there's nothing to swap.
  • Native input dust. A native amount that isn't a whole 6-decimal unit has the leftover refunded to the sender.
  • Gas on Arc is paid in USDC from the same balance. When a user spends their whole USDC balance, leave room for gas.
  • Native output is paid as native USDC. The recipient, and your fee recipient when there is a fee, must be able to receive it. A contract without a payable receive fails simulation with NativeTransferFailed.
  • Addresses may be all-lowercase or correctly checksummed. Mixed case with a wrong checksum is refused, to catch typos.
  • Unsupported tokens. Tokens with transfer taxes, rebasing balances or transfer restrictions can't be routed and get 422 NO_ROUTE.

Fees​

FeeRatePaid inTo
AchSwap fee0.25% today, reported in every responsethe output tokenAchSwap
Your feefeeBps, 0 to 100 (up to 1%)the output token, in the same transactionyour feeRecipient

Both fees are taken from the route's actual output:

protocolAmount = floor(grossAmountOut × protocolBps / 10000)
partnerAmount = floor(grossAmountOut × feeBps / 10000)
amountOut = grossAmountOut − protocolAmount − partnerAmount

amountOut and minAmountOut are already net of both fees, and the minimum is enforced on what the recipient actually receives. The 1% cap on your fee is enforced by the contract.

If AchSwap's fee changes between your quote and the user's transaction, the transaction reverts with StaleFeeConfig rather than charging a fee the quote didn't show.

POST /quote​

Prices a trade. Nothing is built or simulated for a particular sender.

Request​

FieldTypeRequiredMeaning
tokenInaddressyesToken you pay. See the table above.
tokenOutaddressyesToken you receive.
amountInstringyesInput, in tokenIn's smallest unit.
feeBpsintegernoYour fee, 0 to 100. Default 0.
feeRecipientaddresswhen feeBps > 0Receives your fee. Can't be the zero address or the executor.
slippageBpsintegerno0 to 2000. Default 50 (0.5%). Sets minAmountOut.
chainIdintegernoMust be 5042 if sent.

Response​

1000 USDC to EURC with a 30 bps fee and 1% slippage:

{
"chainId": 5042,
"tokenIn": "0x3600000000000000000000000000000000000000",
"tokenOut": "0xbEf5f6d51CB62b58e6A8f77868681825C6fe21c1",
"amountIn": "1000000000",
"amountOut": "884689432",
"minAmountOut": "875842537",
"slippageBps": 100,
"grossAmountOut": "889582133",
"fees": {
"token": "0xbEf5f6d51CB62b58e6A8f77868681825C6fe21c1",
"protocolBps": 25,
"protocolAmount": "2223955",
"partnerBps": 30,
"partnerAmount": "2668746",
"partnerRecipient": "0x7Da3…4100"
},
"priceImpactBps": 5,
"gasEstimate": "256000",
"route": [
{
"shareBps": 10000,
"hops": [
{ "dex": "Aero CL", "pool": "0x…", "tokenIn": "0x3600…", "tokenOut": "0xbEf5…21c1" }
]
}
],
"requestId": "req_5f2c…"
}
FieldTypeMeaning
chainIdintegerAlways 5042.
tokenIn, tokenOutaddressAs requested, checksummed.
amountInstring (integer)As requested, in tokenIn's smallest unit.
amountOutstring (integer)What the recipient receives at the quoted price, after the AchSwap fee and your fee. In tokenOut's smallest unit.
minAmountOutstring (integer)The least the recipient will receive: amountOut less slippageBps. The transaction reverts below this.
slippageBpsintegerThe slippage used, as requested or the default 50.
grossAmountOutstring (integer)The route's output before fees.
fees.tokenaddressThe token fees are paid in: always tokenOut.
fees.protocolBps, fees.protocolAmountinteger, stringAchSwap's fee rate and amount.
fees.partnerBps, fees.partnerAmountinteger, stringYour fee rate and amount; 0 and "0" without a fee.
fees.partnerRecipientaddress or nullYour feeRecipient; null without a fee.
priceImpactBpsinteger or nullThe route's output against market reference prices, before fees, in basis points. Negative when the route beats the reference. null when a token has no reference price.
gasEstimatestring (integer) or nullEstimated gas units. Treat it as a guide; /swap returns the simulated figure.
routearrayOne entry per split: shareBps (integer, that split's share of the input, all adding up to 10000) and hops (array of { dex, pool, tokenIn, tokenOut }, in order). pool is the pool address, a Uniswap V4 pool's 32-byte pool ID, or null if unknown.
requestIdstringQuote it to support. Also in the X-Request-Id header.

To show amounts to a person, divide by 10 to the power of the token's decimals: "884689432" EURC (6 decimals) is 884.689432 EURC. Never do this conversion with floating-point numbers for amounts you send back to the API or to a contract; keep them as integers (BigInt).

Quotes are indicative: prices move. /swap prices the trade again.

POST /swap​

Everything /quote takes, plus who sends and who receives. Returns everything /quote returns, plus the transaction.

Request​

All /quote fields, plus:

FieldTypeRequiredMeaning
senderaddressyesThe address that sends the transaction. It pays tokenIn and receives any refund.
recipientaddressnoReceives tokenOut. Default: sender. Can't be the zero address or the executor.
deadlineintegernoUnix seconds. Must be in the future and at most one hour ahead. Default: 20 minutes from now.

Response​

The /quote fields, plus:

{
"sender": "0x…",
"recipient": "0x…",
"deadline": 1790000000,
"tx": {
"to": "0x1B844738455b8060D12839331b35893526E9d314",
"data": "0x…",
"value": "0",
"gas": "645291"
},
"approval": {
"token": "0x3600000000000000000000000000000000000000",
"spender": "0x1B844738455b8060D12839331b35893526E9d314",
"amount": "100000000"
},
"simulated": true
}
FieldTypeMeaning
sender, recipientaddressAs requested; recipient defaults to sender.
deadlineintegerUnix seconds after which the transaction reverts.
tx.toaddressAlways the AchRouteExecutor.
tx.datahex stringThe encoded execute(...) call, with minAmountOut, the deadline and your fee built in.
tx.valuestring (integer)Native USDC to send, in 18-decimal units. "0" unless the input is native USDC.
tx.gasstring (integer) or nullA gas limit: the simulated gas plus headroom. null when simulated is false.
approvalobject or nullFor ERC-20 input: { token, spender, amount }, the allowance the sender needs. null for native input.
simulatedbooleantrue when the exact transaction was simulated from the sender and succeeded.

Executing it​

  1. If approval isn't null, read the sender's allowance of approval.token for approval.spender. If it's below approval.amount, send approve(spender, amount) and wait for it to confirm.
  2. Send tx from sender, with to, data, value and gas exactly as returned.

The API simulates the transaction from the sender before returning it, filling in the balance and allowance the sender will have, so the check works before the approval is sent.

  • simulated: false means the check couldn't run at that moment. The transaction is still protected by minAmountOut and the deadline, but gas is null: estimate it yourself.
  • A transaction that would revert isn't returned. You get 409 SIMULATION_FAILED with a reason instead. See errors.

Call /swap right before your user signs, not when you first show a price. A transaction built minutes earlier is more likely to revert on price movement.

Contracts​

ContractAddress
AchRouteExecutor (spender and tx.to)0x1B844738455b8060D12839331b35893526E9d314
USDC (ERC-20)0x3600000000000000000000000000000000000000

The executor is source-verified. See swap execution for what execute does on chain, and the contract reference for everything else.