HTTP API
/v1/quote prices a trade, and /v1/swap prices it and returns calldata you can send. Both are plain GET requests with query parameters and JSON responses, and both are callable without credentials.Base URL and conventions
https://exen.wraxyn.io/v1/quote?chainId=8453&mode=sell&…
- HTTP
GET, query parameters, JSON response bodies.
- Always decimal strings of base units (wei).
"1500000"is 1.5 USDC. Never floats — and never assume 6 decimals for a stablecoin, because BSC's are 18.
- 0x-prefixed 20-byte hex, case-insensitive.
- ETH / BNB / AVAX / OKB / POL is the sentinel
0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeon either side.
- camelCase everywhere, on every endpoint.
chainIdis a query parameter on quote and swap, and a{chain}path segment onsources— which also accepts a name (ethereum, base, arbitrum, bsc, avalanche, xlayer, polygon, robinhood), a common alias (eth,bnb,avax) or the numeric id.
Nothing is reserved and there is no quote-id handshake — /v1/swap re-solves at its own block. If you intend to execute, call /v1/swap directly rather than pairing a /v1/quote with a later swap and expecting the same numbers.
Access
One base URL for everyone: https://exen.wraxyn.io. What differs between callers is how much they may ask for, not what they can reach — every route works without credentials.
- API key
- Not required.
- Rate
- 3 requests/second, metered per IP. Enough to evaluate the API and build against it — this is what the examples on this page call.
- Fee
- 10 bps (0.10%) on the buy token, stated in the
feeblock of every response.
- API key
x-api-keyheader- Rate
- Whatever your key is provisioned for. Your own budget rather than a shared per-IP one.
- Fee
- Whatever we agreed with you — for most integration keys, none.
Calling the API directly without a key means the anonymous rate limit and a default10 bps (0.10%) fee; if you need higher limits, or want to negotiate a fee split, reach out. The app’s own tiers, and the note that all of these are provisional, are at /docs#fees.
Email admin@wraxyn.io with what you are building, the chains you need and a rough request rate. Keys are free — we issue them so we know who to contact when something changes, and so your traffic is not competing with a shared IP budget.
There is no reason to wait for one before building: the base URL does not change and the only difference is one extra header.
What an API key changes
The key goes in the x-api-key header. It is always optional — every route answers without one. Sending one raises your rate budget, removes the anonymous fee, and unlocks the configuration held against it:
- A rate budget of your own, instead of sharing the anonymous per-IP one with everyone else on your network.
- Attribution. Your partner id is hashed into the on-chain
tagof every settlement, so fills you routed are identifiable on-chain and reconcilable to your responses. - A negotiated fee policy, which replaces the10 bps (0.10%) anonymous fee and overrides any fee in the request. For most integration keys that policy is no fee at all.
- Access to gated venues. A few venues price only for allowlisted parties (tokenised real-world assets, for instance) and are invisible without an approved key.
Keep it on a server. Never put it in a browser bundle or a mobile app — anything shipped to a device is readable, and a key in a front end is a key you have published. Tell us if one leaks and we will rotate it.
Rate limits
Every request is admitted against a token bucket: a sustained rate plus a burst reservoir that refills continuously at that rate. Bursting is fine; sustaining above the rate is not — the reservoir is a one-time allowance, not extra throughput.
- Metered per IP, default 3 requests per second. Fine for a wallet, a dashboard or a spreadsheet; not enough for a quoting loop. An unrecognised key is treated exactly like no key, so a wrong or stale one silently lands here.
- A budget agreed with you and attached to the key. Tell us the shape of your load — steady polling, bursty, or latency-critical — and it is set accordingly.
- Over-budget requests get
429with aRetry-Afterheader (seconds, never zero). Honour it rather than retrying immediately — a tight retry loop spends the next second's budget too. X-RateLimit-LimitandX-RateLimit-Remainingcome back on every response, not just refusals. Pace against those instead of discovering the ceiling by hitting it.- Cache nothing that prices. A quote is a point in time; a cached one is not a cheaper quote, it is a wrong one.
- If you need a lot of quotes,
timeLimitMsis the lever that matters more than the rate — see latency.
GET /v1/quote
Solves the best route for a pair and size at the current block and returns exact integer amounts, the per-hop route breakdown, gas and USD values.
curl -s "https://exen.wraxyn.io/v1/quote\ ?chainId=8453&mode=sell\ &sellToken=0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\ &buyToken=0x833589fcd6edb6e08f4c7c32d4f71b54bda02913\ &sellAmount=1000000000000000000\ &slippagePct=0.5"
No key, no header, no account — that call works as written. See access for when you would want one.
Parameters
chainId- Required
- yes
- Meaning
- Numeric EIP-155 id. A chain this instance does not host answers 404.
mode- Required
- yes
- Meaning
sell— exact input, requiressellAmount.buy— exact output, requiresbuyAmount.
sellToken- Required
- yes
- Meaning
- Address, or the native sentinel. Must differ from
buyToken.
buyToken- Required
- yes
- Meaning
- Address, or the native sentinel.
sellAmount- Required
- on
sell - Meaning
- Input amount, decimal wei string.
buyAmount- Required
- on
buy - Meaning
- Target output, decimal wei string.
slippagePct- Required
- no
- Meaning
- Percent (
0.5= 0.50%), default0.5, clamped to [0.00001, 50]. SetsminBuyAmounton a sell. Ignored on a buy — the response echoesslippagePct: 0.
timeLimitMs- Required
- no
- Meaning
- Solve budget in milliseconds, 50–5000. Unset applies the maximum. Below 300 also excludes RFQ venues — see latency.
maxHops- Required
- no
- Meaning
- Hop-depth override, 1–7. The default scales with trade size in USD (2 below $25k, then 3, 4 and 6 at $25k / $250k / $10M).
gasPriceGwei- Required
- no
- Meaning
- Overrides the gas price used to value routes and to compute
gasUsd. Affects route selection, never execution.
taker- Required
- no here
- Meaning
- The wallet holding the input. Optional on quote (echoed back), required on swap.
recipient- Required
- no
- Meaning
- Who receives the output. Defaults to
taker.
excludeRfq- Required
- no
- Meaning
truedrops RFQ market-maker venues and attestation-gated hooks at any budget. Use it when you want a firm, on-chain-only quote. Does not drop real-world-asset venues.
excludeRwa- Required
- no
- Meaning
truedrops real-world-asset venues, independently ofexcludeRfq.
excludeVenuesincludeVenues- Required
- no
- Meaning
- Comma-separated venue labels — the exact
sourcestrings the response reports, case-insensitive; list them withGET /v1/{chain}/sources. Exclude routes through everything else; include routes through only these. Mutually exclusive — sending both is a 400. Matching is hierarchical: a family label also matches itsfamily:varianthook variants. The syntheticWRAPhop is not a venue and is never excludable, so native swaps keep working under any include list.
feeBpsfeeRecipient- Required
- no
- Meaning
- Your integrator fee — see fees. Both or neither;
feeBpsalone is a 400.
Response
quoteId- Unique per response, and the high 16 bytes of the on-chain
tagin the router'sSwapevent — so a settlement is attributable to the exact response that produced it.
sellAmount- On a sell, your input echoed. On a buy, the solved maximum input; unused input is swept back to the taker.
buyAmount- Output from exact integer re-simulation — the same math the settlement enforces. Net of any fee, gross of gas. On a buy it is exactly the amount you asked for.
minBuyAmount- The floor enforced on-chain. On a sell,
buyAmountless your slippage tolerance. On a buy, equal tobuyAmount.
priceImpact- A ratio, not a percentage:
amountOutUsd / amountInUsd, where 1.0 is lossless and 0.98means 2% lost. Null when either side is unpriced. Printing it raw understates a 1% impact as “0.99”.
gas- Our estimate of how much gas this route will burn, in gas units — the plan's hops priced by venue class. Use it to compare routes and to show a cost; it is an estimate, not a
gasLimitto send (see the swap response). L2 calldata / data-availability fees are not included — add your own on Base, Arbitrum, X Layer and Robinhood before comparing aggregators.
gasPriceWeigasUsd- The price gas was valued at and the resulting USD figure. Priority tips are excluded — the tip is the submitter's choice.
gasPriceWeican exceed 2⁵³; parse it as a big integer.
gasModeblindmeans no gas price was available and routes were not penalised for gas;guardrail/prunemean gas-aware solving. Worth reading before a cross-aggregator comparison.
blockNumber- The state block this solve was pinned to.
route- Every hop of every split:
source(the venue label),poolAddress,tokenIn/tokenOut, exactamountIn/amountOut, andbps— that hop's share of the token balance at that point, not of the trade. Group per-venue analytics bysource.WRAPhops are the synthetic native⇄wrapped edge and carry a zero pool address.
allowanceTarget- The contract to approve before executing. Null for native sells, which fund via
value.
fee- Fee breakdown; omitted when no fee applies.
buyAmountis already net.
warnings- Advisories — see warnings. Omitted when empty.
GET /v1/swap
Identical pricing and identical parameters, plus a required taker. It then firms up any market-maker legs with their makers and encodes the route into a transaction. The response is the quote body flattened at the top level plus the execution fields.
taker- Required.The wallet that holds and sends the input; the transaction's
from.
userAddress- The real end user, when your
takeris a shared settlement contract rather than the swapper. It is passed to the makers that meter per user, so one busy shared taker does not trip a limit for your whole flow. It never changes settlement, and defaults totaker.
deadline- Unix seconds; the transaction reverts after it. Default now + 20 minutes. When the route carries maker legs the effective deadline is capped to the earliest maker expiry.
fundingallowanceHolder(default) orpermit2— how the input is pulled. See below.
Three funding shapes
transaction.tois the router andtransaction.valueis your input. No approval of any kind. Sign and send as returned.
- Approve
allowanceTargetonce per token from the taker, then sendtransactionas returned (valueis 0). The default.
- One-time approval to canonical Permit2, then sign the returned
permit2.eip712payload and write the 65 signature bytes into the 0x-decodedtransaction.dataatpermit2.signatureOffsetbefore sending. The permit nonce derives fromquoteIdand is single-use.
Response
Every field from the quote response is present at the top level — not nested under a quote key — with these added:
transaction- What you send.
to,data,value(decimal wei — your input for a native sell, otherwise"0"), andfromechoing yourtaker. Wheretopoints depends on the funding shape above.
deadline- The effective unix-seconds horizon encoded in the calldata — your requested
deadline, capped at the earliest maker-order expiry when the route carries firm market-maker legs. If you delay submission at all (batching, auction flow), read this rather than assuming the horizon you asked for.
rfqExpiry- The maker expiry that did the capping, when one did. Present only when the route kept a firm market-maker leg — its presence tells you a maker leg, not your own choice, is what bounds this transaction's life.
driftExposedShareBps- How much of the gross output can actually move between quote and settlement, in bps of gross — 0 fully pinned, 10000 fully exposed. Output funded entirely through fixed-rate hops (firm maker legs, wrap/convert, ERC-4626, the native wrap) cannot drift. Size
slippagePctfrom it: a fully pinned route survives an arbitrarily tight floor, an exposed one needs a budget on the exposed share. Sells with firm maker legs only.
permit2- Present only with
funding=permit2:eip712(typed data ready foreth_signTypedData_v4),hash(the digest — check it equals what you compute),signatureOffset(byte offset into the 0x-decodedtransaction.data) andsignatureLength(always 65).
warnings- Degradations hit while building this one — see warnings. Omitted when empty, and never a reason not to send the transaction.
Gas: an estimate, not a limit
gas is our estimate of how much gas the route will take, and it is returned on both endpoints — price it against gasPriceWei yourself, or read the gasUsd we already computed. That is what you show a user and what you subtract when comparing routes net of cost.
What is not in the response is a gasLimit on transaction, and that is deliberate: our number is a model of the plan, and the limit you sign has to come from the chain as it is at that second. Run eth_estimateGas against transaction before sending. It is also your final validity check — a pool that moved or a maker quote that expired surfaces there as a revert, instead of as a failed transaction you paid for.
The two should land close on an AMM route. A large gap usually means the route contains something the model prices generically, or the chain charges for calldata (Base, Arbitrum, X Layer, Robinhood) — where eth_estimateGas covers execution only and the data-availability component is still yours to add.
Execution semantics
- The only on-chain guarantee is
minBuyAmount. Settle below it and the transaction reverts. - On a
buy, the recipient receives exactly the requested amount or the transaction reverts; unused input is swept back to the taker. Slippage does not apply — sendingslippagePctwill not soften that floor. - Output above the quote (“positive slippage”) is captured by default, so the recipient receives the quoted amount. Where it goes is a per-integrator setting held against your key — it can be left with the taker or shared instead. Ask if you want a different split.
- The router's
Swapevent carriestag=quoteId‖ hash of your partner id, so on-chain fills reconcile to API responses.
Latency — what to set timeLimitMs to
The budget is stamped when the request arrives, so queueing counts against it. Enforcement is best-effort: direct routes are always evaluated, and deeper search stops at the deadline.
- On-chain venues only — market-maker venues are excluded because their firm-up cannot fit. Deterministic latency; the setting for a solver loop (e.g.
299).
- Market makers participate, and
/v1/swapspends the remaining budget firming their quotes (capped at 2 s). Better prices, variable latency.
- The maximum (5000 ms) is applied — no request runs unbounded, but you should still set your own.
Fees
- Fees are taken on the buy token, and
buyAmount/minBuyAmountare returned net of them — you can show the returned number to your user as-is. - Set your own with
feeBps+feeRecipient(capped at 1500 bps = 15%). A request-supplied fee splits 85% to your recipient, 15% to the protocol. - A fee policy negotiated against your API key overrides anything in the request.
- On a
buy, the routing target is grossed up so your user still receives exactly what they asked for after the fee.
Warnings
warnings[] is omitted when empty. Every code leaves the transaction executable — a warning means the response degraded on the way, never that the calldata is unusable. A request that cannot produce an executable route returns an error instead.
RFQ_FIRMUP_FAILED- A market-maker leg won pricing but could not be firmed up; the returned route is an on-chain-only re-solve. Also the usual reason for a slow response with no maker leg visible. Accept it, or set
timeLimitMsunder 300 to skip makers entirely.
ANGSTROM_ATTESTATION_UNAVAILABLE- An attestation-gated hook leg could not fetch its per-block attestation; same contract as above — an on-chain-only re-solve.
STALE_STATE_RESOLVED- Blocks landed while the transaction was being built and left the original plan below its own floor; it was re-solved against fresher state and certified. The returned numbers are the fresher plan's. Recurring on every call means the pair is moving faster than your firm-up budget.
HIGH_PRICE_IMPACT- The route gives up more than 15% of the trade's USD value (
amountOutUsdbelowamountInUsd × 0.85). At that size the output is bounded by the liquidity reachable for this pair, not by a market price — the quote is honest and will settle, but raising the input stops raising the output. Only emitted when both sides are priced, sopriceImpact: nullmeans unknown, not healthy. Re-quote smaller, or confirm with the user — do not auto-execute on it.
FEE_ON_TRANSFER- A token in the trade charges a transfer fee. The quote already models the measured tax plus a margin, and
detailcarries the modelled bps per side. Realised output often exceedsthe quote; a tax rate the token owner raises between quote and execution can still revert the fill at the floor — treat that as “re-quote”, not an outage.
Errors
The body is { "error": "…" }. There are no stable machine codes on errors — branch on the status, never on the message text. (Success-path degradations do have stable codes: the warnings above.)
- Bad parameters: a missing amount for the mode, identical tokens, a malformed wei string,
timeLimitMsout of range,feeBpswithoutfeeRecipient, a missingtakeron swap, or both venue filters at once.
- An unhosted chain, or no route for this pair and size — routine, usually the size, not an outage. Also returned with a token-specific message when a token fails an on-chain transfer-integrity check: a transfer-gated token (honeypot or blacklist), a tax above the routable cap, any fee-on-transfer token on a
buy, or a universal-scope taxed token on the bought side. Those verdicts are re-measured on-chain and the owner can change the rate — key on the message and never cache the verdict.
- We could not serve this one — a chain still coming up after a restart, or an internal failure building the calldata. Retryable.
- We declined to price because our view of that chain is evidently stale. Retryable with backoff.
A 503 on quote or swap does not mean “no liquidity” — the pair and size are probably fine and the same request is expected to succeed once the indexer catches up. Do not fall back to a cached quote for that pair: a price computed from stale state is wrong rather than merely old, and settling it loses the difference. That is the whole reason this status exists rather than serving you a number.
GET /v1/{chain}/sources
The venue labels routable on a chain — the exact strings route[].source reports and excludeVenues / includeVenues accept. It is the only place those strings are enumerated, so read them from here rather than hard-coding a list that grows without you.
curl -s "https://exen.wraxyn.io/v1/base/sources"
{ "chainId": 8453, "chain": "base",
"sources": ["aerodrome-v1", "uniswap-v3", "uniswap-v4:doppler", …] }{chain} is a path segment here, not the chainId query parameter quote and swap use: a name (ethereum, base, arbitrum, bsc, avalanche, xlayer, polygon, robinhood), a common alias (eth, bnb, avax) or the numeric id.
GET /healthz
200 while the API is serving. 503 with status: degradedwhen a chain's view of the network has fallen behind — the same condition that makes quote and swap answer 503 for that chain. Use it for a status page or an uptime check.
curl -s "https://exen.wraxyn.io/healthz"
{ "status": "ok",
"chains": [ { "chain": "base", "healthy": true }, … ] }Treat status, and each chain's chain and healthy, as the contract. It is a liveness signal, not a coverage report — do not build on anything else you find in the body.
Machine-readable: OpenAPI and one-file Markdown
Everything on this page also exists as files you can hand to a tool, and as a rendered spec page if you would rather browse it than download it. All are generated from the same specification as the API itself, so they cannot describe a parameter we do not serve.
- OpenAPI 3.1. Load it into Swagger UI, Redoc, Postman, Insomnia or Bruno, or run a client generator against it —
openapi-generator,oapi-codegen,openapi-typescript.
- The same document as JSON, for tooling that will not parse YAML.
- The whole contract as one flat Markdown file — every endpoint, parameter, response field, error and warning, no navigation. Give this one to an LLM or an agent: it is a single fetch and needs no HTML parsing or link-following.
# generate a typed TypeScript client npx openapi-typescript https://app.wraxyn.io/openapi.yaml -o exen-api.d.ts # hand the whole API to an agent in one fetch curl -s https://app.wraxyn.io/api.md
The spec is. It is derived from the wire contract the engine actually serves; this page is written by hand from the same source. If the two ever disagree, that is a bug on this page — tell us and read the spec.
The spec covers the published surface, which is what these three endpoints promise and nothing more. The engine answers other routes; they describe how it is built rather than what we commit to, they change when the internals change, and they are not on the public host.
Worked example — sell 1 ETH for USDT
# 1. Build the transaction (native sell ⇒ no approval needed)
curl -s "https://exen.wraxyn.io/v1/swap\
?chainId=1&mode=sell\
&sellToken=0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
&buyToken=0xdac17f958d2ee523a2206206994597c13d831ec7\
&sellAmount=1000000000000000000\
&taker=$TAKER&slippagePct=0.3&timeLimitMs=299" \
| jq '{buyAmount, minBuyAmount, gasUsd,
to: .transaction.to, value: .transaction.value,
route: [.route[] | {source, bps, amountIn, amountOut}]}'
# 2. eth_estimateGas against .transaction — the final validity check
# 3. Sign and send it from $TAKER
# ERC-20 sell instead? One extra step first:
# approve(<allowanceTarget from the response>, <sellAmount>) from $TAKERtimeLimitMs=299 above is the latency-critical profile: on-chain venues only, deterministic. Drop it (or raise it past 300) to let market makers compete for the fill.
Note there is no key in that call — none is needed at the anonymous rate, so the quote it returns is net of the 10 bps (0.10%) anonymous fee. Add -H "x-api-key: $EXEN_KEY" once you have one and the same call prices on your own terms; the URL does not change.