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:

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 mirror of the LP mint’s total supply. Use it for LP-share math; the value should match the mint’s on-chain supply, but reading it from PoolState avoids an extra account fetch.
  • 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.
  • 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.

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. 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 by the CPMM admin via CreatePermissionPda and revoked via ClosePermissionPda. End users do not interact with this account directly — it is plumbing for cross-program flows.

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 exception is a small hard-coded mint whitelist in the program (a handful of specific pubkeys) that bypasses the extension check; it is used to onboard specific mints case-by-case. The vetted-extension list and the mint whitelist live in the CP-Swap source under programs/cp-swap/src/utils/token.rs and can change with future program upgrades.

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, so the array alone is 4,000 bytes; the full ObservationState PDA is around 4,100 bytes after the surrounding fields and discriminator. 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. 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: