Skip to main content
Under https://router.raze.bot/evm the router quotes swaps on four EVM chains and builds the transactions that execute them: the exact token approvals the swap needs, then the swap, simulated together at a recent block. It never signs and never sends. You sign with your own key and send the transactions, approvals first, through your own RPC. The EVM routes speak the Solana routes’ API, which follows Jupiter’s: GET or POST /evm/quote with inputMint, outputMint and amount, then the quote back unchanged as quoteResponse to POST /evm/swap with userPublicKey. What differs is what EVM needs: see What differs from the Solana routes.

Chains and venues

  • Exact-in only. You say how much you spend; the quote says how much arrives (outAmount) and the least that will (otherAmountThreshold).
  • One venue per route, direct or through up to two intermediate tokens: the chain’s hubs (WETH, WBNB, USDC, USDT, …, listed by GET /evm/chains) and tokens recently seen paired with yours. No split routes, no exact-out, no platform fee.
  • Verified venues only. A venue is quoted once the router has checked its contracts against the chain (GET /evm/venues). Its dex label is the label of quotes and what dexes / excludeDexes take; its id (base-uniswap-v3) works in the filters too.

Routes

The key is the one of the market-data API and the Solana routes, sent the same ways (x-api-key, Authorization: Bearer or ?apiKey=; see Authentication). Every request the key lets in counts against your account’s limit a minute and spends credits, like any other router request (Rate limits and credits). A missing or unknown key is 401, a request over the limit 429, both with the API’s body ({"error": {"code": "unauthorized", "message": "…"}}). Requests are read like the Solana routes’: parameters in the query of GET /evm/quote or in a JSON body (read as JSON whatever the Content-Type, at most 256 KiB), numbers as JSON numbers or decimal strings, unknown fields ignored. Every request is cut at 10 seconds.

Quote, swap, sign, send

1

Quote

GET or POST /evm/quote with chain, inputMint, outputMint, amount and slippageBps. It answers outAmount, the floor otherAmountThreshold, the routePlan, a quoteId and expiresAt.
2

Build

POST /evm/swap with userPublicKey and the quote unchanged as quoteResponse, before expiresAt (15 seconds after the quote; 30 on eth). It answers setupTransactions, swapTransaction, simulation and costs.
3

Check the simulation

Send only when simulation.status is passed. The other statuses say what to do instead: see Simulation.
4

Send the approvals, then the swap

Sign each transaction with the wallet’s key and send them in the order given: every entry of setupTransactions, then swapTransaction. Wait for each approval’s receipt before the next transaction, or give them consecutive nonces. The swap must be mined before deadline.
5

Wait for the swap's receipt

A successful swap delivered at least otherAmountThreshold to the recipient. A reverted one moved nothing but gas: quote again.
The quote (a real answer of the public router):
Its build for 0x…dEaD (a burn address that happens to hold ETH on Base, used only so the simulation could pass; calldata trimmed). The input is native, so there is nothing to approve:
The swap is the venue router’s multicall(deadline, …) with exactInput and amountOutMinimum 2455312, then refundETH(); value is the ETH spent. Selling 2 USDC for ETH from the same wallet needs an approval first (real answer, trimmed):
The approval is approve(router, 2000000): exactly inAmount. The swap is exactInput with amountOutMinimum 798189491199717, then unwrapWETH9(798189491199717, recipient) to pay out ETH.

The quote

Answers write addresses in lower case, the native coin as 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee. The router tries up to 48 routes (candidates says how many it priced) and prices each on the venue’s own contracts, the V2 router’s getAmountsOut or the V3 QuoterV2, all at one block: contextSlot is its number, blockHash its hash. The most output wins, then fewer hops, then less gas. routePlan has one step per hop, percent 100 and bps 10000 (no splits): the pool (ammKey), the venue (label), the hop’s amounts and, on V3, feeTier in hundredths of a basis point (100 is 0.01%). Inside a route the wrapped token stands for the native coin. router is the contract the swap will go to, gasEstimate is indicative (the build’s simulation gives the real figure) and priceImpactPct is always "0". Minimum output. otherAmountThreshold is outAmount × (10000 − slippageBps) / 10000, rounded down. The build writes it into the swap’s calldata, so the venue’s router reverts the swap rather than deliver less. A floor of zero is refused with 422 declined amount_too_small; an amount so small that no route gives anything is 404 declined no_route. Lifetime. A quote can be built until expiresAt: 15 seconds after quoting, 30 on eth (quoteTtlSecs in GET /evm/chains), as many times as you like until then. Quotes live in the router’s memory: quote again rather than store them.

The swap

POST /evm/swap (or /evm/swap-instructions, the same handler) takes: Without quoteResponse or quoteId, the quote parameters at the top level (chain, inputMint, outputMint, amount, …) are quoted and built in the same call. The router builds only the quotes it made and still holds: the route, the amounts, the spender and the calldata come from the stored quote, never from your body. A quoteResponse whose chain, inputMint, outputMint, inAmount, outAmount, otherAmountThreshold or slippageBps differs from the stored quote is 400 quote_mismatch (reason names the field), not a different build: to change the slippage, send the top-level slippageBps. A quote past expiresAt, or one the router never made, is 409 route_expired: quote again. At a fresh block the router prices the quoted route again and reads the wallet’s balance and allowance. If the route now gives less than otherAmountThreshold, the answer is 409 declined quote_stale, with what it gives now (outAmount, null when the route no longer executes). The floor is never lowered: quote again. Each transaction is {chainId, from, to, data, value, gas, maxFeePerGas, maxPriorityFeePerGas}: value in wei as a decimal string, gas the gas limit, present only when the simulation passed, and the two fees present when the router could read them. Add the nonce, sign with from’s key, send.

Approvals

  • Exact. approve(spender, inAmount) on the input token, to the venue’s router (the quote’s router). Never an unlimited allowance.
  • Reset first. When the wallet already has a smaller, non-zero allowance for that router, the build adds approve(spender, 0) before it: tokens like USDT refuse to change one non-zero allowance into another.
  • None needed. setupTransactions is empty with a native input, for a wrap or an unwrap, and when the allowance already covers inAmount.
  • Not atomic with the swap. They are separate transactions. Send them first, in order, and wait for each receipt, or give approvals and swap consecutive nonces. If the swap is never sent or fails, the allowance stays, at most inAmount for that router; send approve(spender, 0) yourself to take it back.
If the approvals take long to confirm, check deadline before you send the swap. When it is close or past, quote and build again: the allowance is in place by then, so the new build has no approvals.

Native coin in and out

  • Spelling. 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE (any case) or native is the chain’s coin, ETH or BNB, distinct from its wrapped token (WETH, WBNB). Answers write it in lower case. Inside a routePlan the pools trade the wrapped token, so the steps name it instead.
  • Native in. swapTransaction.value is inAmount and there is nothing to approve. A V3 swap with native input ends with refundETH(): anything a pool does not take goes back to the wallet in the same transaction instead of staying in the router.
  • Native out. The swap pays the native coin to the recipient, with the floor checked on the unwrap itself (unwrapWETH9(otherAmountThreshold, recipient) on V3).
  • Wrap and unwrap. From the native coin to its wrapped token is a wrap, the other way an unwrap: one call to the wrapped-token contract, deposit() with value = inAmount, or withdraw(inAmount). The quote has one routePlan step labelled Wrap or Unwrap whose ammKey is the wrapped token; it is 1:1 (otherAmountThreshold = inAmount, candidates 0, slippageBps has no effect, venue filters do not apply) and needs no approval. Both credit the caller, so destinationTokenAccount must be userPublicKey (422 declined recipient_unsupported otherwise).

Gas and fees

  • gas on each transaction is the gas the simulation used × the chain’s headroom: 120%, 130% on robinhood (gasHeadroomPct). Use it as the gas limit.
  • maxFeePerGas is twice the next block’s base fee plus the tip; maxPriorityFeePerGas is the median tip of recent blocks, never below the chain’s minPriorityFeeWei (0.05 gwei on eth and bsc, 0.001 gwei on base, none on robinhood). costs.gasPrice is a price for a legacy (type 0) transaction. When the router could not read the fees they are absent from the transactions and null in costs: use your own estimate. Any fee strategy works; the swap’s floor and deadline do not depend on it.
  • The wallet pays gas in the native coin on top of inAmount. The simulation does not check that it can, nor the nonce.

Simulation

The build runs the approvals and the swap in order at its block (eth_simulateV1), between reads of the wallet’s input balance and the recipient’s output balance. It judges what moved, not what the router contract says it returned. A wallet without the input gets the transactions anyway, unsimulated and without gas:
A pass is true at the build’s block. The chain moves before your transactions land: the floor and the deadline in the swap are what protect you on chain.
Fee-on-transfer, rebasing and honeypot tokens are not supported: their builds fail in simulation (output_below_minimum, partial_fill, swap_reverted). Quotes price the gross output and cannot tell.

Sign and send with viem

The build’s transactions, signed by a local key and sent through your RPC, approvals first, each mined before the next:
On another chain use the viem chain whose id is the transactions’ chainId, and an RPC of that chain. To send without waiting between transactions, take the wallet’s pending nonce and give the approvals and the swap consecutive nonces, in the same order.

Errors

Errors have the Solana routes’ shape, {"error", "reason"}, without quoteId: error is a closed code, reason a stable label after declined (or rpc) and a sentence otherwise. They never carry your input, a URL, a key or a provider’s message. not_ready carries a list, reasons, instead of reason. The key gate’s 401 and 429 use the API’s body, {"error": {"code", "message"}}.
no_route is about the market and rpc about the router’s node: a missing pool is never reported as an RPC failure, nor the reverse.

Readiness

GET /evm/ready answers 200 when at least one chain can trade (an RPC, a verified venue and a fresh block head) and 503 otherwise, with the Solana router’s body: ready, every reasons it is not, and warnings for a chain that cannot trade while another can. chains lists every chain with its own ready, the dex labels of its verified venues and its newest block (head, from the router’s stream with ageMs, or read over HTTP). Chains are independent: one that is not ready does not stop quotes and builds on the others. GET /evm/health is plain ok while the process serves HTTP.

What differs from the Solana routes