Jupiter v6 Routing API: Getting Best Execution Inside a Bot
Jupiter v6 introduced versioned transaction support, dynamic slippage, and platform fees — all of which change how you wire a bot to the aggregator. This tutorial shows how to call the Quote and Swap endpoints, parse route plans across Orca, Raydium, and Meteora, and handle failed transactions without burning compute budget.
Jupiter v6 Routing API is the practical entry point for any Solana bot that needs to swap tokens at the best available price across the fragmented DEX landscape. The upgrade from v4/v5 was not cosmetic — versioned transactions, dynamic slippage, and the platform fee hook all require explicit wiring if you want the aggregator to do what you actually expect in production.
What Changed in v6 and Why It Matters for Bots
The biggest structural change is versioned transactions (v0). Jupiter's Quote API now returns a routePlan and the Swap API returns a base64-encoded swapTransaction that is already a VersionedTransaction, not a legacy Transaction. Deserializing it with the old Transaction.from() will throw. You need VersionedTransaction.deserialize(Buffer.from(swapTransaction, 'base64')).
Two other changes affect execution quality:
- Dynamic slippage: v6 can compute a per-route slippage estimate from recent liquidity depth and set
slippageBpson your behalf if you passdynamicSlippage: { maxBps: 300 }. For bots trading illiquid pairs this is meaningfully better than a static 100 bps that either leaves money on the table or causes unnecessary reverts. - Platform fees: you can embed a fee account and take a cut of the output token. This is primarily useful for products charging users, but it also exposes a pattern worth knowing — Jupiter enforces the fee on-chain, so a misconfigured
feeAccountwill cause the swap instruction to fail silently on simulation.
Calling the Quote Endpoint
The Quote endpoint is a GET request to https://quote-api.jup.ag/v6/quote. The parameters that matter most for a bot:
inputMint, outputMint, amount (in lamports or smallest unit)
slippageBps | dynamicSlippage
onlyDirectRoutes=false // allow multi-hop
maxAccounts=64 // hard cap on total accounts; reduce if you're composing the swap into a larger tx
swapMode=ExactIn | ExactOut
The response includes routePlan — an array of swap steps, each with swapInfo containing the AMM label (Orca, Raydium CLMM, Meteora DLMM, etc.), the input/output amounts, and the fee taken by that pool. Parse this to understand where slippage is actually occurring. If a three-hop route passes through a thin Meteora pool in the middle leg, you will see it here before you commit.
Relevant fields to log in production:
priceImpactPct— anything above ~0.5% on a route you expect to be deep is a warning sign that liquidity shifted between quote and execution.otherAmountThreshold— this is the minimum output enforced on-chain. Confirm it matches your intent before signing.contextSlot— the Solana slot the quote was computed at. A quote that is more than ~10 slots old (roughly 4 seconds) in a fast-moving market should be re-fetched, not reused.
Calling the Swap Endpoint and Signing
The Swap endpoint is a POST to https://quote-api.jup.ag/v6/swap. You pass the full quoteResponse from the previous step plus your userPublicKey. Key optional fields:
{
"quoteResponse": { ... },
"userPublicKey": "...",
"wrapAndUnwrapSol": true,
"dynamicComputeUnitLimit": true,
"prioritizationFeeLamports": "auto"
}
dynamicComputeUnitLimit: true tells Jupiter to simulate the transaction and set the compute unit limit to the actual CU consumption plus a small buffer, rather than the default 200,000. This matters because an over-allocated CU limit raises your effective priority fee cost; an under-allocated one causes ComputeBudget exceeded failures. Passing "auto" for prioritizationFeeLamports uses Jupiter's fee oracle, which is fine for most bots — but if you are running a latency-sensitive strategy alongside Jito bundles, you will want to set this yourself and coordinate with your bundle tip.
Signing is two lines:
const swapTx = VersionedTransaction.deserialize(
Buffer.from(swapTransaction, 'base64')
);
swapTx.sign([wallet]);
Send via connection.sendRawTransaction(swapTx.serialize(), { skipPreflight: false, maxRetries: 2 }). Keep skipPreflight: false unless you have a specific reason; preflight simulation on a Solana RPC catches most failures before they consume priority fees.
Parsing the Route Plan Across Orca, Raydium, and Meteora
Jupiter routes through a heterogeneous set of AMMs, and not all of them behave the same way under stress. Understanding the routePlan lets you react intelligently:
- Orca Whirlpools (CLMM): concentrated liquidity, tighter spreads on major pairs, but price impact can cliff sharply at tick boundaries. If your route goes through Orca and
priceImpactPctis high, the pool is likely near a tick transition. - Raydium CLMM / CPMM: similar CLMM behavior on CLMM pools; the older CPMM pools (constant-product) are deeper for some mids but have predictable
k-curve impact you can estimate offline. - Meteora DLMM: dynamic liquidity bins mean spreads can compress or widen faster than CLMMs. Great for execution on stable pairs; less predictable on volatile mids.
For a bot that does significant daily volume — like a Solana MEV and arbitrage engine scanning these same pools — you want to parse the ammKey on each step of routePlan and maintain a local price cache per pool, so you can validate the quote against your own state before submitting.
Handling Failed Transactions Without Burning Compute Budget
The failure modes to handle explicitly:
Slippage exceeded: The swap instruction reverts with 0x1771 (slippage tolerance exceeded). This is not an error in your code; it is a market condition. The correct response is to re-quote — not retry the same transaction. Retrying a stale transaction repeatedly is the fastest way to build up wasted priority-fee spend.
Simulation failure before send: If sendRawTransaction returns a simulation error, parse the logs. Jupiter transactions typically include a Program log: Error: ... line that tells you exactly which AMM instruction failed. A missing account error almost always means a pool was closed or migrated between quote and swap.
Transaction timeout (not confirmed in ~30s): This usually means your priority fee was too low for the block's fee market. Increase prioritizationFeeLamports and re-quote. Do not rebroadcast the same signed transaction — resigning a fresh quote is cleaner and avoids nonce confusion.
A production bot should track a per-swap state machine: quoted → signed → submitted → confirmed | failed. Failed states should trigger re-quote with a backoff, not blind retries. For market-making strategies on Solana DEXes, where you are placing many small swaps continuously, a leaky-bucket limiter on retry attempts prevents runaway fee spend during periods of network congestion.
Composing Jupiter Swaps Into Larger Transactions
If you need to compose the Jupiter swap instruction with your own program instructions — for example, logging a trade to an on-chain program, or managing vault accounting — you can extract the instructions from the swapTransaction using TransactionMessage.decompile():
const msg = TransactionMessage.decompile(swapTx.message, {
addressLookupTableAccounts
});
// msg.instructions contains the compute budget + Jupiter swap instructions
// Prepend or append your own instructions here
The critical constraint is the 64-account limit on versioned transactions. Jupiter uses address lookup tables (ALTs) to compress multi-hop routes, but complex routes can still push you close to the limit. Pass maxAccounts=40 in the quote request to leave headroom for your additional instructions, and always check msg.accountKeys.length after decompile before signing.
Pulling the address lookup tables referenced in swapTransaction.message.addressTableLookups requires fetching each AddressLookupTableAccount from the cluster. Cache these aggressively — they change infrequently, and fetching them on every swap adds latency that compounds under load. The full Solana trading services catalog covers deeper infrastructure patterns for bots where this kind of latency discipline matters.
If you are building a production Solana bot that routes through Jupiter or any Solana DEX aggregator and want it done right the first time, reach out via the contact page — this is exactly the kind of system we build and run every day.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article