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.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
* 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_mintby byte order). - Neither mint uses an extension outside the CPMM allow-list (
TransferFeeConfig,MetadataPointer,TokenMetadata,InterestBearingConfig,ScaledUiAmount) — seeproducts/cpmm/accounts. A mint whoseSupportMintAssociatedPDA (seed[b"support_mint", mint]) exists skips the extension check — but you must append that PDA toremaining_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 withNotSupportMint(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. creatorhas at leastinit_amount_0andinit_amount_1in the respective ATAs.amm_config.disable_create_pool == false.
pool_state.lp_supply = sqrt(init_amount_0 * init_amount_1)— the full square root. The creator is mintedlp_supply − 100; the 100 locked base units are counted inlp_supplybut never minted.- So
lp_mint.supply == pool_state.lp_supply − 100for the life of the pool. All LP-share math (deposit, withdraw) divides bylp_supply, so use that field and do not substitute the mint’s on-chain supply. Reverts withInitLpAmountTooLessifsqrt(...) < 100. observation_stateis initialized;observation_index = 0andpool_id = pool_state.key().create_pool_feelamports 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 = falseandcreator_fee_on = BothToken.Initializedoes not support enabling the creator fee — that path isInitializeWithPermission.open_timeis bumped toblock_timestamp + 1if the caller passed a value<= block_timestamp. Swaps are rejected beforeopen_time; deposits and withdrawals work immediately.
reference/error-codes)
InvalidInput— mints unsorted, or identical mints.NotSupportMint— blocked Token-2022 extension.ExceededSlippage— rarely; ifinit_amount_0/1result in zero LP due to decimals mismatch.
Deposit
Add liquidity in both tokens proportional to the pool.
Arguments
Math
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).
ExceededSlippage, ZeroTradingTokens, InvalidStatus if deposit is paused.
Withdraw
Burn LP tokens and receive both underlying tokens pro-rata.
Arguments
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
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
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_statusallows swap.- Neither mint paused or frozen for this authority.
amount_in > 0.
ExceededSlippage—amount_out < minimum_amount_out.ZeroTradingTokens— the trade rounds to zero.NotApproved— pool is paused for swaps viaUpdatePoolStatus.InvalidInput— mints do not match either of the pool’s vault mints.
SwapBaseOutput
Exact-output swap.
Arguments
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
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
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
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
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
Preconditions
- No existing
AmmConfigwith the sameindex. 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(assertstrade_fee_rate + creator_fee_rate < 1_000_000)param = 1→protocol_fee_rate(asserts≤ 1_000_000and+ fund_fee_rate ≤ 1_000_000)param = 2→fund_fee_rate(asserts≤ 1_000_000and+ protocol_fee_rate ≤ 1_000_000)param = 3→protocol_owner. The new key is not invalue: append it asremaining_accounts[0](read-only is fine). It must not be the default pubkey, and omitting the account panics on anunwrap().param = 4→fund_owner. Same mechanism as3.param = 5→create_pool_feeparam = 6→disable_create_pool(any non-zerovaluedisables)param = 7→creator_fee_rate(assertscreator_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; seeproducts/cpmm/fees. It is not related toprotocol_fee_rate, which splits the trade fee.
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
Preconditions
share_rate <= 1_000_000, otherwiseInvalidInput(6003).- The PDA must not already exist — Anchor’s
initfails on a second call for the same pair. To change a rate, close the account and create it again.
creator_fee_sharestoresbump,creator,amm_configandshare_rate.- Every subsequent
CollectCreatorFee/CollectCreatorFeePermissionlesson a pool created bycreatorunderamm_configresolves 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_rateagain — which is0unless an admin has setUpdateAmmConfigparam8.
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.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
products/cpmm/code-demos— runnable TypeScript samples for the above.reference/error-codes— the complete Anchor error table.products/cpmm/fees— the fee-accrual model that theCollect*Feeinstructions drain.

