Swap
Execute token swaps without the AI agent — same-chain EVM, Solana, and cross-chain. Get a quote first, then execute.
Quote
POST /wallet/swap-quote
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
fromChain | string | Yes | Source chain — one of base, mainnet (Ethereum), polygon, unichain, arbitrum, bnb, worldchain, robinhood, solana |
fromToken | string | Yes | Sell token — EVM address, or a base58 mint when fromChain is solana |
toChain | string | Yes | Destination chain (same list). May differ from fromChain for a cross-chain swap |
toToken | string | Yes | Buy token — EVM address, or a base58 mint when toChain is solana |
amount | string | Yes | Human-readable sell amount (e.g. "0.5") |
slippageBps | number | No | Max slippage tolerance in bps (10–2000). Defaults to 500 (5%). Sets the quote's minBuyAmount; see the note below for how execution applies it |
slippageBps always shapes the quote's minBuyAmount. At execution, only Relay-routed pairs carry your full tolerance through — cross-chain, Solana, and the relay-first chains, except tokenized-stock legs, which take the RFQ path instead. Everywhere else the execution re-quote is clamped to 2% (200 bps), deliberately: the gap between your looser quote tolerance and the tighter execution tolerance preserves headroom for price drift between quote and submit. A 2000 bps quote does not execute at 2000 bps there.
Executing a swap that involves tokenized stocks on robinhood requires a passed location check; without one /wallet/swap returns 403 with instructions to verify at the Bankr console. The check applies to each leg's chain, so a cross-chain buy into a stock is gated too. Quotes are not gated — /wallet/swap-quote will price a stock swap you aren't cleared to execute, so don't treat a successful quote as clearance.
For native ETH / POL / BNB, pass the native sentinel address 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee (the zero address works too) as fromToken / toToken — it's normalized automatically.
Solana legs use the wallet's Solana address. If the wallet has no Solana address provisioned, the endpoint returns 400.
Response (200 OK)
from.amount mirrors the human-readable input (identical to from.formattedAmount), while to.amount is the quoted buy amount in raw base units.
{
"from": {
"chain": "base",
"token": "0x4200000000000000000000000000000000000006",
"amount": "0.5",
"formattedAmount": "0.5",
"symbol": "WETH",
"decimals": 18,
"usdValue": "1622.50"
},
"to": {
"chain": "base",
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "1622500000",
"formattedAmount": "1622.50",
"symbol": "USDC",
"decimals": 6,
"usdValue": "1622.50"
},
"minBuyAmount": "1622.50",
"feeBps": 100,
"feeWaivedForEcosystemToken": false,
"slippageBps": 500,
"priceImpactBps": 12,
"swapImpactBps": 12,
"maxPriceImpactBps": 1500,
"sellTokenPriceUsd": 3245,
"buyTokenPriceUsd": 1,
"quoteId": "7c2f9f0e-8f1a-4d1a-9a2b-3c4d5e6f7a8b"
}
| Field | Type | Description |
|---|---|---|
minBuyAmount | string | Human-readable minimum acceptable buy amount — pass it back on execution |
feeBps | number | Bankr fee applied to this swap, in bps (0 = fee-free) |
feeWaivedForEcosystemToken | boolean | true when the fee is 0 because a leg is a Bankr ecosystem token |
slippageBps | number | Slippage tolerance baked into minBuyAmount |
priceImpactBps | number | null | Estimated route price impact for display (pool impact + token taxes, excluding the Bankr fee) |
swapImpactBps | number | null | Fee-exclusive pool impact — the number server-side execution gates on. null = unknown (the server fails open) |
networkCostsUsd | number | null | Fixed route costs in USD (gas + bridge/relayer). 0 = none; null = no breakdown available |
maxPriceImpactBps | number | null | The wallet's price-impact protection limit. Execution is rejected above it; null = protection disabled |
sellTokenPriceUsd | number | null | USD unit price of the sell leg |
buyTokenPriceUsd | number | null | USD unit price of the buy leg |
quoteId | string | Id of this served quote — echo it on execution to reuse the quote while it's fresh (see the note below) |
to is optionalOnly from, to and minBuyAmount are guaranteed. The rest depend on the venue that priced the quote — the same-chain EVM aggregator path omits networkCostsUsd entirely (hence its absence from the Base example above), and the pUSD unwrap path omits quoteId. Treat a missing field the same as null: unknown, don't hard-block on it.
quoteId only affects same-chain EVM executionQuote reuse is read on the same-chain EVM path only. Cross-chain and Solana executions route to the Relay executor before the stored-quote lookup, so a quoteId from one of those quotes is accepted and silently ignored. It's an optimization either way — a stale, unknown or param-mismatched id just falls back to a fresh quote.
Errors
| Status | Cause |
|---|---|
400 | Invalid body, unsupported chain, same-token swap, amount too small, an untradable pair, or no Solana address on the wallet |
401 | Missing or invalid authentication |
403 | Buy token is banned or flagged by the security scan |
502 | No quote available from the routing venue — Relay-routed pairs only (cross-chain, Solana, relay-first chains) |
500 | No quote available on the same-chain EVM aggregator path, or an unexpected quote failure |
Execute
POST /wallet/swap
Request Body
All quote fields, plus minBuyAmount:
| Field | Type | Required | Description |
|---|---|---|---|
fromChain | string | Yes | Source chain (see quote table above) |
fromToken | string | Yes | Sell token address or Solana mint |
toChain | string | Yes | Destination chain — may differ from fromChain |
toToken | string | Yes | Buy token address or Solana mint |
amount | string | Yes | Human-readable sell amount |
minBuyAmount | string | Yes | Minimum acceptable buy amount (from the quote response) |
slippageBps | number | No | Max slippage tolerance in bps (10–2000). Defaults to 500 |
quoteId | string | No | quoteId from a fresh quote — reuses it to skip re-pricing. Unknown or stale ids fall back silently |
idempotencyKey | string | No | UUID duplicate-submit guard. A repeat POST with the same key returns the original result, never a second broadcast |
Send an idempotencyKey on every execution. Without one, a retried request after a network timeout can broadcast a second swap. While the original is still in flight, a repeat returns 409.
Response (200 OK)
{
"success": true,
"hash": "0xabc123...",
"amountSold": 0.5,
"amountReceived": 1622.5,
"amountSoldRaw": "500000000000000000",
"amountReceivedRaw": "1622500000"
}
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the swap filled. false means it mined and reverted — see below |
hash | string | On-chain transaction hash |
amountSold | number | Human-readable sell amount |
amountReceived | number | Human-readable buy amount |
amountSoldRaw | string | Sell amount in base units (bigint as string) |
amountReceivedRaw | string | Buy amount in base units (bigint as string) |
200, not an errorIf the transaction mines but reverts, the endpoint returns 200 with success: false
and the real on-chain hash — no tokens were exchanged, but a transaction exists and you
are billed gas for it. Check success; do not treat any 2xx as a filled swap.
Use the hash to show the user their failed transaction on a block explorer.
Before 2026-08, a revert returned 400 on some venues and 200 success:false on others,
depending on which venue happened to serve the trade. It is now 200 on every venue.
Errors
| Status | Cause |
|---|---|
400 | Invalid body, unsupported chain, insufficient balance or gas, venue-side price impact too high, or swap validation failure |
401 | Missing or invalid authentication |
403 | Read-only API key, wallet paused, price impact above your wallet's own protection limit, failed location check, fee beneficiary selling its own fee token, or a Bankr Terminal spend limit would be exceeded |
409 | A swap with the same idempotencyKey is still processing, or a pending/replacement transaction is in the way |
429 | Rate limited — retry in a moment |
502 | No fresh quote at execution time, a Relay fill failed, or a LaunchLab fill was broadcast but couldn't be confirmed |
503 | The swap service is temporarily unavailable — retry shortly |
504 | Submitted, but confirmation is taking longer than expected |
500 | Signer error, swap execution error, or venue failure |
504 or a LaunchLab 502Both mean the transaction may already be on-chain — it was broadcast and only the confirmation is missing. Check the wallet's Activity for the hash before retrying; an automatic retry can execute a second swap. Everything else in the table is either pre-broadcast (safe to retry, ideally with the same idempotencyKey) or a terminal failure; 429 and 503 are always safe to retry.
400 is the venue refusing the trade before broadcast — the pool can't absorb it; retry smaller. (A trade that broadcast and then reverted is a 200 with success: false, not a 400.) 403 is your wallet's own price-impact protection (maxPriceImpactBps) rejecting the fresh execution quote; a smaller amount may help, or raise the limit in Security settings. Don't read a 403 here as an auth or location problem.
Access Control
- Authentication: API key (
X-API-Key) or Privy JWT - Wallet API (
walletApiEnabled): required for both endpoints - Read-only keys: allowed for quotes, rejected for execution
- Allowed recipients: not enforced — swap output always returns to the caller's wallet
- Spend limits: enforced on execution — the sell-side USD value is priced and checked against your Bankr Terminal per-transaction and daily limits, and a successful swap counts toward your rolling 24h spend total
- Price-impact protection: execution is rejected when the quote's fee-exclusive pool impact exceeds your wallet's
maxPriceImpactBps - Token security: buys into banned or scan-flagged tokens are refused at both quote and execution. Sells are essentially never gated, so you can exit a position you hold — the one exception is a fee beneficiary selling its own fee token, which must go through a Glidepath instead
Routing
Bankr picks the venue for you; there's nothing to configure on the request.
- Same-chain EVM — routed through the DEX aggregator, with a direct-pool venue preferred on thin-liquidity chains
- Cross-chain, and any Solana leg — routed through the bridge/swap aggregator
- Robinhood Chain tokenized stocks — quoted and filled through the DEX aggregator's RFQ makers, settling against USDG. Non-stock Robinhood pairs keep the thin-pool protections
- pUSD → USDC.e on Polygon — not a DEX swap. It's quoted and executed as the 1:1 on-chain Offramp unwrap: no fee, no slippage, no price impact. The reverse direction (USDC.e → pUSD) is not a special route and is quoted like any other pair
Solana LaunchLab fallback
Brand-new Solana launches with no aggregator route can still fill against their Raydium LaunchLab bonding curve, but the fallback is narrow. All of the following must hold:
- Both legs are on Solana — a cross-chain leg into Solana doesn't qualify
- The pair is SOL ↔ token, and the token has an active (un-migrated) LaunchLab curve
- The aggregator returned a genuine no-route, not some other failure
- The wallet has no price-impact limit set. The bonding-curve venue exposes no impact figure, so it can't be checked against
maxPriceImpactBps. Rather than fill an unguardable venue, Bankr fails closed and surfaces the clean no-route instead — so with price-impact protection enabled, these swaps are rejected by design. TheminBuyAmountfloor is still enforced inside the fill.
Constraints
- Same-token swaps are rejected —
fromTokenandtoTokenmust differ whenfromChainandtoChainmatch - Solana legs require a provisioned Solana address on the wallet
Migration from Legacy Endpoints
POST /leaderboard/swap-quote and POST /leaderboard/swap are deprecated aliases that forward to these endpoints. They now return Deprecation and Sunset headers. Migrate to /wallet/swap-quote and /wallet/swap.
Examples
curl
# 1. Get a quote
curl -X POST https://api.bankr.bot/wallet/swap-quote \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key_here" \
-d '{
"fromChain": "base",
"fromToken": "0x4200000000000000000000000000000000000006",
"toChain": "base",
"toToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "0.5"
}'
# 2. Execute the swap (use minBuyAmount from quote response)
curl -X POST https://api.bankr.bot/wallet/swap \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key_here" \
-d '{
"fromChain": "base",
"fromToken": "0x4200000000000000000000000000000000000006",
"toChain": "base",
"toToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "0.5",
"minBuyAmount": "1622.50"
}'