Skip to main content

Exact-in and exact-out

Both are single-hop, single-pool swaps, exposed as two separate on-chain instructions rather than one instruction with a mode flag — the per-bin math genuinely branches on which side is fixed:
  • swap_exact_in: fixes the gross input, drains bins until it’s fully consumed or the pool runs out. min_amount_out is the slippage floor.
  • swap_exact_out: fixes the exact net output the trader wants to receive, drains bins until that target is reached. max_amount_in is the slippage ceiling.
x_to_y = true: caller pays X, receives Y, price moves down, traversal walks bins in descending id order from the active bin. x_to_y = false: the mirror — caller pays Y, receives X, price moves up, traversal ascends.

Traversal

Bins below the active bin are Y-only, bins above are X-only (see Positions and liquidity) — a swap simply walks that partition in the appropriate direction, consuming the output side of each bin it passes through, until the trade is filled. Traversal uses sparse bin arrays with no bitmap. The client supplies the relevant BinArray accounts directly (including verified gaps for any uninitialized stretch); the program has no on-chain index of which price regions are populated. This is the load-bearing design trade-off behind the protocol’s scale limit:
MAX_BIN_ARRAYS_PER_TRAVERSAL = 6, 100 bins per array, so a single swap can cross at most 600 bins. At the tightest supported bin step (1 bp), that’s roughly a 6.2% worst-case price drift ceiling per swap — the floor, since every coarser bin step covers proportionally more price range per bin. A trade that would need to walk further must be split across multiple transactions. This is a deliberate trade-off for the no-bitmap design, not an oversight — it keeps a swap’s compute and account-loading cost bounded regardless of how much of the price curve is populated.
The fold terminates the instant the trade is filled — it does not necessarily touch every account the client provided. If the supplied bin arrays run out before the trade fills, the instruction fails with InsufficientLiquidity: a correct signal that the trade needs more account coverage or must be split, not a bug.

Fees are per-bin, and rise mid-swap

Each bin crossed pays a fee: gross_amount_in − net_amount_in for that bin, split between LPs and the protocol (see Fees). Critically, the fee rate itself isn’t constant across one swap — advancing the active bin also accumulates the dynamic fee’s volatility state, so later bins in the same large swap are priced at a higher rate than earlier ones. A quote that doesn’t simulate this bin-by-bin will systematically underprice a swap that crosses more than one bin.

Every rounding decision favors the pool

Whichever side of a fill is the “cost” (the input on a drain, the input on an exact-out fill) rounds up; whichever side is the free variable rounds down. This holds symmetrically for both directions and both modes — a trader can never extract more value than the bin’s true reserves represent, and the accumulated rounding always lands with LPs, never against them.

Token-2022 transfer fees

Bins never see a transfer-fee-extension mint directly — the gross/net conversion happens outside the bin fold entirely:
  • Exact-in: nets the input down before folding, then nets the fold’s raw output down before checking it against min_amount_out.
  • Exact-out: grosses the output target up before folding, then grosses the fold’s raw input up before checking it against max_amount_in.
So slippage protection always applies to what the trader actually receives or actually pays, not the pre-transfer-fee intermediate value — a fee-bearing mint can’t be used to bypass it.

Transfer hooks

If either mint carries an active Token-2022 TransferHook extension, the swap instruction resolves and appends its extra accounts automatically as part of the settlement CPI — nothing extra to configure from the SDK’s perspective. Building the instruction by hand (without the SDK) requires supplying those extra accounts yourself via the TransferHookX/TransferHookY remaining-accounts group.