Skip to content
Solana

Docs · Guides

Swapping and quoting

How to quote a swap exactly, set its limit, and build the instruction with the accounts each block needs.

The swap instruction

Swap takes four arguments: side (0 buy: quote in, base out; 1 sell), mode (0 exact-in, 1 exact-out), amount, and limit, which is the minimum output for exact-in and the maximum input for exact-out. A swap that can't meet its limit fails with SlippageExceeded and moves nothing.

There is no router contract: a swap is one instruction against one pool. To split a trade across a launch's pools, put several Swap instructions in one transaction.

Quote first

quoteSwap runs the program's pipeline in TypeScript against a decoded pool at a given time and slot, and returns the input, the output, every charge and the pool afterwards.

ts
import { Connection, PublicKey } from "@solana/web3.js"
import { decodePool, ix, minOut, poolRef, quoteSwap } from "@/sdk/src"

const info = await connection.getAccountInfo(poolAddress)
const pool = decodePool(info!.data)
const slot = BigInt(await connection.getSlot())
const now = BigInt(Math.floor(Date.now() / 1000))

const q = quoteSwap(pool, 1_000_000_000n, { now, slot, buy: true }) // 1 SOL in
const swap = ix.swap({
  user, pool: poolRef(poolAddress, pool), userBase, userQuote,
  side: 0, mode: 0, amount: 1_000_000_000n, limit: minOut(q.output, 50), // 0.5%
})

Quote close to sending: the dynamic fee, the Storm level, the Snipe tax and the LP stream all move with time. maxIn does the same job for exact-out.

Accounts a swap may need

Every optional account slot is filled with the reel program id when unused. With the SDK, pass the ones the pool's blocks need in extra; anything you leave out is filled for you.

SlotWhen
launch (writable)during an Anti-Snipe or Wallet Cap guard
wallet counterWallet Cap, during the guard
seller holderExit Clock on a new token's sells
oracleMarket Guard, or recapture against Pyth
auxrecapture against a sibling pool
advisory accountsagent gate (Agent, Score, recipient Agent), market-hours (Calendar), Swap Reward Mint (reward mint, trader's reward account)
referral link, referrer ledger, claims vaultthe trader has a live referral link
opener ledgerrecapture with a gap opener to credit

When the base mint is a reel-launched token, the seven reel-hook accounts follow, in this order: reel-hook, extra metas, mint guard, reel program, source holder, destination holder, destination agent. ix.swap appends them for a hooked pool, and hookExtras(mint, srcOwner, dstOwner) returns them for your own builders.

Rules that refuse a swap

ErrorWhy
BuysPausedbefore the pool's buy opening (12 s after a new token's launch, or a Buy/Sell Block pause)
SellsLockedduring a Buy/Sell Block sell lock
ExactOutDuringGuardexact-out buys are refused during the Anti-Snipe guard
BuyCapExceededthe launch's per-slot buy budget is spent; try the next slot
WalletCapExceededthe buy would pass the Wallet Cap
ZapTargetOnlyonly the pool's feeder vaults may buy it
AgentRequired, ScoreTooLow, CpiRefusedthe agent gate
OracleUnavailableMarket Guard's feed is missing or stale: every swap is refused until it updates
InsufficientLiquiditya sell would take more quote than the pool really holds

Market Guard's feed check

Market Guard reads the Pyth PriceUpdateV2 account pinned at PoolInit and checks its feed_id on every read. A missing or stale feed refuses every swap, sells too. So who can write that account matters: a creator-written PriceUpdateV2 lets its writer halt the pool by not posting, while Pyth's sponsored push feed can be refreshed by anyone with a verified update.

A Market Guard pool's Details tab reads the pinned account when it opens (and again when you come back to the browser tab), and shows its write authority (bytes 8..40):

  • "Market Guard reads a Pyth sponsored feed" (info) when all three hold: the account is owned by the Pyth receiver (rec5EKMG…), its write authority is the account itself, and its address is the Pyth Push Oracle's (pythWSns…) shard-0 PDA for the pool's feed_id. Pyth's SOL/USD sponsored feed, 7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE, is that PDA for its feed id.
  • "Check Market Guard's feed writer" (warning) otherwise, naming the writer. Only shard 0 is recognised, so a push-oracle feed on another shard gets the warning too.
  • "Market Guard feed not readable" (warning) when the account is missing or can't be read.

The check doesn't read the price's age: oracle_max_age is enforced by the program at swap time. Pin the sponsored feed for the pair when you open a Market Guard pool.

Exact-out, SOL and rounds

  • Exact-out is solved by inverting the charges at their ceilings, then running the swap forward. You receive at least what you asked for, sometimes a few atoms more, and pay at most limit. A swap that closes a recapture gap usually fails as exact-out: use exact-in.
  • SOL trades as wrapped SOL. wrapSolIxs creates and funds the account before a buy, unwrapSolIx closes it after a sell, in the same transaction.
  • King of the Pool rounds are closed by KotpSettle, which anyone may send. Put it before the first swap after a round ends (ix.kotpSettle) so the round settles as trading goes on.