All articles
Risk·March 30, 2026·7 min read

Slippage & Price-Impact Guards for Jupiter Swap Bots

Build jupiter swap slippage protection into your bot with dynamic caps, per-route minOut simulation, and price-impact rejection so thin Meteora/Orca pools never wreck a fill.

A 40% price-impact fill is not a slippage problem. It's a routing problem that your bot signed off on because the default slippage tolerance was set to something lazy like auto or a flat 5000 bps, and nobody wired a guard between the quote and the send. On Solana this happens constantly: the Jupiter aggregator finds a route that technically fills your size, but half of it goes through a Meteora DLMM pool with $3k of liquidity in the active bin, and the effective price you get is nowhere near the mid you saw two seconds ago.

The fix isn't "set a tighter slippage." Slippage tolerance and price impact are two different numbers, and confusing them is how bots eat 40% fills while their slippage guard sits there reporting green.

Slippage tolerance vs. price impact — they are not the same knob

Slippage tolerance is the delta between the quoted out-amount and the executed out-amount. It protects you against the state moving between quote and land — someone else's swap, a bin flipping, an oracle tick. Jupiter enforces it by encoding an otherAmountThreshold (the on-chain minOut) into the swap instruction. If the fill would come in below that, the transaction reverts. That's real protection and it works.

Price impact is the delta between the mid price and the price your own order moves the pool to. It's a property of the route and your size versus available depth. Jupiter returns it as priceImpactPct in the quote response. Nothing on-chain enforces it. A route can have a perfect fill relative to its quote — zero slippage — and still be a 40% price-impact disaster, because the quote itself was already 40% underwater. Your slippage guard passes because execution matched the quote. The quote was the trap.

So you need both checks, and they run at different stages. Reject on price impact before you build the transaction. Enforce minOut inside the transaction. If you only do the second one, you're protected against the market moving but not against your own order eating a thin pool.

Read priceImpactPct on every quote and reject early

The /quote response gives you everything you need to bounce a bad route before spending a lamport on it. The two fields that matter:

  • priceImpactPct — decimal string, e.g. "0.41" means 41%.
  • routePlan — the ordered list of AMMs the swap hops through, each with its swapInfo.label (Meteora, Orca, Raydium, etc.) and per-hop amounts.

A minimal guard, assuming you've already got a quote object:

const MAX_IMPACT = 0.015; // 1.5% hard cap for normal size

function assertRouteSafe(quote: QuoteResponse) {
  const impact = Number(quote.priceImpactPct);
  if (!Number.isFinite(impact)) throw new Error("impact NaN — reject");
  if (impact > MAX_IMPACT) {
    throw new RejectRoute(`price impact ${(impact * 100).toFixed(2)}% > cap`);
  }
  // per-hop thin-pool sniff: flag any single hop eating most of the impact
  for (const leg of quote.routePlan) {
    const label = leg.swapInfo.label;
    if (THIN_VENUES.has(label) && impact > 0.005) {
      throw new RejectRoute(`${label} hop with ${(impact*100).toFixed(2)}% impact`);
    }
  }
}

THIN_VENUES in practice is Meteora DLMM and a lot of the long-tail Orca Whirlpools, where a single concentrated bin can be nearly empty and the aggregator still routes through it because on paper the math clears. Raydium CLMM and the big Orca pools are usually deep enough that a 1.5% global cap already covers you.

The subtle part: a flat impact cap is wrong for large orders. If you're moving $200k through a mid-cap SPL token, 1.5% might be the best available execution and rejecting it just means you never trade. So make the cap a function of notional. I run a piecewise curve — 0.5% under $5k, 1.5% up to $50k, 3% up to $250k, and above that the order gets sliced instead of sent whole. Position-level sizing discipline is a prerequisite here; the same notional-aware sizing logic I use on Hyperliquid perps drives what impact ceiling a given order is even allowed to clear.

Simulate minOut per route before you commit

Jupiter will happily compute otherAmountThreshold for you from your slippage bps, but the number it picks is only as good as the slippage figure you fed it. For anything size-sensitive, compute minOut yourself off the quote and pass it back explicitly rather than trusting slippageBps: auto.

The mechanic that saves you: request the swap transaction, then run it through simulateTransaction against the current bank before signing and sending. The simulation returns the actual out-amount for the current chain state. Compare it to your independently-computed minOut:

const minOut = BigInt(quote.outAmount) * 985n / 1000n; // your own 1.5% floor
const sim = await connection.simulateTransaction(tx, { replaceRecentBlockhash: true });
const simulatedOut = extractOutFromLogs(sim.value.logs); // parse the swap CPI event
if (simulatedOut < minOut) {
  throw new RejectRoute(`sim out ${simulatedOut} < floor ${minOut}`);
}

Two gotchas here. First, replaceRecentBlockhash: true is mandatory or the sim fails on a stale hash and you'll misread that as a bad route. Second, the sim reflects state at the current slot, not the slot you'll actually land in — on a volatile token those can differ by a few hundred milliseconds and several bins. That's exactly why you keep the on-chain otherAmountThreshold as the backstop: sim rejects the obviously-broken routes, minOut catches the ones that go bad in flight. Belt and suspenders, and both belts are cheap.

If your simulate step is throwing on account resolution or the CPI log format shifts under a Jupiter program upgrade, that's the kind of silent breakage that only shows up as mysterious rejections in production. A second set of eyes on the swap-execution path tends to catch the assumptions you baked in during a good-liquidity week that fall apart the first thin Tuesday.

Wire it into a kill path, not just a rejection

Rejecting one bad route is table stakes. The real protection is treating repeated rejections as a signal about the venue, not just the order. If three consecutive quotes for the same pair come back over your impact cap, that pool's liquidity has probably been pulled, and you want to halt trading on that pair rather than retrying into the same hole. This is the same discipline as circuit breakers that halt on stale oracles or depegs — the trigger is different (route impact instead of price feed staleness) but the response is identical: stop, don't retry blindly.

A few operational rules that have kept my bots out of trouble:

  • Never send with dynamicSlippage unbounded. Jupiter's dynamic slippage is fine as a ceiling helper, but always pass a maxBps so it can't quietly widen to 30% on a volatile pair.
  • Log the full routePlan on every rejection. When you're debugging a bad Tuesday, knowing it was always the same Meteora bin turns a two-hour investigation into a two-minute one.
  • Re-quote, don't re-send. A rejected route is stale by definition. Fetch a fresh quote; the aggregator may have already re-routed around the drained pool.
  • Watch the priority fee interaction. A route you underpaid on can land three slots late, well past where your sim was valid. Fee estimation and slippage protection are coupled whether you like it or not.

Getting all of this visible in one place — impact per route, rejection counts per venue, minOut vs. actual fills — is worth building a proper execution dashboard for. You cannot tune caps you can't see, and the difference between a 1.5% and a 2.2% cap on your specific flow is a P&L question you answer with data, not vibes.

The resolution-risk mindset from pre-settlement hedging on Polymarket applies here too: the worst fills aren't random, they cluster around specific structural conditions, and the job is to identify those conditions and refuse to trade through them.

If you'd rather have someone stress-test your swap path against real thin-pool conditions before it costs you a fill, our code review and audit work is built for exactly this.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#Solana#Jupiter#Slippage#MEV#Risk Management