Skip to main content
Stable AMM is its own program; its pool-side account structure resembles AMM v4 (AmmInfo, vaults, authority), and it additionally has a ModelDataInfo account that stores the lookup table. This page covers both.

Inventory

Pure AMM. Stable AMM holds all liquidity in its own vaults and does not depend on OpenBook. It carried an OpenBook market-making path early in its life, but that path has been dormant for years, and the 2026-06-22 upgrade removed the leftover code. The serum_* market accounts and amm_open_orders below are therefore legacy: they may still appear in old-layout transactions for backwards compatibility, but the program does not validate or read them, and new-layout instructions omit them entirely.
The active inventory is now entirely pool-side:
CategoryAccountOwnerRole
PoolAmmInfoStable programPool state, references to vaults and the model-data account.
Poolamm_authorityStable programProgram-owned PDA that signs vault moves. Shared across all Stable AMM pools.
Poolamm_target_ordersStable programPool-side grid account (retained in layouts; no longer drives OpenBook orders).
Poolpool_coin_token_accountSPL TokenPool’s coin-side vault.
Poolpool_pc_token_accountSPL TokenPool’s pc-side vault.
Poollp_mintSPL TokenFungible LP mint.
Modelmodel_data_accountStable programThe lookup table: 50,000 × DataElement.
Legacyamm_open_ordersOpenBookThe pool’s old OpenBook OpenOrders account. Unused.
Legacyserum_marketOpenBookOpenBook market. Unused.
Legacyserum_bids, serum_asksOpenBookBid/ask queues. Unused.
Legacyserum_event_queueOpenBookEvent queue. Unused.
Legacyserum_coin_vault, serum_pc_vaultSPL TokenOpenBook market-level vaults. Unused.
Legacyserum_vault_signerOpenBookMarket-level vault signer. Unused.

AmmInfo

Root state account. Layout is nearly identical to AMM v4 — pool params, decimals, fees, vault/mint references — with one addition: a model_data_key field pointing to the lookup table.
// raydium-stable/program/src/state.rs (abridged)
pub struct AmmInfo {
    pub account_type: u64,              // = 0 (AmmAccount)
    pub status: u64,                    // bitmask: swap/deposit/withdraw/crank enabled
    pub nonce: u64,                     // bump for amm_authority
    pub order_num: u64,
    pub depth: u64,
    pub coin_decimals: u64,
    pub pc_decimals: u64,
    pub state: u64,                     // state machine (IdleState, etc.)
    pub reset_flag: u64,
    pub min_size: u64,
    pub vol_max_cut_ratio: u64,
    pub amount_wave: u64,
    pub coin_lot_size: u64,             // mirrors OpenBook
    pub pc_lot_size: u64,
    pub min_price_multiplier: u64,
    pub max_price_multiplier: u64,
    pub sys_decimal_value: u64,
    pub abort_trade_factor: u64,
    pub price_tick_multiplier: u64,
    pub price_tick: u64,
    
    pub fees: Fees,                     // see below
    pub out_put: OutPutData,            // PnL, swaps, punish amounts
    
    pub coin_vault: Pubkey,
    pub pc_vault: Pubkey,
    pub coin_mint: Pubkey,
    pub pc_mint: Pubkey,
    pub lp_mint: Pubkey,
    pub model_data_key: Pubkey,         // ← THE LOOKUP TABLE
    pub open_orders: Pubkey,            // legacy: OpenBook OpenOrders (unused post-decoupling)
    pub serum_market: Pubkey,           // legacy: unused post-decoupling
    pub serum_program: Pubkey,          // legacy: unused post-decoupling
    pub target_orders: Pubkey,
    pub amm_admin: Pubkey,              // admin key
    pub client_order_id: u64,
    pub lp_amount: u64,                 // LP supply
    pub lp_net: u64,                    // LP value metric
    pub padding: [u64; 61],
}

pub struct Fees {
    pub min_separate_numerator: u64,
    pub min_separate_denominator: u64,
    pub trade_fee_numerator: u64,       // 25
    pub trade_fee_denominator: u64,     // 10_000 → 0.25%
    pub pnl_numerator: u64,             // 12
    pub pnl_denominator: u64,           // 100 → 12% of fee = 0.03% of volume
    pub swap_fee_numerator: u64,        // 25
    pub swap_fee_denominator: u64,      // 10_000
}

pub struct OutPutData {
    pub need_take_pnl_coin: u64,        // accrued protocol fee (coin)
    pub need_take_pnl_pc: u64,          // accrued protocol fee (pc)
    pub total_pnl_pc: u64,
    pub total_pnl_coin: u64,
    pub pool_open_time: u64,
    pub punish_pc_amount: u64,
    pub punish_coin_amount: u64,
    pub orderbook_to_init_time: u64,
    pub swap_coin_in_amount: u128,
    pub swap_pc_out_amount: u128,
    pub swap_pc_in_amount: u128,
    pub swap_coin_out_amount: u128,
    pub swap_pc_fee: u64,
    pub swap_coin_fee: u64,
}
Key integrator-facing fields:
  • model_data_key — the address of the lookup table. Must be passed to every instruction.
  • fees — identical structure to AMM v4. Defaults to 0.25% trade fee, 0.22% LP / 0.03% protocol split.
  • coin_vault, pc_vault — the pools’ vaults.
  • status — bitmask gating swap/deposit/withdraw/crank.
  • out_put.need_take_pnl_* — swept by WithdrawPnl.

ModelDataInfo

The lookup table. A large sparse array of price/quantity points.
// raydium-stable/program/src/state.rs
pub const ELEMENT_SIZE: usize = 50000;

pub struct DataElement {
    pub x: u64,         // table X (e.g., coin amount)
    pub y: u64,         // table Y (e.g., pc amount)
    pub price: u64,     // price at (x, y)
}

pub struct ModelDataInfo {
    pub account_type: u64,              // = 2 (ModleDataAccount)
    pub status: u64,                    // Initialized or Uninitialized
    pub multiplier: u64,                // scale factor for x, y (e.g., 10^6)
    pub valid_data_count: u64,          // how many elements are populated
    pub elements: [DataElement; 50000], // the table itself
}
Lifecycle: The setup instructions that built these tables — InitModelData (created the account) and UpdateModelData (populated elements, setting valid_data_count) — were removed in the 2026-06-22 upgrade. The tables on existing pools are now fixed. At runtime, the remaining callable instructions still consume them:
  • Swap / deposit / withdraw call lookup functions that binary-search and interpolate within elements[0..valid_data_count].

DataElement

The atomic entry in the table. Must be sorted (x ascending, y descending, price ascending) for binary search to work.
pub struct DataElement {
    pub x: u64,         // X coordinate (e.g., token_a balance, scaled by multiplier)
    pub y: u64,         // Y coordinate (e.g., token_b balance, scaled by multiplier)
    pub price: u64,     // price (x/y in scaled form, scaled by multiplier)
}
When populating the table, the admin specifies these pre-scaled. The program does not validate sort order on-chain (for speed), so missorting causes incorrect quotes.

Authority and vaults

Same as AMM v4:
  • amm_authority is a single program-wide PDA derived with seed ["amm authority"]. It owns all pool vaults and signs their moves.
  • Vaults are SPL Token accounts whose owner is amm_authority, not ATAs.
Token-2022 is not supported.

Status bitmask

Identical to AMM v4. Controls whether swap/deposit/withdraw/crank are enabled.

Fee and PnL tracking

The out_put struct tracks:
  • need_take_pnl_coin, need_take_pnl_pc — protocol fees accrued but not yet swept. WithdrawPnl moves these out.
  • swap_coin_in_amount, swap_pc_in_amount, etc. — analytics counters.
Pool-asset calculation (post-decoupling). Because no funds are escrowed as OpenBook open orders anymore, the pool’s total assets are now computed entirely from the vaults:
Old: total assets = vault balances + open-order funds (native_coin_total / native_pc_total) − pending PnL (need_take_pnl)
New: total assets = vault balances − pending PnL (need_take_pnl)
Indexers and quoting code that reconstructed pool value from OpenOrders balances must drop that term.

Account size

The ModelDataInfo is large (~1.2 MB, since 50,000 elements × 24 bytes per element). This is why creating a Stable pool requires explicit rent and account pre-allocation. The Raydium SDK and tools handle this transparently; integrators rarely need to manually allocate.

Deriving accounts from scratch

Like AMM v4, Stable AMM uses seeded keys (not pure PDAs). The canonical pool identity is derived via:
ammId = createWithSeed(
  owner: ammAuthority,
  seed: marketPubkey.toBase58().slice(0, 32),
  programId: STABLE_PROGRAM_ID,
)
Similarly for vaults, LP mint, target orders, etc. In practice, use the SDK or API to fetch pre-computed addresses.

What to read where

Sources: