Swap
Execute token swaps without the AI agent — same-chain EVM, Solana, and cross-chain. Get a quote first, then execute. The field-by-field schemas are in the OpenAPI spec; this page covers the behavior around them.
Quote
POST /wallet/swap-quote
Send fromChain, fromToken, toChain, toToken, and amount (human-readable sell amount, e.g. "0.5"), plus an optional slippageBps (10–2000, default 500). Chains: base, mainnet (Ethereum), polygon, unichain, arbitrum, bnb, worldchain, robinhood, solana (arc passes validation, but no swap venue serves Arc yet, so an Arc leg gets no quote); toChain may differ from fromChain for a cross-chain swap. Tokens are EVM addresses, or base58 mints on solana.
slippageBps always shapes the quote's minBuyAmount. Execution re-quotes and keeps your full tolerance only for cross-chain and Solana swaps, Robinhood Chain pairs other than tokenized stocks, Base B20 stock legs, and a Base quote re-routed to the bridge/swap aggregator that you execute with its quoteId. Everything else — including Robinhood Chain stocks and a no-route fallback from the DEX aggregator — re-quotes at no more than 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 — Robinhood Chain stocks on robinhood, or B20 equities on base — 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. Pass minBuyAmount (human-readable) back on execution.
{
"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"
}
swapImpactBps is the fee-exclusive pool impact that execution gates on against your wallet's maxPriceImpactBps (null = unknown, and the server fails open); priceImpactBps is the display estimate. maxPriceImpactBps: null means the wallet's price-impact protection is off.
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 executionCross-chain and Solana executions ignore it. On same-chain EVM pairs, a quoteId up to 90 seconds old whose params and minBuyAmount match the execution request lets execution reuse the quote's price-impact measurement; a stale, unknown or mismatched id silently falls back to a fresh quote. On Base it also carries the route: a quote re-routed to the bridge/swap aggregator only executes there when you send its quoteId.
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 | Wallet API not enabled on the key, or the buy token is banned or flagged by the security scan |
502 | No quote available from the bridge/swap aggregator |
503 | A re-routed Base quote couldn't be prepared — request a fresh quote |
500 | No quote available on the same-chain EVM aggregator path, or an unexpected quote failure |
Execute
POST /wallet/swap
Send the same fields as the quote, plus the quote's minBuyAmount. Optional: quoteId from a fresh quote, and idempotencyKey (a UUID).
Send an idempotencyKey on every execution. A repeat POST with the same key returns the original result instead of broadcasting a second swap; while the original is still in flight, it returns 409. Without one, a retried request after a network timeout can broadcast a second swap.
Response (200 OK)
{
"success": true,
"hash": "0xabc123...",
"amountSold": 0.5,
"amountReceived": 1622.5,
"amountSoldRaw": "500000000000000000",
"amountReceivedRaw": "1622500000"
}
amountSold / amountReceived are human-readable numbers; the Raw fields are base-unit amounts as strings.
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.
Errors
| Status | Cause |
|---|---|
400 | Invalid body, unsupported chain, insufficient balance or gas, the price moved past your tolerance, venue-side price impact too high, a transaction that would revert, or swap validation failure |
401 | Missing or invalid authentication |
403 | Wallet API not enabled, read-only API key, wallet paused, price impact above your wallet's own protection limit, failed location check, fee beneficiary selling its own Base launch token (when that guard is on), 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 (wallet writes share a cap of about 10 requests per minute per IP; a routing venue can also throttle), or too many transactions in flight for the wallet — retry in a moment |
502 | No fresh quote at execution time, the signing service failed, a bridge/swap aggregator fill failed (any input taken is returned), 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 | Unexpected swap execution error |
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 a signed-in bankr.bot session - 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 Base launch token, which can be refused and pointed to a Glidepath instead
Routing
Bankr picks the venue for you; there's nothing to configure on the request. Same-token swaps (fromToken equal to toToken on one chain) are rejected.
- Same-chain EVM — routed through the DEX aggregator. A pair it has no route for is retried on the bridge/swap aggregator, except Robinhood Chain stocks. On Base, when the aggregator's route shows a price impact of 10% or more (or above your wallet's limit) and the bridge/swap aggregator returns materially more, the quote is served from the bridge/swap aggregator instead
- Robinhood Chain — a direct pool when one qualifies, otherwise the bridge/swap aggregator, which keeps the thin-pool protections. Tokenized stocks are the exception: quoted and filled through the DEX aggregator's RFQ makers, settling against USDG
- Base B20 tokenized stocks — routed through the bridge/swap aggregator
- Cross-chain, and any Solana leg — routed through the bridge/swap aggregator
- 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 unwrap names its own payout address, so when the agent runs a pUSD swap-and-send it settles in one transaction and pays the recipient directly (the pUSD balance is still read from your own wallet). On this endpoint the output still lands in the caller's wallet, as it does for every other pair. 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 bridge/swap 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.
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 (minBuyAmount and quoteId from the 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",
"quoteId": "7c2f9f0e-8f1a-4d1a-9a2b-3c4d5e6f7a8b",
"idempotencyKey": "3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
}'