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.
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.
| Slot | When |
|---|---|
| launch (writable) | during an Anti-Snipe or Wallet Cap guard |
| wallet counter | Wallet Cap, during the guard |
| seller holder | Exit Clock on a new token's sells |
| oracle | Market Guard, or recapture against Pyth |
| aux | recapture against a sibling pool |
| advisory accounts | agent gate (Agent, Score, recipient Agent), market-hours (Calendar), Swap Reward Mint (reward mint, trader's reward account) |
| referral link, referrer ledger, claims vault | the trader has a live referral link |
| opener ledger | recapture 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
| Error | Why |
|---|---|
BuysPaused | before the pool's buy opening (12 s after a new token's launch, or a Buy/Sell Block pause) |
SellsLocked | during a Buy/Sell Block sell lock |
ExactOutDuringGuard | exact-out buys are refused during the Anti-Snipe guard |
BuyCapExceeded | the launch's per-slot buy budget is spent; try the next slot |
WalletCapExceeded | the buy would pass the Wallet Cap |
ZapTargetOnly | only the pool's feeder vaults may buy it |
AgentRequired, ScoreTooLow, CpiRefused | the agent gate |
OracleUnavailable | Market Guard's feed is missing or stale: every swap is refused until it updates |
InsufficientLiquidity | a 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'sfeed_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.
wrapSolIxscreates and funds the account before a buy,unwrapSolIxcloses 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.