Skip to main content
Program ID and PDA seeds for CPMM are listed canonically in reference/program-addresses. This page focuses on what each account is for and the invariants it maintains, not the hardcoded addresses.

The six accounts of a CPMM pool

Every CPMM pool is fully described by six program-derived addresses (PDAs) under the CPMM program, plus one shared AmmConfig account it references. Once you have the two mints, you can derive everything deterministically without touching the network. And the shared config: And one optional per-creator account:

Deriving a pool from nothing but two mints

Always sort mints before deriving the pool PDA. The seed hashes the two mints in byte order, not in user order. Two pools with (A, B) and (B, A) would collide on-chain — sorting is how the program makes the mapping canonical.
Pool ID is not always the canonical PDA. Initialize accepts an arbitrary signer keypair as pool_state in addition to the PDA above. If the passed account does not match the canonical PDA, the program requires it to be a signer — i.e., the creator passes a fresh keypair that they sign with. This is the front-run defence: any third party racing to grab the canonical PDA can be sidestepped by the legitimate creator using a random keypair instead. The downstream PDAs (lpMint, vault0, vault1, observation) are still derived from poolState.key(), so they remain unique to whichever address was used. When you index pools, always discover the pool ID from the on-chain state (e.g., PoolState accounts under the CPMM program), not by deriving the canonical PDA — the latter will miss random-keypair pools.

Account layouts

The full Rust definitions live in the raydium-cp-swap source. The fields below are the ones you will read from an integration.

PoolState

What to actually read:
  • lp_supply — the pool’s internal LP total. It is not equal to the LP mint’s supply: it is exactly 100 base units higher, because the 100 locked units are counted here but never minted. All LP-share math (deposit, withdraw) divides by lp_supply, so use this field and do not substitute the mint’s on-chain supply.
  • protocol_fees_token{0,1}, fund_fees_token{0,1} — accrued fees not yet swept. These do not affect swap pricing; they sit in the vaults until CollectProtocolFee / CollectFundFee is called. protocol_fees_token{0,1} also receives the protocol’s share of the creator fee when a creator fee is collected, so it grows outside of swaps — see products/cpmm/fees.
  • status — a bitmask controlling whether Swap, Deposit, Withdraw are allowed. Updated by the admin via UpdatePoolStatus. The SDK checks this before building a transaction; if you are CPI-ing directly, check it yourself.
  • token0_program / token1_program — the token program to CPI into for each vault. One can be classic SPL Token and the other Token-2022; they are independent.
  • open_time — a Unix timestamp. Swaps before this time fail. Deposits are permitted before open_time so the pool can be seeded.
  • creator_fee_on / enable_creator_fee — together control whether the optional creator fee is active for this pool and which side of the swap it is collected from. enable_creator_fee == false zeroes the creator-fee path entirely. When enabled, creator_fee_on selects: 0 = take fee from whichever token is the swap input (BothToken); 1 = take fee from token_0 only (skip on token_1 → token_0 swaps); 2 = take fee from token_1 only. Set at pool creation via InitializeWithPermission; cannot change later.
  • creator_fees_token_{0,1} — accrued creator fees, swept by CollectCreatorFee or CollectCreatorFeePermissionless. Both paths zero the full counters, but since the 2026-09-19 creator-fee-share upgrade only part of the balance leaves the pool: the protocol’s share is added to protocol_fees_token_{0,1} and the remainder is transferred to the creator. The permissionless path fixes the recipients to pool_creator’s canonical ATAs. PoolState itself did not change — there is no separate counter for the shared amount.

AmmConfig

Three things to be careful about:
  1. trade_fee_rate and creator_fee_rate are fractions of volume, both denominated in units of 1/1_000_000. 2500 means 0.25% of the trade volume. protocol_fee_rate and fund_fee_rate are fractions of the trade fee (not of volume), in the same 1/1_000_000 denominator. The creator fee is not a fraction of the trade fee — it is its own independent rate. Full arithmetic is in products/cpmm/fees.
  2. index is a u16, so the seed hash uses 2 bytes big-endian. Off-by-one on the byte order is a common integration bug.
  3. AmmConfig is immutable at pool level. A pool points at one AmmConfig at creation and never switches. Fee changes propagate because the pool reads the config each swap — but the pool cannot be moved between fee tiers.
A note on creator fees: the rate itself (creator_fee_rate) lives on AmmConfig and is shared across the fee tier. Whether a particular pool actually charges it (enable_creator_fee) and which side of the swap it lands on (creator_fee_on) live on PoolState. The creator fee is independent of the trade fee — it is its own rate, accrued to its own counters (creator_fees_token_{0,1}), and never reduces the LP / protocol / fund shares of the trade fee. Sweep is via CollectCreatorFee or the destination-constrained CollectCreatorFeePermissionless, and both paths hand a share of the accrued balance to the protocol on the way out — at creator_fee_share_rate, or at the rate on a CreatorFeeShare PDA when one exists for that (creator, amm_config) pair. See products/cpmm/fees for the full mechanics.

Permission

A small access-control account used by InitializeWithPermission. The CPMM program supports a permissioned pool-creation path so that other programs (e.g. LaunchLab when graduating a token to CPMM) can prove they are entitled to create a pool against a given AmmConfig.
The Permission PDA is created via CreatePermissionPda by either the CPMM admin or a dedicated permission-PDA creator authority. Since the 2026-09 upgrade, ClosePermissionPda accepts the same two signers; before it was admin-only. End users do not interact with this account directly — it is plumbing for cross-program flows. See security/admin-and-multisig for the role boundary and reference/program-addresses for canonical addresses.

CreatorFeeShare

An optional account that overrides the protocol’s share of the creator fee for one (pool creator, AmmConfig) pair. Added by the 2026-09-19 creator-fee-share upgrade.
How it behaves:
  • It is optional, but the account is never optional in the instruction. CollectCreatorFee and CollectCreatorFeePermissionless both declare creator_fee_share with the seed constraint above and take it on every call. The program then checks whether the account is empty or foreign-owned; if so it falls back to AmmConfig.creator_fee_share_rate. So a client must always derive and pass the address, whether or not the account exists.
  • share_rate is capped at FEE_RATE_DENOMINATOR_VALUE (1_000_000) at creation, and again when the split runs. 1_000_000 routes the entire creator fee to the protocol; 0 routes none of it.
  • Created and closed by the admin or a dedicated authority through CreateCreatorFeeShare / CloseCreatorFeeShare. Closing it returns the rent to the signer and falls the pair back to the config default; the pool creator is not a signer on either path.
  • It is keyed on the creator, not the pool. One account governs every pool that creator has on that AmmConfig. A creator with pools on two fee tiers needs two accounts to be covered on both.
The split arithmetic it drives is in products/cpmm/fees.

Vaults and Token-2022

vault0 and vault1 are owned by the CPMM authority PDA, and their token-program owner (token_program) is either SPL Token or Token-2022, determined at pool creation by the mint’s program. The pool handles the two cases transparently — you pass the right token-program ID for each side in the Swap / Deposit / Withdraw instruction accounts. CPMM enforces a strict extension allow-list at pool creation (is_supported_mint in utils/token.rs). A Token-2022 mint can be used in a CPMM pool only if every extension it carries is on this list:
  • TransferFeeConfig. Applied by the mint on every transfer. The pool is on the receiving side for SwapBaseInput deposits and the sending side for withdrawals. The program computes the net amount landing in the vault and sets the curve accordingly. See algorithms/token-2022-transfer-fees.
  • MetadataPointer and TokenMetadata. Standard on-mint metadata. No effect on swap math.
  • InterestBearingConfig. The mint’s UI amount accrues interest. The vault stores raw amounts; the curve operates on raw amounts only. UIs that show APR should call the Token-2022 helpers to render the UI amount.
  • ScaledUiAmount. UI-display scaling extension. Same treatment as InterestBearingConfig — the curve uses raw amounts.
Any other extension — PermanentDelegate, TransferHook, DefaultAccountState, NonTransferable, ConfidentialTransfer, Group/GroupMember, MintCloseAuthority, etc. — causes Initialize to reject with NotSupportMint. The one exception is a per-mint registry: if a SupportMintAssociated PDA exists at seed [b"support_mint", mint], the mint is admitted regardless of its extension set. That PDA is created and removed by the admin (or a dedicated support-mint authority) through CreateSupportMintAssociated / CloseSupportMintAssociated, so onboarding a specific mint no longer needs a program upgrade.
Changed in 2026-09. CPMM previously also carried a hardcoded four-address MINT_WHITELIST that short-circuited the extension check. That array was removed; the registry PDA is now the only bypass. Any mint that was relying on the hardcoded list needs a SupportMintAssociated PDA before a new pool can be created for it — existing pools are unaffected, because the check runs only at pool creation.
The vetted-extension list lives in the CP-Swap source under programs/cp-swap/src/utils/token.rs and can change with future program upgrades. See reference/token-2022-support for the cross-program matrix.

Observation

The observation account is a ring buffer of ObservationState entries, each storing a block_timestamp and a cumulative price. On every swap the program appends a new observation if enough time has passed since the last one. TWAPs are computed by reading two observations and dividing Δcumulative / Δtime.
The ring buffer is sized for 100 observations. Each observation is 40 bytes (8 + 16 + 16), so the array alone is 4,000 bytes; ObservationState::LEN is exactly 4,075 bytes (8 + 1 + 2 + 32 + 4,000 + 8 × 4). Two consumer rules:
  • Do not use a single observation as a price. It is a cumulative, not a spot price. Use two of them to compute a TWAP.
  • Pick observations at least one block apart. Swaps within the same block may not produce a new observation; reading back-to-back can return the same record.
More math in products/clmm/accounts.

Account lifecycle

CPMM pools and their PDAs are never closed. Permission, SupportMintAssociated and CreatorFeeShare are the exceptions — they are standalone admin-managed records, not pool state, and each has an explicit close instruction. Even at zero liquidity the poolState remains. This is deliberate: re-seeding the same pool later preserves its historical observation buffer and its PDA derivation remains stable.

What to read where

Sources: