DevelopersIntermediate

ChainBridge API: Free DEX Aggregator API Guide

Get quotes, build swap transactions and fetch prices with the open ChainBridge REST API and TypeScript SDK, with cURL, JavaScript and Python examples.

ChainBridge ResearchUpdated 9 min read

On this page7 sections

Key takeaways

  • The ChainBridge API is open: no key, no sign-up, rate limited per IP, with CORS enabled on GET routes.
  • GET /api/sor/quotes compares 0x, 0x gasless, KyberSwap and ParaSwap and ranks quotes by net amount received after fees and gas.
  • GET /api/sor/swap returns the same quotes with ready-to-send transaction data, and needs no CSRF token.
  • POST endpoints require a token from GET /api/csrf-token in the x-csrf-token header.
  • Every amount is an integer string in the token’s smallest unit; responses wrap data in { success, data } or { success: false, error, code }.

Your first quote in two minutes

The base URL is https://www.chainbridge.trading/api. There is nothing to install and no key to request: send a GET request and read the JSON. The example asks every live source for a quote on 0.1 ETH to USDC on Base.

"color:#5c6370"># Quote 0.1 ETH -> USDC on Base(amounts are in the token's smallest unit)
curl "https://www.chainbridge.trading/api/sor/quotes?chainId=8453\
&sellToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\
&buyToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\
&sellAmount=100000000000000000"

Three conventions apply everywhere:

  • Amounts are integers in smallest units. 1 ETH is 1000000000000000000, 1 USDC is 1000000. Convert with parseUnits / formatUnits and the token's real decimals.
  • The native gas token (ETH, BNB, POL, AVAX) uses the address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
  • Responses share one envelope: { success: true, data } or { success: false, error, code } with a 4xx or 5xx status.

Quotes: parameters and response

GET /api/sor/quotes queries the sources enabled on the chain in parallel. Each quote is ranked by what you would actually receive: output minus the platform fee and minus gas converted to the buy token. The winner is bestQuote. Failed sources stay in quotes with an error field, so filter them out before you display a list.

Query parameters of /api/sor/quotes and /api/sor/swap
ParameterTypeDescription
chainIdnumber1, 10, 56, 137, 8453, 42161, 43114 or 59144. Defaults to 1.
sellTokenaddressToken you sell. Native gas token: 0xEeee...EEeE.
buyTokenaddressToken you buy. Must differ from sellToken.
sellAmountstringInteger in the sell token’s smallest unit (wei for ETH, 6 decimals for USDC).
takerAddressaddressOptional on /sor/quotes, required on /sor/swap. Needed for gasless quotes.
slippageBpsnumber0 to 5000. Defaults to 100 (1%). Sets buyAmountMin.
aggregatorsstringComma-separated filter: 0x, 0x-gasless, kyber, paraswap.
preferredAggregatorstringAsk one source first. Quotes are still ranked by net amount.
sellTokenDecimals, buyTokenDecimalsnumberOptional. Used for fee and gas pricing of tokens outside the built-in lists.
feeRecipient, feeBpsaddress, numberPartner fee. Only for allowlisted recipients, max 300 bps; an unknown recipient is rejected.
Response (trimmed)
{
  "success": true,
  "data": {
    "request": { "chainId": 8453, "sellToken": "0xEeee...", "buyToken": "0x8335...", "sellAmount": "100000000000000000", "slippageBps": 100 },
    "quotes": [
      { "aggregator": "paraswap", "buyAmount": "252599701", "buyAmountMin": "250073704", "gas": "159640", "gasCostUSD": "0.002469", "feeBps": 5, "netBuyAmount": "252597232", "allowanceTarget": "0x6a00...", "sources": [{ "name": "...", "proportion": 1 }] },
      { "aggregator": "0x", "buyAmount": "0", "buyAmountMin": "0", "error": "no Route matched with those values" }
    ],
    "bestQuote": { "aggregator": "paraswap", "buyAmount": "252599701", "...": "..." },
    "feeBps": 5,
    "stats": { "totalAggregators": 4, "successfulQuotes": 2, "timestamp": 1791878513443 }
  }
}
Quote fields
FieldMeaning
aggregatorSource of the quote: 0x, 0x-gasless, kyber or paraswap.
buyAmount / buyAmountMinExpected output and the minimum after slippage, in buy-token smallest units.
netBuyAmountbuyAmount minus estimated gas, in buy-token units. Used for ranking when gas can be priced.
gas, gasPrice, gasCostUSDGas estimate for the swap transaction.
feeBpsFee actually applied by this quote (0 for a source that carries no fee).
allowanceTargetContract to approve before an ERC-20 sell.
to, data, valueTransaction to send (on /sor/swap only).
sourcesLiquidity sources and their share of the route, when the aggregator reports them.
errorPresent when that source failed. Its amounts are "0": skip it.

The top-level feeBps is the platform fee due on this trade: 5 bps under $1,000, 10 bps from $1,000 to $9,999, 20 bps from $10,000, 10 bps when the USD value is unknown and 1 bp on stablecoin pairs. The USD value is computed on the server. Positive slippage is never captured: it stays with the taker. Read smart order routing for how the ranking works.

Build and send a swap

GET /api/sor/swap takes the same parameters, requires takerAddress and returns quotes that include to, data and value. ChainBridge never holds funds: you sign and broadcast the transaction yourself.

  1. Request an executable quote

    Pass the wallet that will send the transaction as takerAddress. Calldata is built for that address only.

  2. Approve the exact amount (ERC-20 only)

    If the allowance is short, approve sellAmount to allowanceTarget. Avoid unlimited approvals.

  3. Simulate, sign and send

    Run an eth_call with the same fields first: a revert costs nothing there. Then send to, data and value and wait for the receipt.

swap.mjs (viem)
import { createWalletClient, createPublicClient, http, erc20Abi } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'

const API = 'https://www.chainbridge.trading/api'
const NATIVE = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase()
const account = privateKeyToAccount(process.env.PRIVATE_KEY)
const wallet = createWalletClient({ account, chain: base, transport: http() })
const client = createPublicClient({ chain: base, transport: http() })

const sellToken = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' "color:#5c6370">// USDC
const sellAmount = 25_000_000n "color:#5c6370">// 25 USDC(6 decimals)

"color:#5c6370">// 1. Executable quotes: GET needs no CSRF token
const params = new URLSearchParams({
  chainId: '8453',
  sellToken,
  buyToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
  sellAmount: sellAmount.toString(),
  takerAddress: account.address,
  slippageBps: '50',
})
const res = await fetch(`${API}/sor/swap?${params}`)
const { success, data, error } = await res.json()
if (!success) throw new Error(error)
const quote = data.bestQuote
if (!quote?.to || !quote?.data) throw new Error('No executable route')

"color:#5c6370">// 2. ERC-20 only: approve the exact amount to the quote's allowanceTarget
if (sellToken.toLowerCase() !== NATIVE) {
  const allowance = await client.readContract({
    address: sellToken, abi: erc20Abi, functionName: 'allowance',
    args: [account.address, quote.allowanceTarget],
  })
  if (allowance < sellAmount) {
    const hash = await wallet.writeContract({
      address: sellToken, abi: erc20Abi, functionName: 'approve',
      args: [quote.allowanceTarget, sellAmount],
    })
    await client.waitForTransactionReceipt({ hash })
  }
}

// 3. Simulate, then send the swap
const tx = { account, to: quote.to, data: quote.data, value: BigInt(quote.value ?? '0') }
await client.call(tx) "color:#5c6370">// throws if the swap would revert
const hash = await wallet.sendTransaction(tx)
const receipt = await client.waitForTransactionReceipt({ hash })
console.log(receipt.status, hash)

Gasless quotes. When aggregator is 0x-gasless, data is a JSON string holding a trade (and sometimes an approval) EIP-712 payload. Sign each one and send them to POST /api/gasless/submit as { chainId, trade: { type, eip712, signature }, approval? }, then follow GET /api/gasless/status?tradeHash=&chainId=. Gasless is not available on Linea, and smart contract wallets cannot use it.

POST requests and the CSRF token

Four POST endpoints change state or build transactions: /api/sor/swap, /api/gasless/submit, /api/bridge/transaction and /api/simulation. They reject requests without an x-csrf-token header (403, CSRF_VALIDATION_FAILED). Fetch a token from GET /api/csrf-token; it is signed and valid for two hours.

TOKEN=$(curl -s https://www.chainbridge.trading/api/csrf-token | jq -r .token)

curl -X POST https://www.chainbridge.trading/api/sor/swap \
  -H "Content-Type: application/json" \
  -H "x-csrf-token: $TOKEN" \
  -d '{"chainId":8453,"sellToken":"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE","buyToken":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","sellAmount":"100000000000000000","takerAddress":"0xYourAddress","preferredAggregator":"kyber"}'

POST /api/sor/swap accepts the same fields as the GET version in a JSON body, plus strictAggregator: true to fail instead of falling back when the preferred source has no route.

Prices and live streams

GET /api/price?chainId=1&tokens=0x...,0x... returns USD prices for up to 100 tokens on one chain, keyed by lowercase address. A token without a price returns null.

GET /api/price/stream is a Server-Sent Events stream for up to 20 tokens. Set interval in milliseconds (2,000 to 60,000, default 5,000). Each message is a JSON object with a type: price_update carries chainId, prices and timestamp; error carries a message; timeout is sent when the stream ends after 30 minutes. A comment line is sent every 30 seconds as a heartbeat. Each IP can hold up to 5 streams open at once.

Browser (EventSource)
const params = new URLSearchParams({
  chainId: '1',
  tokens: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48,0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
  interval: '5000',
})
const stream = new EventSource(`https://www.chainbridge.trading/api/price/stream?${params}`)

stream.onmessage = (e) => {
  const msg = JSON.parse(e.data)
  if (msg.type === 'price_update') render(msg.prices) "color:#5c6370">// { "0xa0b8...": 0.9998, ... }
  if (msg.type === 'timeout') stream.close() "color:#5c6370">// 30-minute cap: reconnect if you need more
}

Cross-chain quotes

GET /api/crosschain/quote compares Relay and LI.FI for EVM-to-EVM transfers and THORChain for native BTC, LTC, DOGE and BCH, ranked by net amount received. Each quote lists the steps to execute in order: an exact-amount approve when needed, then a transaction.

USDC from Base to Arbitrum
curl "https://www.chainbridge.trading/api/crosschain/quote?fromChainId=8453&toChainId=42161\
&fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\
&toToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831\
&fromTokenDecimals=6&toTokenDecimals=6\
&amount=100000000&fromAddress=0xYourAddress"
Cross-chain quote parameters
ParameterDescription
fromChainId or fromChainEVM chain id, or BTC / LTC / DOGE / BCH (quote only).
toChainId or toChainDestination. Exactly one of the two.
fromToken, toTokenToken addresses on each EVM side.
fromTokenDecimals, toTokenDecimalsRequired, 0 to 36.
amountInteger in smallest units.
fromAddress, toAddressSender (required from an EVM chain) and recipient (defaults to the sender; a native address for BTC and others).
slippageBps, providers1 to 5000, default 100. Optional filter: relay,lifi,thorchain.

The response is { quotes, best, errors }. Each quote has toAmount, toAmountMin, gasUSD, providerFeeUSD, platformFeeBps, estimatedSeconds, executable, trackingId and warnings. After sending, poll GET /api/crosschain/status with provider, srcTxHash and fromChainId (plus requestId for Relay, and toChain or toChainId for THORChain) until phase is delivered, refunded or failed. THORChain deposits are accepted from Ethereum, Base, BNB Chain and Avalanche.

Rate limits, errors and other endpoints

Limits are counted per IP over a one-minute window. Responses include X-RateLimit-Limit and X-RateLimit-Remaining; a 429 response carries Retry-After in seconds and the code RATE_LIMIT_EXCEEDED.

Requests per minute per IP
EndpointsLimit / min
/sor/quotes, /crosschain/*, most GET routes60
/sor/swap, /gasless/*20
/bridge/*30
/price, /price/history100
Status routes (/gasless/status, /bridge/status...)120
/mcp120
All routes combined, per IP300

Validation errors return 400 with a readable error such as Invalid chain ID or takerAddress is required for swap quotes. Upstream failures return 5xx; retry after a short delay.

Other endpoints
EndpointUse
GET /sor/aggregators?chainId=Sources available on a chain and the list of supported chains.
GET /token-safety?chainId=&addresses=GoPlus checks for up to 20 tokens: honeypot, sell tax, owner permissions.
GET /price/history?chainId=&sellToken=&buyToken=&range=Price points for 24h, 7d or 30d (pair price, or sell token in USD).
POST /gasless/submit, GET /gasless/statusSubmit a signed 0x gasless order and follow it (also /gasless/status/stream).
GET /bridge/quote, POST /bridge/transaction, GET /bridge/statusLegacy LI.FI / Socket bridge routes, kept for existing clients.
GET /healthService status: { status, checks, version, uptime } without the success envelope.
POST /mcpModel Context Protocol server with read-only tools for AI agents.

Building an AI agent? The MCP endpoint (POST /api/mcp) exposes quotes, prices, token lists and unsigned swap transactions as read-only tools. For ready-made scripts, read the DevTools quickstart.

Explore the full API reference

Every endpoint, parameter and response in one place, with a live playground.

Open developer docs