Skip to main content
This page is the authoritative instruction reference. For code that actually composes these instructions, see products/cpmm/code-demos. For error-code meanings see reference/error-codes.The 2026-09 program upgrade rebuilt CPMM on Anchor 1.0.2 / Solana 3.1.10, added the admin instruction CollectExcessLamports, removed the hardcoded Token-2022 mint whitelist, and changed what CreateAmmConfig writes into protocol_owner / fund_owner. No user-facing instruction changed its accounts, arguments or math. See the 2026-09-09 changelog entry.
Both creator-fee collection instructions changed their account lists on 2026-09-19. CollectCreatorFee gains creator_fee_share; CollectCreatorFeePermissionless gains amm_config and creator_fee_share. Both are appended after system_program, so every account an existing client already passes keeps its index — but the new accounts are mandatory, so a transaction built against the older layout comes up short and is rejected with Anchor’s AccountNotEnoughKeys (3005). Two admin instructions — CreateCreatorFeeShare and CloseCreatorFeeShare — are added, and UpdateAmmConfig takes a new param = 8. See the 2026-09-19 changelog entry.

Instruction summary

Status bitmask: each pool’s status is a u8 where bit 0 = deposit disabled, bit 1 = withdraw disabled, bit 2 = swap disabled (PoolStatusBitIndex { Deposit, Withdraw, Swap } in the program). A clear bit means the operation is allowed; a set bit means it is paused. UpdatePoolStatus takes a raw u8 and overwrites the existing value. The next sections go through each in detail. Account ordering follows the CPMM IDL; the SDK and the Rust client in raydium-cp-swap/programs/cp-swap/src/instructions match this order.

Initialize

Create a new CPMM pool. Arguments
Accounts (W = writable, S = signer) * pool_state signs only on the random-keypair path; the canonical-PDA path runs without pool_state signing. Preconditions
  • Mints are sorted (token_0_mint < token_1_mint by byte order).
  • Neither mint uses an extension outside the CPMM allow-list (TransferFeeConfig, MetadataPointer, TokenMetadata, InterestBearingConfig, ScaledUiAmount) — see products/cpmm/accounts. A mint whose SupportMintAssociated PDA (seed [b"support_mint", mint]) exists skips the extension check — but you must append that PDA to remaining_accounts. The program only scans the accounts you pass and never loads the PDA itself, so relying on the registry without supplying the account still fails with NotSupportMint (6007). Order does not matter (matching is by key); pass one entry per mint that needs the bypass. That registry is the only bypass since the 2026-09 upgrade removed the hardcoded four-mint whitelist.
  • creator has at least init_amount_0 and init_amount_1 in the respective ATAs.
  • amm_config.disable_create_pool == false.
Postconditions
  • pool_state.lp_supply = sqrt(init_amount_0 * init_amount_1) — the full square root. The creator is minted lp_supply − 100; the 100 locked base units are counted in lp_supply but never minted.
  • So lp_mint.supply == pool_state.lp_supply − 100 for the life of the pool. All LP-share math (deposit, withdraw) divides by lp_supply, so use that field and do not substitute the mint’s on-chain supply. Reverts with InitLpAmountTooLess if sqrt(...) < 100.
  • observation_state is initialized; observation_index = 0 and pool_id = pool_state.key().
  • create_pool_fee lamports are transferred from the creator to the receiver and synced as native SOL (it is a wSOL ATA).
  • The pool’s status bitmask is 0 (deposit / withdraw / swap all enabled).
  • enable_creator_fee = false and creator_fee_on = BothToken. Initialize does not support enabling the creator fee — that path is InitializeWithPermission.
  • open_time is bumped to block_timestamp + 1 if the caller passed a value <= block_timestamp. Swaps are rejected before open_time; deposits and withdrawals work immediately.
Common errors (full list in reference/error-codes)
  • InvalidInput — mints unsorted, or identical mints.
  • NotSupportMint — blocked Token-2022 extension.
  • ExceededSlippage — rarely; if init_amount_0/1 result in zero LP due to decimals mismatch.

Deposit

Add liquidity in both tokens proportional to the pool. Arguments
Accounts Math
Two details worth pinning down: the pro-rata base is the fee-excluded vault total (vault_amount_without_fee, i.e. the raw balance minus the accrued protocol, fund and creator counters), not the raw vault balance; and the slippage cap is checked against what the payer actually transfers, after the Token-2022 transfer fee is added on, not against the gross vault movement. No change to k’s proportionality — both totals and lp_supply scale by the same factor. Postconditions
  • lp_supply += lp_token_amount.
  • vault_0 += needed_token_0 (net of any Token-2022 transfer fee on input).
  • vault_1 += needed_token_1 (net of any Token-2022 transfer fee on input).
Common errors — ExceededSlippage, ZeroTradingTokens, InvalidStatus if deposit is paused.

Withdraw

Burn LP tokens and receive both underlying tokens pro-rata. Arguments
Accounts The first 13 accounts are identical to Deposit, and lp_mint is writable because the LP tokens are burned. Withdraw additionally takes a 14th account, memo_program (constrained address = memo::ID) — Deposit does not. A 13-account Withdraw fails Anchor deserialization, so the LP cannot exit. Math
Postconditions
  • lp_supply -= lp_token_amount.
  • Vaults send out_token_0 / out_token_1 (gross; the user receives net of any Token-2022 transfer fee).

SwapBaseInput

Exact-input swap. Arguments
Accounts The ordering input → output is by the user’s direction, not by the pool’s canonical token_0 / token_1. The program figures out which vault is which by matching mints. Math — see products/cpmm/math. Preconditions
  • open_time <= now.
  • pool_status allows swap.
  • Neither mint paused or frozen for this authority.
  • amount_in > 0.
Common errors
  • ExceededSlippage — amount_out < minimum_amount_out.
  • ZeroTradingTokens — the trade rounds to zero.
  • NotApproved — pool is paused for swaps via UpdatePoolStatus.
  • InvalidInput — mints do not match either of the pool’s vault mints.

SwapBaseOutput

Exact-output swap. Arguments
Accounts — same as SwapBaseInput. Math — inverse curve with ceiling, see products/cpmm/math. Common errors — ExceededSlippage (gross_in > max_amount_in), ZeroTradingTokens, InvalidInput, NotApproved.

CollectProtocolFee

Sweep accrued protocol fees from the vaults to the protocol destination. Arguments — none. Accounts Effect
No change to the curve’s effective balances (accrued fees were already excluded). Common error — InvalidOwner (6001) if the signer is neither amm_config.protocol_owner nor the program admin. (There is no NotApproved on this path.)

CollectFundFee

Same shape as CollectProtocolFee but signed by amm_config.fund_owner — or, again, the program admin — and zeroing the fund_fees_* counters. Same InvalidOwner on a wrong signer.

CollectCreatorFee

Signed by pool_state.pool_creator. It settles the accrued creator fee and transfers the creator’s part to the creator’s token accounts. Arguments — none. Accounts Effect
The protocol’s share never leaves the vault here — it is re-labelled as a protocol fee and waits for CollectProtocolFee. Both counters are already excluded from the curve’s view of the vault, so the pool’s price does not move. Full derivation in products/cpmm/fees. Common errors — NoFeeCollect when both creator counters are zero (checked before the split), InvalidInput (6003) if the resolved share_rate exceeds 1_000_000, MathOverflow (6011) if booking the share would overflow protocol_fees_token_*, and Anchor’s ConstraintSeeds error if creator_fee_share is not the canonical PDA.

CollectCreatorFeePermissionless

Anyone can trigger creator-fee collection. The instruction always sends the creator’s part to the canonical associated token accounts owned by pool_state.pool_creator; the caller cannot choose another creator or destination. If either ATA is missing, the payer funds its creation. The original CollectCreatorFee remains callable, so a creator who wants to sign for their own collection still can. Arguments — none. Accounts
The two new accounts are appended after system_program, not inserted. Every account from payer through system_program keeps the position it had before the upgrade, so the break is a clean one: a transaction built against the pre-upgrade fourteen-account layout does not misread a vault as a config — it simply passes too few accounts and Anchor rejects it with AccountNotEnoughKeys (3005) before any constraint runs. The accounts are still mandatory, so append both and refresh the IDL; there is no compatibility path for the old layout.
Effect — identical to CollectCreatorFee above: the share is resolved from creator_fee_share or amm_config, the protocol’s part is booked into protocol_fees_token_{0,1}, the creator’s part is transferred to the creator ATAs, both creator counters are zeroed, and recent_epoch is updated. Returns NoFeeCollect when both counters are zero.

UpdatePoolStatus

Pause or resume individual operations on a pool. The status field is a bitmask: Arguments
Accounts The admin key is a pubkey compiled into the program (crate::admin::ID), not the BPF upgrade authority — changing it requires a program upgrade. See reference/program-addresses for the value and security/admin-and-multisig for who holds it.

CreateAmmConfig

Create a new fee tier. Arguments
Accounts Preconditions
  • No existing AmmConfig with the same index.
  • protocol_fee_rate + fund_fee_rate <= FEE_RATE_DENOMINATOR_VALUE.
Changed in 2026-09: the new config’s fee owners no longer come from the signer. create_amm_config now writes the program’s hardcoded protocol_fee_owner::ID into protocol_owner and fund_fee_owner::ID into fund_owner, instead of copying the admin signer’s key into both. Addresses are in reference/program-addresses.Consequences: fees on a newly created AmmConfig land in the dedicated fee wallets rather than the admin’s. The admin does remain an accepted signer for collection — CollectProtocolFee / CollectFundFee accept amm_config.protocol_owner / fund_owner or crate::admin::ID — so nothing has to be rotated to sweep; what changed is only where the proceeds go by default. Existing AmmConfig accounts are not rewritten — whatever is stored on them still governs, so always read protocol_owner / fund_owner off the account instead of assuming either value. UpdateAmmConfig params 3 and 4 still rotate them.

UpdateAmmConfig

Change fee rates or ownership on an existing AmmConfig. Takes a param: u8 (which field to update) and a value: u64. The full dispatch table:
  • param = 0 → trade_fee_rate (asserts trade_fee_rate + creator_fee_rate < 1_000_000)
  • param = 1 → protocol_fee_rate (asserts ≤ 1_000_000 and + fund_fee_rate ≤ 1_000_000)
  • param = 2 → fund_fee_rate (asserts ≤ 1_000_000 and + protocol_fee_rate ≤ 1_000_000)
  • param = 3 → protocol_owner. The new key is not in value: append it as remaining_accounts[0] (read-only is fine). It must not be the default pubkey, and omitting the account panics on an unwrap().
  • param = 4 → fund_owner. Same mechanism as 3.
  • param = 5 → create_pool_fee
  • param = 6 → disable_create_pool (any non-zero value disables)
  • param = 7 → creator_fee_rate (asserts creator_fee_rate + trade_fee_rate < 1_000_000)
  • param = 8 → creator_fee_share_rate (asserts ≤ 1_000_000). Added 2026-09-19. The protocol’s default share of the creator fee on this tier; see products/cpmm/fees. It is not related to protocol_fee_rate, which splits the trade fee.
Any other param returns InvalidInput. Changes are signed by the admin and affect every pool bound to this AmmConfig on the next swap. No migration; pools simply read the new values.

CreateCreatorFeeShare

Set a custom protocol share of the creator fee for one (creator, amm_config) pair, overriding AmmConfig.creator_fee_share_rate for every pool that creator owns on that fee tier. Added in the 2026-09-19 creator-fee-share upgrade. Arguments
Accounts Preconditions
  • share_rate <= 1_000_000, otherwise InvalidInput (6003).
  • The PDA must not already exist — Anchor’s init fails on a second call for the same pair. To change a rate, close the account and create it again.
Postconditions
  • creator_fee_share stores bump, creator, amm_config and share_rate.
  • Every subsequent CollectCreatorFee / CollectCreatorFeePermissionless on a pool created by creator under amm_config resolves the share from this account instead of the config.
The pool creator is not a party to this instruction and does not sign it. The rate is read at collection time, so an override created after fees have already accrued applies to that accrued balance too.

CloseCreatorFeeShare

Remove the override. The pair falls back to AmmConfig.creator_fee_share_rate. Arguments — none. Accounts Postconditions
  • The account is closed and its lamports go to owner.
  • Collections for that pair resolve the share from amm_config.creator_fee_share_rate again — which is 0 unless an admin has set UpdateAmmConfig param 8.

CollectExcessLamports

Admin sweep of lamports sitting above the rent-exempt minimum on accounts CPMM controls. Added in the 2026-09 upgrade so the protocol can reclaim the over-funding that the SIMD-0437 rent reduction leaves behind on accounts created before each step. Only the excess moves. Token balances, account data, owners, pool state and the curve are untouched, and the instruction is a no-op against an account already at its minimum — so it is safe to re-run after each rollout step. Arguments — none. Accounts
Ordering fix, 2026-09-19. The program now makes two passes over remaining_accounts — every token-program CPI first, then the direct debits of CPMM-owned PDAs. Interleaving them aborted with the runtime’s UnbalancedInstruction (“sum of account balances before and after instruction do not match”) whenever a PDA was debited ahead of a CPI, because the caller’s pending lamport changes are only flushed into accounts a CPI actually carries. Callers do not have to group or sort the list themselves.
How each source account is handled The program dispatches on the source account’s owner: Because it takes an unbounded remaining_accounts list, transaction size is the real limit — the same constraint as the wallet-side sweep described in solana-fundamentals/rent-and-reclaimable-rent. Common errors — InvalidOwner (6001, wrong signer), LamportsCalculateError (6015, the wSOL round-trip did not net to zero), and InsufficientFunds from the program-owned path when an account holds less than its own rent minimum. No SDK builder. @raydium-io/raydium-sdk-v2 does not ship a builder for this instruction, and neither does the raydium-sdk-V2-demo repo — it is an admin path. Encode it by hand, the way the wallet-side sweep in solana-fundamentals/rent-and-reclaimable-rent does for the token-program instruction.

State-change matrix

Where to go next

Sources: