# Feedback If you encounter incorrect, outdated, or confusing documentation on any page, submit feedback: POST https://docs.raydium.io/feedback ```json { "path": "/current-page-path", "feedback": "Description of the issue" } ``` Only submit feedback when you have something specific and actionable to report. # Bonding curves Source: https://docs.raydium.io/algorithms/bonding-curves The mathematics behind token-issuance curves — quadratic, linear, and virtual-reserves CPMM variants — derivations for cost / proceeds / spot price, and the graduation-threshold math used by LaunchLab. This page derives the general bonding-curve mathematics. For LaunchLab's specific implementation, see [`products/launchlab/bonding-curve`](/products/launchlab/bonding-curve). The derivations are given in continuous form; the on-chain code implements the discrete analog in fixed-point arithmetic. ## What a bonding curve is A **bonding curve** is a deterministic price function `p(s)` that relates the price of a token to the amount currently in circulation (`s` for "supply sold"). Buyers purchase by sending collateral to the contract; the contract emits new token units at the marginal price dictated by the curve. Sellers return token units and receive the integrated refund. Two key properties compared to a CPMM pool: * **No counterparty needed.** The issuing contract is the market maker; liquidity exists by fiat. * **Monotonic price.** Price rises with every net-buy and falls with every net-sell. Bonding curves are the standard launch mechanism when the issuing entity does not want to pre-seed an AMM pool with collateral. ## Generic pricing formulas For any continuous price function `p(s)`: **Spot price** at supply `s`: ``` p(s) = the curve formula ``` **Cost to buy** supply from `s_0` to `s_1` (with `s_1 > s_0`): ``` cost(s_0, s_1) = ∫_{s_0}^{s_1} p(s) ds = P(s_1) − P(s_0) ``` where `P(s) = ∫ p(s) ds` is the curve's antiderivative. Geometrically, `cost` is the area under `p` between `s_0` and `s_1`. **Proceeds from selling** supply back from `s_1` to `s_0`: ``` proceeds(s_1, s_0) = cost(s_0, s_1) ``` (Symmetry: buying and selling across the same interval exchanges the same collateral — modulo fees.) **Average price** for the buy: ``` avg = cost(s_0, s_1) / (s_1 − s_0) ``` ## Common curve families ### Linear ``` p(s) = a + b · s ``` ``` P(s) = a·s + (b/2)·s² cost(s_0, s_1) = a·(s_1 − s_0) + (b/2)·(s_1² − s_0²) ``` Price rises proportionally with supply. Used for "steady" launches where the issuer wants a predictable, moderate markup over the lifetime. ### Quadratic ``` p(s) = k · s² // or k · (s / S_max)² for a normalized form ``` ``` P(s) = (k / 3) · s³ cost(s_0, s_1) = (k / 3) · (s_1³ − s_0³) ``` Price rises quadratically. Early buyers get a near-zero price (flat starting region); late buyers pay a steeper premium. This is the curve type LaunchLab defaults to (`curve_type = 0`). ### Virtual-reserves CPMM (Pump-style) The curve is a standard CPMM with a pretend initial quote reserve `V_q`: ``` effective_y = V_q + collateral_received effective_x = S_max − s (effective_x) · (effective_y) = V_q · S_max // invariant ``` Spot price: ``` p(s) = effective_y / effective_x = V_q · S_max / (S_max − s)² · ... (derivable via implicit differentiation) ``` Cost to move from `s_0` to `s_1`: ``` cost(s_0, s_1) = V_q · S_max / (S_max − s_1) − V_q · S_max / (S_max − s_0) = V_q · (s_1 − s_0) · S_max / ((S_max − s_0) · (S_max − s_1)) ``` This variant has the elegant property that at graduation (where `s = S_graduate`), the marginal price equals the opening price of the downstream CPMM pool seeded with reserves `(S_max − S_graduate, V_q + cost(0, S_graduate))`. Handoff is seamless. LaunchLab exposes this as `curve_type = 1`. ## Discrete implementation On-chain, `s` and `cost` are both integers (smallest-denomination units). The continuous integral `cost(s_0, s_1)` is computed directly from the closed form whenever one exists (linear, quadratic). For curves without a closed-form inverse (quadratic, given `cost`, find `s_1`), Newton iteration is used: ``` # Solve quadratic: (k/3)·s_1³ = (k/3)·s_0³ + cost # Initialize with s_guess ≈ cbrt(3·cost/k + s_0³) for i in 0..MAX_ITER: f = (k/3)·s_guess³ − (k/3)·s_0³ − cost f' = k·s_guess² step = f / f' s_guess -= step if |step| < precision_floor: break ``` LaunchLab caps iterations at \~10 and reverts with `NotConverged` if the residual is still above tolerance. In practice this only triggers near the domain's extremities; production swaps converge in 2–3 iterations. ## Fee integration Fees are applied on top of the curve cost, not inside it. On buy: ``` cost_curve = cost(base_sold, base_sold + base_out) fee = ceil(cost_curve · buy_numerator / buy_denominator) quote_in = cost_curve + fee ``` On sell: ``` proceeds_curve = cost(base_sold − base_in, base_sold) fee = ceil(proceeds_curve · sell_numerator / sell_denominator) quote_out = proceeds_curve − fee ``` The LP portion of the fee is retained in `quote_vault` and effectively makes the curve stiffer for later buyers — the reserve grows without issuing more supply. The protocol and creator portions are tracked in separate counters for later sweep. ## Graduation threshold A curve "graduates" when it has received enough collateral to seed an external AMM pool at a price matching the current curve price. For a quadratic curve with parameters `(k, S_max, S_graduate)`: ``` quote_to_graduate = cost(0, S_graduate) · (1 + buy_fee_rate) = (k / 3) · S_graduate³ · (1 + f_buy) ``` Once `quote_vault ≥ quote_to_graduate`, the `Graduate` instruction creates a CPMM pool with: ``` cpmm_base_reserve = S_max − S_graduate // unsold curve supply cpmm_quote_reserve = quote_vault − accrued_fee_counters cpmm_initial_price = cpmm_quote_reserve / cpmm_base_reserve ``` For the virtual-reserves curve, by construction: ``` cpmm_initial_price == p(S_graduate) // exact equality ``` For the quadratic, the equality is approximate; the "slop" is absorbed into the rounding of `S_graduate` (typically `0.8 · S_max`) and the surplus collateral from the final threshold-crossing buy. ## Impermanence vs a CPMM pool A pure bonding-curve launch has **no impermanence** in the Uniswap sense: there is no "other side" of the market to rebalance against. The curve issues supply on demand, and the only "LP" is the contract itself. Post-graduation, the resulting CPMM pool behaves like any other CPMM pool — if the LP was not burned, they are subject to the usual impermanent-loss dynamics. This is why the **burn** post-graduation policy is dominant in public launches: it keeps the pool permanent and removes any LP-withdrawal-driven price shocks. ## Worked example Curve: quadratic, `k = 40`, `S_max = 1e9`, `S_graduate = 0.8 · S_max = 8e8`. Buy fee 1%. ### Price at `s = 5e8` ``` p(5e8) = 40 · (5e8 / 1e9)² = 40 · 0.25 = 10 ``` 10 units of quote per base unit. ### Cost of the first buy of 1e6 base ``` cost(0, 1e6) = (40/3) · (1e6)³ = (40/3) · 1e18 ≈ 1.333e19 (smallest quote units) ``` With 1% fee: ``` quote_in = 1.333e19 · 1.01 ≈ 1.347e19 ``` ### Graduation threshold ``` cost(0, 8e8) = (40/3) · (8e8)³ = (40/3) · 5.12e26 ≈ 6.827e27 quote_to_graduate ≈ 6.827e27 · 1.01 ≈ 6.895e27 ``` ### Price at graduation ``` p(8e8) = 40 · 0.64 = 25.6 ``` ### Post-graduation CPMM reserves ``` cpmm_base = 1e9 − 8e8 = 2e8 cpmm_quote ≈ 6.827e27 (less fee-counter deductions) cpmm_price ≈ 3.41e19 per base — which matches p(8e8) after units are accounted for ``` (Units: decimals need to be tracked carefully; the example is illustrative.) ## Pointers * [`products/launchlab/bonding-curve`](/products/launchlab/bonding-curve) — the on-chain LaunchLab implementation of these formulas. * [`products/launchlab/instructions`](/products/launchlab/instructions) — `Buy`, `Sell`, `Graduate` account-level specs. * [`algorithms/constant-product`](/algorithms/constant-product) — what the post-graduation CPMM does with the reserves. Sources: * Raydium LaunchLab program source (quadratic + virtual-reserves curve implementations). * Bancor white paper (linear bonding curves, historical). * Pump.fun public post-mortems (virtual-reserves variant). # Estimating CLMM APR Source: https://docs.raydium.io/algorithms/clmm-apr How Raydium computes the APR numbers shown on CLMM pools, how to estimate your own APR for a hypothetical range before opening a position, and the edge cases that make trailing APR misleading. The **APR shown on the Raydium UI** for a CLMM pool is the realised fee APR of the **in-range liquidity** over the past 24 hours, projected to a year. It is not the APR *your position* would have earned — that depends on your range, your time-in-range, and your share of the liquidity that was active during the trading window. ## The headline formula For a CLMM pool, the daily fee APR shown on Raydium is computed as: ``` apr_24h = (fees_24h / tvl_in_range_24h) · 365 ``` Where: * `fees_24h` is the sum of LP-side swap fees accrued in the last 24 hours (in USD). * `tvl_in_range_24h` is the time-weighted average USD value of liquidity that was **in range** during the window. The denominator is what distinguishes CLMM APR from CPMM APR. CPMM uses total pool TVL because every dollar is always contributing. CLMM uses only the in-range subset because out-of-range dollars earn nothing. ## What your APR will actually be The headline APR is a statistic of the pool, not of your position. Your APR depends on four multipliers: ``` your_APR = headline_APR · (your_in_range_fraction / pool_in_range_fraction) # concentration bonus · time_in_range # range discipline · (1 − transfer_fee_haircut) # token-2022 tax · compounding_factor # if you auto-restake ``` * **Concentration bonus.** If your range is tighter than the pool-wide average, every active tick has more of your liquidity per dollar than the average LP's. Tighter = bigger bonus (and proportionally bigger IL amplification). * **Time in range.** If you are in range only 40% of the time, multiply by 0.40. * **Transfer-fee haircut.** For Token-2022 mints with transfer fees, every fee collection hops through a transfer that itself bleeds basis points. * **Compounding.** If you `collectFee` and redeploy into the same range weekly, the effective APR is about `(1 + daily_APR)^365 − 1`. Without compounding it is linear. ### Worked example Suppose a SOL/USDC CLMM pool has: * 24h volume: \$120M * Fee tier: 0.05% (LP share 88% of fees after protocol cut) * Total TVL: \$40M * In-range TVL: \$18M (45% of pool is currently in range) ``` fees_24h = 120M · 0.0005 · 0.88 = $52,800 apr_24h = (52,800 / 18,000,000) · 365 = 107% ``` The Raydium UI shows **107% APR** for the pool. (This example uses a deep, high-volume pool to keep the numbers concrete; typical CLMM pools display anywhere from 10% to 50% APR, with low-volume pools well under 10%.) Now you are considering opening a position: * Your range: tight enough that you have 2× the average concentration. * Expected time-in-range: 70% (you will check weekly). * No Token-2022 fees. No auto-compounding. ``` your_APR ≈ 107% · 2 · 0.70 = 150% ``` That is an estimate, not a guarantee. Realised volume can halve or double in a week. ## Why trailing APR is a lagging signal CLMM APR moves **fast** relative to CPMM APR because the in-range TVL denominator moves fast: * A large price move pushes chunks of positions out of range, shrinking the denominator. Suddenly your remaining in-range TVL looks "higher APR" — but only because competitors left, and typically volume falls too. * A pool reaching a new ATH can temporarily show 500–1000% APR for an hour as most positions were calibrated for lower prices and only a few aggressive LPs remain in range. * Once the market settles, LPs rebalance and APR mean-reverts. Rules of thumb: * **Ignore sub-24h APR.** Too much noise. * **Prefer 7d and 30d windows.** Raydium exposes both via [`GET /pools/info/ids`](/sdk-api/rest-api) — fields `week.apr` and `month.apr`. * **Backtest your specific range** on historical volume and price data before committing meaningful capital. ## How the math works (single-tick step) Inside a single tick the CLMM behaves like a CPMM on the amount of liquidity `L` active in that tick. Fees accrue in `fee_growth_global_X` and `fee_growth_global_Y` per unit of liquidity. For a position with liquidity `Lₚ` that spans tick ranges `[i_lo, i_hi]`: ``` fees_earned_X = Lₚ · (fee_growth_inside_X(i_lo, i_hi, now) − fee_growth_inside_X(i_lo, i_hi, t_open)) fees_earned_Y = Lₚ · (fee_growth_inside_Y(i_lo, i_hi, now) − fee_growth_inside_Y(i_lo, i_hi, t_open)) ``` Where `fee_growth_inside` subtracts fee growth below `i_lo` and above `i_hi` from the global accumulator. Details in [`algorithms/clmm-math`](/algorithms/clmm-math). To compute *expected* fees over a future period for a prospective range, estimate: ``` expected_fees_per_day = Σ_ticks_in_range (volume_at_tick · fee_rate · your_share_at_tick) ``` Raydium's SDK exposes `getEstimateAprFromPositionAndPool` which does this estimation using the recent volume-per-tick histogram. ## SDK helper ```ts theme={null} import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; const raydium = await Raydium.load({ owner, connection }); const pool = await raydium.clmm.getPoolInfoFromRpc({ poolId }); const apr = await raydium.clmm.estimatedApr({ poolInfo: pool.poolInfo, poolKeys: pool.poolKeys, tickLower: -100, // your intended range tickUpper: 100, volumeUsd24h: pool.poolInfo.day.volume, // or a custom estimate }); console.log(`Expected APR: ${apr.feeApr * 100}% (fee-only, excludes farm incentives)`); ``` The method also separately returns any farm APR if the pool has an attached farm. Your full "LP APR" is `feeApr + farmApr`. ## Where farm APR fits in Raydium CLMM pools can have Farm v6 emissions layered on top. Farm rewards are paid in a **reward mint** (often RAY or a partner token) independent of swap fees. The Raydium UI typically shows: ``` Total APR = Fee APR + Farm APR [in $X, Y, Z reward mints] ``` Farm APR is computed similarly but using the reward emission schedule and the current price of the reward mint in USD. Unlike fee APR it is not volatility-dependent — it is a fixed schedule. See [`products/farm-staking/overview`](/products/farm-staking/overview) for farm emission math. ## Common mistakes * **"APR is compounded automatically."** No. Fees must be claimed via `collectFee`, then manually redeployed. Raydium does not auto-compound CLMM fees. * **"My APR = headline APR."** Only if your concentration is average, your time-in-range is 100%, and you compound at the same cadence. All three are usually false. * **"Higher fee tier = higher APR."** Only if volume survives the higher fee. At 1% a pair may quote 50% APR but do 1/10 the volume; net less than 0.25% at 120% × 0.1 = 12%. * **"Out-of-range positions have zero risk."** They have zero fee income but full IL on the existing token composition — the position is now "100% of whichever side". ## Pointers * [`algorithms/clmm-math`](/algorithms/clmm-math) — full derivation of the fee-growth accumulator. * [`algorithms/impermanent-loss`](/algorithms/impermanent-loss) — the loss side of the LP equation. * [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type) — decision framework that uses this APR estimate. * [`sdk-api/rest-api`](/sdk-api/rest-api) — live volume / APR endpoints. Sources: * Raydium SDK v2 `estimatedApr` implementation. * Raydium UI pool page (live APR fields). * Uniswap V3 fee-growth derivation. # Concentrated-liquidity math Source: https://docs.raydium.io/algorithms/clmm-math sqrt-price representation, liquidity-to-amount formulas, single-tick and multi-tick swap steps, fee-growth accounting — the math behind Raydium CLMM. This page consolidates the derivations behind CLMM. For the on-chain implementation, see [`products/clmm/math`](/products/clmm/math) (which cites this page) and [`products/clmm/ticks-and-positions`](/products/clmm/ticks-and-positions) (which motivates the tick lattice). ## Why sqrt-price, not price Uniswap-v3-family CLMMs represent price as its square root, stored in a fixed-point `Q64.64`: ``` sqrt_price_x64 = floor(sqrt(price) · 2^64) ``` Three reasons: 1. **Linear liquidity math.** The amount of token0 or token1 in a price range turns out to be a linear function of `sqrt_price`, not of `price`. Storing `sqrt_price` lets the swap step evaluate those linear formulas without computing a square root. 2. **Overflow control.** `sqrt_price · L` fits in `u256` for all reasonable parameters; `price · L` can overflow much sooner. 3. **Tick math is uniform.** Because ticks are defined as `1.0001^i`, `sqrt(price) = 1.00005^i` is also an exact power-of-1.00005 ladder. Each tick-cross translates to a small multiplication in `sqrt_price_x64` space. Price and sqrt-price are one-to-one; the conversion is `price = (sqrt_price_x64 / 2^64)^2`. ## Tick lattice Prices are discretized onto a grid: ``` price(tick_i) = 1.0001^i ``` `tick_i` is an `i32`. The live range is `[MIN_TICK, MAX_TICK] = [−443636, 443636]`, giving a price range of roughly `[2^−128, 2^128]`. Each pool's `tick_spacing` is set by its fee tier: smaller spacings for tight pairs (e.g. stablecoin 0.01% tier uses spacing 1), larger spacings for volatile pairs (0.25% tier uses 60, 1% tier uses 120). Positions must have `tick_lower` and `tick_upper` aligned to `tick_spacing`. A pool's active ticks (those with liquidity starting or ending there) are the only ticks the swap step cares about. ## Liquidity-to-amount For a position with liquidity `L` and price range `[sqrt_lo, sqrt_hi]` (all `sqrt_price` values): | Pool state | Amount of token0 | Amount of token1 | | -------------------------------------- | ----------------------------------------------- | ------------------------- | | Price above range (`sqrt_p ≥ sqrt_hi`) | 0 | `L · (sqrt_hi − sqrt_lo)` | | Price in range | `L · (sqrt_hi − sqrt_p) / (sqrt_p · sqrt_hi)` | `L · (sqrt_p − sqrt_lo)` | | Price below range (`sqrt_p ≤ sqrt_lo`) | `L · (sqrt_hi − sqrt_lo) / (sqrt_lo · sqrt_hi)` | 0 | Derivation: differentiate the CPMM invariant locally. Inside any single tick range, the position behaves as a CPMM with virtual reserves `(x_v, y_v)` chosen so the pool's current `(sqrt_p, L)` is consistent with `L = sqrt(x_v · y_v)`. Integrating from `sqrt_p` to the range boundary yields the amounts above. **Inverse formulas** (used when minting a position for a given `amount0` or `amount1`): ``` L_from_amount0(amount0, sqrt_lo, sqrt_hi, sqrt_p) = amount0 · sqrt_p · sqrt_hi / (sqrt_hi − sqrt_p) L_from_amount1(amount1, sqrt_lo, sqrt_hi, sqrt_p) = amount1 / (sqrt_p − sqrt_lo) // For a symmetric deposit into an in-range position, take the min. L = min(L_from_amount0, L_from_amount1) ``` ## Single-tick swap step Within a single tick range the pool behaves like a CPMM. Given current `sqrt_p` and target `sqrt_target`: ``` Δamount0_step = L · (sqrt_target − sqrt_p) / (sqrt_p · sqrt_target) // if swapping for token0 Δamount1_step = L · (sqrt_target − sqrt_p) // if swapping for token1 ``` ### Exact-input step Given `Δin_remaining`: ``` // Candidate new sqrt_p if we filled to the tick boundary: sqrt_after_full = sqrt_target amount_to_full = Δamount_in_to_reach(sqrt_p → sqrt_target) if Δin_remaining ≥ amount_to_full: // consume the rest of the bucket sqrt_p' = sqrt_target Δin_consumed = amount_to_full Δout = amount_out_at_boundary else: // we don't cross; solve for the terminal sqrt_p sqrt_p' = L · sqrt_p / (L + Δin_remaining · sqrt_p) // for 0→1 swaps Δin_consumed = Δin_remaining Δout = L · (sqrt_p − sqrt_p') // proportional to Δsqrt ``` The `0→1` swap lowers `sqrt_p` (price declines as we sell token0 in). A `1→0` swap raises it. The formulas are symmetric with `sqrt_p` and `sqrt_target` swapped. ### Exact-output step Same structure, solving for `Δin` instead. ## Multi-tick swap loop A swap iterates over ticks until the input is exhausted or the price limit is hit: ``` while Δin_remaining > 0 and sqrt_p != sqrt_price_limit: next_tick = find_next_initialized_tick(pool.tick_current, direction) sqrt_target = min(next_tick.sqrt_price, sqrt_price_limit) // directionally (Δin, Δout, sqrt_p') = single_step(sqrt_p, sqrt_target, L, Δin_remaining) Δin_remaining -= Δin accumulated_out += Δout if sqrt_p' == next_tick.sqrt_price: // crossing the tick L += next_tick.liquidity_net * direction_sign flip_fee_growth_outside(next_tick) match_limit_orders_at_tick(next_tick, ...) // see products/clmm/math pool.tick_current = next_tick.tick_index sqrt_p = sqrt_p' ``` Each `single_step` uses the pool's current `L`. `L` changes **only** when crossing an initialized tick. Liquidity between ticks is constant, which is what makes the step math closed-form. `liquidity_net` at a tick is the signed sum of position liquidities that start at that tick minus those that end there. Crossing upward adds `liquidity_net`; crossing downward subtracts it. When the pool has limit orders open at a tick, the cross-tick step also opportunistically consumes part of the swap input to fill those orders (FIFO across cohorts). The matching algorithm and the dynamic-fee surcharge that may apply on top of the base step are documented in [`products/clmm/math`](/products/clmm/math); they don't change the closed-form single-step formulas above. ## Fee-growth accumulators CLMM tracks fees per unit of active liquidity, per side, globally and per tick: ``` fee_growth_global_0_x64 // Q64.64, monotone fee_growth_global_1_x64 tick.fee_growth_outside_0_x64 // "fees accrued while this tick was outside the active range" tick.fee_growth_outside_1_x64 ``` On each `single_step`: ``` step_lp_fee = (Δin · fee_rate) · (1 − protocol_fraction − fund_fraction) fee_growth_global += step_lp_fee · 2^64 / L // only for the input side ``` (The other side's `fee_growth_global` does not move on this step, since no token on that side was paid as input.) When crossing a tick, the program **flips** `fee_growth_outside`: ``` tick.fee_growth_outside_0_x64 = fee_growth_global_0_x64 − tick.fee_growth_outside_0_x64 tick.fee_growth_outside_1_x64 = fee_growth_global_1_x64 − tick.fee_growth_outside_1_x64 ``` "Outside" is relative to `tick_current`. When `tick_current` is above the tick, outside means "below". When `tick_current` is below, outside means "above". The flip swaps the interpretation. ### `fee_growth_inside` for a position Given a position `[tick_lower, tick_upper]` and the current `tick_current`: ``` if tick_current >= tick_upper: inside = tick_lower.fee_growth_outside − tick_upper.fee_growth_outside else if tick_current < tick_lower: inside = tick_upper.fee_growth_outside − tick_lower.fee_growth_outside else: // position is in range inside = fee_growth_global − tick_lower.fee_growth_outside − tick_upper.fee_growth_outside ``` A position's uncollected fees for token side `s` are: ``` tokens_owed_s += L · (fee_growth_inside_s − fee_growth_inside_last_s) / 2^64 fee_growth_inside_last_s = fee_growth_inside_s ``` This update runs on every interaction with the position (`IncreaseLiquidity`, `DecreaseLiquidity`, `CollectFees`). ## Worked example — crossing one tick Pool (simplified): * `sqrt_p_x64 = 2^64 · 1.0 = 2^64` (price = 1.0) * `L = 1_000_000` * `tick_current = 0` * Next initialized tick below: `tick = −60`, `sqrt_price = 1.0001^(−30) ≈ 0.99700`, `liquidity_net = −400_000` (this tick ends a position, so a downward cross removes 400k) * Fee rate: 0.25% Swap: `Δin = 10_000` token0, direction = 0→1. **Step 1 — up to `sqrt_target = 0.99700 · 2^64`**: ``` amount_in_to_target = L · (1/sqrt_target − 1/sqrt_p) = 1_000_000 · (1/0.99700 − 1/1.0) ≈ 1_000_000 · 0.003009 ≈ 3_009 ``` 3,009 \< 10,000, so we fill this step completely: ``` Δin_step = 3_009 / (1 − 0.0025) = 3_017 // gross of fee Δout_step = L · (sqrt_p − sqrt_target) ≈ 1_000_000 · 0.00299 ≈ 2_990 sqrt_p = 0.99700 · 2^64 tick_current = −60 L = 1_000_000 + (−400_000) = 600_000 // crossed the tick fee_growth_outside at tick −60 is flipped Δin_remaining = 10_000 − 3_017 = 6_983 ``` **Step 2 — with new `L = 600_000`**: The next initialized tick (say `tick = −120`) is at `sqrt = 0.99402`. Recompute `amount_in_to_target`: ``` amount_in_to_target = 600_000 · (1/0.99402 − 1/0.99700) ≈ 600_000 · 0.003010 ≈ 1_806 ``` Still less than `Δin_remaining`. Cross again. Continue until `Δin_remaining` reaches zero. The full sequence of `Δout` accumulates to the final swap output. ## Initialization and overflow guards * `MIN_SQRT_PRICE_X64` and `MAX_SQRT_PRICE_X64` correspond to `tick = ±443636`. Any swap that would push `sqrt_p` outside this range reverts. * The user's `sqrt_price_limit` parameter must lie in the same interval; the program checks. * Products of `L · Δsqrt` are computed in `u256` then shifted back to `u128` to avoid overflow. ## Differences vs Uniswap v3 * **Oracle.** Raydium's `ObservationState` stores `(block_timestamp, tick_cumulative, seconds_per_liquidity_cumulative)` ring buffer; slightly different wire format from Uniswap's but the same TWAP math. * **Token-2022.** Raydium CLMM supports Token-2022 mints; the transfer-fee variant requires additional pre/post-swap amount adjustments. See [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees). * **Tick bitmap.** Raydium packs the initialized-tick bitmap into `[u64; 16]` per pool for fast `find_next_initialized_tick`; Uniswap uses a per-word on-chain mapping. The tradeoff is rent vs lookup cost. * **Reward slots.** Raydium supports 3 per-pool reward streams with separate `reward_growth_global_x64` counters; same structure as the fee-growth accumulator. ## Pointers * [`products/clmm/math`](/products/clmm/math) — the on-chain implementation and worked example with actual CLMM struct fields. * [`products/clmm/ticks-and-positions`](/products/clmm/ticks-and-positions) — tick lattice, `liquidity_net`/`gross`, active-range semantics. * [`products/clmm/fees`](/products/clmm/fees) — the fee-growth accumulator in action. Sources: * Uniswap v3 whitepaper (canonical derivation of sqrt-price math). * Raydium CLMM program source. # Constant-product AMM Source: https://docs.raydium.io/algorithms/constant-product The x·y=k invariant, reserve-based pricing, slippage derivation, and the fee handling variants used by Raydium CPMM and AMM v4. This is the reference math page every x·y=k product at Raydium links back to. ## The invariant A constant-product market maker (CPMM) holds two reserves `x` and `y` and enforces: ``` x · y ≥ k (after every trade) ``` where `k` is the product of the reserves before the trade. For a fee-free market, `x · y = k` exactly. With fees, `k` strictly grows (the LP share of the fee is retained in the reserves). The invariant is deliberately geometric: it guarantees that no matter how small one reserve becomes, the other grows unboundedly to match — i.e. the pool can never be drained to zero on either side. ## Pricing ### Spot price The marginal price of `y` denominated in `x` at any instant is the tangent of the curve: ``` p = y / x ``` (derivation: implicit differentiation of `x · y = k` gives `dy/dx = −y/x`; ignoring the sign, `|dy/dx| = y/x`). This is the price that the pool quotes for an infinitesimally small trade. For any finite trade, the realized price is worse due to slippage along the curve. ### Exact-input swap (give `Δx`, receive `Δy`) With fees, let `f` be the fee rate (e.g. `f = 0.0025` for 25 bps). Apply the fee to the input, then use the invariant to solve for the output: ``` Δx_after_fee = Δx · (1 − f) Δy = y · Δx_after_fee / (x + Δx_after_fee) ``` Post-trade reserves: ``` x' = x + Δx y' = y − Δy ``` The full `Δx` enters the reserves. The LP portion of the fee stays in `x'`; the protocol portion is excluded from the curve via a separate accounting step (see [Fee accounting variants](#fee-accounting-variants) below). ### Exact-output swap (receive `Δy`, pay the minimal `Δx`) ``` Δx_after_fee = x · Δy / (y − Δy) Δx = Δx_after_fee / (1 − f) ``` `Δx` is rounded up to ensure the pool does not undercharge. ## Slippage and price impact **Price impact** measures how much the pool's spot price moves as a result of the trade: ``` p_before = y / x p_after = y' / x' = (y − Δy) / (x + Δx) impact = (p_before − p_after) / p_before ``` For small `Δx / x`, a first-order expansion gives: ``` impact ≈ 2 · Δx / x (ignoring fees) ``` Intuition: a 1% swap causes a \~2% price impact. This factor of 2 is the reason CPMM pools quoted for mid-size trades look "thin" compared to orderbook markets — you are not just buying against the current best bid, you are walking up your own marginal price. **Effective price** paid by the swapper: ``` effective = Δx / Δy ``` The spread between `p_before` and `effective` is **slippage**. On-chain `slippage` UI is usually expressed as `(effective − p_before) / p_before`; the SDK's `computeAmountOut` returns both `amountOut` and `priceImpact` for this reason. ## Invariant check in code After a swap, protocols re-verify: ``` k' = x' · y' ≥ k = x · y ``` Any violation is a program bug or an arithmetic overflow. Raydium's swap instructions make this check explicit as a post-condition: ```rust theme={null} let k_before = coin_reserve_before as u128 * pc_reserve_before as u128; let k_after = coin_reserve_after as u128 * pc_reserve_after as u128; require!(k_after >= k_before, ErrorCode::InvariantViolation); ``` ## Fee accounting variants The invariant check assumes the LP fee stays in the reserves. Different Raydium products handle the protocol / fund / creator components differently: ### CPMM convention Fees are `u64` basis-point-like rates on a `1_000_000` denominator. The trade fee is split into `trade_fee_rate` (total) and then subdivided via `protocol_fee_rate`, `fund_fee_rate`, `creator_fee_rate`. On each swap: ``` trade_fee = ceil(Δx · trade_fee_rate / 1_000_000) protocol_fee = trade_fee · protocol_fee_rate / 1_000_000 fund_fee = trade_fee · fund_fee_rate / 1_000_000 creator_fee = trade_fee · creator_fee_rate / 1_000_000 lp_fee = trade_fee − protocol_fee − fund_fee − creator_fee ``` The three non-LP shares accrue into separate counters (`protocol_fees_*`, `fund_fees_*`, `creator_fees_*`) that are **excluded** from the reserves used in the invariant. This is how fees can be swept without moving the curve. See [`products/cpmm/fees`](/products/cpmm/fees). ### AMM v4 convention Fees are `numerator / denominator` ratios on a `10_000` denominator. The split is fixed at pool creation and stored on `AmmInfo.fees`: ``` swap_fee = ceil(Δx · swap_fee_numerator / swap_fee_denominator) // e.g. 0.25% pnl_share = swap_fee · pnl_numerator / swap_fee_numerator // e.g. 0.03 / 0.25 = 12% lp_share = swap_fee − pnl_share // 0.22% of volume ``` `pnl_share` accrues into `state_data.need_take_pnl_*` and is excluded from reserves; `lp_share` stays in the vault. See [`products/amm-v4/fees`](/products/amm-v4/fees). Both conventions preserve the invariant the same way — the difference is cosmetic (denominator + number of sub-categories). ## Rounding rules * **Fee calculation rounds up.** Ensures the pool never undercharges on the fee. * **Output amount rounds down.** Ensures the invariant holds strictly (`k' > k` even before the fee is added). * **Exact-output input amount rounds up.** Ensures the user does not underpay. All arithmetic uses `u128` for the intermediate `x · Δx` products to avoid overflow on large reserves. Final results are cast back to `u64` with a saturation check. ## Edge cases ### Empty pool Before the first `Deposit`, `x = y = 0`. Swap instructions reject pre-deposit. ### Zero output If `Δx` is small enough that the rounded-down `Δy` is 0, the instruction reverts with `ZeroTradingTokens`. This prevents extraction of value without payment; also means dust swaps on highly imbalanced pools fail. ### Dust LP The first `Deposit` has special handling: it computes the initial LP supply as `sqrt(x · y)` and burns a small "init burn" amount (usually 100 LP units) to prevent the "first-depositor inflation attack" (where an attacker donates to the vault and inflates the LP token value). Subsequent deposits use pro-rata math. ## Relationship to arbitrage A CPMM pool's price only changes via: 1. Trades through the pool itself (users walking the curve). 2. Donations (sending tokens to the vault without a swap). Because trades move the price deterministically with the curve, any pool whose price diverges from the broader market creates an arbitrage opportunity. Arbitrageurs bring the pool price back toward the market price in expectation. This is why CPMM pools are said to "quote a price without an oracle": the market finds the price through arbitrage rather than the pool reading it externally. The flip side: the pool itself is the arbitrageur's counterparty, so any arbitrage profit is LP impermanent loss (minus the fee captured by LPs). ## Worked examples ### Example 1 — small trade, negligible slippage Pool: `x = 1_000_000, y = 2_000_000, k = 2·10^12`. Fee `f = 0.0025`. Trade `Δx = 1_000`: ``` Δx_after_fee = 1000 · 0.9975 = 997.5 Δy = 2_000_000 · 997.5 / (1_000_000 + 997.5) = 1_995_000_000 / 1_000_997.5 ≈ 1_993.01 ``` Effective price: `1000 / 1993.01 ≈ 0.5018`. Spot before: `0.5`. Impact: \~0.36%. ### Example 2 — mid-size trade, visible slippage Same pool, `Δx = 100_000` (10% of `x`): ``` Δx_after_fee = 100_000 · 0.9975 = 99_750 Δy = 2_000_000 · 99_750 / (1_000_000 + 99_750) = 199_500_000_000 / 1_099_750 ≈ 181_405 ``` Effective: `100_000 / 181_405 ≈ 0.5513`. Impact: \~10.3% — roughly half the `2 · 10% = 20%` rule of thumb (the rule is a worst-case ceiling for a no-fee constant-product curve; the trade fee plus the inversion in the formula brings it down). ## Pointers * [`products/cpmm/math`](/products/cpmm/math) — CPMM's specific rounding + fee-denominator choices. * [`products/amm-v4/math`](/products/amm-v4/math) — how AMM v4's OpenBook-integrated reserves extend this model. * [`algorithms/slippage-and-price-impact`](/algorithms/slippage-and-price-impact) — dedicated page on slippage tolerance sizing for UIs. Sources: * Uniswap v2 whitepaper — the canonical statement of `x · y = k`. * Raydium CPMM program source. * Raydium AMM v4 program source. # Impermanent loss Source: https://docs.raydium.io/algorithms/impermanent-loss The fundamental LP risk: value lost relative to simply holding the two tokens. Formulas for CPMM (constant-product) and CLMM (concentrated-liquidity) pools, worked examples, and the break-even fee-APR thresholds that matter for LPs. **Impermanent loss (IL)** is the gap between (a) the value of your LP position and (b) the value you would have had if you had simply held the two tokens from deposit to now. It is "impermanent" in name only — if you withdraw while prices have diverged, the loss is realised. This page gives the formulas, the intuition, and the break-even APRs for CPMM and CLMM. ## The simple case: `xy=k` (CPMM, AMM v4) Suppose at deposit you put in `x₀` tokens of A and `y₀` tokens of B with price `P₀ = y₀ / x₀`. A constant-product pool maintains `x · y = k = x₀ · y₀`. When the external price of A in B moves to `P₁`, arbitrageurs rebalance the pool until marginal price matches, giving: ``` x₁ = √(k / P₁) y₁ = √(k · P₁) ``` Your LP share is worth `x₁ · P₁ + y₁` tokens of B. Compare to simply holding: `x₀ · P₁ + y₀`. The ratio is: ``` V_LP / V_HOLD = 2 · √r / (1 + r) where r = P₁ / P₀ ``` **IL** is `1 − V_LP / V_HOLD`. A few sample values: | Price change `r` | `V_LP / V_HOLD` | IL | | ----------------- | --------------- | ------ | | 1.00× (no change) | 1.0000 | 0.00% | | 1.25× (+25%) | 0.9938 | 0.62% | | 1.50× (+50%) | 0.9798 | 2.02% | | 2.00× (+100%) | 0.9428 | 5.72% | | 3.00× (+200%) | 0.8660 | 13.40% | | 5.00× (+400%) | 0.7454 | 25.46% | | 0.50× (−50%) | 0.9428 | 5.72% | | 0.25× (−75%) | 0.8000 | 20.00% | IL is symmetric in `P₁ / P₀` vs `P₀ / P₁`: doubling and halving produce the same IL. ### Intuition The pool is constantly selling whichever side goes up and buying whichever side goes down, at average prices worse than the new equilibrium. IL is the rebate you pay to arbitrageurs for keeping the pool honest. In exchange, you earn trading fees — the hope is that fees more than offset IL over the holding period. ## Break-even fee APR (CPMM) Given realised volatility `σ` (annualised stdev of log-returns) and no correlation, a rough first-order IL rate is: ``` dIL/dt ≈ σ² / 8 per year ``` So the break-even fee APR for a CPMM LP is approximately `σ² / 8`. | Realised vol (annualised) | IL-per-year | Break-even fee APR | | ------------------------- | ----------- | ------------------ | | 20% | 0.50% | 0.50% | | 40% | 2.00% | 2.00% | | 80% | 8.00% | 8.00% | | 120% | 18.00% | 18.00% | | 200% | 50.00% | 50.00% | A SOL/USDC pair running \~80% annualised vol needs roughly 8% fee APR to break even on IL. If the pool quotes 30% fee APR, the LP is netting \~22% after IL (before SOL-denominated PnL). ### Caveats * This ignores fees compounding into the position. * It assumes continuous rebalancing by perfectly-efficient arbitrage, which overstates IL slightly on Solana where arb has latency. * It assumes the path is log-normal; fat-tailed meme tokens underestimate IL at this formula. ## CLMM-specific IL In a concentrated-liquidity pool you pick a range `[P_lo, P_hi]`. Three things change versus CPMM: 1. **Inside the range, IL is amplified** because you have effectively leveraged your capital. The multiplier is roughly `1 / (1 − √(P_lo/P_hi))`, so a range that goes from −50% to +100% (P\_hi / P\_lo = 4) has a leverage of \~2× and an IL \~2× a CPMM position over the same move. 2. **Outside the range, you hold only one token.** Once price crosses `P_hi`, you hold 100% of the lower-numbered token (typically the "base"); below `P_lo`, you hold 100% quote. No further swaps happen against your position, so IL is bounded but so are fees (zero). 3. **Rebalancing = realising IL + opening a new range** at the new price. Every rebalance locks in the loss to that point and starts fresh. ### IL vs HODL for a CLMM position For a position with range `[P_lo, P_hi]` and current price `P`, with `P₀` the entry price (somewhere in the range), let: ``` √P₀̄ = √P₀ · (bounded by √P_lo, √P_hi) √P̄ = √P · (bounded by √P_lo, √P_hi) ``` The IL relative to holding the entry composition is the standard V\_LP / V\_HOLD formula applied to the *clamped* prices. That is: if price stays inside the range, IL behaves like a CPMM IL on a position leveraged by the range-width factor. If price exits the range, IL is fixed at the single-token composition: all base or all quote, priced at `P` but valued against a portfolio you would have held 50/50 at `P₀`. ### Worked CLMM example Assume you deposit SOL/USDC into a CLMM at SOL = \$160, range `[$120, $200]`, \$10,000 equally split. * Range width: `P_hi / P_lo = 200 / 120 ≈ 1.67`. Leverage factor ≈ `1 / (1 − √(120/200)) ≈ 4.6×`. * If SOL moves to \$180 (+12.5%), HODL value = \$10,625; CLMM position ≈ \$10,597; IL ≈ 0.26%. * If SOL moves to \$200 (+25%), HODL value = \$11,250; CLMM position is now 100% USDC worth \~\$11,180; IL ≈ 0.62%. * If SOL moves to \$240 (+50%, outside range), HODL value = \$12,500; CLMM position is still 100% USDC worth \~\$11,180; IL ≈ 10.6%. The same position in a CPMM (no range) would have IL of \~2.02% at \$240 and continue earning fees. The CLMM position has higher fee capture *while in range* but much worse IL once out of range for an extended period. ## Single-sided deposits Raydium CLMM supports opening a position by depositing only one token if the current price is at or outside the corresponding range boundary. This is equivalent to placing a limit order — the pool will swap your single-side deposit into the other token as price moves through the range. IL on single-sided deposits is calculated the same way but with a different entry composition. ## Mitigations * **Stick to correlated pairs.** Stable/stable and LST/SOL have near-zero IL at realistic horizons. * **Use LaunchLab tokens cautiously.** Newly-launched tokens typically run 200–400% annualised vol, meaning IL eats 20–50% per year. * **Widen CLMM ranges if you won't rebalance.** A 2× wider range has \~half the fee density and \~half the IL amplification — roughly the same IL-per-fee-earned ratio over long horizons. * **Auto-compound fees.** CPMM does this implicitly; CLMM requires manual `collectFee` + re-deposit. Farm-auto-compound vaults (several 3rd-party) automate this. ## Verification tools * [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — `getLpTokenAmount` and `getPositionInfo` return current exposure; diff against your entry basis. * [`sdk-api/rest-api`](/sdk-api/rest-api) — `/pools/info/ids` returns historical price series for IL backtests. * External: dune.com and defillama.com both host IL simulators that can import a Raydium pool id and replay history. ## Pointers * [`algorithms/constant-product`](/algorithms/constant-product) — the invariant IL is derived from. * [`algorithms/clmm-math`](/algorithms/clmm-math) — liquidity-to-amount conversion used in CLMM IL derivation. * [`algorithms/clmm-apr`](/algorithms/clmm-apr) — fee-side estimate to weigh against IL. * [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type) — LP decision guide that uses this page's break-even logic. Sources: * Uniswap V2 whitepaper (CPMM IL derivation). * Uniswap V3 whitepaper (concentrated-liquidity IL amplification). * Panoptic and GFX Labs CLMM IL research, 2024–2025. # Algorithms Source: https://docs.raydium.io/algorithms/index Mathematical deep-dive chapter: the invariants, pricing formulas, and edge cases behind every Raydium product. ## Who this chapter is for Protocol researchers, auditors, and advanced integrators who want to understand the *why* behind the math rather than just call the instructions. Product pages give a summary and defer here for derivations. ## Chapter contents Invariant, marginal price, swap input/output formulas, slippage as a function of trade size. Sqrt-price representation, liquidity ↔ amount conversion, single-tick swap step, fee-growth accumulator, multi-tick swap iteration. LaunchLab's curve formula(s), graduation threshold derivation, continuous vs. discrete pricing. Definitions, how SDK computes `minAmountOut`, what "price impact" means across AMM types, MEV considerations. How a transfer-fee-bearing token changes the effective swap input and output amounts, and how the CPMM/CLMM programs handle it. IL formulas for CPMM and CLMM, worked examples, and the break-even fee APR thresholds every LP should know. How Raydium computes the APR shown on CLMM pools and how to project APR for a prospective range before opening it. ## Writing brief * Use LaTeX-style math sparingly; show formulas as code blocks with inline prose. * Every formula is followed by a worked numeric example. * Every page has a "Where this is implemented" section that points to the relevant line range in the on-chain program source, for auditors. * Do not re-derive the same formulas on product pages — link here. # Slippage and price impact Source: https://docs.raydium.io/algorithms/slippage-and-price-impact Precise definitions of slippage vs price impact, how Raydium's SDK sizes minAmountOut and maxAmountIn, differences across AMM types, and MEV considerations for production routing. ## Two distinct concepts **Price impact** and **slippage** are frequently conflated in UIs but refer to different things. * **Price impact** is a deterministic property of a trade against a specific pool state. Given `(Δin, reserves)`, price impact is fully computable before the trade is submitted. * **Slippage** is the realized difference between the price you *expected* at quote time and the price you *actually got* at execution time. It is a function of latency, concurrent transactions, and block inclusion order — not of the pool math. A 1% quote against an otherwise-idle pool has 0% slippage if it lands in the next block; the 1% was the price impact. That same quote lands 0.2% worse if another trade hits the pool first — the extra 0.2% is slippage. ## Formal definitions ### Price impact ``` p_before = pool.spot_price() p_after = pool.spot_price_if_trade(Δin) applied impact = (p_before − p_after) / p_before // can be signed ``` For a CPMM: `impact ≈ 2 · Δin / reserve_in` for small trades. For CLMM: depends on how many ticks the trade crosses; often flat within the current tick range, jumping at each tick cross. ### Realized slippage ``` quoted_out = amount_out computed at quote time actual_out = amount_out observed on-chain slippage = (quoted_out − actual_out) / quoted_out ``` Slippage is always non-negative (or zero), assuming the quote was honest. A negative value would mean you got *more* than quoted — possible if the pool state moved in your favor between quote and execution. ## Sizing `minAmountOut` and `maxAmountIn` Every Raydium swap takes a slippage-protection bound: * `SwapBaseInput(amount_in, min_amount_out)` — exact-input, lower-bound the output. * `SwapBaseOutput(max_amount_in, amount_out)` — exact-output, upper-bound the input. The SDK computes these as: ```ts theme={null} const computed = raydium..computeAmountOut({ poolInfo, amountIn, mintIn, mintOut, slippage: 0.005, // 0.5% tolerance }); // computed.amountOut — the "expected" quote // computed.minAmountOut — amountOut × (1 − slippage), used as the on-chain bound // computed.priceImpact — deterministic, pool-state-only // computed.fee — total fee charged (all components summed) ``` The slippage tolerance is a **buffer around the price impact**, not the price impact itself. A 0.5% tolerance means "accept at most 0.5% worse than my quote" — independent of whether the price impact was 0.01% (a tiny trade) or 2% (a large trade). For a 2% price-impact trade with 0.5% tolerance, `minAmountOut` is `2.5%` below the pre-trade spot — the sum of impact and tolerance, essentially. ## Recommended slippage tolerances There is no single right number; the right bound depends on: 1. **Pair stability.** Stablecoin-stablecoin pools can safely use 0.1%. Volatile meme-pair pools often need 3–5% just to reliably land. 2. **Trade size.** Larger trades have larger price impacts, so tolerance needs to scale with them to avoid reversion. The SDK's auto-slippage defaults around `max(0.5%, 2 × price_impact)` for this reason. 3. **Block inclusion latency.** Transactions that sit in the mempool for multiple blocks are exposed to more concurrent trades. Jito bundles and priority fees reduce this. Rules of thumb (Raydium UI defaults): | Pair type | Default tolerance | | ------------------------------------------ | ----------------- | | Stable-stable (USDC-USDT, USDC-USDS) | 0.1% | | Stable-major (USDC-SOL, USDC-BTC) | 0.5% | | Major-major (SOL-BTC, SOL-ETH) | 1% | | Volatile (meme tokens, illiquid long-tail) | 3–5% | ## Differences across AMM types ### CPMM Price impact is smooth and continuous (closed-form `2 · Δin / reserve_in`). Slippage tolerance scales linearly with trade size. ### AMM v4 Same curve math as CPMM, but the "effective reserves" include the pool's OpenBook-posted limit orders. In practice this means: * Quoting off raw vault balances *underestimates* reserves and therefore overestimates impact. * The SDK fetches `AmmInfo` and sums `vault + on_book.free + on_book.locked` to get the right number. * Stale OpenBook state (crank blocked) can cause the quoted impact to diverge from on-chain reality. Aggregators routinely pre-crank (permissionless `MonitorStep`) before a large AMM-v4 trade. ### CLMM Price impact is **piecewise**. Within the current tick range, impact is approximately linear in `Δin / L`. Crossing a tick boundary can change `L` discretely, causing a sudden jump in the marginal price. A trade that crosses several sparsely-populated ticks can have much higher impact than the `2 · Δin / reserve` rule of thumb suggests. The SDK's CLMM quote iterates the swap step deterministically to return an exact expected `amountOut`, so `minAmountOut = amountOut · (1 − slippage)` is correct. But the **priceImpact** return value should be interpreted as "the spread between pre-trade spot and post-trade spot", which on CLMM can be much larger than the swap's effective slippage for a user who only cares about `amount_out`. ### LaunchLab curve Similar to CPMM but with an asymmetric curve (quadratic or virtual-reserves). Impact grows faster for late buyers as the curve steepens toward graduation. Pre-buyer UIs should warn when a buy is expected to push the curve more than \~5% of `quote_reserve_target` in one transaction. ## MEV considerations On Solana, MEV extraction against swaps mostly takes the form of **sandwich attacks**: a bot places a back-run transaction that trades after yours, plus a front-run that trades before, both at the same slot. Your trade fills at a worse price than it would have absent the sandwich; the back-run captures the difference. Mitigations: 1. **Tight `minAmountOut`.** Aggressive slippage bounds cause the victim transaction to revert if sandwiched heavily, protecting funds (but wasting gas). On Solana this is standard practice — rejection is cheap. 2. **Jito bundles.** Submitting through Jito with a bundled tip excludes middlemen from reordering your tx. Bundles land as atomic blocks. 3. **Priority fees.** A high priority fee increases the chance your trade lands in the current leader's block before a sandwicher can react. Less robust than bundles, more standard. 4. **Private RPC.** Submitting through a private RPC (or via a validator's direct endpoint) reduces the window during which a mempool sandwicher can observe your transaction. Raydium's SDK does not bundle; integrators typically layer Jito on top. See [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev) for patterns. ## Slippage for multi-hop routes When a swap routes through multiple pools (e.g. `USDC → SOL → RAY`), slippage tolerance should be applied per-hop, not just end-to-end: ```ts theme={null} // Bad: 0.5% applied at the end only, so any intermediate hop sliding fails the second hop. const finalMin = finalAmount * (1 - 0.005); // Better: each hop enforces its own bound. const hop1Min = hop1Amount * (1 - 0.005); const hop2Min = hop2Amount * (1 - 0.005); // End-to-end this is tighter (compound), but atomic — if either hop degrades, the tx reverts early. ``` The SDK's router applies per-hop bounds automatically when you call `raydium.trade.swap`. For custom routers, replicate the pattern. ## Reporting to users Rules-of-thumb for a good swap UI: * Display **both** expected price impact and slippage tolerance separately. * Highlight when price impact exceeds \~2% — "high impact" warning. * Highlight when price impact exceeds tolerance — the transaction is almost certain to revert. * For volatile pairs, offer a "high slippage mode" that relaxes the bound and shows a stronger warning. ## Pointers * [`products/cpmm/math`](/products/cpmm/math), [`products/amm-v4/math`](/products/amm-v4/math), [`products/clmm/math`](/products/clmm/math) — impact derivations per pool type. * [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev) — multi-hop routing + MEV defenses. * [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) — sizing priority fees to reduce slippage. Sources: * Raydium SDK v2 slippage / impact implementation. * Flashbots / Jito on Solana MEV. # Token-2022 transfer fees in swaps Source: https://docs.raydium.io/algorithms/token-2022-transfer-fees How Token-2022's transfer-fee extension changes the pre/post-swap amount accounting, which Raydium products support it, and the edge cases integrators need to handle (asymmetric fees, fee updates, max-fee caps, non-transferable extensions). Support matrix: **CPMM** supports Token-2022 fully, including transfer-fee mints. **CLMM** supports Token-2022 with transfer fees via explicit `SwapV2` accounts. **AMM v4** does not support Token-2022 at all. **LaunchLab** does not support Token-2022 for the base mint (it creates classic SPL mints). **Farm v6** supports Token-2022 on both staking and reward mints. ## What a transfer fee is Token-2022 is the second SPL Token program (`TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` → `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb`). Among its extensions, the **transfer-fee** extension makes every `TransferChecked` on a token mint deduct a fee from the transferred amount. The fee routes to a recipient designated by the mint authority and can be updated by the authority (within bounds). A mint with transfer fee has two relevant parameters: * **`transfer_fee_basis_points`** — the rate (e.g. 100 = 1%). * **`maximum_fee`** — an absolute cap per transfer (so whales moving huge amounts don't pay unbounded fees). A mint can have **two** active transfer-fee configurations at once: the "newer" one (in effect now) and the "older" one (being scheduled out). This is the "epoch transition" design — transfer fee changes take effect across an epoch boundary to avoid surprising in-flight transactions. ## Why this matters for swaps A pool's vaults hold actual balances. When a user calls a Raydium swap: 1. User sends `amount_in` to the pool vault. If the **in-mint** has a transfer fee, the vault receives `amount_in − fee_in`, not `amount_in`. 2. The swap math operates on the vault-received amount. 3. The pool sends `amount_out` to the user's ATA. If the **out-mint** has a transfer fee, the user receives `amount_out − fee_out`, not `amount_out`. If the swap program is naive and uses the raw `amount_in` argument, the invariant check fails because the vault got less than the program thinks. Conversely, if it computes `amount_out` without subtracting the outbound transfer fee, the user sees a shortfall and blames the program. Raydium CPMM and CLMM (via `SwapV2`) handle this by: * **Pre-swap**: computing `in_after_fee = amount_in − transfer_fee_on(amount_in, in_mint)`, and using `in_after_fee` in the curve math. * **Post-swap**: computing `out_gross = amount_out_from_curve`, sending `out_gross` to the user via `TransferChecked` which the Token-2022 program will itself reduce by the transfer fee. The user's `minAmountOut` slippage bound is checked against `out_gross` (what the pool sends), **not** against what the user receives. This is how every major Solana DEX handles Token-2022, and it matters because: * If the pool checked post-fee, a fee-update between quote and execution would cause the trade to revert. * Checking pre-fee pins the failure to the quote's own quality, not to the user's out-of-band fee changes. UIs should subtract the expected Token-2022 transfer fee when showing "You receive" to the user. ## Computing the Token-2022 fee The SPL Token-2022 program exposes a deterministic helper. In Rust: ```rust theme={null} use spl_token_2022::extension::transfer_fee::TransferFeeConfig; use spl_token_2022::extension::StateWithExtensions; let mint_data = ...; let state = StateWithExtensions::::unpack(&mint_data)?; let config = state.get_extension::()?; let epoch = Clock::get()?.epoch; let fee_bp = config.get_epoch_fee(epoch).transfer_fee_basis_points; let max_fee = u64::from(config.get_epoch_fee(epoch).maximum_fee); let fee = (amount as u128 * fee_bp as u128 / 10_000).min(max_fee as u128) as u64; ``` In TypeScript (via `@solana/spl-token`): ```ts theme={null} import { getTransferFeeConfig, getTransferFeeAmount } from "@solana/spl-token"; const config = getTransferFeeConfig(mintAccount); const currentEpochFee = config.olderTransferFee.epoch <= currentEpoch ? config.newerTransferFee : config.olderTransferFee; const rate = currentEpochFee.transferFeeBasisPoints; const maxFee = currentEpochFee.maximumFee; const fee = Math.min(Math.floor(amount * rate / 10_000), Number(maxFee)); ``` ## Adjusted swap formulas (CPMM, exact-input) Let `f_pool` be the pool fee rate, `f_in` the in-mint transfer fee rate, `max_in` its max cap, `f_out` the out-mint transfer fee rate, `max_out` its max cap. ``` transfer_fee_in = min(amount_in · f_in / 10_000, max_in) vault_received = amount_in − transfer_fee_in pool_fee = ceil(vault_received · f_pool / 1_000_000) amount_after = vault_received − pool_fee amount_out_gross = y · amount_after / (x + amount_after) transfer_fee_out = min(amount_out_gross · f_out / 10_000, max_out) user_receives = amount_out_gross − transfer_fee_out ``` Slippage check: `amount_out_gross ≥ min_amount_out` (not `user_receives ≥ min_amount_out`). The user's `minAmountOut` is set by the SDK to `expected_gross · (1 − slippage)` — keep the bound on the "sent" side, not the "received" side. ## Adjusted formulas (CPMM, exact-output) The SDK iterates to find `amount_in` such that `user_receives = amount_out_exact`: ``` # user wants to receive amount_out_exact after transfer fee amount_out_gross = amount_out_exact + transfer_fee_out_for(amount_out_exact) # then solve CPMM exact-output for amount_after: amount_after = ceil(x · amount_out_gross / (y − amount_out_gross)) # then add pool fee: vault_received = ceil(amount_after · 1_000_000 / (1_000_000 − f_pool)) # then add in transfer fee: amount_in = ceil(vault_received · 10_000 / (10_000 − f_in)) # (or iterate — see fee-cap edge case below) ``` The `max_in` / `max_out` caps make the computation non-linear because once the cap is hit the fee stops growing. The SDK's `computeAmountIn` / `computeAmountOut` handle this by iterating if the naive formula would push past the cap. ## Edge cases ### Asymmetric fees (one side has a fee, the other does not) Common in practice. The formulas above already handle this — if one side has `f_in = 0`, the relevant terms collapse. There is no special case in the program. ### Fee updates mid-swap If the mint's transfer fee changes between quote time and execution time, the swap will either land with slightly worse economics (user bears the difference within the slippage tolerance) or revert (the gross output drops below `minAmountOut`). Slippage bounds absorb this; no additional protection is needed. ### Maximum-fee cap Once the trade is large enough to hit `maximum_fee`, the fee saturates and further growth is zero. This makes the effective rate asymptotic to zero for very large trades, which can cause odd pricing curves on deeply illiquid markets. The SDK's `computeAmountOut` accounts for this. ### Non-transferable extension Some Token-2022 mints use the `NonTransferable` extension, which rejects all `Transfer` calls except to and from the mint authority. Such mints **cannot** be used in a Raydium pool at all. `CreatePool` rejects them at init. ### Interest-bearing mints Token-2022 also supports an `InterestBearingConfig` extension that makes balances appear to grow over time. Raydium's pools read raw vault balances (which ignore the interest accrual), so on a pool with an interest-bearing mint, LPs capture the accrued interest as a pure gift whenever they redeem (the vault balance grew faster than the LP supply representation). Integrators should treat this as a non-issue but document it for the LP side. ### Transfer hooks Token-2022's `TransferHook` extension allows arbitrary CPI on every transfer. Raydium CPMM supports these — the swap instruction forwards the hook accounts — but it adds CU overhead and requires the hook to be well-behaved. CLMM `SwapV2` also supports hooks. AMM v4 does not support Token-2022 at all, so the question does not arise. ## Worked example CPMM pool, `x = 1_000_000 USDY, y = 1_000_000 USDC`, pool fee 0.25%. * USDY has a 1% transfer fee, `max_fee = 10_000` (0.01 USDY with 6 decimals). * USDC has no transfer fee. User swaps `amount_in = 1_000 USDY` for USDC (exact-input). ``` transfer_fee_in = min(1_000 · 100 / 10_000, 10_000) = 10 // 1%, well under cap vault_received = 1_000 − 10 = 990 pool_fee = ceil(990 · 2_500 / 1_000_000) = 3 // 0.25% amount_after = 990 − 3 = 987 amount_out_gross = 1_000_000 · 987 / (1_000_000 + 987) = 986_027 / ... ≈ 985.97 ``` ≈ 985.97 USDC. No outbound transfer fee, so the user receives 985.97 USDC. ## Pointers * [`products/cpmm/overview`](/products/cpmm/overview) — CPMM Token-2022 support. * [`products/clmm/instructions`](/products/clmm/instructions) — `SwapV2` vs `Swap` for Token-2022 routing. * [`solana-fundamentals/spl-token-and-token-2022`](/solana-fundamentals/spl-token-and-token-2022) — the general Token-2022 extension model. Sources: * [SPL Token-2022 transfer-fee extension docs](https://spl.solana.com/token-2022/extensions#transfer-fees) * Raydium CPMM program source (`SwapBaseInput` / `SwapBaseOutput` Token-2022 handling). * Raydium CLMM program source (`SwapV2`). # Choosing a wallet Source: https://docs.raydium.io/getting-started/choosing-a-wallet How to pick a Solana wallet for using Raydium: the features that matter, the security choices to make, and what to verify before you sign. Raydium does not endorse any specific wallet provider. Any reputable, non-custodial Solana wallet that supports versioned (V0) transactions can connect to Raydium and sign swaps, LP actions, and farm operations. This page focuses on **what to look for**, not which one to pick — that decision is yours. ## What a Raydium-compatible wallet must do A wallet is a Raydium-compatible wallet if it can do all of the following: * Hold a Solana keypair locally and **never** transmit the private key off-device. * **Sign versioned (V0) transactions.** Raydium swaps use Address Lookup Tables and require V0 support. Wallets that only sign legacy transactions cannot complete a Raydium swap. * **Connect to dApps via the Wallet Adapter standard.** This is what makes the "Connect Wallet" button on raydium.io work. * **Show a clear transaction preview** before you approve, including the program(s) being called and the token movements. * **Support associated token accounts** (creating an ATA the first time you receive a new mint). These are baseline requirements. Any modern Solana wallet meets them. Anything that does not is unsafe to sign Raydium transactions with — period. ## Features that matter beyond the baseline Once a wallet clears the baseline, the differences come down to your workflow and risk profile. Decide which of the following matter to **you**, and pick a wallet that handles them: | Feature | When it matters | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Hardware-wallet pairing** | You are holding more than a casual amount. The software wallet becomes a transaction builder; the hardware device holds the key and signs. Most reputable Solana wallets pair with at least Ledger; some pair with Keystone, SafePal, or other devices. | | **Mobile dApp browser** | You expect to use Raydium primarily from a phone. The wallet's in-app browser is what loads raydium.io and signs transactions on mobile. | | **Multiple-account management** | You separate funds across hot / cold / experimental wallets and want to switch quickly without juggling extensions. | | **Token-2022 extension awareness** | You will interact with pools that use Token-2022 mints (e.g. transfer-fee tokens). A wallet that surfaces these extensions in the signing UI helps you spot unexpected fees before you sign. | | **Priority-fee display** | You trade during congestion and want to see what the wallet has set for compute-budget price before approving. | | **Solana Actions / Blinks support** | You want to use the one-click swap links Raydium publishes on social media. See [`user-flows/referrals-and-blinks`](/user-flows/referrals-and-blinks). | | **Open-source codebase** | You want to be able to audit the wallet client (or rely on the community having done so). | | **Multi-chain accounts** | You hold assets on chains other than Solana and prefer one wallet for all of them. Verify the multi-chain support is mature for the chains you actually use; it varies. | ## Popular Solana wallets The following are well-known wallet projects with active Solana support. They are listed alphabetically; **inclusion is informational, not an endorsement**, and the list is not exhaustive. Verify the official URL before installing — phishing clones routinely buy sponsored ads against the real domain names. ### Software wallets | Wallet | Where it runs | Official site | | ---------- | ---------------------------------------- | -------------------------------------------- | | Phantom | Browser extension, iOS, Android | [phantom.app](https://phantom.app) | | Solflare | Browser extension, iOS, Android, web | [solflare.com](https://solflare.com) | | OKX Wallet | Browser extension, iOS, Android | [okx.com/wallet](https://www.okx.com/wallet) | | Backpack | Browser extension, iOS, Android, desktop | [backpack.app](https://backpack.app) | All four are non-custodial, support Wallet Adapter, sign V0 transactions, and pair with at least one mainstream hardware device. Past that, the differences are workflow-specific — use the criteria in the previous section to decide which fits you. ### Hardware wallets A hardware wallet pairs with one of the software wallets above (the software side becomes the transaction builder; the hardware device holds the key and signs). | Device | Type | Official site | | -------- | ---------------------------- | ------------------------------------ | | Ledger | USB / Bluetooth | [ledger.com](https://www.ledger.com) | | Keystone | Air-gapped (QR-code signing) | [keyst.one](https://keyst.one) | Each software wallet documents which hardware devices it supports; check before buying. ## Hot / cold wallet pattern A common practice that works regardless of which wallet you pick: * A **hot wallet** holds a small amount you accept might be lost — for daily swapping and trying things. * A **cold wallet** (paired with a hardware device) holds anything you would be unhappy to lose. Move funds in only when you actively need them in the hot wallet. Raydium treats each wallet as completely independent: separate positions, separate LP tokens, separate farm rewards, separate history. Switching wallets is one click. ## Where to find wallet options The Raydium UI's "Connect Wallet" modal lists every wallet that has integrated with Raydium via the Wallet Adapter. Browse it to see what is currently supported. Solana's official wallet directory ([`solana.com/ecosystem/explore?categories=wallet`](https://solana.com/ecosystem/explore?categories=wallet)) is another neutral starting point. When you evaluate a wallet, run through this checklist: * [ ] Maintained: recent releases, an active issue tracker, a security-disclosure channel. * [ ] Reviewed: a published audit by a reputable firm, or open-source code with an active community. * [ ] V0-transaction support confirmed (test on devnet before transferring real funds). * [ ] If you plan to use a hardware device: the wallet documents which devices it supports and how the pairing works. * [ ] You found the official download via the project's verified domain — **not** via a sponsored ad or a forwarded link. ## Wallet security basics Regardless of which wallet you pick: * **Write down the seed phrase on paper.** Store the paper in two physically separate locations. **Never** type the seed phrase anywhere except during a wallet recovery on a fresh install. * **Use a password manager** for the wallet's unlock password — and keep that password completely separate from the seed. * **Use a dedicated browser profile** for crypto activity, with the minimum number of extensions installed. Phishing extensions are the single largest drain vector. * **Treat every wallet-connect prompt with suspicion.** Read what you are signing. If a transaction transfers all of a token instead of a specific amount, reject it. * **Bookmark `raydium.io`.** Always navigate from the bookmark, never from a search-engine ad. More security guidance is in [`getting-started/trust-and-safety`](/getting-started/trust-and-safety). ## Where to go next * **Prerequisites:** [`getting-started/what-you-need`](/getting-started/what-you-need). * **Fund the wallet:** [`getting-started/funding-your-wallet`](/getting-started/funding-your-wallet). * **First swap:** [`getting-started/first-swap`](/getting-started/first-swap). * **Safety:** [`getting-started/trust-and-safety`](/getting-started/trust-and-safety). # FAQ Source: https://docs.raydium.io/getting-started/faq Consolidated answers to the questions new Raydium users actually ask: fees, failed transactions, LP tokens, unwrapping SOL, finding transactions, and more. Questions are grouped by topic. For the detailed flow of any specific action, follow the cross-references — this page is the quick-answer index. ## Basics ### What is Raydium? A decentralised exchange (DEX) on Solana. You use it to swap tokens, provide liquidity, farm rewards, and launch tokens — all without an account or a centralised intermediary. See [`introduction/what-is-raydium`](/introduction/what-is-raydium). ### Do I need an account? No. Your Solana wallet is your identity. There's no signup, no KYC (on Raydium itself), no password. ### Is there a mobile app? Raydium doesn't ship its own app. Use the built-in dApp browser of any reputable Solana wallet that has one — raydium.io loads inside it. ### Which wallet should I use? Any reputable, non-custodial Solana wallet that supports versioned (V0) transactions. See [`getting-started/choosing-a-wallet`](/getting-started/choosing-a-wallet) for the features that matter when picking one. ### What is RAY? Raydium's native SPL token. You can stake RAY in the Raydium app and track RAY buybacks from protocol fees. You **do not** need RAY to swap or provide liquidity — it's optional. See [`ray/index`](/ray). ## Fees ### How much does a swap cost? Two kinds of fee: * **Trading fee:** 0.25% on most pools (some tiers offer 0.01%, 0.05%, or 1%). Split between LPs and protocol. Charged on every swap. * **Network fee:** \~0.0005–0.002 SOL (a few cents) for the Solana transaction itself. Paid in SOL. ### What's the "priority fee"? An optional tip to Solana validators to prioritise your transaction during congestion. The Raydium UI has a priority-fee selector (Normal / Turbo / Max). On a quiet network, pick Normal. On a congested network, Turbo prevents your transaction from getting dropped. See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning). ### How much SOL do I need for fees? Budget **0.02 SOL** as a minimum safety buffer. One swap costs \~0.002 SOL; you don't want to run down to exactly zero. ### Why did the trade fee look bigger than 0.25%? If either token is Token-2022 with a transfer fee, the total cost compounds: pool fee + transfer fee + another transfer fee on output. See [`reference/fee-comparison`](/reference/fee-comparison). ## Transactions ### My transaction failed. Why? Most common causes: 1. **Slippage exceeded** — price moved between quote and execution. Raise slippage (0.5% → 1% or 2%) and retry. 2. **Insufficient SOL for fees** — always leave at least 0.02 SOL above your swap amount. 3. **Stale route** — the pool changed (unlikely in short windows). Refresh the page and retry. 4. **Congestion** — Solana was overloaded; raise priority fee. The transaction fee is still paid on a failed transaction (that's how the network charges for the wasted compute). The trade itself does not happen. ### How do I find a transaction? * Your wallet's **Transaction history** tab. * The Raydium UI's **confirmation toast** links to an explorer. * Search your wallet address on [`solscan.io`](https://solscan.io) or [`explorer.solana.com`](https://explorer.solana.com). ### What's the transaction signature / hash? A base58 string (\~88 characters) that uniquely identifies a Solana transaction. It's shown in your wallet log and on explorers. ### Can I cancel a pending transaction? Usually no. Solana transactions either land or expire (in \~30 seconds) — there's no mempool to cancel from. If a transaction is stuck pending, wait 30 seconds; it will either confirm or drop. ### Can I reverse a completed transaction? No. Solana transactions are final. If you sent funds to the wrong address, they're gone unless the recipient voluntarily returns them. ## Tokens ### How do I know a token is legitimate? * Look for the **verified** badge in the Raydium UI. * Cross-check the **mint address** against known canonical addresses ([`reference/program-addresses`](/reference/program-addresses) for protocol addresses; see [`getting-started/trust-and-safety`](/getting-started/trust-and-safety) for common tokens). * Check the token on [`solscan.io`](https://solscan.io) — look at holder count, mint authority, freeze authority. ### What's wrapped SOL? SOL, the native token, is **not** an SPL token — the Solana runtime treats it specially. For pools and many dApps, you need SOL in SPL form ("wrapped SOL", `So11111111111111111111111111111111111111112`). Your wallet and Raydium wrap and unwrap automatically. Occasionally small dust amounts of wSOL remain; they're unwrapped on your next swap. ### I see two versions of the same token (e.g. USDC and USDC.wh). Why? * **USDC** = native USDC issued by Circle. Preferred. * **USDC.wh** = USDC bridged from Ethereum via Wormhole; a different token on-chain. They trade at slightly different prices because they live in separate pools. Raydium will tell you which you're holding; prefer native for most use cases. ### What's an LP token? When you deposit into an AMM pool, you get **LP tokens** (Liquidity Provider tokens) representing your share. Burn them to withdraw your share plus accrued fees. * **CPMM / AMM v4:** LP tokens are fungible SPL tokens. You can stake them in a farm, send them to another wallet, etc. * **CLMM:** your position is a **position NFT**, not a fungible LP token. You can transfer it, but can't stake it in Farm v6. See [`user-flows/add-remove-liquidity`](/user-flows/add-remove-liquidity). ### How do I close an empty token account? Some wallets offer a "Close" or "Burn empty accounts" button. Each closed account returns \~0.002 SOL of rent. Safe for standard SPL tokens; **don't close Token-2022 accounts with transfer hooks** (hook program runs on close). ## LP & farming ### What's impermanent loss? The value gap between holding LP tokens and simply holding the two underlying tokens. For correlated pairs it's negligible; for volatile uncorrelated pairs it can be 10–50% over a year. See [`algorithms/impermanent-loss`](/algorithms/impermanent-loss). ### Should I use CPMM or CLMM? CPMM = set-and-forget, lower fee capture per dollar. CLMM = active management, higher fee capture per dollar, more IL risk. Full decision guide: [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type). ### Can I stake my CLMM position? CLMM positions can't be staked in Farm v6 (farms accept only fungible LP tokens). However, some CLMM pools have built-in farm rewards that accrue to the position directly. See [`products/clmm/fees`](/products/clmm/fees). ### What's Burn & Earn? A mechanism for permanently locking an LP position while retaining a **Fee Key NFT** that continues to collect trading fees. Used heavily by LaunchLab graduated tokens. See [`user-flows/burn-and-earn`](/user-flows/burn-and-earn). ## LaunchLab specifically ### What is LaunchLab? Raydium's token-launch program: a bonding curve that graduates into a full Raydium pool when it hits a funding target. See [`products/launchlab/overview`](/products/launchlab/overview). ### Can I launch a token myself? Yes, via raydium.io/launchlab or a third-party platform built on LaunchLab. See [`user-flows/launch-token-launchlab`](/user-flows/launch-token-launchlab). ### What are creator fees? LaunchLab splits fees into a pre-graduation pool (claimed from a vault) and a post-graduation pool (claimed via a Fee Key NFT from Burn & Earn). See [`products/launchlab/creator-fees`](/products/launchlab/creator-fees). ## Perps ### Does Raydium have perpetual futures? Yes — Raydium Perps, powered by Orderly Network. Gasless CLOB trading with up to 100× leverage. See [`products/perps/index`](/products/perps/index). ### How is it different from spot swap? Perps are leveraged derivatives — you trade price exposure, not the actual token. Gasless (orders don't send a Solana transaction). See [`products/perps/trading-basics`](/products/perps/trading-basics). ## Referrals ### How do referrals work? Share a Raydium swap URL with your wallet as referrer; you earn 1% of every swap through the link. See [`user-flows/referrals-and-blinks`](/user-flows/referrals-and-blinks). ### What are Blinks? Shareable one-click transaction links. When a referral link is posted on X (Twitter), compatible wallets render it as an interactive widget that lets the viewer swap without leaving the post. ## Still stuck? * **Raydium Discord** — [discord.gg/raydium](https://discord.gg/raydium). Public support channels. **Ignore all DMs.** * **GitHub issues** — for bugs in the SDK or on-chain programs. * **Documentation** — you're reading it. Use the search bar. ## Where to go next * [`getting-started/first-swap`](/getting-started/first-swap) — your first trade. * [`user-flows/index`](/user-flows) — step-by-step flows for everything else. * [`reference/glossary`](/reference/glossary) — definitions. # Your first swap Source: https://docs.raydium.io/getting-started/first-swap Step-by-step walk-through of a single SOL → USDC swap on raydium.io/swap. What to enter, what to expect on each screen, and how to verify it worked. **Prerequisite:** a funded Solana wallet with at least \~0.05 SOL. If you don't have one yet, see [`getting-started/what-you-need`](/getting-started/what-you-need). ## Goal Swap a small amount of SOL (say, 0.1 SOL) for USDC. This is the simplest possible Raydium trade and covers the full flow — connecting a wallet, picking tokens, reviewing a quote, signing, and verifying. **What you'll pay:** * 0.25% trading fee (split between LPs and protocol). * A few thousandths of a dollar in network fee. * At current SOL price \~\$160, the total friction cost on a 0.1-SOL trade is well under a dollar. **What you'll have after:** * 0.1 SOL less SOL (plus a small wSOL cleanup dust). * \~16 USDC (give or take, depending on exact price at execution). * A transaction signature you can look up on a Solana explorer. ## Step-by-step ### 1. Open raydium.io/swap Go to [`raydium.io/swap`](https://raydium.io/swap) in your browser. **Verify the URL** — phishing clones exist. The correct domain is exactly `raydium.io`. The swap panel has: * A **"From"** section (which token you're spending). * A **"To"** section (which token you're buying). * A **swap button** in the middle to flip directions. * A **settings gear** in the top-right for slippage and priority fee. * A **share icon** for referral links (see [`user-flows/referrals-and-blinks`](/user-flows/referrals-and-blinks)). ### 2. Connect your wallet Click "Connect Wallet" in the top-right. A modal appears with the Solana wallets currently integrated with Raydium. Pick yours. Your wallet prompts you to approve the connection — this is **read-only**. It allows the site to see your address and suggest transactions. It does not move funds. Confirm. After connecting, the top-right shows your wallet address (truncated) and your SOL balance. ### 3. Pick tokens * **From:** SOL (should be default). * **To:** click the dropdown and type "USDC". The token with the **verified** badge is the canonical USDC (mint `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`). **Avoid lookalikes** with similar names but different mints. ### 4. Enter amount Type `0.1` in the "From" field. The "To" field auto-populates with the quoted output (e.g. `16.2 USDC`). Below the fields, Raydium shows: * **Price:** "1 SOL ≈ 162 USDC" or similar. * **Slippage tolerance:** "0.5%" by default. Acceptable for SOL/USDC. * **Routing:** shows which pool(s) Raydium will route through. For SOL/USDC this is usually a single CLMM pool. * **Price impact:** how much your trade moves the price. For 0.1 SOL, near zero. ### 5. Review and swap Click the **Swap** button. A confirmation modal appears showing: * Minimum amount you'll receive (after slippage). * Estimated network fee. * The "route" breakdown. Click **Confirm**. Your wallet pops up a signing prompt. ### 6. Approve in your wallet The wallet shows: * The transaction details: two or three instructions (maybe wrap SOL, swap, unwrap). * Network fee estimate. * Simulation result (token balance changes). **Read the simulation.** You should see SOL decrease by about 0.1 and USDC increase by about 16.2. If the simulation says otherwise — e.g. "SOL decrease by 1.0" — reject and investigate. Click **Approve / Confirm**. The wallet signs and broadcasts. ### 7. Confirmation Within a few seconds, Raydium shows "Transaction succeeded" with a link to Solscan / Solana Explorer. Your balances update. If it fails, see [Troubleshooting](#troubleshooting) below. ## Verifying it worked ### In your wallet Open the wallet's token list. You should now see: * **USDC** balance: \~16.2 USDC. * **SOL** balance: decreased by \~0.1 SOL plus a small network fee (\~0.0015 SOL). * A recent transaction with status "Success". ### On a Solana explorer Copy the transaction signature (from Raydium's success toast or your wallet's transaction log) and paste into: * [`solscan.io`](https://solscan.io) * [`solana.fm`](https://solana.fm) * [`explorer.solana.com`](https://explorer.solana.com) You'll see: * **Status:** Success. * **Signer:** your wallet address. * **Token balance changes:** your SOL out, your USDC in, plus pool-side transfers. * **Programs invoked:** a Raydium program (CPMM, CLMM, or AMM v4 depending on route), the Token program, the System program. This is final. The transaction cannot be reversed. ## What just happened, one level deeper 1. You asked Raydium to swap X SOL for Y USDC. 2. The frontend (or Trade API) looked up pools, picked the best, and constructed a transaction. 3. The transaction had 2–4 instructions: possibly create a USDC ATA (if you didn't have one yet), wrap SOL into wSOL, call the Raydium swap program, unwrap any residual wSOL. 4. Your wallet signed the transaction with your private key. 5. The RPC broadcast it to Solana validators. 6. Once confirmed, the Raydium program moved tokens: wSOL from you to the pool, USDC from the pool to you, and accrued fees to LPs. For the developer view of this, see [`user-flows/swap`](/user-flows/swap). ## Troubleshooting ### "Transaction simulation failed" or "custom error: 0x1" Usually means insufficient SOL for fees, or the route changed between quote and execution. **Fix:** ensure you have at least 0.02 SOL *above* your swap amount for fees, and try again. ### Slippage tolerance exceeded The pool price moved more than your slippage cap between quote and execution. Common during volatile moments. **Fix:** open settings (gear icon), raise slippage to 1% or 2%, and retry. Don't raise much higher than 2% for majors — that's an invitation for MEV sandwich attacks. ### "Token account does not exist" The destination token (USDC in this case) needs an ATA in your wallet. Raydium's swap transaction normally includes the ATA-creation step, but some old wallets skip this. **Fix:** update your wallet. If it persists, manually create the ATA via your wallet's "Add token" flow, then retry. ### It just "hangs" in pending Solana is congested. Your transaction may be waiting, dropping, or re-trying. **Fix:** wait 30 seconds. If nothing lands, close the modal, open settings, raise the **priority fee** ("Turbo" or "Max" in the Raydium UI), and retry. See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning). ### USDC didn't arrive Check the transaction status on the explorer. * **Success:** USDC is in your wallet. Refresh your wallet's token list. * **Failed:** fees were paid but no swap happened. Check the on-chain error message in the explorer log. * **Not found:** the transaction never landed; your SOL is safe. Retry. ## After your first swap * **Scale up gradually.** Once comfortable, do bigger trades or try other tokens. * **Explore LP:** [`user-flows/add-remove-liquidity`](/user-flows/add-remove-liquidity) and [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type). * **Stake RAY:** earn a share of protocol fees. [`products/farm-staking/overview`](/products/farm-staking/overview). * **Learn referral mechanics:** [`user-flows/referrals-and-blinks`](/user-flows/referrals-and-blinks). ## Common mistakes first-timers make * **Not leaving enough SOL for fees.** Swapping literally all your SOL leaves no room for the fee — the transaction fails. Always keep \~0.02 SOL. * **Slippage set too low on volatile pairs.** 0.5% default is fine for SOL/USDC but tight for memecoins. Raise to 2–5% for those. * **Slippage set too high.** 10%+ slippage on a major pair invites sandwich attacks. Keep it at the minimum that still lets the trade land. * **Swapping the wrong token.** "USDC" vs "USDC.wh" (wrapped from Ethereum via Wormhole) are **different** mints. Always use the one with the **verified** badge unless you specifically want the wrapper. * **Clicking random "airdrop" tokens.** If an unknown token appears in your wallet, **do not interact with it**. Many are scams where interacting triggers a malicious approval. ## Where to go next * [`user-flows/swap`](/user-flows/swap) — technical walkthrough of the same flow. * [`getting-started/trust-and-safety`](/getting-started/trust-and-safety) — avoiding scams. * [`getting-started/faq`](/getting-started/faq) — more answers. # Funding a wallet Source: https://docs.raydium.io/getting-started/funding-your-wallet Three ways to get SOL into your Solana wallet: fiat on-ramps, cross-chain bridges, and centralized exchange withdrawals. Tradeoffs in speed, fees, and friction. **TL;DR.** Cheapest: withdraw SOL from a centralized exchange (Coinbase, Binance, Kraken). Fastest: your wallet's built-in fiat on-ramp (most Solana wallets ship one — typically Moonpay or similar). Most flexible if you have crypto on another chain: bridge via Wormhole or deBridge. ## The three funding paths | Path | Typical cost | Speed | Setup required | | --------------------------------------------- | ----------------------------------------- | ------------------------------------- | ---------------------------------------- | | **Centralized exchange withdrawal** | Flat SOL withdrawal fee (often 0.01 SOL) | 1–2 minutes after withdrawal approved | Existing CEX account with SOL | | **In-wallet fiat on-ramp (Moonpay / Stripe)** | 2–4% of purchase amount | Minutes to hours (KYC-dependent) | Card / bank details entered | | **Cross-chain bridge** | 0.1–0.5% bridge fee + gas on origin chain | Minutes to an hour | Existing wallet / funds on another chain | Pick based on where your money currently lives. ## Path 1: Centralized exchange withdrawal (cheapest) If you already have an account at Coinbase, Binance, Kraken, OKX, Bybit, or any major exchange, this is the path of least friction. ### Steps 1. **Buy SOL on the exchange.** If you already hold some crypto there (USDC, BTC, ETH), swap it to SOL internally — usually 0.1% fee or less. 2. **Copy your wallet address** from your Solana wallet. It is a base58 string around 32–44 characters. **This is your deposit address.** 3. **On the exchange, go to Withdraw.** Select SOL. Paste your address. Select network **Solana** (not BEP20, not ERC20 — if the exchange offers SOL on multiple networks, pick native Solana). 4. **Double-check the first 4 and last 4 characters** of the pasted address match what's in your wallet. Clipboard-hijacking malware is real. 5. **Send a small test amount first** if you are withdrawing a large sum for the first time. 6. **Confirm withdrawal.** It usually arrives in under 2 minutes. ### Fees * **Exchange withdrawal fee:** typically 0.01–0.02 SOL (\~\$1–\$2). A one-time cost. * **No further fees** for the transfer itself on Solana's side. ### Gotchas * **Wrong network.** Some exchanges list SOL as an ERC20 wrapper. Always pick native Solana when withdrawing to a Solana wallet, or the funds will be stuck on the wrong chain. * **Memo/tag field.** Solana does not use memos — ignore this field if the exchange shows it. * **Minimum withdrawal.** Exchanges have minimums (often 0.1 SOL). If your order is smaller, the withdrawal will fail. ## Path 2: In-wallet fiat on-ramp (fastest for first-timers) All three major Solana wallets have built-in fiat buy flows, typically via Moonpay, Transak, or Stripe. ### Steps 1. Open your Solana wallet. 2. Tap "Buy" or "Deposit → Buy crypto". 3. Select SOL and the amount in your local currency. 4. Provide card details (or bank transfer details in some regions). 5. Complete KYC if required — typically a photo of ID + selfie. First-time KYC takes 5–30 minutes. 6. SOL is credited to your wallet once payment clears. ### Fees * **Service fee:** Moonpay is \~3.5–4.5%. Transak is \~2.5–4%. Stripe is \~1.5–2% but only in select countries. * **Spread:** Additional 0.5–1.5% hidden in the quoted price. * **Total cost:** Plan for **\~3–5%** above spot for this path. ### When it makes sense * You have no crypto anywhere yet. * You value speed and simplicity over cost. * You're buying a small amount (\$50–\$500) where the fee dollar value is acceptable. For anything larger, use an exchange. ## Path 3: Cross-chain bridges If you have funds on Ethereum, Arbitrum, Base, BNB Chain, Avalanche, or similar, bridge them to Solana. ### Main bridges * **Wormhole Portal** — [portalbridge.com](https://portalbridge.com). Oldest, widest chain support. Bridges tokens as wrapped versions (e.g. USDC becomes USDC.wh) unless the bridge has a CCTP integration for that asset. Check for native asset deliveries. * **deBridge** — [debridge.finance](https://debridge.finance). Bridge + DEX in one: you can send ETH from Ethereum and receive USDC on Solana. Handles the conversion automatically. * **Allbridge Core** — [allbridge.io](https://allbridge.io). Focuses on stablecoins, native delivery. * **Jupiter's bridge aggregator** — built into [jup.ag/bridge](https://jup.ag/bridge). Routes across multiple bridges. * **Circle's CCTP (for USDC)** — native USDC transfers between supported chains (Ethereum, Base, Avalanche, Arbitrum, Solana). No wrapping, no liquidity risk. Accessible through most bridge UIs. ### Steps (generic) 1. Visit the bridge UI. Connect both your origin-chain wallet (e.g. MetaMask) and your Solana wallet. 2. Select origin chain, destination chain (Solana), input asset, output asset. 3. Review the quote — **fees, slippage, estimated time**. 4. Approve the bridge contract on the origin chain (one-time per asset, costs origin-chain gas). 5. Execute the bridge. Wait for confirmation on both chains. 6. Funds appear in your Solana wallet. ### Fees and timing * **Bridge fee:** 0.1–0.5% typical. * **Origin-chain gas:** varies wildly. Ethereum mainnet gas can be \$5–\$50; Base / Arbitrum are cents. * **Time:** 5–30 minutes on most routes. Some Wormhole routes can take longer if finality is slow on the origin chain. ### Gotchas * **Wrapped vs native USDC on Solana.** "USDC" on Solana can be either native USDC (preferred — `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`) or a Wormhole wrapper (`A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM`). They are **not interchangeable on Raydium** — pools exist for both, but liquidity concentrates on the native mint. Always prefer native. * **Fake bridge sites.** Phishing is common; bookmark bridges you use. * **Minimum amounts.** Bridges often have \$5–\$20 minimums to cover their own fixed costs. ## After funding: verify in the wallet Open your wallet and confirm: 1. **SOL balance** is what you expect. 2. **Network is "Solana Mainnet Beta"** (the default; occasionally testnet is accidentally enabled). 3. **Transaction history** shows the incoming transfer. If SOL doesn't appear within 5 minutes of withdrawal confirmation, check your wallet's transaction log — it may be a pending transfer, a wrong address, or a wrong-network error. ## Reverse flow: off-ramping Getting funds **out** of Raydium / your Solana wallet back to fiat or another chain is the same three paths in reverse: * **Exchange deposit:** send SOL (or any Solana SPL token the exchange lists) to your exchange deposit address. Sell it for fiat. * **In-wallet off-ramp:** some wallets offer Moonpay off-ramps (wallet → bank transfer). Availability varies by country. * **Bridge:** send SOL or bridged USDC back to the origin chain. ## SOL-specific gotchas ### Minimum balance reserved for rent When you use an address for the first time, 0.00203928 SOL is held as "rent" for the account. It cannot be sent. If your wallet tries to send its entire balance, the transaction will fail — leave at least 0.003 SOL in reserve. ### Wrapped SOL (wSOL) Raydium and many protocols use "wrapped SOL" — an SPL-token form of SOL (mint `So11111111111111111111111111111111111111112`). Your wallet automatically wraps and unwraps when you swap in / out of SOL. Occasionally a swap fails with wSOL remaining in your wallet; unwrap via the wallet's UI or your next Raydium swap. ### The "close ATA" reclaim Every token you hold occupies an ATA (Associated Token Account) that cost \~0.002 SOL to open. If you close a token position entirely and don't need the ATA anymore, your wallet may offer a "Close account" button that reclaims the rent. Harmless — do it when you're sure you won't need the token again soon. ## Where to go next * **Do your first swap:** [`getting-started/first-swap`](/getting-started/first-swap). * **Safety:** [`getting-started/trust-and-safety`](/getting-started/trust-and-safety). * **FAQ:** [`getting-started/faq`](/getting-started/faq). # Getting Started Source: https://docs.raydium.io/getting-started/index Zero-to-first-trade guide for non-developer users: pick a wallet, fund it, do your first swap, and avoid the common scams. Everything you need before opening Raydium for the first time. ## Who this chapter is for End users — people who want to **use** Raydium to swap, farm, or provide liquidity, not build on top of it. If you've never used a Solana app before, start here. If you have, skip to [`user-flows`](/user-flows). This chapter is parallel to [`solana-fundamentals`](/solana-fundamentals), which targets developers. Both cover prerequisites, but from different angles: this one is about setting up a wallet and making your first trade; that one is about account models and program invocation. ## Chapter contents The shortest possible prerequisites checklist: a wallet, some SOL, and a browser. Five minutes. Features that matter when picking a Solana wallet for Raydium, security baselines, and the hot/cold wallet pattern. Fiat on-ramps (Moonpay, Stripe), cross-chain bridges (Wormhole, deBridge), and exchange withdrawals. Tradeoffs. Walk-through of a single SOL → USDC swap on raydium.io/swap. Screenshots plus the exact numbers to expect. Rug pulls, fake tokens, phishing sites, seed-phrase scams. The specific things that get Solana users drained. Consolidated answers to the questions new users actually ask: fees, failed transactions, LP tokens, unwrapping SOL. ## The 30-second overview **Raydium is a DEX on Solana.** It lets you swap one token for another without using a centralized exchange. To use it you need: 1. A **Solana wallet** (browser extension or mobile app) — holds your private key and signs transactions. 2. Some **SOL** in that wallet — pays for transaction fees (\~\$0.0005 per swap) and any tokens you want to buy. 3. A browser at [`raydium.io`](https://raydium.io) — the official UI. Everything else — choosing a wallet, funding it, picking your first trade — is covered on the pages above. ## Red flags to watch for from day one Before you deposit any money, internalise these rules: * **Never share your seed phrase.** Not with support, not with a "verify your wallet" site, not over DMs. The only place a seed phrase is typed is during initial wallet setup. * **Only use `raydium.io`.** Phishing copies the UI pixel-perfectly. Bookmark the real URL. * **Trust the token address, not the token name.** Anyone can create a token called "USDC". The real USDC mint is `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`. The UI shows a **verified** badge next to curated mints. * **A dApp should never ask you to "approve" your whole wallet.** Normal transactions sign specific instructions. A blanket approval is a drain. More detail in [`getting-started/trust-and-safety`](/getting-started/trust-and-safety). ## What this chapter is not * **Not a trading tutorial.** Picking tokens and managing risk are your own responsibility; this chapter only covers the Raydium mechanics of executing trades. * **Not financial advice.** "Should I provide liquidity?" is a strategy question; see [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type) for the *mechanics* of that decision. * **Not a Solana deep-dive.** For that, see [`solana-fundamentals`](/solana-fundamentals). ## Where to go next * **First time on Solana:** read [`getting-started/what-you-need`](/getting-started/what-you-need), then [`getting-started/choosing-a-wallet`](/getting-started/choosing-a-wallet), then [`getting-started/first-swap`](/getting-started/first-swap). * **Already have a Solana wallet with SOL:** skip to [`getting-started/first-swap`](/getting-started/first-swap) or [`user-flows/swap`](/user-flows/swap). * **Want to provide liquidity:** [`user-flows/add-remove-liquidity`](/user-flows/add-remove-liquidity) and [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type). * **Want to launch a token:** [`user-flows/launch-token-launchlab`](/user-flows/launch-token-launchlab). # Trust & safety Source: https://docs.raydium.io/getting-started/trust-and-safety Stay safe while using Raydium and other dApps. How to prevent and detect scams that drains Solana Users. **The single most important rule on Solana:** never share your seed phrase. Everything else on this page is secondary to that. # Safety basics ## Verify the URL Always check you're on [`raydium.io`](https://raydium.io) before connecting your wallet. This is Raydium's only domain. Any variation you see on social media, video descriptions, or NFTs is a scam. ## Verify transactions Always double-check what you're signing in your wallet — even on legitimate sites. ## Do your own research Check the token address. Tickers and logos can be set by anyone. The mint address is the true identity of a token. Copy-paste contract addresses instead of typing tickers. Read warnings. Slippage alerts, unknown token prompts, and confirmation dialogs exist for a reason. Ask questions. Join Discord or Telegram for 24/7 support if you're unsure about something. Before using Raydium, make sure you've read and understood the [`disclaimer`](https://raydium.io/docs/disclaimer/). ## The taxonomy of Solana scams Most losses fall into five buckets: 1. **Seed-phrase theft** — you hand over the 12/24 words, usually to a fake support agent or phishing site. 2. **Phishing dApps** — a pixel-perfect clone of Raydium (or another app) harvests signatures that drain the wallet. 3. **Fake tokens** — a token masquerading as USDC / SOL / a legitimate project gets listed, you buy it, you can't sell. 4. **Airdrop bait** — unknown tokens appear in your wallet; interacting with them triggers a drain. 5. **Social engineering** — Discord DMs, Telegram impersonators, "Twitter support". Rug pulls (where a project exits with investor funds) are separate and more about **token selection** than **wallet security**; covered briefly below. ## 1. Seed-phrase theft Your seed phrase (12 or 24 words, given to you once when you created the wallet) is the **master key**. Anyone with the phrase has full control of the wallet forever. **Who asks for your seed phrase?** * **Legitimate services:** no one. Ever. Not Raydium support, not your wallet's support, not "Anthropic", not "Solana Foundation". Full stop. * **Scammers:** everyone, in many guises: * "Verify your wallet" modals on fake sites. * "Wallet Connect support" DMs. * "Anti-phishing team" emails. * "Migrate your wallet" tweets with a link. **Rules** * Write the phrase on paper during setup. Store it offline, in two physically distinct locations (e.g. home safe + bank deposit box). * **Do not** photograph it, screenshot it, paste it into any app, email it, or type it in a "verification" field. * The only place the phrase should ever be typed is when **you** choose to restore the wallet on a new device. * If you think the phrase has been exposed, immediately create a new wallet and move all funds. ## 2. Phishing dApps Scammers host sites that look exactly like raydium.io (or jup.ag, or a popular project). They buy Google ads for brand terms so the phishing site appears above the real one. **What happens** You click the ad, see the familiar UI, connect your wallet, approve what looks like a swap — but the transaction is a drain instruction. Fees and tokens disappear. **Defenses** * **Bookmark the real raydium.io**. Always navigate via the bookmark; never via a search engine ad. * **Check the domain character-by-character.** `raydi︎um.io` (with a lookalike character) vs `raydium.io` is indistinguishable without careful inspection. * **Look at the browser's URL bar.** HTTPS padlock alone is insufficient (phishers get certificates too); read the domain. * **When your wallet pops up a signing prompt, read it.** If the transaction says "Transfer all your USDC to ABC..." instead of "Swap 1 SOL for USDC", reject. * **Use wallets with transaction simulation.** Most modern Solana wallets simulate the transaction and display the projected balance changes before you sign. If the simulation shows losses you didn't expect, reject. **Specifically what a drain looks like** A drain is often **not** a simple "send tokens" instruction. It's usually: * A malicious `TokenProgram::Approve` giving unlimited allowance to the scammer's address. * A signed off-chain message (not a transaction) that later lets the scammer signs transactions on your behalf — some Solana "permit"-style signatures. * A batched set of instructions where one of them is a transfer to the scammer, buried among harmless ones. If a signing prompt has more than 3–4 instructions and you don't recognise the programs involved, reject it. ## 3. Fake tokens Solana has permissionless token creation. Anyone can mint a token called "USDC" with any metadata they want. The Raydium UI guards against this with a **verified-mint check** — but if you paste a token address directly, the verified check does not apply. **How it presents** * A token with symbol "USDC" appears in search results with a price of \$0.99 — looks right. * You swap into it, then try to swap out. The pool has been drained, fee configured at 99%, or the mint has a freeze authority that now freezes your account. * You can't recover the funds. **Defenses** * **Always use the mint with the verified badge** in the Raydium UI for any major token (USDC, USDT, SOL, BTC, ETH). * **Paste the mint address and cross-check it.** Canonical mints: * USDC: `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` * USDT: `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` * wSOL: `So11111111111111111111111111111111111111112` * RAY: `4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R` * **For memecoins, use the original launch source** (usually pump.fun, LaunchLab, or the team's official site). Do not trust random Telegram links. * **Check the mint authority.** Legitimate tokens typically burn the mint authority post-launch — meaning no more tokens can be printed. A fake USDC will have a live mint authority. Solscan shows this on the mint page. ## 4. Airdrop bait Unknown tokens sometimes appear in your wallet. You didn't acquire them — someone sent them to you. **How it works** The token metadata often contains a URL ("Visit xyz-airdrop.com to claim!"). Visiting the site either: * Phishes your seed phrase. * Asks you to connect and sign what looks like a claim but is a drain. * Embeds a malicious token program that charges a fee on every subsequent interaction. **Defenses** * **Ignore unknown tokens.** Do not click them, do not visit their websites, do not try to sell them. * **If the wallet UI allows, hide unknown tokens.** Most Solana wallets ship a "hide spam tokens" or "hide unknown tokens" setting; turn it on. * **Do not interact with the token's metadata URL** even out of curiosity. * **Do not try to "send them back".** Interaction may trigger a malicious instruction. Some users "close" airdrop token accounts to reclaim \~0.002 SOL of rent. This is safe if the token program is the standard SPL Token program — but Token-2022 tokens with transfer hooks can execute arbitrary code on close. Play safe and just leave them. ## 5. Social engineering Almost all crypto scams have a human layer. Common patterns: * **"Raydium support" DMs** on Discord or Telegram. Raydium does not DM users first. Support happens in public channels. * **"Helper" on X** who responds to your panicked "my wallet is stuck" post with a fix. The fix is always to sign something. * **Fake partnership pitches** asking you to connect your wallet to "sign a partnership agreement". * **Impersonators** of well-known Solana devs with a slightly modified handle. **Rules** * **Never unprompted DMs.** If anyone DMs you first claiming to be support, assume they're a scammer. * **Close open support requests.** If you posted "help" in a public channel, the replies in your DMs are the ones to ignore. * **Verify identities out-of-band.** If someone claims to be an Anthropic employee / Raydium team member, check their X handle against the team page on the official site. ## Rug pulls (different category) A "rug pull" is when a project's own team exits with investor funds. This is a **token-selection** problem, not a wallet-security problem. Defenses: * **Check the LP.** Is the liquidity locked (Burn & Earn or an equivalent)? Unlocked LP = the team can pull liquidity any time. * **Check the mint authority.** Is it burned? Live mint authorities = the team can print unlimited supply. * **Check concentration.** If the top-10 holders own 80% of supply, one of them dumping kills the price. * **Prefer tokens that have graduated from LaunchLab** — the mint authority is burned and the LP is in Burn & Earn by default. Tools like Rugcheck, Birdeye's holder views, and Solscan's token page help surface these. See [`reference/glossary`](/reference/glossary) for more on "rug pull", "honeypot", and "LP lock" terminology. ## If something goes wrong ### Seed phrase may be exposed 1. Create a new wallet (different seed phrase). 2. Transfer all funds to the new wallet — SOL and any SPL tokens you care about. 3. Abandon the old wallet forever; do not reuse it even if it "looks fine". ### You signed a malicious transaction and funds are gone 1. Move any remaining funds to a new wallet immediately. 2. Report the scammer's address to Raydium's Discord and via the X report button for publicity. 3. Unfortunately, Solana transactions are final. Funds sent to a scammer are not recoverable absent law-enforcement action. ### You approved unlimited token allowance Visit [revoke.cash](https://revoke.cash) (they support Solana) and revoke suspicious approvals. This stops future drain transactions but does not recover already-drained funds. ### You see a transaction you didn't authorise Your wallet may be compromised. Move funds **immediately**; do not investigate first. ## Quick self-audit Before depositing serious money, check: * [ ] Seed phrase is on paper, in a secure physical location. Not digital. * [ ] raydium.io is bookmarked; you navigate via the bookmark. * [ ] Your wallet simulates transactions before signing. * [ ] For balances >\$1000, you have a hardware wallet or plan to get one. * [ ] You have a **separate browser profile** for crypto activity (fewer extensions). * [ ] You know the canonical mint addresses for the tokens you care about. If any of those are "no", close this tab and fix them before proceeding. ## Where to go next * [`getting-started/first-swap`](/getting-started/first-swap) — your first trade, safely. * [`getting-started/faq`](/getting-started/faq) — common questions. * [`reference/glossary`](/reference/glossary) — terminology. # What you need Source: https://docs.raydium.io/getting-started/what-you-need The absolute minimum prerequisites to use Raydium: a Solana wallet, some SOL, and a browser. Five-minute checklist. **The whole list in one line.** Any reputable Solana wallet, at least \~0.05 SOL to cover fees, and a desktop or mobile browser. That's it. ## The three prerequisites ### 1. A Solana wallet A wallet is a browser extension or mobile app that holds your private key and signs transactions. **It is not a custodial account** — you alone control it, which means losing the seed phrase means losing the funds. Any reputable, non-custodial Solana wallet that supports versioned (V0) transactions works with Raydium. Browse the wallet list at [`solana.com/ecosystem`](https://solana.com/ecosystem) or open the **Connect Wallet** modal on raydium.io to see currently-integrated options. For a checklist of features that matter when picking one, see [`getting-started/choosing-a-wallet`](/getting-started/choosing-a-wallet). Do **not** use a random wallet you have never heard of. Solana wallets have extensive access to your funds; stick to projects with a verifiable history, an active codebase, and a public security-disclosure channel. ### 2. Some SOL You need SOL for two reasons: * **Transaction fees** — every Solana transaction pays a tiny fee (\~0.000005 SOL base + priority) regardless of what it does. Budget \~0.01 SOL (around a dollar at SOL = \$100) to cover a month of casual usage. * **Whatever you want to trade** — if you want to end up with USDC, you start by swapping SOL → USDC. You need enough SOL to fund both the swap and the fees. A comfortable starter balance is **0.05 SOL minimum**, ideally **0.2–1 SOL** depending on what you plan to do. See [`getting-started/funding-your-wallet`](/getting-started/funding-your-wallet) for how to acquire it. ### 3. A browser Raydium's UI at [`raydium.io`](https://raydium.io) runs in any modern browser — Chrome, Firefox, Safari, Arc, Brave. Mobile browsers work too, though the wallet-connection flow is usually smoother through the in-app browser of a wallet that has one. ## What you do NOT need Common misconceptions cleared up early: * **Not** an account on Raydium. Raydium has no accounts, no KYC, no login. Your wallet is your identity. * **Not** email or phone verification. Nothing. Just a wallet. * **Not** a minimum deposit. You can swap \$1 worth if you want (transaction fee applies). * **Not** a specific operating system. macOS, Windows, Linux, iOS, Android all work. * **Not** any Raydium-specific token. RAY is Raydium's protocol token but you don't need it to swap or LP. Some farms pay rewards in RAY; others don't. ## The 5-minute setup Assuming you have none of the above: 1. **Install a Solana wallet** of your choice. Get the installer from the project's verified domain (and double-check the URL — phishing clones run sponsored ads). Follow the wallet's setup flow and **write the seed phrase down on paper**. Do not photograph it, screenshot it, or paste it anywhere. 2. **Acquire SOL.** Options: * If you have crypto on a centralized exchange (e.g. Coinbase, Binance, Kraken): withdraw SOL directly to your wallet address. * If you have only fiat: most wallets ship a built-in fiat on-ramp (typically Moonpay or similar) that accepts cards and bank transfers. * If you have crypto on Ethereum or another chain: bridge via [portalbridge.com](https://portalbridge.com) or [debridge.finance](https://debridge.finance). 3. **Visit raydium.io**, click "Connect Wallet", and approve in your wallet. You are now set up for everything in this chapter. ## Security baseline before you deposit anything Read these once before touching money: * **Seed phrase is the master key.** Anyone who has it has your wallet. No legitimate service ever asks for it. * **Bookmark raydium.io.** Phishing clones buy ads; typing the URL or clicking an ad is the most common way to lose funds. * **Use a dedicated browser profile** for crypto if you can — fewer extensions = smaller attack surface. * **Consider a hardware wallet** for balances above, say, \$1000. Most modern Solana software wallets pair with at least one mainstream hardware device — check your wallet's documentation for the supported list and the pairing flow. More in [`getting-started/trust-and-safety`](/getting-started/trust-and-safety). ## Where to go next * **Pick a wallet:** [`getting-started/choosing-a-wallet`](/getting-started/choosing-a-wallet). * **Fund it:** [`getting-started/funding-your-wallet`](/getting-started/funding-your-wallet). * **Do your first swap:** [`getting-started/first-swap`](/getting-started/first-swap). * **Already familiar with wallets:** [`user-flows/swap`](/user-flows/swap). # Raydium Documentation Source: https://docs.raydium.io/index Reference and guides for Raydium's AMM v4, CPMM, CLMM, Farm, and LaunchLab programs on Solana.

Build, integrate, and trade
on Raydium.

The complete reference for AMM v4, CPMM, CLMM, Farm, LaunchLab, and Perps —
on-chain accounts, math, instructions, fees, SDK, and runnable code.

Choose your track

Three audiences. One source of truth.

Six on-chain programs

Every program. Every instruction. Every account.

Each chapter follows the same template — overview, accounts, math, instructions, fees, runnable code.

AMM v4
Stewardship

Hybrid AMM with OpenBook integration. Deepest liquidity for many pairs.

Instructions
swap · deposit · withdraw
Read AMM v4 reference
CPMM
Default

Standard constant-product. Token-2022 native. Recommended for new pools.

Instructions
initialize · swap · deposit · withdraw
Read CPMM reference
CLMM
v1

Concentrated liquidity. Capital-efficient market making across price ranges.

Instructions
openPosition · increaseLiquidity · swapV2
Read CLMM reference
Farm / Staking
v6

Reward distribution. Up to 5 streams. Token-2022 staking on v6.

Instructions
deposit · harvest · withdraw
Read Farm / Staking reference
LaunchLab
New

Bonding-curve token launches. Configurable curves, creator fees, graduation.

Instructions
initialize · buy · sell · graduate
Read LaunchLab reference
Perps
Beta

Perpetual futures via Orderly Network. REST + WebSocket + Ed25519 signing.

Instructions
deposit · placeOrder · cancelOrder
Read Perps reference

Quick start

Run a real swap in five minutes.

Install the SDK, fund a Devnet wallet, and execute a swap end-to-end. Then drill into the SDK & API chapter for the full builder reference.

quick-start.ts

Built for coding agents

MCP server · llms.txt · per-page copy menu

Wire Raydium docs into Claude Code, Cursor, Windsurf, ChatGPT, or your own RAG pipeline. Every page exposes a copy menu and a stable JSON-LD slug.

Open source

Community-maintained. PRs welcome.

Spotted a stale code sample, a wrong program ID, or a confusing explanation? Open an issue or send a PR. Translators welcome — Chinese skeleton is in place.

© 2026 Raydium Docs contributors · MIT License
# Ecosystem position Source: https://docs.raydium.io/introduction/ecosystem-position Raydium in the broader Solana DeFi landscape — its relationships with OpenBook, aggregators, wallets, oracles, and the Token-2022 ecosystem — and what depending on each means in practice. Raydium doesn't stand alone. It consumes inputs from and produces outputs for a web of other Solana protocols. This page maps those relationships so you understand the boundaries of Raydium's responsibility and where other protocols' behavior affects you as a user or integrator. ## Upstream dependencies ### OpenBook (formerly Serum) * **Role**: central limit orderbook. AMM v4 pools were originally hybrid against OpenBook — swaps could fill against either the pool's AMM curve or the matching OpenBook market — but **the OpenBook integration has since been deactivated**. AMM v4 pools no longer post or maintain orders on OpenBook. * **Where it matters**: historically only for AMM v4 pools; today, none of Raydium's products has a live OpenBook dependency. * **Risk surface**: previously, OpenBook upgrades or outages could affect AMM v4 behavior (one such incident in January 2023). Now that the integration is off, that coupling is removed. CPMM and CLMM never had an OpenBook dependency. * **Status**: OpenBook continues running on Solana; Raydium does not depend on it for any current swap traffic. The OpenBook market accounts referenced by existing AMM v4 pools remain on-chain as inert state for backwards compatibility. ### Solana runtime * **Role**: the execution substrate for all Raydium programs. * **Where it matters**: everywhere. Solana's finality time (\~1s), compute budget limits (1.4M CU per tx), and address lookup tables all directly affect Raydium UX. * **Status**: Solana upgrades periodically; Raydium tracks validator client releases (Agave, Firedancer) and adjusts if behavior changes. ### Token programs (SPL Token and Token-2022) * **Role**: the token primitives all Raydium pools hold. * **SPL Token**: the legacy program; works on all products. * **Token-2022**: extensions-enabled program; works on CPMM, CLMM, Farm v6, not AMM v4. * **Risk surface**: extension behavior (transfer fees, hooks, etc.) can affect pool functionality. See [`security/oracle-and-token-risks`](/security/oracle-and-token-risks). ## Downstream integrations ### Jupiter and other aggregators * **Role**: route trades across many Solana DEXes, including Raydium. * **How it integrates**: Jupiter reads pool state from Raydium's REST API and via direct on-chain calls, then composes swap instructions via the Raydium SDK (or sometimes directly). * **Volume share**: roughly 60% of Jupiter's volume touches Raydium pools at least once. * **Integrator path**: the general aggregator pattern is documented in [`integration-guides/aggregator`](/integration-guides/aggregator). ### Wallets * **Role**: let users swap, track LP positions, and participate in farms without leaving the wallet. * **How they integrate**: * Swap: route via Jupiter or directly through Raydium SDK. * Position display: enumerate user's ATAs and NFT holdings, cross-reference with Raydium's pool list ([`integration-guides/wallet-integration`](/integration-guides/wallet-integration)). * Farm display: call Raydium's `/positions/staking` endpoint. * **User impact**: any wallet with in-app swap likely routes through Raydium without you noticing. ### Lending protocols (Kamino, Marginfi, Drift) * **Role**: borrow/lend protocols that need oracle prices and liquidation execution venues. * **How they integrate**: CLMM ObservationState TWAPs as oracles; direct swaps through Raydium for liquidation collateral conversion. * **Risk**: if a lending protocol uses a spot Raydium price instead of TWAP, they can be exploited by flash-loan-style manipulation (see [`security/attack-vectors`](/security/attack-vectors)). ### MEV searchers and arbitrageurs * **Role**: keep prices in line across Raydium, Orca, Phoenix, and centralized exchanges. * **Effect on users**: tight prices most of the time; sandwich attacks on less-protected swaps. See [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev). ### Bots (market makers, DCA executors, liquidators) * **Role**: provide liquidity (LP on shallow pools), execute scheduled trades, close positions. * **Integration path**: typically Python or Rust bots using [`sdk-api/python-integration`](/sdk-api/python-integration) or Rust CPI wrappers. ## Position in aggregated volume As of April 2026: * Raydium handles \~35–45% of Solana-wide DEX volume on any given day. * Peak market share \~55% during periods when memecoin activity concentrates in LaunchLab + graduated pools. * Low end \~25% during periods of heavy orderbook-driven activity (large institutional flow goes to Phoenix when available). These numbers fluctuate; the point is Raydium is reliably one of the top two venues by volume. ## Token-2022 ecosystem Raydium is one of the first major DEXes to natively support Token-2022. Implications: * New mints with transfer fees (e.g., royalties, fee-to-issuer tokens) can be LP'd. * Transfer-hook mints can exist (though with risk caveats). * Non-transferable and permanent-delegate mints are blocked. As more projects launch under Token-2022 — particularly the "institutional" crowd using regulated mints — Raydium's support positions it as a venue for that class of liquidity. ## Future-looking dependencies Watch-items that could change Raydium's ecosystem position: * **Solana validator diversification**: if Firedancer achieves dominance, performance characteristics could shift. * **Cross-chain bridging**: Raydium has been studying extensions to other L1s/L2s. No deploys yet. * **Jito's evolution**: ongoing integration between block engines and DeFi could change MEV dynamics. * **On-chain orderbooks (Phoenix)**: continued maturity could shift some AMM-ish flow to orderbooks. ## Pointers * [`introduction/what-is-raydium`](/introduction/what-is-raydium) — the elevator pitch. * [`integration-guides/aggregator`](/integration-guides/aggregator) — aggregator integration. * [`integration-guides/wallet-integration`](/integration-guides/wallet-integration) — wallet integration. * [`security/oracle-and-token-risks`](/security/oracle-and-token-risks) — oracle and token dependencies. Sources: * [DefiLlama](https://defillama.com/chain/Solana) — market share stats. * [Jupiter docs](https://docs.jup.ag) — aggregator routing. * Raydium API integration observations (2021–2026). # History and milestones Source: https://docs.raydium.io/introduction/history-and-milestones Raydium's release timeline — product launches, major migrations, significant incidents, and the evolution of the protocol since 2021. Raydium's product portfolio grew over five years through successive deployments. This page is the timeline, with context on why each launch mattered and what it changed. Treat it as a guide to *why* the codebase looks the way it does today. ## 2021 — Founding and AMM v4 ### February 2021 — Mainnet launch Raydium launched as Solana's first hybrid AMM — constant-product pools integrated with Serum's (later OpenBook's) central limit orderbook. The key insight: Solana's fast state access let a DEX combine AMM liquidity with orderbook liquidity in a single swap instruction, producing tighter quotes than either could alone. Initial product: **AMM v4**. Pool math is standard `x × y = k`, but orderbook integration lets the pool pass a trade to OpenBook when the book offers a better price. ### March 2021 — Farm v3 Farm v3 launched alongside AMM v4. Purpose: distribute Raydium's native \$RAY token to LPs who stake their LP tokens, bootstrapping TVL. ### May 2021 — Token launchpad "AcceleRaytor" Early version of a launchpad mechanism. Retired in 2023 when LaunchLab replaced it. ### November 2021 — MadShield audits complete First round of audits on AMM v4 and Farm v3. No critical findings; minor code-quality recommendations addressed. ## 2022 — CLMM ### April 2022 — CLMM public testnet Raydium's CLMM implementation enters public testing. Implementation takes inspiration from Uniswap V3's tick-based model but adapts for Solana's account architecture — ticks stored in chunked `TickArray` PDAs rather than a single sparse mapping. ### August 2022 — CLMM mainnet CLMM launches on Solana mainnet. First concentrated-liquidity DEX on Solana. ### October 2022 — Farm v5 Farm v5 deployed. Changes from v3: supports multiple reward streams per farm, scheduled emission changes, per-stream admin controls. ### November 2022 — OtterSec + MadShield CLMM audits Concurrent audits by two firms. One critical finding (tick-crossing accounting bug in an edge case) fixed pre-deploy. Three high-severity findings fixed. ### December 2022 — Pool authority compromise An AMM v4 pool authority key was compromised; several pools drained. Scope: operational key management, not program bug. Response: * Moved all authority roles to Squads multisig. * Community-approved use of protocol fees earmarked for RAY buybacks to obtain target balances and compensate affected LPs. * Published postmortem on forum. This incident shaped Raydium's operational posture: multisig + public transparency. ## 2023 — Farm v6 and infrastructure hardening ### March 2023 — Farm v6 Farm v6 deployed with Anchor architecture (previous versions were pre-Anchor). Changes: * Cleaner IDL for integration. * Up to 5 reward streams per farm (vs 3 in v5). * Better timestamp-based accounting (v3/v5 used slot counts, leading to drift on leader changes). * Token-2022 support for reward mints. Existing v3/v5 farms continue to run; new farms use v6 exclusively. ### April 2023 — CLMM Token-2022 support CLMM extended to accept Token-2022 mints. Required new `SwapV2` instruction variant (the original `Swap` is deprecated for Token-2022 pools). Re-audited by OtterSec. ### May 2023 — Farm v6 audit by OtterSec Audit complete; several medium findings fixed in v6.1 hotfix. ## 2024 — CPMM and LaunchLab ### March 2024 — CPMM announcement Raydium team announces CPMM as the long-term replacement for AMM v4. Rationale: * AMM v4's OpenBook dependency complicates integration. * AMM v4 protocol-fee structure doesn't incentivize LPs as strongly as a pure-LP-share model. * No Token-2022 support on AMM v4. ### June 2024 — CPMM mainnet CPMM deploys to mainnet. Initial AmmConfigs: 0.01%, 0.25%, 1%. Immediately recommended for new pool creation. Migration path from AMM v4 published; community-led migrations commence over next 6–12 months. ### August 2024 — LaunchLab LaunchLab deploys to mainnet. Succeeds the legacy AcceleRaytor with bonding-curve + CPMM-graduation model. First graduated token: TOKEN1 (graduated within 24 hours of LaunchLab enabling). ### September 2024 — MadShield + OtterSec CPMM audits Concurrent audits complete. One high finding fixed pre-deploy; four mediums addressed; remaining lows tracked in issue tracker. ### October 2024 — LaunchLab audit OtterSec audit of LaunchLab complete. Two high-severity findings addressed (both around graduation edge cases); two medium findings accepted as documented trade-offs. ## 2025 — Product maturity ### January 2025 — CPMM TVL exceeds AMM v4 Community-led migration accelerates; CPMM becomes the dominant constant-product venue. AMM v4 TVL starts declining as LPs migrate to CPMM counterparts. ### July 2025 — CLMM TVL crosses \$1B First time any single Raydium product exceeds \$1B in TVL. ### November 2025 — CPMM minor version update Deployment of CPMM v0.2 (post-timelock) fixes accounting edge case identified by OtterSec re-audit. No user funds impacted. ## Maintenance status | Product | Status | New functionality | | ------------ | ----------------------------------------------------------- | -------------------------------- | | AMM v4 | Fully operational — UI no longer surfaces new-pool creation | None (new pools default to CPMM) | | Farm v3 | Maintenance | None (new farms use Farm v6) | | Farm v5 | Maintenance | None (new farms use Farm v6) | | AcceleRaytor | Retired 2023 | Replaced by LaunchLab | **No sunsets are planned.** Older programs continue running; existing pools/farms accept deposits, trades, and withdrawals indefinitely. Raydium's policy is never to retire a program in a way that strands user funds. ## Key incidents (linked, not re-summarized) * December 2022 — Pool authority compromise — see [`security/audits`](/security/audits). * January 2023 — OpenBook integration freeze — same references. Both resolved with no systemic impact on active users. ## Versioning philosophy Raydium versions products by major number (AMM v4, Farm v6). A new major number means a fresh program ID — old program continues running; new program is a separate deployment. This is distinct from in-place upgrades (which are possible via the upgrade authority but rare). Rationale: users hold positions that depend on specific program behavior. Shipping a new product as a new program gives users an opt-in migration path rather than forcing everyone onto the new version at once. ## Pointers * [`introduction/what-is-raydium`](/introduction/what-is-raydium) — the elevator pitch. * [`introduction/ecosystem-position`](/introduction/ecosystem-position) — Raydium in context. * [`reference/changelog`](/reference/changelog) — version-by-version release notes. * [`security/audits`](/security/audits) — audit history. Sources: * [Raydium on Medium](https://medium.com/@RaydiumProtocol) — product announcements and postmortems. # Navigating the documentation Source: https://docs.raydium.io/introduction/index How to find the right Raydium documentation for your task. Raydium docs are organized by what you are trying to do. Start with the section that matches your role, then use search or the AI assistant when you need a specific concept, product, or error message. ## Start here Get the high-level overview of Raydium, the protocol, and the product surfaces on Raydium.io. Compare Raydium products before going deeper into architecture, shared infrastructure, and migration notes. Learn the app flow for wallets, swaps, liquidity, LaunchLab, Perps, and safety basics. Build against Raydium with SDK walkthroughs, algorithms, addresses, and integration references. ## Common paths Swap, use referral links, trade Raydium Perps, and understand the UI choices that affect execution. Add liquidity, choose a pool type, create pools, claim rewards, and manage migrations. Read about RAY, protocol fees, buybacks, treasury, staking, metrics, and the white paper. Find REST API surfaces, route APIs, Perps APIs, LaunchLab APIs, and endpoint-level details. ## Find precise answers Use the search bar when you know the term, page title, program, or error code you need. Use the AI assistant when you want to ask a question in natural language, compare pages, or find the right guide without knowing the exact page name. For safety or support questions, use the **Support** link in the User Guide space to reach the Raydium community. # What is Raydium Source: https://docs.raydium.io/introduction/what-is-raydium A plain-English introduction to Raydium — what it does, how its product surfaces fit together, and where it sits in the Solana DeFi landscape. Raydium is Solana's most-used decentralized exchange and liquidity protocol. It started as an AMM + OpenBook-integrated DEX in 2021 and has grown into a full suite: classic AMM pools, constant-product market-makers, concentrated liquidity, token launches, and perpetuals on the UI. This page is the pitch — what Raydium does, why it exists, and where to go next. ## In one paragraph Raydium Protocol is the set of permissionless, non-custodial smart contracts that power swaps, pools, liquidity positions, and token launches on Solana. [Raydium.io](https://raydium.io) is the official web app and one-stop shop for using those markets: you can swap tokens, provide liquidity, launch tokens through LaunchLab, and access Raydium Perps from the same UI. Raydium is the largest-TVL DEX on Solana at approximately \$1.8B as of April 2026, routes the majority of Solana volume through its pools, and powers the liquidity that appears in most Solana wallets' in-app swap features. ## Core product surfaces Raydium's core product surfaces cover swaps, liquidity, token launches, incentives, and perpetuals. Some are native Raydium on-chain programs; Raydium Perps is a UI surface powered by Orderly Network. ### AMM v4 — Constant-product The original Raydium product (2021). Constant-product math (`x × y = k`) for pool pricing. It originally also bridged to OpenBook's central limit orderbook so trades could fill against either the AMM or the book, but **the OpenBook integration has since been deactivated** — pools no longer share liquidity to OpenBook, and all current swap traffic flows through the AMM curve only. Still running \~\$300M TVL and fully operational, but **no longer the recommended default for new pools**: Token-2022 isn't supported, and CPMM is cheaper and more flexible for the typical new pair. See [`products/amm-v4/overview`](/products/amm-v4/overview). ### CPMM — Modern constant-product AMM The successor to AMM v4 (2024). Same `x × y = k` math, but simpler and cheaper to use: no OpenBook dependency, Token-2022 native support, \~4× lower creation cost, all swap fees go to LPs by default. The recommended default for new pools. See [`products/cpmm/overview`](/products/cpmm/overview). ### CLMM — Concentrated liquidity Uniswap-V3-style concentrated liquidity (2022). LPs pick a price range; their liquidity is only active when the price is in that range. Same capital → much better quotes for traders → higher fee APR for LPs, at the cost of needing to manage ranges actively. The dominant product for deep-liquidity pools in 2026. See [`products/clmm/overview`](/products/clmm/overview). ### LaunchLab — Token launches **LaunchLab** is Raydium's token launch venue (2025). Projects launch tokens on a bonding curve; buyers trade against the curve directly; once a graduation threshold is met, the curve's accumulated liquidity migrates automatically to a fully-fledged CPMM pool. Primary-market and secondary-market unified. See [`products/launchlab/overview`](/products/launchlab/overview). ## Who uses Raydium * **Retail swappers**: swap tokens via [raydium.io](https://raydium.io) or via any wallet's in-app swap that routes through Solana's aggregator layer. * **LPs**: deposit into pools to earn swap fees; stake in farms for additional rewards. * **Perps traders**: trade perpetual markets through Raydium Perps, powered by Orderly Network. * **Token teams**: launch new tokens via LaunchLab, seed CPMM pools for post-launch liquidity, operate farms to incentivize LP retention. * **Aggregators**: Jupiter and most third-party routers source Raydium as a primary liquidity venue. * **Bots and protocols**: arbitrage, market-making, auto-compounders, lending-protocol liquidations — all compose with Raydium via CPI or direct RPC. ## Where Raydium liquidity shows up You're already using Raydium if you: * Use any Solana wallet's built-in swap — most route through Raydium pools (often via Jupiter). * Trade on Jupiter — \~60% of Jupiter's routed volume hits Raydium pools. * Use Drift, Kamino, MarginFi, or another DeFi protocol — several of them source price feeds and liquidation routes through Raydium CLMM. * Hold a Solana memecoin — many are launched via LaunchLab and live in a graduated CPMM pool. Aggregators and wallets all build on Raydium's public API (`api-v3.raydium.io`) and direct on-chain pool state. Raydium doesn't require any special partnership for integration — it's permissionless. ## Key differentiators Compared to other Solana DEXes: | | Raydium | Orca | Phoenix | Jupiter | | -------------- | ------------------------------------------------------ | ---------- | ---------------- | -------------------------------- | | AMM | CPMM, AMM v4 | N/A | N/A | N/A (aggregator) | | CLMM | CLMM | Whirlpools | N/A | N/A | | Orderbook | None today (AMM v4's OpenBook integration deactivated) | N/A | Native orderbook | N/A | | Token launches | LaunchLab | WaveBreak | N/A | N/A | | Farms | Yes | Yes | N/A | N/A | | TVL (Apr 2026) | \~\$1.8B | \~\$800M | \~\$200M | Aggregator; routes multiple B/mo | Compared to Ethereum DEXes like Uniswap: * **Transaction finality**: \~1 second on Solana vs \~12 seconds on Ethereum. * **Transaction cost**: \$0.001–0.01 typical vs \$5–50 typical on Ethereum mainnet. * **Mempool design**: Solana has no public mempool; Ethereum does. This affects MEV, front-running patterns, and integration design. * **Token-2022**: Raydium supports native Token-2022 extensions (transfer fees, hooks, etc.); Uniswap V3 doesn't have an equivalent on ERC-20. ## Next reads by audience ### If you're a **user** wanting to swap, provide liquidity, or launch a token * [`user-flows/swap`](/user-flows/swap) * [`user-flows/add-remove-liquidity`](/user-flows/add-remove-liquidity) * [`user-flows/launch-token-launchlab`](/user-flows/launch-token-launchlab) ### If you're a **developer** integrating Raydium * [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — the primary integration path. * [`sdk-api/rest-api`](/sdk-api/rest-api) — for pool discovery and metadata. * [`integration-guides/aggregator`](/integration-guides/aggregator) or [`integration-guides/wallet-integration`](/integration-guides/wallet-integration) — depending on your use case. ### If you're a **protocol researcher or auditor** * [`protocol-overview/architecture`](/protocol-overview/architecture) * [`products/cpmm/overview`](/products/cpmm/overview), [`products/clmm/overview`](/products/clmm/overview), etc. * [`algorithms/constant-product`](/algorithms/constant-product), [`algorithms/clmm-math`](/algorithms/clmm-math) * [`security/audits`](/security/audits) ### If you're a **builder composing at the CPI level** * [`sdk-api/rust-cpi`](/sdk-api/rust-cpi) * [`integration-guides/cpi-integration`](/integration-guides/cpi-integration) ## Pointers * [`introduction/history-and-milestones`](/introduction/history-and-milestones) — the timeline. * [`introduction/ecosystem-position`](/introduction/ecosystem-position) — Raydium vs the broader Solana DEX landscape. * [`protocol-overview/architecture`](/protocol-overview/architecture) — how Raydium products fit together technically. Sources: * Raydium TVL: [DefiLlama](https://defillama.com/protocol/raydium). * Volume share: computed from on-chain data. * Aggregator coverage: Jupiter routing stats. # AMM v4 accounts Source: https://docs.raydium.io/products/amm-v4/accounts The full account inventory of an AMM v4 pool — AmmInfo, TargetOrders, OpenOrders, vaults, LP mint, authority — plus the OpenBook accounts the pool is bound to. AMM v4 is significantly more account-heavy than CPMM or CLMM because every operation touches OpenBook state. This page groups the accounts into "pool-owned" and "OpenBook-owned" sections so an integrator can quickly see which side is which. ## Inventory An AMM v4 pool binds to exactly one OpenBook market at creation. The full live picture is: | Category | Account | Owner | Role | | ----------------- | ------------------------------------ | -------------- | --------------------------------------------------------------------------- | | Pool | `AmmInfo` | AMM v4 program | Pool state: fees accrued, status, references to vaults and OpenBook market. | | Pool | `amm_authority` | AMM v4 program | Program-owned PDA that signs vault moves. Shared across all AMM v4 pools. | | Pool | `amm_open_orders` | OpenBook | The pool's OpenBook `OpenOrders` account for this market. | | Pool | `amm_target_orders` | AMM v4 program | Pool-side grid of target limit orders to post back onto OpenBook. | | Pool | `pool_coin_token_account` | SPL Token | Pool's coin-side vault (ATA of `amm_authority`). | | Pool | `pool_pc_token_account` | SPL Token | Pool's pc-side vault. | | Pool | `lp_mint` | SPL Token | Fungible LP mint. | | Pool | `pool_withdraw_queue` | AMM v4 program | Legacy queue for delayed withdrawals; kept zero-length. | | Pool | `pool_temp_lp` | AMM v4 program | Auxiliary LP account used during `Initialize`. | | Market (OpenBook) | `serum_market` | OpenBook | The market itself (base/quote mints, vault signer, etc.). | | Market | `serum_bids`, `serum_asks` | OpenBook | The bid and ask queues. | | Market | `serum_event_queue` | OpenBook | Pending events (fills, cancellations). | | Market | `serum_coin_vault`, `serum_pc_vault` | SPL Token | OpenBook's market-level vaults. | | Market | `serum_vault_signer` | OpenBook | Market-level PDA that signs `serum_*_vault` moves. | Note: "serum" is kept as the prefix in AMM v4's IDL and field names for backward compatibility. It refers to the OpenBook market today. ## `AmmInfo` The pool's root state account. Large (≈ 752 bytes) because it carries both pool and OpenBook references inline. ```rust theme={null} // programs/amm/src/state.rs (abridged; field order / names follow the IDL) pub struct AmmInfo { pub status: u64, // bitmask: swap/deposit/withdraw/crank enabled pub nonce: u64, // bump used to derive amm_authority pub order_num: u64, pub depth: u64, pub coin_decimals: u64, pub pc_decimals: u64, pub state: u64, // internal state machine 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 market pub pc_lot_size: u64, pub min_price_multiplier: u64, pub max_price_multiplier: u64, pub sys_decimal_value: u64, pub fees: Fees, // trade/protocol/fund fee rates pub state_data: StateData, // Pool-owned accounts: pub coin_vault: Pubkey, pub pc_vault: Pubkey, pub coin_vault_mint: Pubkey, pub pc_vault_mint: Pubkey, pub lp_mint: Pubkey, pub open_orders: Pubkey, // pool's OpenOrders on OpenBook pub market: Pubkey, // OpenBook market pub market_program: Pubkey, // OpenBook program ID pub target_orders: Pubkey, pub withdraw_queue: Pubkey, pub lp_vault: Pubkey, // = pool_temp_lp pub owner: Pubkey, // admin (multisig) pub lp_reserve: u64, pub padding: [u64; 3], } pub struct Fees { pub min_separate_numerator: u64, // 5 pub min_separate_denominator: u64, // 10_000 pub trade_fee_numerator: u64, // 25 → used by OpenBook integration pub trade_fee_denominator: u64, // 10_000 pub pnl_numerator: u64, // 12 → protocol's share OF the swap fee pub pnl_denominator: u64, // 100 → so 12/100 = 12% of fee, = 0.03% of volume pub swap_fee_numerator: u64, // 25 → 0.25% gross swap fee pub swap_fee_denominator: u64, // 10_000 } pub struct StateData { pub need_take_pnl_coin: u64, pub need_take_pnl_pc: u64, 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_acc_pc_fee: u64, pub swap_pc_in_amount: u128, pub swap_coin_out_amount: u128, pub swap_acc_coin_fee: u64, } ``` Integrator-facing fields: * **`coin_vault`**, **`pc_vault`** — the pool's SPL Token vaults. `coin` is `token_0` by Serum/OpenBook convention (base), `pc` is `token_1` (quote). * **`coin_decimals`**, **`pc_decimals`** — matching the mints. * **`open_orders`**, **`target_orders`**, **`market`** — must be passed to every swap/deposit/withdraw instruction. * **`fees.swap_fee_numerator / swap_fee_denominator`** — the combined trade fee. Default `25 / 10_000 = 0.25%`. * **`status`** — bitmask gating operations. Admin-settable via `AdminSetStatus`. * **`state_data.need_take_pnl_*`** — delta between gross accrued fees and what's been swept. `TakePnl` zeroes these. ## The OpenBook wiring **No longer active.** AMM v4 pools no longer share liquidity to OpenBook — the limit-order grid has been deactivated. The OpenBook accounts described in this section remain on each pool's `AmmInfo` and are still validated by V1 swap entrypoints (and by `Initialize`, `Deposit`, `Withdraw`) for backwards compatibility, but the on-book state they reference is empty in practice. Use the **V2 swap entrypoints** ([`SwapBaseInV2` / `SwapBaseOutV2`](/products/amm-v4/instructions)) which skip these accounts entirely and represent the canonical execution path today. When you call any **V1** read or write instruction on an AMM v4 pool, you must pass the OpenBook accounts. The program re-derives and validates them, so passing a mismatched set reverts. (The V2 swap variants do not require these accounts at all.) ```ts theme={null} const market = ...; // OpenBook market PublicKey // Fields OpenBook exposes on its market account: const marketDecoded = OpenBookMarket.decode(marketAccountData); const { bids: serumBids, asks: serumAsks, eventQueue: serumEventQueue, requestQueue: serumRequestQueue, baseVault: serumCoinVault, quoteVault: serumPcVault, vaultSignerNonce, } = marketDecoded; const serumVaultSigner = PublicKey.createProgramAddressSync( [market.toBuffer(), u64ToBytes(vaultSignerNonce)], OPENBOOK_PROGRAM_ID, ); ``` The AMM's `amm_open_orders` is an OpenBook-owned account holding the pool's limit-order state on this market: active orders, settled balances, referrers, etc. `amm_target_orders` is AMM-side: it holds the AMM's *intended* grid (price/size for each order slot) so the program can cheaply compare against what's currently posted and place / cancel the diff. ## Authority PDAs There is exactly one `amm_authority` PDA for the entire AMM v4 program. Its seed is trivial (`["amm authority"]`) and its bump is stored on every `AmmInfo`. This authority signs all token moves for all AMM v4 pools. ```ts theme={null} const AMM_V4_PROGRAM_ID = new PublicKey( "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", ); const [ammAuthority] = PublicKey.findProgramAddressSync( [Buffer.from("amm authority")], AMM_V4_PROGRAM_ID, ); ``` There is a separate **pool-scoped** authority derived per pool to sign OpenBook operations (`amm_authority` above actually covers both in this program's design; different versions used different derivation, so check the specific pool's `AmmInfo.nonce` in code). ## Vaults The pool's SPL Token vaults are standard token accounts whose `owner` is `amm_authority`. Not ATAs — their addresses are specific PDAs derived at `Initialize` with `["amm_associated_seed", coin_mint_or_pc_mint, market, amm_id]` seeds. Addresses are stored on `AmmInfo`; derivation is a one-time curiosity. Token-2022 is **not** supported. The program hardcodes SPL Token's program ID for all vault moves. Attempting to bind an AMM v4 pool to a Token-2022 mint fails at `Initialize`. ## LP mint A classic SPL Token mint whose authority is `amm_authority`. Total supply tracks LP ownership of the pool; burning LP returns tokens from both vaults pro-rata. Because AMM v4 predates CPMM, there is no `lp_supply` mirror in the pool state — read the mint's on-chain supply directly. ## Status bitmask `AmmInfo.status` gates operations. Bits (position may differ across program versions — confirm via the source): | Bit | Flag | Effect | | --- | ------------------- | -------------------------------- | | 0 | `SWAP_DISABLED` | `Swap*` rejects. | | 1 | `DEPOSIT_DISABLED` | `Deposit` rejects. | | 2 | `WITHDRAW_DISABLED` | `Withdraw` rejects. | | 3 | `CLMM_LIKE_MIGRATE` | Migration-gate flag used by ops. | The Raydium multisig sets these via `AdminCancelOrders`, `AdminSetParams`, etc. ## Observation / oracle **AMM v4 has no dedicated observation account.** Other protocols that need an on-chain TWAP typically consume OpenBook's book crossings indirectly or read off-chain. If you need a Raydium TWAP with program support, use CPMM or CLMM. ## Deriving a pool's accounts from scratch Because AMM v4 was not designed for deterministic per-pair PDAs (it pre-dates that Solana convention), the canonical `amm_id` is a **seeded keypair** derived with: ``` ammId = createWithSeed( owner: ammAuthority, seed: marketPubkey.toBase58().slice(0, 32), programId: AMM_V4_PROGRAM_ID, ) ``` The same seeded-key pattern applies to `amm_open_orders`, `amm_target_orders`, `amm_withdraw_queue`, `pool_temp_lp`, `pool_coin_token_account`, `pool_pc_token_account`, and `lp_mint`. The SDK and API pre-compute these for you; see `raydium-sdk-v2`'s `Liquidity.getAssociatedPoolKeys`. In practice, integrators read the pool's full account set from `GET https://api-v3.raydium.io/pools/info/ids?ids=` or from the SDK. Hand-deriving is rarely needed. ## Lifecycle quick reference | Event | Accounts created | Accounts destroyed | | ---------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------ | | `Initialize2` | `amm_info`, `amm_open_orders`, `amm_target_orders`, vaults, `lp_mint`, `pool_withdraw_queue`, `pool_temp_lp` | — | | `Deposit` | — (may create user LP ATA) | — | | `Withdraw` | — | — | | `SwapBaseIn` / `SwapBaseOut` | — (may create user ATA) | — | | `TakePnl` | — | — | | `MonitorStep` (crank) | — | — | Pools and their accounts persist indefinitely. Even if liquidity is fully withdrawn, `AmmInfo` stays. ## What to read where * **Math and fee arithmetic**: [`products/amm-v4/math`](/products/amm-v4/math). * **Fee split and how it compares to CPMM/CLMM**: [`products/amm-v4/fees`](/products/amm-v4/fees). * **Instruction account lists**: [`products/amm-v4/instructions`](/products/amm-v4/instructions). * **OpenBook account derivation**: OpenBook program docs ([`github.com/openbook-dex/program`](https://github.com/openbook-dex/program)). Sources: * [Raydium AMM program — `raydium-io/raydium-amm`](https://github.com/raydium-io/raydium-amm) * [`reference/program-addresses`](/reference/program-addresses) for canonical program IDs * OpenBook / Serum protocol for the counterparty accounts # AMM v4 code demos Source: https://docs.raydium.io/products/amm-v4/code-demos TypeScript examples for swapping against and providing liquidity to an existing AMM v4 pool. New pool creation is out of scope — use CPMM instead. **Version banner.** All demos target `@raydium-io/raydium-sdk-v2@0.2.42-alpha` against Solana mainnet-beta, verified 2026-04. Program ID: `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8` (see [`reference/program-addresses`](/reference/program-addresses)). **New-pool creation is not shown here.** The Raydium UI no longer offers AMM v4 pool creation — new pairs default to [CPMM](/products/cpmm/code-demos). The AMM v4 program itself still accepts `Initialize2` on-chain; it just isn't the recommended path. The demos below cover the live-pool operations every integrator still needs: swap, deposit, withdraw. ## Setup ```ts theme={null} import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js"; import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; import fs from "node:fs"; const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta")); const owner = Keypair.fromSecretKey( new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))), ); const raydium = await Raydium.load({ owner, connection, cluster: "mainnet" }); ``` ## Fetch a pool by id ```ts theme={null} import { PublicKey } from "@solana/web3.js"; const poolId = new PublicKey(""); // Pull the SDK-normalized pool object. For AMM v4 this includes the OpenBook // accounts the instruction builders will need. const data = await raydium.liquidity.getPoolInfoFromRpc({ poolId }); const { poolInfo, poolKeys, poolRpcData } = data; console.log("Pair:", poolInfo.mintA.symbol, "/", poolInfo.mintB.symbol); console.log("Version:", poolInfo.version); // 4 for AMM v4 console.log("Market:", poolKeys.marketId.toBase58()); ``` `poolKeys` is the struct the instruction builders consume. It carries every AMM v4 and OpenBook account in the order the program expects. ## Swap (base-in) ```ts theme={null} import BN from "bn.js"; const amountIn = new BN(1_000_000); // 1 USDC (6-decimal quote) const inputMint = new PublicKey(poolInfo.mintB.address); // USDC const slippage = 0.005; const computed = raydium.liquidity.computeAmountOut({ poolInfo, amountIn, mintIn: inputMint, mintOut: new PublicKey(poolInfo.mintA.address), slippage, }); const { execute } = await raydium.liquidity.swap({ poolInfo, poolKeys, amountIn, amountOut: computed.minAmountOut, fixedSide: "in", inputMint, txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Swap tx:", txId); ``` The SDK adds every OpenBook account automatically. Do not try to substitute them by hand — the program validates every slot. ## Swap (base-out) ```ts theme={null} const amountOut = new BN(1_000_000_000); // 1 SOL (9-decimal base) const slippage = 0.005; const computed = raydium.liquidity.computeAmountIn({ poolInfo, amountOut, mintOut: new PublicKey(poolInfo.mintA.address), mintIn: new PublicKey(poolInfo.mintB.address), slippage, }); const { execute } = await raydium.liquidity.swap({ poolInfo, poolKeys, amountIn: computed.maxAmountIn, amountOut, fixedSide: "out", inputMint: new PublicKey(poolInfo.mintB.address), txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` ## Add liquidity ```ts theme={null} const amountA = new BN(100_000_000); // 0.1 SOL const { anotherAmount, maxAnotherAmount } = raydium.liquidity.computePairAmount({ poolInfo, amount: amountA, baseIn: true, slippage: 0.01, }); const { execute } = await raydium.liquidity.addLiquidity({ poolInfo, poolKeys, amountInA: amountA, amountInB: maxAnotherAmount, fixedSide: "a", txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` `fixedSide: "a"` tells the SDK you supplied the exact `amountInA` and that `amountInB` should be at-most `maxAnotherAmount`. The pool's on-book liquidity is settled before the pro-rata math so the deposit ratio matches the freshest reserves. ## Remove liquidity ```ts theme={null} const lpAmount = new BN(50_000); // LP to burn const { execute } = await raydium.liquidity.removeLiquidity({ poolInfo, poolKeys, lpAmount, baseAmountMin: new BN(0), quoteAmountMin: new BN(0), txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` Slippage mins protect against the pool's state shifting between your pre-quote and the land time. ## Compute-unit / priority fee tuning AMM v4 swaps are heavy on compute because every instruction validates the full OpenBook state. A typical swap uses 180k–250k CU depending on how many open orders need settling on the way through. Always pass a compute-unit limit: ```ts theme={null} import { ComputeBudgetProgram } from "@solana/web3.js"; const { execute, innerTransactions } = await raydium.liquidity.swap({ /* ...params... */ computeBudgetConfig: { units: 400_000, microLamports: 50_000, // priority fee }, }); ``` If you omit `computeBudgetConfig`, the SDK may still use its own default; inspect `innerTransactions` to confirm. See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning). ## Direct Rust CPI If you must CPI into AMM v4 from your own Anchor program, you will need to model `SwapBaseIn`'s account list verbatim. A minimal sketch: ```rust theme={null} use anchor_lang::prelude::*; use anchor_lang::solana_program::program::invoke_signed; use anchor_lang::solana_program::instruction::Instruction; const AMM_V4_PROGRAM_ID: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); #[derive(Accounts)] pub struct ProxyAmmV4Swap<'info> { /// CHECK: pub token_program: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub amm: UncheckedAccount<'info>, /// CHECK: pub amm_authority: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub amm_open_orders: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub amm_target_orders: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub pool_coin_token_account: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub pool_pc_token_account: UncheckedAccount<'info>, /// CHECK: pub market_program: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub market: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub market_bids: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub market_asks: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub market_event_queue: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub market_coin_vault: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub market_pc_vault: UncheckedAccount<'info>, /// CHECK: pub market_vault_signer: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub user_source: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub user_dest: UncheckedAccount<'info>, pub user_owner: Signer<'info>, } pub fn proxy_swap( ctx: Context, amount_in: u64, minimum_amount_out: u64, ) -> Result<()> { // Instruction discriminator for SwapBaseIn is 9 on AMM v4. let mut data = vec![9u8]; data.extend_from_slice(&amount_in.to_le_bytes()); data.extend_from_slice(&minimum_amount_out.to_le_bytes()); let ix = Instruction { program_id: AMM_V4_PROGRAM_ID, accounts: vec![ AccountMeta::new_readonly(ctx.accounts.token_program.key(), false), AccountMeta::new(ctx.accounts.amm.key(), false), AccountMeta::new_readonly(ctx.accounts.amm_authority.key(), false), AccountMeta::new(ctx.accounts.amm_open_orders.key(), false), AccountMeta::new(ctx.accounts.amm_target_orders.key(), false), AccountMeta::new(ctx.accounts.pool_coin_token_account.key(), false), AccountMeta::new(ctx.accounts.pool_pc_token_account.key(), false), AccountMeta::new_readonly(ctx.accounts.market_program.key(), false), AccountMeta::new(ctx.accounts.market.key(), false), AccountMeta::new(ctx.accounts.market_bids.key(), false), AccountMeta::new(ctx.accounts.market_asks.key(), false), AccountMeta::new(ctx.accounts.market_event_queue.key(), false), AccountMeta::new(ctx.accounts.market_coin_vault.key(), false), AccountMeta::new(ctx.accounts.market_pc_vault.key(), false), AccountMeta::new_readonly(ctx.accounts.market_vault_signer.key(), false), AccountMeta::new(ctx.accounts.user_source.key(), false), AccountMeta::new(ctx.accounts.user_dest.key(), false), AccountMeta::new_readonly(ctx.accounts.user_owner.key(), true), ], data, }; invoke_signed(&ix, &ctx.accounts.to_account_infos(), &[])?; Ok(()) } ``` AMM v4 does not ship an Anchor crate for CPI. The sketch above uses a manually-constructed `Instruction`. ## Pitfalls * **Missing an OpenBook account.** All 8 OpenBook-side accounts are required on every swap, deposit, and withdraw; the SDK handles this, hand-built instructions often do not. * **Reading raw vault balances.** Does not reflect on-book-escrowed amounts or accrued PnL. Use the SDK's quote or `api-v3.raydium.io/pools/info/ids`. * **OpenBook event queue full.** A pool may revert swaps with `SerumOrderError` when its market's event queue needs cranking. Cranking is permissionless (`MonitorStep` on the market's OpenBook accounts). * **Token-2022 mints.** Not supported. An AMM v4 pool cannot be created against a Token-2022 mint; any Token-2022 pair should be on CPMM or CLMM. ## Where to go next * [`products/amm-v4/instructions`](/products/amm-v4/instructions) — the instruction-level surface behind these demos. * [`user-flows/migrate-amm-v4-to-cpmm`](/user-flows/migrate-amm-v4-to-cpmm) — if you are an LP considering migration. * [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) — priority-fee sizing for heavy AMM v4 swaps. Sources: * [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2) * [Raydium AMM program](https://github.com/raydium-io/raydium-amm) # AMM v4 fees Source: https://docs.raydium.io/products/amm-v4/fees The 0.25% trade fee, its LP / protocol split, PnL generation from OpenBook fills, and how the admin sweeps accrued protocol fees. ## The one published tier Unlike CPMM and CLMM, AMM v4 has **no `AmmConfig` account**. Fees are stored directly on each pool's `AmmInfo.fees` struct and are fixed at pool creation. The defaults that cover essentially every live AMM v4 pool: | On-chain field | Default | Meaning | | --------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ | | `swap_fee_numerator / swap_fee_denominator` | `25 / 10_000` | **Gross trade fee on AMM-path swaps: 0.25% of input volume.** | | `trade_fee_numerator / trade_fee_denominator` | `25 / 10_000` | Used by the OpenBook integration to compute fee-inclusive limit-order pricing. Same `0.25%` as `swap_fee` by default. | | `pnl_numerator / pnl_denominator` | `12 / 100` | **Protocol's share of the swap fee: 12%** — i.e. `0.25% × 12% = 0.03%` of volume. Accrues to `need_take_pnl_*` counters. | | `min_separate_numerator / min_separate_denominator` | `5 / 10_000` | Internal precision floor used by the fee-split rounding logic. | Note that `pnl_numerator / pnl_denominator` is a fraction **of the swap fee**, not of trade volume — a common misreading. The LP share is the complement (`88%` of the fee = `0.22%` of volume) and is implicit; there is no separate "LP share" numerator. A small number of early pools were created with different numerators; always read `AmmInfo.fees` before quoting. There is **no fund-fee** and **no creator-fee** line: these are CPMM/CLMM inventions that did not exist in AMM v4's original fee model. ## How the split is computed On each swap, the pool charges the gross trade fee off the input amount, then apportions: ``` gross_fee = ceil(amount_in * swap_fee_numerator / swap_fee_denominator) // 0.25% of amount_in pnl_portion = gross_fee * pnl_numerator / pnl_denominator // 12% of gross_fee lp_portion = gross_fee − pnl_portion // 88% of gross_fee ``` * `lp_portion` is left in the vault and contributes to the next `k`. LPs capture it by redeeming LP tokens later. * `pnl_portion` increments `AmmInfo.state_data.need_take_pnl_coin` or `need_take_pnl_pc` depending on which side is the swap input. Same invariant-preserving trick as CPMM: the PnL amount physically sits in the vault but is subtracted from the reserves used in the curve, so `TakePnl` moves tokens out without shifting price. ## PnL from OpenBook (historical) **No longer accruing.** The OpenBook integration is deactivated, so the *second* PnL stream described in this section is no longer being generated. `total_pnl_{coin,pc}` counters on existing pools may carry historical values, but no new amounts are added. The 0.03% protocol-fee path (above) is unaffected and still active. Historically, AMM v4 had a *second* fee-like revenue stream: when its limit orders on OpenBook got filled, the pool could be on the taker side of the fill and earn or pay the market's maker/taker spread. These PnL events settled into the pool vaults during `MonitorStep` and the program credited them to `state_data.total_pnl_{coin,pc}` as informational counters. * When the pool's posted grid was correctly calibrated around the curve price, OpenBook fills tended to be **fee-positive** for the pool — the AMM was effectively market-making on OpenBook and earning maker rebates. * When OpenBook paused or the event queue filled, the pool could sit on stale orders that filled at disadvantageous prices, producing negative PnL. This operational coupling was one of the motivations for moving away from the hybrid design. This OpenBook PnL was **not** the same as the 0.03% protocol fee. OpenBook PnL inflated the pool reserves directly (benefiting LPs + protocol proportionally to the fee split), while the 0.03% protocol fee was tagged specifically for admin sweep. With the OpenBook side off, the only fee accrual today is the 0.25% on AMM swaps and its 22/3 split. ## Collection The admin (Raydium multisig) calls `WithdrawPnl` / `TakePnl` to sweep `need_take_pnl_*` into the pool-level "PnL owner" accounts configured on the program's `AmmConfig` (a different, program-scoped config — not the per-pool CPMM-style AmmConfig). Sweeping: 1. Settles any pending OpenBook fills first. *(No-op now that OpenBook is inactive.)* 2. Transfers `need_take_pnl_coin` / `need_take_pnl_pc` from the pool vaults to the PnL destination. 3. Zeroes the counters. The operation does not move the curve. LPs should not see any price change across a `TakePnl` call. ## LP fee redemption There is no dedicated "collect LP fees" instruction. LP fees accumulate in the vaults and inflate `k` over time; LPs realize them by burning LP tokens via `Withdraw`. The value of an LP token grows monotonically as `(coin_reserve_effective, pc_reserve_effective)` grow. ## Visualization: where 1,000 USDC of volume goes On a USDC-heavy swap of \$1,000 against a default-parameter pool: ``` Gross trade fee (0.25%): $2.50 LP share (0.22%): $2.20 → stays in pool, raises k PnL share (0.03%): $0.30 → need_take_pnl_pc, swept by TakePnl Remainder sent to user: $997.50 (minus curve-driven price impact) ``` Compare to CPMM `AmmConfig[0]` (0.25% tier, no creator fee): LP gets \$2.10, protocol \$0.30, fund \$0.10. CPMM introduces the fund line by carving it out of what would have been LP's share in AMM v4's equivalent tier. ## Comparison table | | AMM v4 | CPMM `index=0` | CLMM `index=2` | | ------------------ | -------------------------------- | ------------------------------------------------ | -------------------------------- | | Trade fee | 0.25% | 0.25% | 0.25% | | LP | 0.22% | 0.21% | Varies by emissions | | Protocol | 0.03% | 0.03% | Per tier | | Fund | N/A | 0.01% | Per tier | | Creator (optional) | N/A | 0 by default | N/A | | Where fees sit | Pool vault + need\_take\_pnl\_\* | Pool vault + protocol\_fees\_\* + fund\_fees\_\* | Global + per-tick + per-position | Full matrix in [`reference/fee-comparison`](/reference/fee-comparison). ## Integrator notes * **Quoting.** Fetch `AmmInfo` via the SDK or `api-v3.raydium.io/pools/info/ids`. Do **not** compute your own quote against raw vault balances — the OpenBook-escrowed amounts and the PnL exclusion both pull the effective reserves away from what `getTokenAccountBalance` shows. * **Stale fee parameters.** In principle `SetParams` could change `swap_fee_numerator`, but in practice the Raydium multisig has not changed defaults for any live pool. Still, always read from on-chain state rather than hardcoding. * **No rewards.** AMM v4 does not support on-pool reward emissions. Legacy ecosystem farms (Farm v3 / v5 / v6) are the staking-layer equivalent — see [`products/farm-staking`](/products/farm-staking). ## Where to go next * [`products/amm-v4/math`](/products/amm-v4/math) — the trade-fee derivation inside the curve. * [`products/amm-v4/instructions`](/products/amm-v4/instructions) — `WithdrawPnl` / `SetParams` account lists. * [`reference/fee-comparison`](/reference/fee-comparison) — side-by-side matrix. Sources: * [Raydium AMM program — `raydium-io/raydium-amm`](https://github.com/raydium-io/raydium-amm) * On-chain AMM v4 fee-numerator/denominator fields (verified against mainnet `AmmInfo` accounts). # AMM v4 Source: https://docs.raydium.io/products/amm-v4/index Constant-product AMM. Originally a hybrid design that shared liquidity to OpenBook; the OpenBook integration has since been deactivated. **AMM v4 no longer shares liquidity to OpenBook.** All current swaps execute on the AMM-only path (`SwapBaseInV2` / `SwapBaseOutV2`); the on-chain OpenBook wiring (limit-order grid, `MonitorStep`, market accounts) is preserved in the program but inert — pools no longer post or maintain orders on OpenBook. The OpenBook integration is documented for completeness and historical context, and continues to apply only as account-level invariants (e.g. `AmmInfo` still references the original market), not as an active liquidity source. ## What it is AMM v4 is Raydium's first-generation pool. It maintains a constant-product invariant (xy=k). The pool was originally designed as a **hybrid AMM**: alongside the curve, it posted a Fibonacci-spaced grid of limit orders onto a bound OpenBook market so that pool liquidity was visible to orderbook takers. **That OpenBook side is no longer active** (see warning above); AMM v4 today is, in practice, a pure constant-product AMM with the original OpenBook accounts retained as inert state. It is still the highest-liquidity product on Raydium for many legacy pairs but is **no longer recommended for new pools** — see [CPMM](/products/cpmm) for new deployments. **Program ID:** see [reference/program-addresses](/reference/program-addresses). **Token-2022:** not supported. AMM v4 pools only accept classic SPL tokens. ## Chapter contents Conceptual model: how AMM v4 couples a constant-product curve to an OpenBook market, and why the design exists. AmmInfo, TargetOrders, OpenOrders, LP mint, token vaults, market vault signer. Seeds, field layouts, invariants. xy=k invariant, price calculation. Includes the historical formulas for the (now inert) OpenBook limit-order grid. Initialize, Deposit, Withdraw, SwapBaseIn / V2, SwapBaseOut / V2, MonitorStep (legacy), SetParams, WithdrawPnl. LP fee, protocol fee, trade fee split. Historical PnL handling for orderbook fills. TypeScript (raydium-sdk-v2) and Rust CPI examples for deposit, withdraw, and swap. ## When to read this * You are auditing or integrating against existing AMM v4 pools. * You are building a router that must support v4 liquidity. * You are planning a migration from v4 to CPMM — see also [user-flows/migrate-amm-v4-to-cpmm](/user-flows/migrate-amm-v4-to-cpmm). If you are creating a **new** pool, read [CPMM](/products/cpmm) instead. # AMM v4 instructions Source: https://docs.raydium.io/products/amm-v4/instructions Every AMM v4 instruction with its arguments, the pool and OpenBook accounts it expects, and the pre/post conditions on each. AMM v4 instructions uniformly expect both a **pool side** (AMM v4 program accounts) and a **market side** (OpenBook accounts for the bound market). Omitting or mismatching either set reverts. The account lists below use the field names from the Raydium SDK for clarity; the underlying IDL sometimes uses `serum_*` prefixes. ## Instruction inventory | Group | Instruction | Notes | | -------------- | --------------------- | ---------------------------------------------------------------------------------------- | | Pool lifecycle | `Initialize2` | Current pool-creation instruction (still functional; UI defaults to CPMM for new pools). | | Liquidity | `Deposit` | Add liquidity, receive LP. | | Liquidity | `Withdraw` | Burn LP, receive both sides pro-rata. | | Swap | `SwapBaseIn` | Exact-input swap (full path: vaults + OpenBook). | | Swap | `SwapBaseOut` | Exact-output swap (full path). | | Swap | `SwapBaseInV2` | Exact-input swap that bypasses OpenBook — vaults only, fewer accounts. | | Swap | `SwapBaseOutV2` | Exact-output swap that bypasses OpenBook. | | Upkeep | `SetParams` | Admin: change pool parameters. | | Upkeep | `WithdrawPnl` | Sweep accrued protocol PnL into the PnL-owner accounts. | | Upkeep | `CreateConfigAccount` | Admin: initialize the program-level `AmmConfig` PDA. | | Upkeep | `UpdateConfigAccount` | Admin: change program-level config params. | The SDK exposes builders for the user-facing instructions only. Upkeep instructions are typically invoked by the Raydium keeper. ## `Initialize2` Bootstrap a new AMM v4 pool bound to an existing OpenBook market. **Arguments** ``` nonce: u8 open_time: u64 init_pc_amount: u64 init_coin_amount: u64 ``` **Accounts** (writable `W`, signer `S`) | # | Name | W | S | Notes | | -- | ---------------------------- | - | - | --------------------------------------------- | | 1 | `token_program` | | | SPL Token. | | 2 | `system_program` | | | | | 3 | `rent` | | | | | 4 | `amm` | W | | `AmmInfo` account (seeded key). | | 5 | `amm_authority` | | | Program PDA. | | 6 | `amm_open_orders` | W | | OpenBook `OpenOrders` (seeded). | | 7 | `lp_mint` | W | | | | 8 | `coin_mint` | | | | | 9 | `pc_mint` | | | | | 10 | `pool_coin_token_account` | W | | | | 11 | `pool_pc_token_account` | W | | | | 12 | `pool_withdraw_queue` | W | | | | 13 | `pool_target_orders_account` | W | | | | 14 | `pool_lp_token_account` | W | | Creator's LP ATA. | | 15 | `pool_temp_lp_token_account` | W | | Scratch account. | | 16 | `market_program` | | | OpenBook program. | | 17 | `market` | | | OpenBook market. | | 18 | `user_wallet` | W | S | Creator. Pays rent and funds initial deposit. | | 19 | `user_token_coin` | W | | | | 20 | `user_token_pc` | W | | | **Postconditions** * `lp_supply = sqrt(init_coin_amount × init_pc_amount) − INIT_BURN`, where `INIT_BURN` ≈ 100 LP units are kept out of circulation. * OpenBook orders have **not** yet been posted; the first `MonitorStep` posts the initial grid. **Common errors** — `InvalidInput` (mismatched decimals, non-sorted), `NotApproved`, OpenBook-side `InvalidMarketState`. ## `Deposit` Add liquidity. **Arguments** ``` max_coin_amount: u64 max_pc_amount: u64 base_side: u64 // 0 = base on coin, 1 = base on pc // (some SDK variants also accept other_amount_min) ``` **Accounts** (abridged) | # | Name | W | S | | -- | ------------------------- | - | - | | 1 | `token_program` | | | | 2 | `amm` | W | | | 3 | `amm_authority` | | | | 4 | `amm_open_orders` | | | | 5 | `amm_target_orders` | W | | | 6 | `lp_mint` | W | | | 7 | `pool_coin_token_account` | W | | | 8 | `pool_pc_token_account` | W | | | 9 | `market` | | | | 10 | `user_coin_token_account` | W | | | 11 | `user_pc_token_account` | W | | | 12 | `user_lp_token_account` | W | | | 13 | `user_owner` | | S | **Math** — standard pro-rata. Using the pool's *effective* reserves (vaults + on-book), the SDK computes the coin/pc pair that yields the given LP amount and checks it against `max_*`. Reverts with `ExceededSlippage` if either side exceeds the cap. ## `Withdraw` Burn LP, receive both sides. **Arguments** ``` amount: u64 // LP to burn ``` **Accounts** — like `Deposit` with the direction reversed; `lp_mint` is writable for burn, the user ATAs are receivers. A `MonitorStep`-like settle-from-OpenBook step happens internally before the pro-rata math so the redemption uses fresh reserves. ## `SwapBaseIn` Exact-input swap. Always an **AMM-path** swap (does not route through OpenBook matching). **Use the V2 variants for new code.** Since AMM v4 no longer shares liquidity to OpenBook, the V1 entrypoints (`SwapBaseIn`, `SwapBaseOut`) — which still require the full set of OpenBook accounts for validation — are functionally redundant. New integrations should use [`SwapBaseInV2` / `SwapBaseOutV2`](#swapbaseinv2-swapbaseoutv2), which take a much smaller account list and represent the canonical execution path today. The V1 forms are documented here for completeness and for reading existing on-chain transactions. **Arguments** ``` amount_in: u64 minimum_amount_out: u64 ``` **Accounts** (abridged) | # | Name | W | S | | -- | --------------------------- | - | - | | 1 | `token_program` | | | | 2 | `amm` | W | | | 3 | `amm_authority` | | | | 4 | `amm_open_orders` | W | | | 5 | `amm_target_orders` | W | | | 6 | `pool_coin_token_account` | W | | | 7 | `pool_pc_token_account` | W | | | 8 | `market_program` | | | | 9 | `market` | W | | | 10 | `market_bids` | W | | | 11 | `market_asks` | W | | | 12 | `market_event_queue` | W | | | 13 | `market_coin_vault` | W | | | 14 | `market_pc_vault` | W | | | 15 | `market_vault_signer` | | | | 16 | `user_source_token_account` | W | | | 17 | `user_dest_token_account` | W | | | 18 | `user_owner` | | S | **Math** — see [`products/amm-v4/math`](/products/amm-v4/math). **Preconditions** * `amm.status` allows swap (bit 0 of the status bitmask not set). * `amm.state_data.pool_open_time <= now`. * `amount_in > 0`. * `user_source_token_account` holds at least `amount_in`. **Postconditions** * User loses `amount_in` of source token, gains `amount_out ≥ minimum_amount_out` of dest token. * `state_data.swap_*_in_amount` and `swap_*_out_amount` incremented (for analytics). * `need_take_pnl_*` incremented by the protocol fee share. **Common errors** — `ExceededSlippage`, `InvalidInput`, `InvalidStatus`, `InvalidMarket`. ## `SwapBaseOut` Exact-output, inverse of `SwapBaseIn`. Same accounts. **Arguments** ``` max_amount_in: u64 amount_out: u64 ``` ## `SwapBaseInV2` / `SwapBaseOutV2` Variant swap entrypoints that **skip the OpenBook accounts entirely**. The math is identical to the V1 path, but the account list shrinks to the AMM side only: | # | Name | W | S | | - | --------------------------- | - | - | | 1 | `token_program` | | | | 2 | `amm` | W | | | 3 | `amm_authority` | | | | 4 | `amm_open_orders` | | | | 5 | `pool_coin_token_account` | W | | | 6 | `pool_pc_token_account` | W | | | 7 | `user_source_token_account` | W | | | 8 | `user_dest_token_account` | W | | | 9 | `user_owner` | | S | The pool's effective reserves still account for tokens posted on OpenBook, so quote math is unchanged. Use V2 to save compute and avoid passing the market accounts when you don't need an OpenBook crank in the same transaction. The Raydium router always uses the V2 form when routing through AMM v4. Arguments are the same as the V1 forms (`amount_in / minimum_amount_out` for `SwapBaseInV2`; `max_amount_in / amount_out` for `SwapBaseOutV2`). ## `MonitorStep` (legacy / inert) **No longer cranked.** AMM v4 no longer shares liquidity to OpenBook, so `MonitorStep` has nothing to do — the pool has no orders posted to settle, cancel, or replace. The instruction remains in the on-chain program for backwards compatibility but the Raydium keeper no longer calls it. Calling it manually is effectively a no-op (other than refreshing zeroed-out state) and should not be needed by integrators. Originally this instruction cranked the pool's OpenBook interaction. **Arguments** ``` plan_order_limit: u16 place_order_limit: u16 cancel_order_limit: u16 ``` **Accounts** — everything above for a swap, plus administrative OpenBook accounts. **Original effect** (no longer relevant in practice): * Settled any filled orders (their proceeds moved from `market_coin_vault`/`market_pc_vault` into the pool's vaults via OpenBook CPI). * Cancelled stale orders whose prices or sizes no longer matched `target_orders`. * Posted new orders to close the gap between `target_orders` and `amm_open_orders`. Permissionless. Any account could call it; historically the Raydium keeper did so routinely. ## `WithdrawPnl` / `TakePnl` Admin sweep of accrued protocol fees. **Arguments** * `WithdrawPnl` takes no args; it reads `need_take_pnl_*` and moves those exact amounts. **Accounts** (abridged) | # | Name | W | S | | | -- | ------------------------- | - | - | --------------------------------- | | 1 | `token_program` | | | | | 2 | `amm` | W | | | | 3 | `amm_authority` | | | | | 4 | `amm_config` | | | | | 5 | `amm_open_orders` | W | | | | 6 | `pool_coin_token_account` | W | | | | 7 | `pool_pc_token_account` | W | | | | 8 | `pnl_coin_token_account` | W | | Recipient, stored on `AmmConfig`. | | 9 | `pnl_pc_token_account` | W | | | | 10 | `pnl_owner` | | S | Admin multisig. | | 11 | `market_program` | | | | | 12 | `market` | W | | | | 13 | `market_event_queue` | W | | | | 14 | `market_coin_vault` | W | | | | 15 | `market_pc_vault` | W | | | | 16 | `market_vault_signer` | | | | **Effect** * Transfers `need_take_pnl_coin` from `pool_coin_token_account` to `pnl_coin_token_account`. * Same for pc. * Zeros `need_take_pnl_coin` and `need_take_pnl_pc`. No change to reserves since accrued PnL was already excluded from the invariant. ## `SetParams` Admin param changes: status bitmask, ordering-grid depth, amount waves, fees (rarely), etc. Called by the Raydium multisig. Arguments are a `param: u8` tag + payload, analogous to CPMM's `UpdateAmmConfig`. ## State-change matrix | Instruction | `lp_mint` supply | Vaults | PnL counters | OpenBook | | ------------- | ----------------------------- | ---------------------------------------- | ------------ | ------------------ | | `Initialize2` | init supply minted to creator | `+ init_coin_amount`, `+ init_pc_amount` | 0 | OpenOrders created | | `Deposit` | + | + both | — | settle fills | | `Withdraw` | − | − both | — | settle fills | | `SwapBaseIn` | — | + in, − out | + pnl share | maybe re-post grid | | `SwapBaseOut` | — | + in, − out | + pnl share | maybe re-post grid | | `MonitorStep` | — | settle fills | — | cancel / post | | `WithdrawPnl` | — | − (pnl swept) | 0 | — | | `SetParams` | — | — | — | — | ## Where to go next * [`products/amm-v4/code-demos`](/products/amm-v4/code-demos) — TypeScript examples for swap and LP flows. * [`products/amm-v4/fees`](/products/amm-v4/fees) — `WithdrawPnl` details and the fee split. * [`reference/error-codes`](/reference/error-codes) — forward-reference table (AMM v4 errors are listed on that page). Sources: * [Raydium AMM program — `raydium-io/raydium-amm`](https://github.com/raydium-io/raydium-amm) * Raydium SDK v2 `Liquidity` module * OpenBook program — account validations on the market side # AMM v4 math Source: https://docs.raydium.io/products/amm-v4/math Constant-product invariant with AMM v4's fee convention, reserve-to-orderbook price conversion, target-order grid construction, and the PnL settlement step. ## The invariant The pool maintains `coin_reserve × pc_reserve = k`, where: ``` coin_reserve = coin_vault_balance + orders_posted_on_openbook.base + pending_coin_fill_not_yet_settled pc_reserve = pc_vault_balance + orders_posted_on_openbook.quote + pending_pc_fill_not_yet_settled - accrued_pnl_pc ``` Two things to notice: 1. Reserves include **committed-on-OpenBook** amounts. The AMM's limit orders remain part of its liquidity — they are not "lost" to the order book, just escrowed there. Computing `k` off only the on-chain vault balances underestimates reserves. 2. The PnL accrual (`need_take_pnl_*`) is subtracted so the curve is conserved when the admin sweeps fees. Same principle as CPMM's `protocol_fees_*` exclusion. Every `Swap*` operation enforces `k' ≥ k` after adding the LP's fee share back into the reserves. ## Fee convention AMM v4 uses **ratio fees** (numerator/denominator pairs) rather than the `1/1_000_000` convention of CPMM / CLMM. The on-chain `Fees` struct (see [`Fees::initialize`](https://github.com/raydium-io/raydium-amm/blob/master/program/src/state.rs) in the program source) defaults to: ``` Fees { min_separate_numerator: 5, min_separate_denominator: 10_000, // 5/10_000 = 0.05% trade_fee_numerator: 25, trade_fee_denominator: 10_000, // 25/10_000 = 0.25% — used for OpenBook limit-order pricing pnl_numerator: 12, pnl_denominator: 100, // 12/100 = 12% — protocol's share OF the swap fee swap_fee_numerator: 25, swap_fee_denominator: 10_000, // 25/10_000 = 0.25% — gross fee on AMM-path swaps } ``` Interpretation (published mainnet defaults): * **Total swap fee:** `swap_fee = amount_in × 25 / 10_000 = 0.25%` of the gross input. * **Protocol share:** `pnl_numerator / pnl_denominator = 12 / 100 = 12%` **of the swap fee**, which works out to `0.25% × 12% = 0.03%` of volume. This share accrues to the PnL counters and is swept by `WithdrawPnl`. * **LP share:** the remaining `88%` of the swap fee, which works out to `0.25% × 88% = 0.22%` of volume. Stays in the pool and inflates `k`. * **No fund share.** AMM v4 does not have the CPMM/CLMM fund-fee split. Note that `pnl_numerator / pnl_denominator` is a fraction **of the fee**, not of trade volume — a common misreading of these field names. `trade_fee_numerator / trade_fee_denominator` (also `25 / 10_000`) is a separate field used by the OpenBook integration when computing fee-inclusive prices for the AMM's grid of limit orders; it equals `swap_fee` by default but is read from a different code path. Deviations from these defaults are rare but do exist on a handful of legacy pools; always read the fees from `AmmInfo.fees` before quoting. ## Direct swap math (AMM path) The simplest case: user swaps against the pool's vaults without interacting with OpenBook. The pool's internal reserves (including on-book allocations) are the denominator. **SwapBaseIn (exact input):** ``` amount_after_fee = amount_in − ceil(amount_in × swap_fee_numerator / swap_fee_denominator) amount_out = amount_after_fee × out_reserve / (in_reserve + amount_after_fee) require(amount_out >= minimum_amount_out) ``` The reserves used here are the **effective** reserves. Historically this was `coin_vault_balance + coin_posted_on_openbook + ...` (the AMM's vault plus the tokens it had locked into OpenBook orders). **As of the OpenBook deactivation, the on-book balance is zero**, so the effective reserves equal the raw vault balances. The `MonitorStep` / implicit-settle path that used to refresh the OpenBook side is no longer needed in practice. **SwapBaseOut (exact output):** ``` amount_in_after_fee = ceil(in_reserve × amount_out / (out_reserve − amount_out)) amount_in_gross = ceil(amount_in_after_fee × swap_fee_denominator / (swap_fee_denominator − swap_fee_numerator)) require(amount_in_gross <= maximum_amount_in) ``` ## Order-book interaction (historical) **No longer active.** The grid construction described in this section reflects how AMM v4 *originally* mirrored the curve onto an OpenBook market. The OpenBook integration has been deactivated; pools no longer post or maintain orders on OpenBook. The math below is preserved for context — it explains what the on-chain `target_orders` / `amm_open_orders` accounts were sized for and why the program still validates `MonitorStep`-related parameters even though the keeper no longer cranks them. Separately from user swaps, AMM v4 historically placed a **grid** of limit orders on the OpenBook market. The grid was computed from `AmmInfo` parameters: * **`depth`** — number of price levels per side. * **`amount_wave`** — base unit of size per level. * **`min_size`**, **`coin_lot_size`**, **`pc_lot_size`** — OpenBook market constraints. * **`state_data.swap_acc_coin_fee`**, **`swap_acc_pc_fee`** — cumulative fee counters since last `TakePnl`. The program derives per-level prices by walking out from the current curve price in constant-ratio steps: ``` price_level(k) = curve_price × (1.0001 ^ k) # conceptually size_level(k) = amount_wave × f(depth, k) # tapered by depth ``` The exact prices and sizes are determined by `target_orders` computed in `build_orders` and compared with `amm_open_orders` each `MonitorStep`. Any divergence results in cancellations + new posts. Freshly filled orders on OpenBook settle into the pool vaults on the next operation that refreshes the OpenBook side. Integrators rarely need to compute the grid — the Raydium keeper maintains it — but it is useful to know that: * A pool with significant **on-book** liquidity has that liquidity contributing to `k`, not sitting idle. * A stale OpenBook market (event queue full, cranks blocked) prevents grid updates; the AMM can then quote prices that diverge from the visible order book until the next crank. ## Settlement step (PnL) The 0.03% protocol share accrues into `state_data.need_take_pnl_coin` and `state_data.need_take_pnl_pc`. `TakePnl` moves these amounts out of the vaults to the admin-specified destination, then zeroes the counters. Crucial property: reserves in the invariant are always computed **minus** accrued PnL, so `TakePnl` does not move the curve. This matches the CPMM convention. ## Worked example Pool state: * `coin_reserve = 1_000_000_000_000` (1,000,000 coin-side; 6 decimals) * `pc_reserve = 2_000_000_000_000` (2,000,000 pc-side; 6 decimals) * Fees: default `swap = 25/10_000`, `pnl = 3/10_000`. User: `SwapBaseIn` exact-input `1_000_000_000` coin (1,000 coin). ``` swap_fee = ceil(1_000_000_000 * 25 / 10_000) = 2_500_000 amount_after_fee = 997_500_000 amount_out = amount_after_fee * pc_reserve / (coin_reserve + amount_after_fee) = 997_500_000 * 2_000_000_000_000 / (1_000_000_000_000 + 997_500_000) ≈ 1_995_015_009 (1,995.015 pc) // Of the 2_500_000 swap fee: pnl_share = 2_500_000 * 3 / 25 = 300_000 (goes to protocol via need_take_pnl_coin) lp_share = 2_500_000 * 22 / 25 = 2_200_000 (stays in coin_reserve) new coin_reserve = 1_000_000_000_000 + 1_000_000_000 = 1_001_000_000_000 (of which 300_000 is accrued PnL) curve coin_reserve = 1_001_000_000_000 − 300_000 = 1_000_999_700_000 new pc_reserve = 2_000_000_000_000 − 1_995_015_009 ≈ 1_998_004_984_991 k' = curve_coin_reserve * new_pc_reserve ≈ 2.000_002_701E24 k = 1_000_000_000_000 * 2_000_000_000_000 = 2.0E24 k' > k ✓ ``` The LP share (`2_200_000`) is not broken out anywhere — it is simply the residual that raises `k'`. ## Precision rules * Reserve multiplications use `u128`; final divisions round toward zero. * `swap_fee` rounds up (so the pool does not undercharge). * `amount_in` for `SwapBaseOut` rounds up (so the user does not underpay). * Pools with extreme reserve ratios can hit `ZeroTradingTokens` on very small inputs; same convention as CPMM. ## Limitations vs CPMM * AMM v4's reserves include the OpenBook-escrowed portion, so an integrator cannot quote correctly from `getTokenAccountBalance` alone. Always fetch the full state (vaults + `open_orders.free` + `open_orders.locked`), or use the SDK / API quote. * AMM v4 does not expose a structured on-chain TWAP. External consumers that want an AMM-v4-backed price must compute it themselves from trade logs. * Token-2022 is not supported. ## Where to go next * [`products/amm-v4/instructions`](/products/amm-v4/instructions) — where `SwapBaseIn`, `Deposit`, etc. plug in. * [`products/amm-v4/fees`](/products/amm-v4/fees) — full fee mechanics, `TakePnl` details. * [`algorithms/constant-product`](/algorithms/constant-product) — the shared derivation. Sources: * [Raydium AMM program source — `raydium-io/raydium-amm`](https://github.com/raydium-io/raydium-amm) * Raydium SDK v2 `Liquidity` module # AMM v4 overview Source: https://docs.raydium.io/products/amm-v4/overview Raydium's original constant-product AMM. Originally a hybrid design that mirrored the curve as limit orders onto OpenBook; the OpenBook integration has been deactivated and pools now operate as pure AMMs. **AMM v4 no longer shares liquidity to OpenBook.** Pools have been switched off the hybrid orderbook-grid path; live swaps execute exclusively against the AMM curve via `SwapBaseInV2` / `SwapBaseOutV2`. The `MonitorStep` crank, the on-chain limit-order grid, and the bound OpenBook market accounts remain in the program for backwards compatibility but are no longer maintained as an active liquidity source. Treat AMM v4 today as a pure constant-product AMM; the hybrid material below is preserved for context and for integrators reading older deployments. ## One-paragraph summary AMM v4 is the program Raydium launched with. It maintains a constant-product invariant (`x · y = k`). It was originally designed as a **hybrid AMM**: each pool sat on top of an OpenBook (formerly Serum) limit-order-book market and mirrored portions of its curve as limit orders on that book, so users and aggregators could swap directly against the pool (AMM path) or against the limit orders the pool owned on OpenBook (CLOB path). **The OpenBook side has since been deactivated** — pools no longer post or maintain orders on OpenBook, and all current swap traffic flows through the AMM-only V2 swap entrypoints. Token-2022 is not supported. AMM v4 is **still fully operational as a constant-product AMM** — every pool keeps trading, fees still accrue, LPs still earn — but the UI and SDK default new pool creation to [CPMM](/products/cpmm) because CPMM is cheaper and supports more mint types. ## Hybrid history (context) At launch, standing up a pool alongside Serum's order book risked fragmenting liquidity. The hybrid design published the AMM's curve onto the book as limit orders, so order-book routers saw the pool's depth for free. After OpenBook forked Serum in late 2022, the program continued working against OpenBook with minimal changes. As CPMM and on-chain aggregators matured, the orderbook-side benefit faded, and AMM v4's OpenBook integration was eventually turned off — the curve is now the pool's only execution surface. ## What AMM v4 gives you (that CPMM does not) * **Deep liquidity for legacy pairs.** Major pairs (SOL-USDC, mSOL-USDC, etc.) with large AMM v4 TVL remain the deepest venue for those trades. (Historically, AMM v4 also offered orderbook-side visibility via the OpenBook hybrid mechanism, but that path is no longer active. Any integrator that previously routed through the AMM's OpenBook orders should now route directly via the AMM swap entrypoints.) ## What AMM v4 does not give you * **Token-2022.** Not supported. AMM v4 was written before Token-2022 existed; its account layouts presume classic SPL Token. * **Low account count.** A swap touches the pool, authority, vaults, plus the entire OpenBook event/request queue and bids/asks sides. Even a direct AMM swap (bypassing CLOB) drags all OpenBook accounts along because the instruction validates them. * **Low compute usage.** Account validation alone costs more than a full CPMM swap. * **Default new-pool flow.** The program is still functional and still accepts `Initialize`, but the Raydium UI, SDK, and `api-v3.raydium.io` no longer surface a "create AMM v4 pool" button — the default path for new pools is CPMM. ## How AMM v4 differs from CPMM | Dimension | AMM v4 | CPMM | | ------------------------- | ------------------------------------ | ------------------------- | | Curve | Constant product | Constant product | | OpenBook dependency | Inert (originally yes; now disabled) | No | | Token-2022 | No | Yes | | Account count per V2 swap | \~9 | \~11 | | Compute units per swap | \~80k–120k (V2 path) | \~60k–100k | | LP fee | 0.22% of volume | Varies by `AmmConfig` | | Protocol fee | 0.03% of volume (fixed) | Varies by `AmmConfig` | | Fund fee | None | Yes | | TWAP oracle | No native observation account | `observation` ring buffer | | Default for new pools | No (still accepted) | Yes | More detail on fees: [`products/amm-v4/fees`](/products/amm-v4/fees) and [`reference/fee-comparison`](/reference/fee-comparison). ## Mental model An AMM v4 pool today is a constant-product `x · y = k` AMM. Both vaults are entirely available to the curve — no fraction is committed to OpenBook orders, because the OpenBook integration is no longer active. Operations are **swap** (user ↔ pool, via `SwapBaseInV2` / `SwapBaseOutV2`), **deposit** / **withdraw** (LP ops), and a now-vestigial **crank** (`MonitorStep`, retained on-chain but no longer needed and not posted by Raydium's keeper). *Historical mental model (pre-deactivation):* a fraction of each vault was escrowed as open-book orders; settlement of filled orders happened during swap and LP operations; routed swaps could fill against the pool's own OpenBook orders. None of this is in operation today. ## Why CPMM is the recommended default CPMM drops the OpenBook dependency. The tradeoffs: * CPMM transactions are 2×–3× cheaper in compute. * CPMM supports Token-2022 mints. * Aggregators now integrate CPMM directly via Raydium SDK, so the order-book-visibility benefit is largely moot. * Operational risk: OpenBook outages or pauses degrade AMM v4 pools (`CrankError`, stale orders). CPMM has no such coupling. See [`protocol-overview/versions-and-migration`](/protocol-overview/versions-and-migration) for the migration guidance. ## When AMM v4 is the right choice * You are routing a swap and one of the candidate pools is an AMM v4 pool with the best price. * You are providing or managing liquidity in an existing AMM v4 pool — migration is a choice, not required; the pool continues to function as a pure AMM. * You maintain an integration built against AMM v4 and have no reason to migrate. (Order-book visibility on OpenBook is no longer a reason to pick AMM v4 — that integration is off.) For a fresh constant-product pool, [CPMM](/products/cpmm) is the simpler default. ## Where to go next * [Accounts](/products/amm-v4/accounts) — the AMM's accounts **plus** the OpenBook accounts it serves. * [Math](/products/amm-v4/math) — constant-product swap math with AMM v4's fee convention. * [Instructions](/products/amm-v4/instructions) — the instruction surface: `Initialize`, `Swap`, `Deposit`, `Withdraw`, crank helpers. * [Fees](/products/amm-v4/fees) — the 0.25% split and how it's collected. * [Code demos](/products/amm-v4/code-demos) — TypeScript examples for swap and LP flows. Sources: * [`reference/program-addresses`](/reference/program-addresses) for the canonical program ID * OpenBook program repository for the counterparty accounts this page references # CLMM accounts Source: https://docs.raydium.io/products/clmm/accounts Every account a CLMM pool touches: PoolState, AmmConfig, TickArrayState, TickArrayBitmapExtension, PersonalPositionState, and Observation. This page describes the **layout and role** of each account. Seeds are canonical and listed in [`reference/program-addresses`](/reference/program-addresses). A CLMM pool is more account-heavy than a CPMM pool because liquidity is stored sparsely across the tick range; understanding that sparsity is the bulk of this page. ## Account inventory A live CLMM pool is described by the following account families. All are owned by the CLMM program except the two mints and their vaults. | Account | Purpose | Count per pool | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------- | | `AmmConfig` | Fee tier: trade-fee rate, protocol share, fund share, default tick-spacing. Shared across all pools in this tier. | 1 (shared) | | `PoolState` | Current `sqrt_price_x64`, current tick, total liquidity, fee growth globals, reward info, observation pointer. | 1 | | `TickArrayState` | A block of `TICK_ARRAY_SIZE` adjacent ticks. Only initialized on demand. | 0 ≤ N ≤ range | | `TickArrayBitmapExtension` | Overflow bitmap tracking which tick arrays exist past the inline bitmap in `PoolState`. | 0 or 1 | | `PersonalPositionState` | One per LP position. Stores the range, liquidity, and last-seen fee/reward growth. Authority = NFT owner. | 1 per position | | Position NFT mint | Mint with supply 1, associated with `PersonalPositionState`. Transferring transfers the position. | 1 per position | | `ObservationState` | Ring buffer of price observations for the TWAP. | 1 | | `token_0_vault`, `token_1_vault` | Token accounts holding the pool's balances. Owned by pool authority. | 2 | | `DynamicFeeConfig` | Reusable parameter set for the dynamic-fee mechanism. Pools created via `create_customizable_pool` can opt in. Admin-managed. | shared (per index) | | `LimitOrderState` | One per open limit order. Records owner, tick, side, total amount, settled-output snapshot. | 1 per order | | `LimitOrderNonce` | Per-`(wallet, nonce_index)` counter that derives unique order PDAs. | 1 per (wallet, index) | ## `PoolState` The pool's live state, read on every swap and every position change. ```rust theme={null} // programs/amm/src/states/pool.rs pub struct PoolState { pub bump: [u8; 1], pub amm_config: Pubkey, // fee tier binding pub owner: Pubkey, // admin (multisig) pub token_mint_0: Pubkey, pub token_mint_1: Pubkey, pub token_vault_0: Pubkey, pub token_vault_1: Pubkey, pub observation_key: Pubkey, pub mint_decimals_0: u8, pub mint_decimals_1: u8, pub tick_spacing: u16, // inherited from amm_config at init pub liquidity: u128, // total active (in-range) liquidity pub sqrt_price_x64: u128, // Q64.64 of sqrt(price) pub tick_current: i32, // current tick index pub padding3: u16, pub padding4: u16, // Global fee growth per unit of liquidity, Q64.64. pub fee_growth_global_0_x64: u128, pub fee_growth_global_1_x64: u128, // Accrued-but-not-swept protocol fees (per mint). pub protocol_fees_token_0: u64, pub protocol_fees_token_1: u64, // Reserved padding for future upgrades. pub padding5: [u128; 4], // Status bitmask. Bits 0-5: open-position, decrease-liquidity, // collect-fee, collect-reward, swap, limit-order. A set bit disables // the corresponding operation. pub status: u8, // Fee-collection mode (CollectFeeOn). // 0 = FromInput (deduct fee from the swap input — Uniswap-V3 default) // 1 = Token0Only (always deduct fee from token0 vault) // 2 = Token1Only (always deduct fee from token1 vault) pub fee_on: u8, pub padding: [u8; 6], // Live reward streams (up to REWARD_NUM = 3). pub reward_infos: [RewardInfo; 3], // Inline bitmap tracking initialized tick-arrays in the primary range. pub tick_array_bitmap: [u64; 16], // Reserved padding for future upgrades. pub padding6: [u64; 4], pub fund_fees_token_0: u64, pub fund_fees_token_1: u64, pub open_time: u64, // currently disabled by the program pub recent_epoch: u64, // Per-pool dynamic-fee state. Zero-valued unless the pool was // created with `enable_dynamic_fee = true` via create_customizable_pool. pub dynamic_fee_info: DynamicFeeInfo, // Reserved for future upgrades. pub padding1: [u64; 14], pub padding2: [u64; 32], } ``` Fields you will actually touch: * **`sqrt_price_x64`** and **`tick_current`** are the pool's price state. They are updated together on every swap. `tick_current` is the floor of `log_{1.0001}(price)`. * **`liquidity`** is the **active** liquidity — the sum of `L` values for all positions whose range contains `tick_current`. It changes every time a swap crosses a tick and every time a position is opened/closed/resized. * **`fee_growth_global_{0,1}_x64`** are the cumulative fees earned per unit of liquidity across the entire pool history. Positions read this to compute what's owed to them. * **`tick_spacing`** is locked to the `AmmConfig` at initialization and never changes. It determines which tick indices are even allowed to be position endpoints. * **`tick_array_bitmap`** is an **inline** bitmap covering the commonly used tick range around spot price. For pools whose positions reach far out, overflow tracking lives in the separate `TickArrayBitmapExtension`. * **`fee_on`** is fixed at pool creation. `0` (FromInput) reproduces classic Uniswap-V3 behavior. `1` and `2` route the swap fee to a single side of the book — see [`products/clmm/fees`](/products/clmm/fees) for trade-offs. * **`dynamic_fee_info`** carries volatility state for the dynamic-fee surcharge. When enabled, every swap recomputes a `dynamic_fee_component` on top of `AmmConfig.trade_fee_rate`. Layout is documented under `DynamicFeeInfo` below; pools without dynamic fee leave the entire struct zero. ## `AmmConfig` ```rust theme={null} pub struct AmmConfig { pub bump: u8, pub index: u16, // uses "amm_config"+u16 seed pub owner: Pubkey, // admin pub protocol_fee_rate: u32, // fraction of trade fee to protocol, denom 1e6 pub trade_fee_rate: u32, // trade fee in 1e6ths of volume pub tick_spacing: u16, // default spacing for pools using this config pub fund_fee_rate: u32, // fraction of trade fee to fund, denom 1e6 pub padding_u32: u32, pub fund_owner: Pubkey, pub padding: [u64; 3], } ``` A typical published set of CLMM fee tiers (confirm against `GET https://api-v3.raydium.io/main/clmm-config`): | Index | `trade_fee_rate` | Tick spacing | Typical use | | ----- | ---------------- | ------------ | ----------------------- | | 0 | `100` (0.01%) | 1 | Stable pairs, USDC/USDT | | 1 | `500` (0.05%) | 10 | Correlated blue-chips | | 2 | `2_500` (0.25%) | 60 | Standard pairs | | 3 | `10_000` (1.00%) | 120 | Volatile or long-tail | `protocol_fee_rate` and `fund_fee_rate` are fractions of the trade fee; same convention as CPMM. See [`products/clmm/fees`](/products/clmm/fees). ## `TickArrayState` CLMM does not store a single record per tick. That would be billions of accounts. Instead it groups **`TICK_ARRAY_SIZE` adjacent initialized-or-not ticks** (typically 60 or 88 depending on program version) into a `TickArrayState` that is lazily created on first use. ```rust theme={null} pub const TICK_ARRAY_SIZE: usize = 60; pub const TICK_ARRAY_SIZE_USIZE: usize = 60; pub struct TickArrayState { pub pool_id: Pubkey, pub start_tick_index: i32, // lowest tick in this array pub ticks: [TickState; TICK_ARRAY_SIZE], // 60 entries pub initialized_tick_count: u8, pub recent_epoch: u64, pub padding: [u8; 107], } pub struct TickState { pub tick: i32, pub liquidity_net: i128, // ΔL when crossing this tick upward pub liquidity_gross: u128, // total L referencing this tick pub fee_growth_outside_0_x64: u128, // see math.mdx pub fee_growth_outside_1_x64: u128, pub reward_growths_outside_x64: [u128; 3], // Limit-order bookkeeping. All zero for ticks that have never carried // a limit order. See products/clmm/math for the matching algorithm. pub order_phase: u64, // monotonic FIFO cohort id pub orders_amount: u64, // unfilled tokens in current cohort pub part_filled_orders_remaining: u64, // remaining tokens of partially-filled cohort pub unfilled_ratio_x64: u128, // Q64.64; starts at 1.0 and shrinks as fills occur pub padding: [u32; 3], } ``` The four limit-order fields are zero on any tick that has never been used for a limit order. When orders are opened on a tick, the program tracks them as a sequence of cohorts: * `order_phase` is the cohort id. It increments every time a cohort transitions from "all unfilled" to "partially filled." * `orders_amount` is the input-token total of the current (newest) cohort. * `part_filled_orders_remaining` tracks the previous cohort that is currently being filled by ongoing swaps. * `unfilled_ratio_x64` is a Q64.64 multiplier carried on the cohort: when a swap fills X% of the cohort, the ratio is multiplied by `(1 − X)`. Each open order stores its own `(order_phase, unfilled_ratio_x64)` snapshot at open time, so settle math reduces to comparing snapshots. Rules: * A position endpoint tick *t* must satisfy `t % tick_spacing == 0`. The program rejects off-spacing positions. * The tick's **array** is located at `floor(t / (TICK_ARRAY_SIZE * tick_spacing)) * (TICK_ARRAY_SIZE * tick_spacing)`. * A tick array is initialized lazily: the first position or swap that touches an uninitialized array creates it, paying the rent. * A tick array is **never closed** by the program. Once allocated it persists for the life of the pool, even after every tick inside it returns to `liquidity_gross == 0`. Subsequent positions and swaps reuse the existing account at no extra rent. There is no `ClosePosition`-driven cleanup path for tick arrays. ### `TickArrayBitmapExtension` `PoolState.tick_array_bitmap` (inline) covers the "close to spot" range — ±1,024 tick arrays. Outside that range (for extreme tick values), the program maintains an extension account: ```rust theme={null} pub struct TickArrayBitmapExtension { pub pool_id: Pubkey, pub positive_tick_array_bitmap: [[u64; 8]; 14], pub negative_tick_array_bitmap: [[u64; 8]; 14], } ``` If your position's range is "normal", you never think about the extension account. Full-range positions (e.g., `(MIN_TICK, MAX_TICK)`) require it; the SDK resolves it for you. ## Positions A CLMM position is a **bundle** of three accounts plus a mint: ### Position NFT mint An SPL Token mint with supply 1. The mint's address is a deterministic PDA; the position NFT in the owner's wallet is just an ATA holding that single token. Transferring the NFT is how a position changes hands — the program keys authorization to the **current holder of the NFT's ATA balance**, not to a Pubkey stored in state. ### `PersonalPositionState` One per open position. Keyed off the NFT mint. ```rust theme={null} pub struct PersonalPositionState { pub bump: [u8; 1], pub nft_mint: Pubkey, // this position's NFT mint pub pool_id: Pubkey, pub tick_lower_index: i32, pub tick_upper_index: i32, pub liquidity: u128, // this position's L // Fee-growth snapshots at last time the position was touched. pub fee_growth_inside_0_last_x64: u128, pub fee_growth_inside_1_last_x64: u128, pub token_fees_owed_0: u64, // accrued since last collect pub token_fees_owed_1: u64, pub reward_infos: [PositionRewardInfo; 3], pub recent_epoch: u64, pub padding: [u64; 7], } pub struct PositionRewardInfo { pub growth_inside_last_x64: u128, pub reward_amount_owed: u64, } ``` ### `ProtocolPositionState` (deprecated) Older CLMM releases stored aggregate per-`(pool, tick_lower, tick_upper)` bookkeeping in a `ProtocolPositionState` PDA. **Newer releases no longer create or read this account.** The slot still appears on the `OpenPosition` / `IncreaseLiquidity` / `DecreaseLiquidity` account lists as an `UncheckedAccount` for ABI compatibility, but the program does not write to it. Existing accounts on-chain are vestigial; the admin can call `CloseProtocolPosition` to reclaim rent for them. Aggregate range bookkeeping is now derived directly from the two endpoint ticks (`liquidity_gross`, `liquidity_net`, and the per-tick `fee_growth_outside_*` / `reward_growths_outside_x64`) in `TickArrayState`. The fee-growth-inside formula `fee_growth_inside = global − outside_lower − outside_upper` continues to work without an aggregate position account. ## Observation ```rust theme={null} pub const OBSERVATION_NUM: usize = 100; pub struct Observation { pub block_timestamp: u32, pub tick_cumulative: i64, // Σ tick_current × Δt pub padding: [u64; 4], } pub struct ObservationState { pub initialized: bool, pub recent_epoch: u64, pub observation_index: u16, pub pool_id: Pubkey, pub observations: [Observation; OBSERVATION_NUM], // 100 entries pub padding: [u64; 4], } ``` CLMM's observation buffer stores a **cumulative tick**, not a cumulative price. External consumers compute the geometric-mean price over an interval from `(tick_cumulative[t1] − tick_cumulative[t0]) / (t1 − t0)` and then `price = 1.0001 ** tick`. See [`algorithms/clmm-math`](/algorithms/clmm-math). ## `DynamicFeeConfig` and `DynamicFeeInfo` Dynamic fee parameters live in two places. The reusable template — `DynamicFeeConfig` — is admin-managed and shared across pools that opt in. The per-pool runtime state — `DynamicFeeInfo` — is embedded in `PoolState` and updated by every swap. ### `DynamicFeeConfig` ```rust theme={null} // programs/amm/src/states/pool_fee.rs pub struct DynamicFeeConfig { pub index: u16, // identifier; PDA seed component pub filter_period: u16, // seconds — within this window the volatility reference is held pub decay_period: u16, // seconds — beyond this window the reference fully decays pub reduction_factor: u16, // fixed-point in [1, 10_000); applied at decay pub dynamic_fee_control: u32, // fixed-point in (0, 100_000); fee-rate gain pub max_volatility_accumulator: u32, // ceiling on the volatility accumulator pub padding: [u64; 8], } ``` PDA seed: `["dynamic_fee_config", index.to_be_bytes()]`. Created via `create_dynamic_fee_config` (admin-gated) and modified via `update_dynamic_fee_config`. A pool created with `enable_dynamic_fee = true` snapshots the config's five calibration parameters (`filter_period`, `decay_period`, `reduction_factor`, `dynamic_fee_control`, `max_volatility_accumulator`) into its own `DynamicFeeInfo` at creation time; later edits to the `DynamicFeeConfig` do not retroactively affect existing pools. ### `DynamicFeeInfo` (embedded in `PoolState`) ```rust theme={null} pub struct DynamicFeeInfo { pub filter_period: u16, pub decay_period: u16, pub reduction_factor: u16, pub dynamic_fee_control: u32, pub max_volatility_accumulator: u32, pub tick_spacing_index_reference: i32, // tick-spacing-units; reference for next swap pub volatility_reference: u32, // running floor for the accumulator pub volatility_accumulator: u32, // current cumulative volatility (capped) pub last_update_timestamp: u64, pub padding: [u8; 46], } ``` The bottom four fields are state; the top five are calibration copied from `DynamicFeeConfig`. The fee math and the decay rules are documented under [`products/clmm/math`](/products/clmm/math) and [`products/clmm/fees`](/products/clmm/fees). Constants used by the formula: | Constant | Value | Meaning | | --------------------------------- | --------- | ----------------------------------------- | | `VOLATILITY_ACCUMULATOR_SCALE` | `10_000` | Granularity of the volatility accumulator | | `REDUCTION_FACTOR_DENOMINATOR` | `10_000` | Denominator for `reduction_factor` | | `DYNAMIC_FEE_CONTROL_DENOMINATOR` | `100_000` | Denominator for `dynamic_fee_control` | | `MAX_FEE_RATE_NUMERATOR` | `100_000` | Hard cap of 10% on the resulting fee rate | ## `LimitOrderState` One account per open limit order. ```rust theme={null} // programs/amm/src/states/limit_order.rs pub struct LimitOrderState { pub pool_id: Pubkey, pub owner: Pubkey, pub tick_index: i32, pub zero_for_one: bool, // direction: true sells token0 for token1 pub order_phase: u64, // snapshot of TickState.order_phase at open time pub total_amount: u64, // input-token amount placed pub filled_amount: u64, // informational; computed precisely on settle pub settle_base: u64, // unfilled remainder at last settle/decrease pub settled_output: u64, // cumulative output-token paid to owner pub open_time: u64, pub unfilled_ratio_x64: u128, // Q64.64 snapshot of TickState.unfilled_ratio_x64 at open pub padding: [u64; 4], } ``` Lifecycle: 1. **Open** — user calls `open_limit_order`, deposits `total_amount` of the input token, the order is bound to a `TickState` cohort. 2. **(optional) Increase / Decrease** — `increase_limit_order` adds to `total_amount`; `decrease_limit_order` returns unfilled tokens (and any settled output up to that point). 3. **Settle** — when the cohort is fully or partially filled, the owner *or* the operational keeper calls `settle_limit_order` to push output tokens to the owner's ATA. 4. **Close** — once `unfilled_amount == 0`, the account is closeable. Rent always returns to `owner`. PDA seed: `[owner.as_ref(), limit_order_nonce.key().as_ref(), limit_order_nonce.order_nonce.to_be_bytes().as_ref()]`. The order PDA is therefore unique per `(owner, nonce_index, order_nonce)`. ## `LimitOrderNonce` Per-`(wallet, nonce_index)` counter that lets a single user run multiple parallel pipelines of limit orders without colliding on PDAs. ```rust theme={null} pub struct LimitOrderNonce { pub user_wallet: Pubkey, pub nonce_index: u8, // user-chosen, 0..255 pub order_nonce: u64, // monotonic, incremented every time a new order is opened pub padding: [u64; 4], } ``` PDA seed: `[user_wallet.as_ref(), &[nonce_index]]`. Most clients use `nonce_index = 0` and let `order_nonce` carry the cardinality. ## Deriving the key accounts ```ts theme={null} import { PublicKey } from "@solana/web3.js"; const CLMM_PROGRAM_ID = new PublicKey( "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" ); // see reference/program-addresses function i32ToBytes(n: number): Buffer { const b = Buffer.alloc(4); b.writeInt32BE(n); return b; } export function deriveClmmAccounts( ammConfig: PublicKey, token0Mint: PublicKey, // must already be sorted token1Mint: PublicKey, ) { const [poolState] = PublicKey.findProgramAddressSync( [Buffer.from("pool"), ammConfig.toBuffer(), token0Mint.toBuffer(), token1Mint.toBuffer()], CLMM_PROGRAM_ID, ); const [observation] = PublicKey.findProgramAddressSync( [Buffer.from("observation"), poolState.toBuffer()], CLMM_PROGRAM_ID, ); const [tickArrayBitmapExtension] = PublicKey.findProgramAddressSync( [Buffer.from("pool_tick_array_bitmap_extension"), poolState.toBuffer()], CLMM_PROGRAM_ID, ); return { poolState, observation, tickArrayBitmapExtension }; } export function deriveTickArray( pool: PublicKey, startTickIndex: number, ) { const [tickArray] = PublicKey.findProgramAddressSync( [Buffer.from("tick_array"), pool.toBuffer(), i32ToBytes(startTickIndex)], CLMM_PROGRAM_ID, ); return tickArray; } export function deriveDynamicFeeConfig(index: number) { const idx = Buffer.alloc(2); idx.writeUInt16BE(index); const [pda] = PublicKey.findProgramAddressSync( [Buffer.from("dynamic_fee_config"), idx], CLMM_PROGRAM_ID, ); return pda; } export function deriveLimitOrderNonce( wallet: PublicKey, nonceIndex: number, ) { const [pda] = PublicKey.findProgramAddressSync( [wallet.toBuffer(), Buffer.from([nonceIndex & 0xff])], CLMM_PROGRAM_ID, ); return pda; } export function deriveLimitOrder( wallet: PublicKey, nonceAccount: PublicKey, orderNonce: bigint, ) { const nonceBytes = Buffer.alloc(8); nonceBytes.writeBigUInt64BE(orderNonce); const [pda] = PublicKey.findProgramAddressSync( [wallet.toBuffer(), nonceAccount.toBuffer(), nonceBytes], CLMM_PROGRAM_ID, ); return pda; } export function derivePersonalPosition(nftMint: PublicKey) { const [personalPosition] = PublicKey.findProgramAddressSync( [Buffer.from("position"), nftMint.toBuffer()], CLMM_PROGRAM_ID, ); return personalPosition; } ``` The exact seed strings should always be double-checked against the on-chain IDL and [`reference/program-addresses`](/reference/program-addresses). ## Lifecycle quick reference | Event | Accounts created | Accounts destroyed | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | `CreatePool` | `poolState`, `observation`, `token_0_vault`, `token_1_vault` | — | | `OpenPosition[WithToken22Nft]` | NFT mint + ATA, `personalPosition`, possibly new `tickArrayState`(s), `tickArrayBitmapExtension` if not yet existing | — | | `IncreaseLiquidity` | Possibly new `tickArrayState`(s) | — | | `DecreaseLiquidity` | — | Possibly clears tick entries (but the `tickArrayState` itself is not closed) | | `ClosePosition` | — | NFT mint, `personalPosition` | | `SwapV2` | Possibly new `tickArrayState` | — | | `OpenLimitOrder` | `limitOrderState`, possibly `limitOrderNonce` (init-if-needed), possibly new `tickArrayState` | — | | `IncreaseLimitOrder` | — | — | | `DecreaseLimitOrder` | — | Closes `limitOrderState` if order is fully consumed | | `SettleLimitOrder` | — | — | | `CloseLimitOrder` | — | `limitOrderState` (rent → `owner`) | | `CreateDynamicFeeConfig` | `dynamicFeeConfig` | — | | `CreateCustomizablePool` | `poolState`, `observation`, vaults — same as `CreatePool`. Snapshots `dynamicFeeConfig` if `enable_dynamic_fee = true`. | — | | `CollectRewards` | — | — | | `UpdateRewardInfos` | — | — | | `CloseProtocolPosition` (admin) | — | Vestigial `protocolPositionState` (rent → admin) | `TickArrayState` accounts are **never closed by the program** — they persist for the life of the pool. Once a tick array has been initialised it remains on-chain even when every tick inside it returns to `liquidity_gross == 0`. Re-using an existing tick array is free; only the first position to touch a never-initialised array pays its rent. ## What to read where * **Tick math and range mechanics**: [`products/clmm/ticks-and-positions`](/products/clmm/ticks-and-positions). * **Swap walk and fee-growth math**: [`products/clmm/math`](/products/clmm/math). * **Instruction account lists**: [`products/clmm/instructions`](/products/clmm/instructions). * **Fees and reward accrual**: [`products/clmm/fees`](/products/clmm/fees). * **Canonical program IDs and seeds**: [`reference/program-addresses`](/reference/program-addresses). Sources: * [`raydium-io/raydium-clmm` — `programs/amm/src/states`](https://github.com/raydium-io/raydium-clmm) # CLMM code demos Source: https://docs.raydium.io/products/clmm/code-demos End-to-end TypeScript examples: create a CLMM pool, open a position in a chosen price range, adjust liquidity, swap, and collect fees and rewards. **Version banner.** All demos target `@raydium-io/raydium-sdk-v2@0.2.42-alpha` against Solana mainnet-beta, verified 2026-04. Program IDs come from [`reference/program-addresses`](/reference/program-addresses) via the SDK. ## Setup ```bash theme={null} npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token bn.js decimal.js ``` Every demo on this page mirrors a file in [`raydium-sdk-V2-demo/src/clmm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/clmm); the GitHub link sits next to each section. Bootstrap follows the demo repo's `config.ts.template` ([source](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template)) — `disableFeatureCheck: true` is the recommended setting for any non-trivial integration: ```ts theme={null} import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js"; import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; import fs from "node:fs"; const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta")); const owner = Keypair.fromSecretKey( new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))), ); const raydium = await Raydium.load({ owner, connection, cluster: "mainnet", disableFeatureCheck: true, blockhashCommitment: "finalized", }); export const txVersion = TxVersion.V0; ``` ## Create a CLMM pool Source: [`src/clmm/createPool.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPool.ts) ```ts theme={null} import { PublicKey } from "@solana/web3.js"; import { CLMM_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2"; import BN from "bn.js"; import Decimal from "decimal.js"; const mintA = await raydium.token.getTokenInfo( new PublicKey("So11111111111111111111111111111111111111112")); // wSOL const mintB = await raydium.token.getTokenInfo( new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")); // USDC const ammConfigs = await raydium.api.getClmmConfigs(); const ammConfig = ammConfigs.find((c) => c.index === 1)!; // 0.05% tier const initialPrice = new Decimal(160); // 160 USDC per SOL const { execute, extInfo } = await raydium.clmm.createPool({ programId: CLMM_PROGRAM_ID, mint1: mintA, mint2: mintB, ammConfig, initialPrice, startTime: new BN(0), txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Pool:", extInfo.address.id.toBase58(), "tx:", txId); ``` The SDK: * Sorts `mint1`/`mint2` by byte order before derivation. * Computes `sqrt_price_x64 = floor(sqrt(initialPrice × 10^(dB−dA)) × 2^64)`. * Creates the `observation` and `tick_array_bitmap_extension` accounts. * Pays the pool-creation fee defined by `ammConfig`. ## Open a position in a chosen range Source: [`src/clmm/createPosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPosition.ts) ```ts theme={null} import { PoolUtils, TickUtils } from "@raydium-io/raydium-sdk-v2"; const poolId = new PublicKey(""); const { poolInfo, poolKeys, rpcData } = await raydium.clmm.getPoolInfoFromRpc(poolId); // Choose a price range. Here: ±10% of current. const currentPrice = new Decimal(poolInfo.price); const lowerPrice = currentPrice.mul(0.9); const upperPrice = currentPrice.mul(1.1); // Snap to valid ticks for this pool's tick_spacing. const { tick: tickLower } = TickUtils.getPriceAndTick({ poolInfo, price: lowerPrice, baseIn: true, }); const { tick: tickUpper } = TickUtils.getPriceAndTick({ poolInfo, price: upperPrice, baseIn: true, }); // How much of each token to deposit. const inputAmount = new BN(10_000_000); // 0.01 SOL const inputMint = poolInfo.mintA.address; const res = PoolUtils.getLiquidityAmountOutFromAmountIn({ poolInfo, slippage: 0.01, inputA: true, tickUpper, tickLower, amount: inputAmount, add: true, amountHasFee: true, epochInfo: await raydium.fetchEpochInfo(), }); const { execute } = await raydium.clmm.openPositionFromBase({ poolInfo, poolKeys, tickUpper, tickLower, base: "MintA", ownerInfo: { useSOLBalance: true }, baseAmount: inputAmount, otherAmountMax: res.amountSlippageB.amount, txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Position opened, tx:", txId); ``` The SDK automatically computes which tick arrays the range touches and bundles `InitTickArray` instructions if any are uninitialized. ## Increase liquidity on an existing position Source: [`src/clmm/increaseLiquidity.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/increaseLiquidity.ts) ```ts theme={null} const positionNftMint = new PublicKey(""); const positionAccount = await raydium.clmm.getPositionInfo({ nftMint: positionNftMint, }); const { execute } = await raydium.clmm.increasePositionFromBase({ poolInfo, poolKeys, ownerPosition: positionAccount, base: "MintA", baseAmount: new BN(5_000_000), otherAmountMax: new BN(1_000_000_000), txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` ## Decrease liquidity (and collect fees at the same time) Source: [`src/clmm/decreaseLiquidity.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/decreaseLiquidity.ts) and [`src/clmm/closePosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/closePosition.ts) ```ts theme={null} const { execute } = await raydium.clmm.decreaseLiquidity({ poolInfo, poolKeys, ownerPosition: positionAccount, liquidity: positionAccount.liquidity.divn(2), // halve amountMinA: new BN(0), amountMinB: new BN(0), closePosition: false, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` To **collect fees only**, call `decreaseLiquidity` with `liquidity = new BN(0)`. The instruction's side-effect is settling `tokens_fees_owed_{0,1}` and transferring them out. To close the position entirely after zeroing liquidity and fees, pass `closePosition: true` on the final `decreaseLiquidity` call. The SDK appends `ClosePosition` and burns the NFT. ## Collect reward(s) Source: [`src/clmm/harvestAllRewards.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/harvestAllRewards.ts) ```ts theme={null} const { execute } = await raydium.clmm.harvestAllRewards({ ownerInfo: { useSOLBalance: true }, allPoolInfo: { [poolInfo.id]: poolInfo }, allPositions: { [poolInfo.id]: [positionAccount] }, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` `harvestAllRewards` walks every position on every pool passed in, batches `CollectReward` (and any `UpdateRewardInfos`) instructions, and splits them across transactions if needed. ## Swap Source: [`src/clmm/swap.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/swap.ts) ```ts theme={null} import { PoolUtils } from "@raydium-io/raydium-sdk-v2"; const amountIn = new BN(10_000_000); const baseIn = true; // swap A (SOL) → B (USDC) const slippage = 0.005; const { minAmountOut, remainingAccounts, priceImpact } = PoolUtils.computeAmountOutFormat({ poolInfo, tickArrayCache: await raydium.clmm.fetchTickArrays({ poolInfo }), amountIn, tokenOut: poolInfo.mintB, slippage, epochInfo: await raydium.fetchEpochInfo(), }); const { execute } = await raydium.clmm.swap({ poolInfo, poolKeys, inputMint: new PublicKey(poolInfo.mintA.address), amountIn, amountOutMin: minAmountOut.amount, observationId: poolKeys.observationId, remainingAccounts, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` `computeAmountOutFormat` walks the tick map off-chain using the same logic as the on-chain program and returns: * the expected amount out, * the minimum amount out after slippage, * the list of tick-array accounts the actual swap will touch (`remainingAccounts`). Always pass `remainingAccounts` returned by the simulation: if you pass too few, the swap reverts mid-walk with `TickArrayNotFound`; if you pass stale ones, wasted compute. ## Create a customizable CLMM pool `createCustomizablePool` is the new entry point that exposes the dynamic-fee and single-sided-fee toggles at pool-creation time. It takes the same shape as `createPool` plus three additions: ```ts theme={null} import { CLMM_PROGRAM_ID, CollectFeeOn } from "@raydium-io/raydium-sdk-v2"; const dynamicFeeConfigs = await raydium.api.getClmmDynamicConfigs(); // GET /main/clmm-dynamic-config const dynamicFeeConfig = dynamicFeeConfigs.find((c) => c.index === 0); // pick a calibration tier const { execute, extInfo } = await raydium.clmm.createCustomizablePool({ programId: CLMM_PROGRAM_ID, mint1: mintA, mint2: mintB, ammConfig, initialPrice, startTime: new BN(0), // New fields: collectFeeOn: CollectFeeOn.Token1Only, // 0 = FromInput, 1 = Token0Only, 2 = Token1Only enableDynamicFee: true, dynamicFeeConfigId: dynamicFeeConfig?.id, // omit when enableDynamicFee is false txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); console.log("Customizable pool:", extInfo.address.id.toBase58()); ``` `createPool` continues to work for the default-fee, no-limit-order, no-dynamic-fee path. Use `createCustomizablePool` whenever you need any of the three new knobs. See [`products/clmm/instructions`](/products/clmm/instructions) for the on-chain account list. ## Limit orders A limit order parks user input at a single tick and is filled FIFO when a swap crosses that tick. Outputs are pushed to the owner's ATA at settle time; the owner does not need to be online to be filled. ### Open a limit order ```ts theme={null} import { TickUtils } from "@raydium-io/raydium-sdk-v2"; const limitConfigs = await raydium.api.getClmmLimitOrderConfigs(); // GET /main/clmm-limit-order-config const limitConfig = limitConfigs.find((c) => c.poolId === poolInfo.id); // Limit price MUST be quantized to tick_spacing. const targetPrice = new Decimal(180); // sell SOL at 180 USDC const { tick: limitTick } = TickUtils.getPriceAndTick({ poolInfo, price: targetPrice, baseIn: true, }); const { execute } = await raydium.clmm.openLimitOrder({ poolInfo, poolKeys, limitOrderConfig: limitConfig, inputMint: poolInfo.mintA.address, // selling SOL inputAmount: new BN(50_000_000), // 0.05 SOL tick: limitTick, txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Limit order opened, tx:", txId); ``` The SDK derives the `LimitOrderState` PDA from `(pool, owner, tick, nonce)`, bumps the per-(pool, owner) `LimitOrderNonce`, and inserts the order into the FIFO cohort at that tick. ### Increase / decrease an open order ```ts theme={null} await raydium.clmm.increaseLimitOrder({ poolInfo, poolKeys, limitOrderId: , addAmount: new BN(20_000_000), txVersion: TxVersion.V0, }).then((b) => b.execute({ sendAndConfirm: true })); await raydium.clmm.decreaseLimitOrder({ poolInfo, poolKeys, limitOrderId: , removeAmount: new BN(10_000_000), txVersion: TxVersion.V0, }).then((b) => b.execute({ sendAndConfirm: true })); ``` `decreaseLimitOrder` can only remove from the **unfilled** portion of the order; the filled portion is locked until settlement. Both instructions revert with `InvalidOrderPhase` if the order has already been fully filled. ### Settle a filled order ```ts theme={null} await raydium.clmm.settleLimitOrder({ poolInfo, poolKeys, limitOrderId: , txVersion: TxVersion.V0, }).then((b) => b.execute({ sendAndConfirm: true })); ``` `settleLimitOrder` reads the order's `unfilled_ratio_x64` against the cohort tracker, computes the filled output, and transfers it to the owner's ATA. The owner can call this themselves; `limit_order_admin` (an off-chain operational keeper) can also call it on the owner's behalf — the output still goes to the owner. For closing fully-settled orders to recover rent, use `closeLimitOrder` (single) or `closeAllLimitOrder` (batch). For settling many at once, `settleAllLimitOrder` packs as many `SettleLimitOrder` calls as fit into a v0 tx. ### List a wallet's parked orders (off-chain) ```ts theme={null} // API helper. See api-reference/temp-api-v1. const active = await fetch( `https://temp-api-v1.raydium.io/limit-order/order/list?wallet=`, ).then((r) => r.json()); ``` The active-orders endpoint returns both unfilled and partially-filled orders in one payload (`totalAmount` / `filledAmount` / `pendingSettle` distinguish the phases). For closed-order history use `/limit-order/history/order/list-by-user?wallet=…` (per-wallet, paginated by `nextPageId`); for the full event log of a specific order use `/limit-order/history/event/list-by-pda?pda=…`. ## Rust CPI skeleton ```rust theme={null} use anchor_lang::prelude::*; use raydium_amm_v3::cpi; use raydium_amm_v3::program::AmmV3; use raydium_amm_v3::cpi::accounts::SwapV2; #[derive(Accounts)] pub struct ProxyClmmSwap<'info> { #[account(mut)] pub payer: Signer<'info>, /// CHECK: pub amm_config: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub pool_state: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub input_token_account: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub output_token_account: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub input_vault: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub output_vault: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub observation_state: UncheckedAccount<'info>, /// CHECK: pub token_program: UncheckedAccount<'info>, /// CHECK: pub token_program_2022: UncheckedAccount<'info>, /// CHECK: pub memo_program: UncheckedAccount<'info>, /// CHECK: pub input_vault_mint: UncheckedAccount<'info>, /// CHECK: pub output_vault_mint: UncheckedAccount<'info>, pub clmm_program: Program<'info, AmmV3>, // `remaining_accounts` carries the tick_array and bitmap_extension accounts. } pub fn proxy_swap( ctx: Context, amount: u64, other_amount_threshold: u64, sqrt_price_limit_x64: u128, is_base_input: bool, ) -> Result<()> { let cpi_accounts = SwapV2 { payer: ctx.accounts.payer.to_account_info(), amm_config: ctx.accounts.amm_config.to_account_info(), pool_state: ctx.accounts.pool_state.to_account_info(), input_token_account: ctx.accounts.input_token_account.to_account_info(), output_token_account: ctx.accounts.output_token_account.to_account_info(), input_vault: ctx.accounts.input_vault.to_account_info(), output_vault: ctx.accounts.output_vault.to_account_info(), observation_state: ctx.accounts.observation_state.to_account_info(), token_program: ctx.accounts.token_program.to_account_info(), token_program_2022: ctx.accounts.token_program_2022.to_account_info(), memo_program: ctx.accounts.memo_program.to_account_info(), input_vault_mint: ctx.accounts.input_vault_mint.to_account_info(), output_vault_mint: ctx.accounts.output_vault_mint.to_account_info(), }; let cpi_ctx = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts) .with_remaining_accounts(ctx.remaining_accounts.to_vec()); cpi::swap_v2(cpi_ctx, amount, other_amount_threshold, sqrt_price_limit_x64, is_base_input) } ``` Remaining-account order for `SwapV2`: ``` [tick_array_bitmap_extension?, tick_array_0, tick_array_1, …] ``` If the swap never needs the extension, omit it; otherwise it is the first remaining account. ## Common pitfalls * **Off-spacing tick endpoints** → `InvalidTickIndex`. Always snap via `TickUtils.getPriceAndTick`. * **Not enough tick arrays supplied in `SwapV2`** → `TickArrayNotFound`. Use `computeAmountOutFormat` to get the full list. * **Full-range position without the bitmap extension** → the extension PDA must be writable; the SDK handles this automatically. * **Mistaking `sqrt_price_x64` for `price`** → a factor-of-2 confusion here is particularly painful. When in doubt, let the SDK compute it from a human-readable price. * **Collecting rewards too eagerly** → each collect costs one transaction. Batch via `harvestAllRewards` across many positions. * **Burning the NFT while rent is still owed to the mint** → `ClosePosition` also closes the NFT mint and ATA; do not close them separately or the program will revert. * **Opening a limit order at a non-spaced tick** → `InvalidTickIndex`. Always quantize via `TickUtils.getPriceAndTick`. * **Calling `decreaseLimitOrder` on a fully-filled order** → `InvalidOrderPhase`. Use `settleLimitOrder` then `closeLimitOrder` instead. * **Forgetting `dynamicFeeConfigId` while passing `enableDynamicFee: true`** → the `CreateCustomizablePool` revert is `InvalidDynamicFeeConfigParams`. Either turn dynamic fee off, or pick a config from `/main/clmm-dynamic-config`. ## Where to go next * [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — complete SDK surface. * [`sdk-api/rest-api`](/sdk-api/rest-api) — quote and pool-metadata endpoints. * [`user-flows/create-clmm-pool`](/user-flows/create-clmm-pool) — non-code walkthrough. * [`integration-guides/aggregator`](/integration-guides/aggregator) — routing CLMM as part of a path. Sources: * [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2) * [Raydium SDK v2 demos](https://github.com/raydium-io/raydium-sdk-V2-demo) * [`raydium-io/raydium-clmm`](https://github.com/raydium-io/raydium-clmm) # CLMM fees and rewards Source: https://docs.raydium.io/products/clmm/fees How CLMM splits a trade fee across LPs, protocol, and fund; how the three-slot reward system emits to in-range positions; and how both settle into per-position balances. ## Fee tiers CLMM pools bind to an `AmmConfig` at creation; the config decides the trade-fee rate, the protocol and fund shares, and the **tick spacing** (see [`products/clmm/ticks-and-positions`](/products/clmm/ticks-and-positions)). Typical published tiers (confirm live against `GET https://api-v3.raydium.io/main/clmm-config`): | `AmmConfig` index | `trade_fee_rate` | Tick spacing | Typical use | | ----------------- | ---------------- | ------------ | --------------------- | | 0 | `100` (0.01%) | 1 | Stable pairs | | 1 | `500` (0.05%) | 10 | Correlated blue-chips | | 2 | `2_500` (0.25%) | 60 | Standard pairs | | 3 | `10_000` (1.00%) | 120 | Volatile or long-tail | The trade-fee rate is in units of `1/FEE_RATE_DENOMINATOR = 1/1_000_000` of volume. The protocol and fund rates are in the same denominator but applied to the **trade fee**, not to volume — the same convention as CPMM. ## Per-swap fee split On each step of a swap (see [`products/clmm/math`](/products/clmm/math)): ``` step_trade_fee = ceil(step_input * trade_fee_rate / 1_000_000) step_protocol = floor(step_trade_fee * protocol_fee_rate / 1_000_000) step_fund = floor(step_trade_fee * fund_fee_rate / 1_000_000) step_lp = step_trade_fee - step_protocol - step_fund ``` * `step_lp` flows into `fee_growth_global_{input_side}_x64` scaled by current active liquidity: `fee_growth_global += step_lp × 2^64 / pool.liquidity`. * `step_protocol` accrues into `PoolState.protocol_fees_token_{input_side}` — swept with `CollectProtocolFee`. * `step_fund` accrues into `PoolState.fund_fees_token_{input_side}` — swept with `CollectFundFee`. Just like CPMM, the protocol and fund portions sit in the vaults but are **excluded from the curve's liquidity view**: the swap math reads `pool.liquidity`, which is not inflated by pending-but-unswept fees. ## Why fees are per-side Unlike CPMM (where a swap's fee is always charged in the input token and the other side of the pool never sees the protocol/fund accrual for this swap), in CLMM the same rule applies at each step: **fees accrue in whichever token is the input for that step**. Since a multi-tick swap has a consistent direction, all steps charge fees in the same token — so in practice fees on any given swap go to *one* side. If a user swaps token0 → token1, `fee_growth_global_0_x64` rises; `fee_growth_global_1_x64` does not. Positions earn fees in token0 on that swap. The next swap may go the other direction and credit `fee_growth_global_1_x64` instead. Over time, a balanced pool accrues on both sides. ## Single-sided fee (`CollectFeeOn`) Pools created via [`CreateCustomizablePool`](/products/clmm/instructions) can opt into a non-default fee-collection mode. The mode is fixed at pool creation and stored in `PoolState.fee_on`. | `CollectFeeOn` value | `fee_on` byte | Behavior | | --------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `FromInput` (default) | `0` | Classic Uniswap-V3 — fee is always deducted from the input token of each swap step. The input token alternates with swap direction. | | `Token0Only` | `1` | Fee is always denominated in token0. For 0→1 swaps, the fee is the input token (same as `FromInput`). For 1→0 swaps, the fee is taken from the swap's output (token0). | | `Token1Only` | `2` | Symmetric to `Token0Only` — fee always in token1. | **Why a pool would choose `Token0Only` or `Token1Only`** — to give LPs a single, predictable accrual currency. Pairs like `MEMECOIN / USDC` where LPs are dollar-denominated benefit from `Token1Only` (fees always settle to USDC); LP P\&L is then unaffected by which side trades dominate. The trade-off is that on directions where the fee comes out of swap output, the user receives `out − fee` rather than `out − ε` from the input, so quote logic must subtract the fee from the output side. The SDK's `computeAmountOut` handles this branching from `fee_on`; client code that reads `pool.fee_on` directly should mirror the helper functions on `PoolState`: ```rust theme={null} pool.is_fee_on_input(zero_for_one: bool) -> bool // true → fee is deducted from input pool.is_fee_on_token0(zero_for_one: bool) -> bool // for telemetry / accounting ``` **LP-level effect** — the fee is still routed through the standard `fee_growth_global_{0,1}_x64` accumulators per swap step, so positions still settle fees with the same `fee_growth_inside` formula. The asymmetry is only on the *direction* of side accrual, not on the math. `fee_on` is **not** mutable post-creation. Pools created via legacy `CreatePool` are permanently `FromInput`. ## Dynamic fee Pools created with `enable_dynamic_fee = true` apply a volatility-driven surcharge on top of `AmmConfig.trade_fee_rate`. The mechanism is a simplified port of the Trader Joe / Meteora dynamic-fee design. ### State `PoolState.dynamic_fee_info` carries five calibration parameters (snapshot of `DynamicFeeConfig` at pool creation) plus four state fields updated by every swap. See [`products/clmm/accounts`](/products/clmm/accounts#dynamicfeeconfig-and-dynamicfeeinfo) for the byte layout. ### Per-swap update On every swap step the program runs three sub-steps: 1. **Decay reference**. If `now - last_update_timestamp > filter_period`, the volatility reference decays: ``` if elapsed > decay_period: volatility_reference = 0 elif elapsed > filter_period: volatility_reference = volatility_accumulator * reduction_factor / 10_000 # else: hold the previous reference ``` 2. **Update accumulator**. The new accumulator is the reference plus the absolute distance traversed (in `tick_spacing`-units), multiplied by a granularity scale, capped at the configured maximum: ``` delta_idx = abs(tick_spacing_index_reference - current_tick_spacing_index) accumulator = volatility_reference + delta_idx * 10_000 // VOLATILITY_ACCUMULATOR_SCALE accumulator = min(accumulator, max_volatility_accumulator) ``` 3. **Compute surcharge**. The surcharge is parabolic in the accumulator (since the swap "tick distance" is squared in the canonical formula), gain-scaled by `dynamic_fee_control`: ``` fee_increment_rate = dynamic_fee_control * (accumulator * tick_spacing)^2 / (100_000 * 10_000^2) fee_rate = AmmConfig.trade_fee_rate + fee_increment_rate fee_rate = min(fee_rate, 100_000) // 10% cap ``` The 10% cap (`MAX_FEE_RATE_NUMERATOR = 100_000` in `1e6`-units) is hard-coded as a safety rail; in practice well-tuned configs land well below. ### Choosing parameters Default ranges that have worked in pilot pools: | Parameter | Typical range | Notes | | ---------------------------- | ----------------------- | ------------------------------------------------------------------- | | `filter_period` | 30 – 60 s | Holds the reference through micro-volatility; lower = more reactive | | `decay_period` | 300 – 1800 s | After this window of quiet, fee returns to base | | `reduction_factor` | 4\_000 – 8\_000 | Of `10_000`. Higher = stickier elevated fee | | `dynamic_fee_control` | 1\_000 – 50\_000 | Of `100_000`. Gain on the curve | | `max_volatility_accumulator` | 100\_000 – 10\_000\_000 | Saturates how high the surcharge can climb | Calibrate by replaying historical swaps offline against the formula, then tuning `dynamic_fee_control` so that the resulting average fee matches a target (e.g., 1.5× base on 1σ days, 5× on 3σ days). ### What LPs see Dynamic fee revenue flows through the same accumulators as the base fee — `fee_growth_global_{0,1}_x64`. There is no separate "dynamic fee growth" field. LPs in volatile pools simply earn higher fees during volatile periods, with no extra claim or settlement instruction needed. ### What integrators need to know * The fee a quote returns can change between block N and block N+1 even if pool reserves haven't moved — every swap shifts the volatility accumulator. The Trade API quotes are valid at block-of-quote and may be off by a few bps if a reactive pool is fired between quote and execution. * `volatility_accumulator` and `last_update_timestamp` are public on-chain — clients can replicate the formula client-side for offline simulations. ## Per-position fee accounting Each position stores, at its last touch time: * `fee_growth_inside_0_last_x64` and `fee_growth_inside_1_last_x64` — the range-specific fee growth at that snapshot. On every subsequent touch (`IncreaseLiquidity`, `DecreaseLiquidity`, and implicitly any state transition that updates the tick-bound fee growth): 1. The program recomputes `fee_growth_inside_{0,1}_x64` from the **global** fee growth and the two endpoint ticks' `fee_growth_outside_*`. 2. Δ is added to `tokens_fees_owed_{0,1}` weighted by the position's liquidity: ``` Δ_fee_growth_inside_0 = fee_growth_inside_now_0 - fee_growth_inside_last_0 tokens_fees_owed_0 += Δ_fee_growth_inside_0 * position.liquidity / 2^64 ``` 3. `fee_growth_inside_{0,1}_last_x64` is updated. Tokens physically move only on `DecreaseLiquidity` or the dedicated `CollectFees` path (in Raydium's current instruction set, fees are swept as part of `DecreaseLiquidity`). Setting `liquidity = 0` in a `DecreaseLiquidity` call is the canonical "collect only" idiom. ### Out-of-range positions earn nothing If a position's range does not contain `tick_current`, the `fee_growth_inside` computed for it is **bounded from above** and does not move while the price sits outside. The position stops accruing fees until the price returns to its range. This is a feature, not a bug — it is how concentrated liquidity concentrates *fee yield* as well as capital. ## Reward streams A CLMM pool can have up to **three** reward streams concurrently active. Each stream is a (reward mint, emissions rate, start time, end time) tuple stored in `PoolState.reward_infos[i]`. ```rust theme={null} pub struct RewardInfo { pub reward_state: u8, // Uninitialized | Initialized | Open | Ended pub open_time: u64, pub end_time: u64, pub last_update_time: u64, pub emissions_per_second_x64: u128, // Q64.64 reward tokens per second pub reward_total_emissioned: u64, pub reward_claimed: u64, pub token_mint: Pubkey, pub token_vault: Pubkey, pub authority: Pubkey, // who can SetRewardParams / fund pub reward_growth_global_x64: u128, // accumulator, Q64.64 } ``` ### Settlement loop Every liquidity-touching instruction (and `UpdateRewardInfos` as a standalone) advances all active streams to `now`: ``` for each reward_info with state in {Open, Ended within grace}: elapsed = min(now, end_time) − last_update_time if elapsed > 0 && pool.liquidity > 0: reward_growth_global_x64 += emissions_per_second_x64 × elapsed × 2^64 / pool.liquidity reward_total_emissioned += emissions_per_second × elapsed last_update_time = min(now, end_time) ``` If `pool.liquidity == 0` across some interval, emissions for that interval are **not** distributed (they cannot be; there is no in-range liquidity to pay). The remaining budget stays in the reward vault. Protocols that mint and forget can top up or end the stream via `SetRewardParams`. ### Per-position reward accrual Exactly like fees, with an additional dimension per stream: ``` for each stream i: reward_growth_inside_now_i = compute_inside_i(pool, tick_lower, tick_upper) Δ_i = reward_growth_inside_now_i - personal_position.reward_infos[i].growth_inside_last_x64 personal_position.reward_infos[i].reward_amount_owed += Δ_i * personal_position.liquidity / 2^64 personal_position.reward_infos[i].growth_inside_last_x64 = reward_growth_inside_now_i ``` Users claim via `CollectReward`, which transfers `reward_amount_owed` from the stream's vault to the user and zeros the counter. ### Only in-range positions earn rewards `reward_growth_inside` uses the same formula as `fee_growth_inside` — via the tick-outside accumulators — so positions outside the current price range do not accrue rewards. This mirrors Uniswap v3's "incentives go to active liquidity" design choice and aligns LP interest with spot price coverage. ### Funding and ending streams A stream is created via `InitializeReward`, which deposits the total budget (`emissions_per_second × (end_time − open_time)`) into the stream's reward vault up-front. The program rejects `InitializeReward` if the funder's balance is short. `SetRewardParams` can extend `end_time` or raise the emission rate; shrinking either is blocked to avoid rug-pulling emissions already promised to LPs. When `now > end_time` the stream transitions to `Ended` but its `reward_growth_global_x64` continues to be read — LPs can still `CollectReward` for historically earned amounts long after emissions stop. ## Admin collection | Signer | Instruction | Effect | | ----------------------- | -------------------- | ------------------------------------------------- | | `amm_config.owner` | `CollectProtocolFee` | Sweep `protocol_fees_token_{0,1}` to a recipient. | | `amm_config.fund_owner` | `CollectFundFee` | Sweep `fund_fees_token_{0,1}` to a recipient. | Neither moves the curve — accrued amounts sit outside `pool.liquidity` already. See [`security/admin-and-multisig`](/security/admin-and-multisig) for who holds these signers on mainnet. ## Token-2022 interactions Fees and rewards are all denominated in one of the pool's or stream's tokens. Token-2022 extensions behave the same way they do in CPMM: * **Transfer fee on the input mint of a swap.** The pool receives `amount_in − mint_transfer_fee`. The CLMM program's step input is computed against the net amount, so the pool's fee accumulators reflect real-in-the-vault tokens. * **Transfer fee on the output mint.** The pool sends `amount_out`; the user receives `amount_out − mint_transfer_fee`. Slippage checks should be done against the user-receive amount. * **Transfer fee on a reward mint.** Emissions are denominated in "into-the-vault" units at `InitializeReward` time (the funder pays the mint transfer fee into the vault). Withdrawals at `CollectReward` then incur another mint transfer fee; LPs should expect a slight haircut on transfer-fee reward tokens. * **Non-transferable / confidential / group-member mints.** Rejected at `CreatePool` / `InitializeReward`. The combined effect on a multi-hop transfer-fee swap can be substantial. Quoters that ignore it will over-promise; see [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees) for the reference calculation. ## Reading fees and rewards off-chain ```ts theme={null} const pool = await raydium.clmm.getPoolInfoFromRpc(poolId); const position = await raydium.clmm.getOwnerPositionInfo({ wallet: owner.publicKey, }); for (const p of position) { console.log("Position", p.nftMint.toBase58(), "range", p.tickLower, "→", p.tickUpper, "L", p.liquidity.toString(), "fees owed:", p.tokenFeesOwed0.toString(), p.tokenFeesOwed1.toString(), "rewards owed:", p.rewardInfos.map(r => r.rewardAmountOwed.toString())); } ``` `tokenFeesOwed*` and `rewardAmountOwed` are snapshots from the last time the position was touched. To see the **current** values (reflecting growth since then), call `IncreaseLiquidity` with zero liquidity in a simulation, or simply recompute using the global `fee_growth_*` and the two tick-outside snapshots. ## Where to go next * [`products/clmm/math`](/products/clmm/math) — full derivation of `fee_growth_inside`. * [`products/clmm/instructions`](/products/clmm/instructions) — `CollectReward`, `InitializeReward`, `SetRewardParams` account lists. * [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees) — quoting with transfer-fee mints. * [`reference/fee-comparison`](/reference/fee-comparison) — side-by-side CLMM/CPMM/AMM-v4 fee matrix. Sources: * [`raydium-io/raydium-clmm` — `states/pool.rs`, `libraries/fixed_point_64.rs`](https://github.com/raydium-io/raydium-clmm) * "Uniswap v3 Core" whitepaper, §7 (fee growth) # CLMM Source: https://docs.raydium.io/products/clmm/index Concentrated-liquidity AMM. Liquidity is deposited into price ranges (ticks); positions earn fees only when the price is in range. ## What it is CLMM is Raydium's concentrated-liquidity AMM, inspired by Uniswap v3's design and adapted to Solana's account model. Liquidity providers choose a price range rather than providing across the whole curve; capital efficiency is dramatically higher for stable and correlated pairs. **Program ID:** see [reference/program-addresses](/reference/program-addresses). **Token-2022:** supported (with caveats documented under [`fees`](/products/clmm/fees)). ## Chapter contents Conceptual model: sqrt-price representation, ticks, liquidity math, and why positions can go "out of range." PoolState, AmmConfig, TickArrayState, PersonalPositionState, ObservationState. Seeds, field layouts. Tick spacing, tick arrays, how a position maps to two tick boundaries, how liquidity crosses a tick. Sqrt-price formulas, liquidity ↔ token-amount conversion, fee growth accounting, swap step algorithm. CreatePool, OpenPosition, IncreaseLiquidity, DecreaseLiquidity, Swap, CollectFee, CollectReward, UpdateRewardInfos. Fee tiers, protocol/fund fee splits, reward schedules (up to three reward mints per pool). Create pool, open position, increase/decrease liquidity, swap, collect fees and rewards. ## When to read this * You are providing concentrated liquidity or building tooling for LPs. * You are implementing range-order or auto-rebalancing strategies. * You are a router that needs to price a CLMM swap accurately. # CLMM instructions Source: https://docs.raydium.io/products/clmm/instructions Complete CLMM instruction reference: pool lifecycle, position management, swap, and reward streams. This page pairs with [`products/clmm/accounts`](/products/clmm/accounts) (what the accounts are) and [`products/clmm/math`](/products/clmm/math) (what the math is). It is authoritative for arguments and account ordering; specific byte layouts come from the IDL. ## Instruction inventory | Group | Instruction | Notes | | ----------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Admin | `CreateAmmConfig` | Define a new fee tier. | | Admin | `UpdateAmmConfig` | Change rates on an existing tier. | | Admin | `UpdatePoolStatus` | Pause/resume operations on a pool. | | Admin | `CreateSupportMintAssociated` | Allow-list a Token-2022 mint extension config for use in CLMM pools. | | Admin | `CreateOperationAccount` | Initialize the program-level operation account (one-time). | | Admin | `UpdateOperationAccount` | Modify the operation-account whitelist. | | Admin | `CreateDynamicFeeConfig` | Create a reusable dynamic-fee parameter set under a u16 index. | | Admin | `UpdateDynamicFeeConfig` | Modify an existing `DynamicFeeConfig`. Pools that already snapshotted it are unaffected. | | Pool | `CreatePool` | Initialize a CLMM pool bound to an `AmmConfig`. Standard, FromInput-fee path. Coexists with `CreateCustomizablePool`. | | Pool | `CreateCustomizablePool` | Recommended for new pools. Same shape as `CreatePool` plus `collect_fee_on` and an opt-in `enable_dynamic_fee` flag. | | Position | `OpenPosition` / `OpenPositionV2` / `OpenPositionWithToken22Nft` | Mint a position NFT. `OpenPositionV2` supersedes V1 (newer account layout with the bitmap-extension slot); `OpenPositionWithToken22Nft` issues the position NFT as Token-2022 instead of SPL Token. New code should use V2 or the Token-2022 variant. | | Position | `IncreaseLiquidity` / `IncreaseLiquidityV2` | Add liquidity to an existing position. | | Position | `DecreaseLiquidity` / `DecreaseLiquidityV2` | Remove liquidity; collects owed fees. | | Position | `ClosePosition` | Burn the NFT and close the `PersonalPositionState`. | | Position | `CloseProtocolPosition` | Admin-only sweep for legacy `ProtocolPositionState` PDAs. The current program no longer creates or reads `ProtocolPositionState` — this instruction exists solely to reclaim rent on accounts created by older program versions. | | Swap | `Swap` / `SwapV2` | Constant-liquidity swap. Both variants apply dynamic fee, single-sided fee routing, and limit-order matching; the only difference is that `SwapV2` accepts Token-2022 mints (the V1 variant requires both vaults to be classic SPL Token). | | Swap | `SwapRouterBaseIn` | Multi-hop, used by the router. | | Limit order | `OpenLimitOrder` | Place a sell order at a tick. Unfilled tokens sit on the tick; the matching engine fills them as price crosses. | | Limit order | `IncreaseLimitOrder` | Add to an existing open order. | | Limit order | `DecreaseLimitOrder` | Reduce or cancel an open order; pays out the unfilled remainder plus any output already settled. | | Limit order | `SettleLimitOrder` | Push filled output tokens to the order owner. Callable by the owner or by the operational keeper. | | Limit order | `CloseLimitOrder` | Close a fully-consumed order account. Rent always returns to the order's `owner`. Callable by owner or keeper. | | Fees | `CollectProtocolFee` | Admin sweep of protocol fees. | | Fees | `CollectFundFee` | Admin sweep of fund fees. | | Rewards | `InitializeReward` | Attach a new reward stream to a pool. | | Rewards | `SetRewardParams` | Change an existing reward's emission rate/end. | | Rewards | `UpdateRewardInfos` | Settle reward growth to now (called by any swap / position change). | | Rewards | `TransferRewardOwner` | Transfer the authority that can set or top up a reward stream. | | Rewards | `CollectRemainingRewards` | After a reward stream's `end_time`, sweep any unallocated tokens back to the funder. | | Utility | `InitTickArray` | Initialize a tick-array account (often bundled with `OpenPosition`). | Most admin-only instructions (`CreateAmmConfig`, `UpdateAmmConfig`, `UpdatePoolStatus`, `CreateSupportMintAssociated`, `CreateOperationAccount`, `UpdateOperationAccount`, `CloseProtocolPosition`) are gated by the program's hardcoded `admin` pubkey. Reward-stream admin instructions (`TransferRewardOwner`, `CollectRemainingRewards`) are gated by the reward funder, not the program admin. V2 suffix means "supports Token-2022 on vaults / NFT, requires bitmap-extension slot". The SDK picks V2 by default for new pools. ## `CreatePool` **Arguments** ``` sqrt_price_x64: u128 // initial price open_time: u64 // swaps rejected before this time ``` **Accounts (abridged)** | # | Name | W | S | Notes | | -- | ----------------------------- | - | - | ----------------------------------------- | | 1 | `pool_creator` | W | S | | | 2 | `amm_config` | | | Chosen fee tier. | | 3 | `pool_state` | W | | `init` here. | | 4 | `token_mint_0` | | | Sorted. | | 5 | `token_mint_1` | | | | | 6 | `token_vault_0` | W | | `init` here, owned by pool authority PDA. | | 7 | `token_vault_1` | W | | | | 8 | `observation_state` | W | | `init` here. | | 9 | `tick_array_bitmap_extension` | W | | `init` here (V2). | | 10 | `token_program` | | | | | 11 | `token_program_2022` | | | | | 12 | `system_program`, `rent` | | | | **Preconditions** * `token_mint_0 < token_mint_1` by byte order. * `amm_config.disable_create_pool == false`. * Mints are not rejected by the Token-2022 extension allow-list. **Postconditions** * `pool_state.sqrt_price_x64 = sqrt_price_x64`, `tick_current = floor(log_{1.0001}(price))`. * `pool_state.liquidity = 0` (no positions yet). * `pool_state.fee_on = FromInput` (legacy default). * `pool_state.dynamic_fee_info` is zeroed (dynamic fee disabled). ## `CreateCustomizablePool` Recommended for new pools. Same effect as `CreatePool` plus per-pool fee-collection mode and an optional dynamic-fee opt-in. **Arguments** ```rust theme={null} pub struct CreateCustomizableParams { pub sqrt_price_x64: u128, pub collect_fee_on: CollectFeeOn, // FromInput | Token0Only | Token1Only pub enable_dynamic_fee: bool, } ``` **Accounts (abridged)** — same as `CreatePool` plus, when `enable_dynamic_fee = true`: | # | Name | W | S | Notes | | - | -------------------- | - | - | ------------------------------------------------------- | | N | `dynamic_fee_config` | | | The shared config to snapshot from. Must already exist. | **Preconditions** — same as `CreatePool`. If `enable_dynamic_fee = false`, `dynamic_fee_config` is ignored. **Postconditions** * `pool_state.fee_on` set to the chosen `CollectFeeOn` variant. * If dynamic fee was enabled: `pool_state.dynamic_fee_info` is initialized from the supplied `DynamicFeeConfig` (five calibration parameters copied; state fields zeroed). * Otherwise: `pool_state.dynamic_fee_info` is zeroed (= dynamic fee inactive forever for this pool). `fee_on` and the dynamic-fee enablement bit are set **only** at pool creation. There is no in-place upgrade — pools created via legacy `CreatePool` cannot retroactively gain dynamic fee or single-sided fee. New deployments should default to this instruction. ## `OpenPositionV2` / `OpenPositionWithToken22Nft` Create a new position inside an existing pool. **Arguments** ``` tick_lower_index: i32 tick_upper_index: i32 tick_array_lower_start_index: i32 tick_array_upper_start_index: i32 liquidity: u128 // desired L (or 0 to use amounts below) amount_0_max: u64 amount_1_max: u64 with_metadata: bool // write NFT metadata (Metaplex) base_flag: Option // true = fit to amount0; false = fit to amount1 ``` **Accounts (abridged)** | # | Name | W | S | | | -- | ----------------------------------------- | - | ----------- | ---------------------------------------- | | 1 | `payer` | W | S | | | 2 | `position_nft_owner` | | | | | 3 | `position_nft_mint` | W | S (keypair) | | | 4 | `position_nft_account` | W | | Owner's ATA for the NFT. | | 5 | `metadata_account` | W | | Metaplex (optional, if `with_metadata`). | | 6 | `pool_state` | W | | | | 7 | `protocol_position` | | | | | 8 | `tick_array_lower` | W | | Created if uninitialized. | | 9 | `tick_array_upper` | W | | Same. | | 10 | `personal_position` | W | | Created here. | | 11 | `token_account_0`, `token_account_1` | W | | User source ATAs. | | 12 | `token_vault_0`, `token_vault_1` | W | | | | 13 | `rent`, `system_program`, `token_program` | | | | | 14 | `associated_token_program` | | | | | 15 | `metadata_program` | | | Optional. | | 16 | `token_program_2022` | | | V2. | | 17 | `vault_0_mint`, `vault_1_mint` | | | V2. | | 18 | `tick_array_bitmap_extension` | W | | V2 (if touched). | **Math** — see [`products/clmm/math`](/products/clmm/math). Given `base_flag`, the program resolves either `liquidity` or `(amount_0_max, amount_1_max)` into the actual `L` and the actual token amounts consumed. **Preconditions** * `tick_lower < tick_upper`, both multiples of `pool.tick_spacing`, within `[MIN_TICK, MAX_TICK]`. * Required tick arrays passed and initialized (or created here via `InitTickArray` CPI in the transaction). * User has at least `amount_0_max` and `amount_1_max` in the source ATAs. **Postconditions** * `personal_position` exists, `liquidity` set, `fee_growth_inside_last` snapshotted. * Tick-array entries at `tick_lower` and `tick_upper` updated (`liquidity_gross += L`, `liquidity_net ± L`, fee-growth snapshots maintained). * `pool_state.liquidity += L` if position is in range (`tick_lower ≤ tick_current < tick_upper`). **Common errors** — `InvalidTickIndex`, `NotApproved`, `ZeroAmountSpecified`, `TransactionTooLarge` (if too many tick arrays). ## `IncreaseLiquidityV2` Add liquidity to an already-open position. **Arguments** ``` liquidity: u128 amount_0_max: u64 amount_1_max: u64 base_flag: Option ``` **Accounts** — like `OpenPosition` minus the NFT mint (position already exists; the NFT is passed as the owner's ATA holding 1 token). **Effect** * Transfers `amount_0_actual` / `amount_1_actual` from user → vaults. * Increments `personal_position.liquidity` and `pool_state.liquidity` (if in range), and the endpoint-tick `liquidity_gross` / `liquidity_net` accordingly. * **Collects fees and rewards owed since last touch** and credits them to `tokens_fees_owed_{0,1}` / `reward_amount_owed`. Those are paid out only on `DecreaseLiquidity` or `CollectReward`, not on increase. ## `DecreaseLiquidityV2` Remove liquidity from a position. **Arguments** ``` liquidity: u128 amount_0_min: u64 amount_1_min: u64 ``` **Accounts** — same shape as `IncreaseLiquidity`. **Effect** * Computes `(amount_0, amount_1)` for the removed `L` given current `sqrt_price_x64`. * Settles fees/rewards accrued since the last touch, same as `IncreaseLiquidity`. * Transfers `amount_0 + fees_owed_0` and `amount_1 + fees_owed_1` out of vaults to the user. * Decrements liquidity counters; if the new `personal_position.liquidity == 0`, the position is eligible for `ClosePosition`. **Slippage** — `amount_0_min` and `amount_1_min` are the minimums the user accepts net of Token-2022 transfer fees on the output side. ## `ClosePosition` Burn the position NFT and close `PersonalPositionState`. **Preconditions** * `personal_position.liquidity == 0`. * `tokens_fees_owed_{0,1} == 0`. * All reward counters `reward_amount_owed == 0`. (I.e., collect everything and decrease-to-zero first.) **Effect** * Burns the NFT. * Closes the NFT mint account and the `personal_position` account, refunding rent to the `payer`. ## `SwapV2` Walk the liquidity curve; exact input *or* exact output depending on `is_base_input`. **Arguments** ``` amount: u64 // input if is_base_input=true, output otherwise other_amount_threshold: u64 // min out or max in sqrt_price_limit_x64: u128 // hard bound; 0 ⇒ unbounded is_base_input: bool ``` **Accounts (abridged)** | # | Name | W | S | Notes | | --- | ---------------------------------------- | - | - | ------------------------------------------------ | | 1 | `payer` | | S | | | 2 | `amm_config` | | | | | 3 | `pool_state` | W | | | | 4 | `input_token_account` | W | | | | 5 | `output_token_account` | W | | | | 6 | `input_vault` | W | | | | 7 | `output_vault` | W | | | | 8 | `observation_state` | W | | | | 9 | `token_program` | | | | | 10 | `token_program_2022` | | | V2. | | 11 | `memo_program` | | | V2 (required for some Token-2022 paths). | | 12 | `input_vault_mint`, `output_vault_mint` | | | V2. | | 13 | `tick_array_bitmap_extension` (optional) | W | | If swap walks into the extension. | | 14+ | `tick_array` (remaining) | W | | Enough arrays to span the walk's expected range. | Callers pass a ranked list of tick arrays covering the expected swap walk; the program uses as many as it needs. The SDK computes this list via `PoolUtils.computeAmountOutFormat` or the API's quote endpoint. **Preconditions** * `pool_state.status` allows swap. * `now >= open_time`. * `sqrt_price_limit_x64` is on the correct side of `sqrt_price_x64` for the direction. **Common errors** — `ExceededSlippage`, `SqrtPriceLimitOverflow`, `TickArrayNotFound`, `LiquidityInsufficient`. **What `SwapV2` does internally that callers should know about** (post-2025 release): 1. **Dynamic fee surcharge** — if `pool.dynamic_fee_info` is non-zero, the program updates the volatility accumulator using the tick distance traversed since the last swap (with the filter/decay rules from [`products/clmm/fees`](/products/clmm/fees)) and adds a `dynamic_fee_component` on top of `AmmConfig.trade_fee_rate`. Total fee is capped at 10% (`MAX_FEE_RATE_NUMERATOR / 1_000_000`). 2. **Limit-order matching** — when the price walk crosses a tick that holds open limit orders, the program first fills available limit-order liquidity at that tick (FIFO by `order_phase`), then proceeds along the LP liquidity curve. Filled amounts update `tick.unfilled_ratio_x64` and `tick.part_filled_orders_remaining` for later settlement; orders themselves remain unspent until their owner calls `SettleLimitOrder`. 3. **Single-sided fee routing** — when `pool.fee_on = Token0Only` or `Token1Only`, the swap step still computes the same input-output trade; the fee is then routed to the configured side. For directions where the configured fee side is the output, the fee is deducted from the swap output (the user receives `out − fee`); for directions where it is the input, behavior matches `FromInput`. See `is_fee_on_input(zero_for_one)` and `is_fee_on_token0(zero_for_one)` on `PoolState`. `Swap` (V1) implements the same dynamic fee, single-sided fee routing, and limit-order matching as `SwapV2`; the only feature it lacks is Token-2022 support — both vaults must be classic SPL Token. Pools with any Token-2022 mint must be swapped via `SwapV2`. The aggregator and SDK already prefer V2 for every CLMM leg so callers don't have to branch on mint type. ## `OpenLimitOrder` Place a sell order at a specific tick. The order sits in a per-tick FIFO cohort and fills as price walks past. **Arguments** ``` nonce_index: u8 // user's chosen nonce-account index (0..255 per wallet) zero_for_one: bool // true: sell token0 for token1; false: sell token1 for token0 tick_index: i32 // must be a multiple of pool.tick_spacing amount: u64 // input-token amount ``` **Accounts (abridged)** | # | Name | W | S | Notes | | -- | ------------------------ | - | - | ----------------------------------------------------------------------------------- | | 1 | `payer` | W | S | Order owner; pays rent. | | 2 | `pool_state` | W | | | | 3 | `tick_array` | W | | The tick array containing `tick_index`. | | 4 | `limit_order_nonce` | W | | PDA. `init_if_needed` — created on the user's first order under this `nonce_index`. | | 5 | `limit_order` | W | | PDA. `init` here. | | 6 | `input_token_account` | W | | User's input ATA. | | 7 | `input_vault` | W | | Pool input vault. | | 8 | `input_vault_mint` | | | Token-2022 fee handling. | | 9 | `input_token_program` | | | SPL or Token-2022. | | 10 | `system_program`, `rent` | | | | **Preconditions** * `tick_index % pool.tick_spacing == 0` and within `[MIN_TICK, MAX_TICK]`. * `tick_index` is on the **right side of `pool.tick_current`** for the chosen direction (selling token0 → tick must be above current, and vice versa). Selling at a tick already crossed would be matched immediately and is rejected. * `pool_state.status` allows the limit-order operation (bit 5). **Postconditions** * `limit_order` exists, snapshotting `tick.order_phase` and `tick.unfilled_ratio_x64` at open time. * `tick.orders_amount += amount` (in the current cohort). * `limit_order_nonce.order_nonce += 1`. * `OpenLimitOrderEvent` emitted. **Common errors** — `InvalidLimitOrderAmount` (zero or below the pool's minimum), `InvalidTickIndex` (out of `[MIN_TICK, MAX_TICK]`, or on the wrong side of `tick_current` for the chosen direction), `TickAndSpacingNotMatch` (`tick_index % pool.tick_spacing != 0`), `OrderPhaseSaturated`. ## `IncreaseLimitOrder` Add to an existing open order. Only callable by the order's `owner`. **Arguments** ``` amount: u64 // additional input-token amount ``` **Accounts** — like `OpenLimitOrder` minus the nonce account; the `limit_order` PDA is passed directly. **Preconditions** * `limit_order.owner == signer`. * The order is still in the same cohort (`tick.order_phase == limit_order.order_phase`). If the cohort has already begun filling, the order is partially settled — the caller should call `DecreaseLimitOrder` or `SettleLimitOrder` first to roll forward. **Effect** * Transfers `amount` from owner ATA to `input_vault`. * `limit_order.total_amount += amount`; `tick.orders_amount += amount`. ## `DecreaseLimitOrder` Reduce or fully cancel an open order. Pays the unfilled remainder back to the owner, plus any output already settled by past partial fills. **Arguments** ``` amount: u64 // input-token amount to withdraw (max = unfilled remainder) amount_min: u64 // slippage floor on the input-side withdrawal ``` **Accounts** — both input and output token sides: | # | Name | W | S | | -- | --------------------------------------- | - | - | | 1 | `owner` | | S | | 2 | `pool_state` | W | | | 3 | `tick_array` | W | | | 4 | `limit_order` | W | | | 5 | `input_token_account` | W | | | 6 | `output_token_account` | W | | | 7 | `input_vault` | W | | | 8 | `output_vault` | W | | | 9 | `input_vault_mint`, `output_vault_mint` | | | | 10 | `token_program`, `token_program_2022` | | | **Effect** * Recomputes the order's filled amount from the cohort's `unfilled_ratio_x64` since open. * Sends filled output to `output_token_account`. * Sends `amount` of unfilled input back to `input_token_account`. * Updates `limit_order` accordingly. If the new unfilled remainder is zero, the program closes the account and refunds rent to `owner`. ## `SettleLimitOrder` Push filled output tokens to the owner without changing the order's unfilled remainder. Useful when `auto_withdraw` keepers want to drip-pay long-running partial fills. **Caller** — either the order's `owner`, or the program's `limit_order_admin` (an off-chain operational hot wallet that runs an automated keeper loop). The keeper has no other authority — it cannot move user funds outside of pushing filled output to the order's `owner` ATA. **Accounts** | # | Name | W | S | | | - | ---------------------- | - | - | ------------------------------ | | 1 | `signer` | | S | owner *or* `limit_order_admin` | | 2 | `pool_state` | | | | | 3 | `tick_array` | | | | | 4 | `limit_order` | W | | | | 5 | `output_token_account` | W | | Owner's output ATA. | | 6 | `output_vault` | W | | Pool output vault. | | 7 | `output_vault_mint` | | | | | 8 | `output_token_program` | | | | **Effect** * Computes the cumulative output owed using `(limit_order.unfilled_ratio_x64, tick.unfilled_ratio_x64)`. * Transfers the delta to `output_token_account`. * Updates `limit_order.settled_output`. * Does **not** close the order; it is still open against any remaining input. ## `CloseLimitOrder` Close a fully-consumed order account. Rent is always returned to `limit_order.owner` regardless of who signs. **Caller** — either `owner` or `limit_order_admin`. **Preconditions** * The order has zero unfilled remainder (either `amount == total_amount` was filled and settled, or the owner previously decreased the order to zero and forgot to close). **Effect** * Closes `limit_order`; rent is sent to `limit_order.owner`. ## `CreateDynamicFeeConfig` (admin) Create a reusable parameter set under a u16 index. **Arguments** ``` index: u16 filter_period: u16 // seconds; e.g. 30 decay_period: u16 // seconds; e.g. 600. Must be > filter_period reduction_factor: u16 // 1..10_000; e.g. 5_000 = 50% retention per decay window dynamic_fee_control: u32 // 1..100_000; gain on the volatility-to-fee curve max_volatility_accumulator: u32 // ceiling ``` **Accounts** | # | Name | W | S | Notes | | - | -------------------- | - | - | ----------------------- | | 1 | `owner` | W | S | Hardcoded admin pubkey. | | 2 | `dynamic_fee_config` | W | | PDA, `init` here. | | 3 | `system_program` | | | | **Common errors** — `InvalidDynamicFeeConfigParams` if `decay_period <= filter_period` or any 0-valued field is out of bounds. ## `UpdateDynamicFeeConfig` (admin) Modify an existing `DynamicFeeConfig`. Pools that already snapshotted the config at creation time are **not** retroactively updated; only newly-created pools that reference this config will pick up the new values. **Arguments** — same five calibration fields as `CreateDynamicFeeConfig` (`filter_period`, `decay_period`, `reduction_factor`, `dynamic_fee_control`, `max_volatility_accumulator`); `index` is fixed at creation and not re-passed here. ## `CollectProtocolFee` / `CollectFundFee` Identical shape to CPMM's `CollectProtocolFee` / `CollectFundFee`. Signer must match `AmmConfig.owner` / `AmmConfig.fund_owner`. Sweep accrued protocol/fund fees from the pool's vaults to a recipient, zero the corresponding `PoolState.protocol_fees_*` / `fund_fees_*` fields. ## `InitializeReward` Add a new reward stream to a pool. Up to 3 streams may be active at once. **Arguments** ``` open_time: u64 end_time: u64 emissions_per_second_x64: u128 // Q64.64 ``` **Accounts** | # | Name | W | S | | | - | ------------------------ | - | - | ------------------------------------------------ | | 1 | `reward_funder` | W | S | | | 2 | `funder_token_account` | W | | | | 3 | `amm_config` | | | | | 4 | `pool_state` | W | | | | 5 | `operation_state` | | | CLMM operation-state PDA gating reward creation. | | 6 | `reward_token_mint` | | | | | 7 | `reward_token_vault` | W | | `init` here. | | 8 | `reward_token_program` | | | | | 9 | `system_program`, `rent` | | | | **Preconditions** * Less than 3 streams currently active on the pool. * Funder deposits `total_emission = emissions_per_second × (end_time − open_time)` worth of reward token into the vault as part of this instruction. * Whitelisted reward mint per `operation_state`. ## `SetRewardParams` Extend, top up, or change emission rate on an existing reward stream. Typically called by a pool creator or the Raydium multisig. Constraints live on-chain: you can usually extend `end_time` or increase emissions, not shrink them retroactively. Check `operation_state`'s owner list. ## `UpdateRewardInfos` Pure bookkeeping — settles `reward_growth_global_x64` to the current time by multiplying `emissions_per_second × Δt / liquidity`. Called internally by every liquidity-touching instruction. Exposed as a standalone instruction because external actors (UIs, cranks) sometimes want to trigger it. ## `CollectReward` Position owner claims owed reward tokens. **Accounts** | # | Name | W | S | | | - | ------------------------- | - | - | ------------------------------------- | | 1 | `nft_owner` | | S | | | 2 | `nft_account` | | | Owner's ATA holding the position NFT. | | 3 | `personal_position` | W | | | | 4 | `pool_state` | W | | | | 5 | `protocol_position` | | | | | 6 | `reward_token_vault` | W | | | | 7 | `recipient_token_account` | W | | | | 8 | `token_program` | | | | | 9 | `token_program_2022` | | | | **Effect** * Settles reward growth (same pattern as fees). * Transfers the owed amount to the recipient ATA, zeroes `reward_amount_owed[i]`. ## State-change matrix | Instruction | `pool.liquidity` | `pool.fee_growth_global` | `pool.reward_growth_global` | `personal_position.liquidity` | Tick array | | ------------------------ | ---------------- | ------------------------ | --------------------------- | ----------------------------- | ----------------------------------------------- | | `CreatePool` | 0 | 0 | — | — | — | | `OpenPosition` | + if in range | — | — | new | add liquidity\_gross/net | | `IncreaseLiquidity` | + if in range | settle owed | settle owed | + | adjust | | `DecreaseLiquidity` | − if in range | settle owed | settle owed | − | adjust | | `ClosePosition` | — | — | — | destroyed | — | | `SwapV2` | ± on crossings | + | — | — | cross & flip outside; match limit-order cohorts | | `OpenLimitOrder` | — | — | — | — | `orders_amount += amount` on the target tick | | `IncreaseLimitOrder` | — | — | — | — | `orders_amount += amount` | | `DecreaseLimitOrder` | — | — | — | — | `orders_amount -=`, may close cohort | | `SettleLimitOrder` | — | — | — | — | — (read-only on tick) | | `CloseLimitOrder` | — | — | — | — | — | | `CreateCustomizablePool` | 0 | 0 | — | — | — | | `UpdateRewardInfos` | — | — | + | — | — | | `CollectReward` | — | — | settle owed | — | — | ## Where to go next * [`products/clmm/code-demos`](/products/clmm/code-demos) — runnable TypeScript samples. * [`products/clmm/fees`](/products/clmm/fees) — details on fee and reward accrual. * [`reference/error-codes`](/reference/error-codes) — complete CLMM Anchor error table. Sources: * [`raydium-io/raydium-clmm` — `programs/amm/src/instructions`](https://github.com/raydium-io/raydium-clmm) * Raydium SDK v2 — `@raydium-io/raydium-sdk-v2` # CLMM math Source: https://docs.raydium.io/products/clmm/math Sqrt-price representation, liquidity ↔ token amounts, the single-tick swap step, multi-tick iteration, and fee-growth accounting. This page is operational: it gives the formulas, fixed-point conventions, and step-through used by the CLMM program. For the reasoning behind the concentrated-liquidity curve itself — why `L = sqrt(x · y)` matters — see [`algorithms/clmm-math`](/algorithms/clmm-math). This page assumes you have read that. ## Sqrt-price representation CLMM stores price as `sqrt_price_x64` — the square root of the token1-per-token0 price, as a Q64.64 fixed-point number: $$ \text{sqrt\_price\_x64} = \lfloor \sqrt{p} \cdot 2^{64} \rfloor $$ where `p = token1_amount / token0_amount`. Working in `sqrt` instead of `p` linearizes the swap math (token-amount deltas become linear in `Δsqrt_price`), and the `x64` fixed-point keeps precision through many-tick swaps. Tick ↔ sqrt-price conversion is precomputed via a `bit-by-bit` log-approximation: $$ \text{sqrt\_price\_x64}(t) \approx 2^{64} \cdot (1.0001)^{t/2} $$ implemented as a lookup-based exponentiation in `tick_math::get_sqrt_price_at_tick`. ## Liquidity as a canonical unit Inside a range `[sqrt_a, sqrt_b]` (with `sqrt_a < sqrt_b`) a position of **liquidity `L`** maps to token amounts as follows. Let `sqrt_c = sqrt_price_x64` be the pool's current price. | Case | `amount0` | `amount1` | | ------------------------------------------- | ------------------------------------------- | ----------------------- | | `sqrt_c <= sqrt_a` (pool price below range) | `L · (sqrt_b - sqrt_a) / (sqrt_a · sqrt_b)` | `0` | | `sqrt_a < sqrt_c < sqrt_b` (in range) | `L · (sqrt_b - sqrt_c) / (sqrt_c · sqrt_b)` | `L · (sqrt_c - sqrt_a)` | | `sqrt_c >= sqrt_b` (pool price above range) | `0` | `L · (sqrt_b - sqrt_a)` | All three identities come from the invariant `x = L / sqrt_p`, `y = L · sqrt_p` that concentrated liquidity satisfies within a range. Integrators typically want the inverse: given a deposit of `amount0` / `amount1`, compute the maximum `L` that fits in the range. The SDK's `LiquidityMath.getLiquidityFromTokenAmounts` does this. The formula for the in-range case: $$ L_0 = \text{amount0} \cdot \frac{\text{sqrt\_c} \cdot \text{sqrt\_b}}{\text{sqrt\_b} - \text{sqrt\_c}}, \qquad L_1 = \frac{\text{amount1}}{\text{sqrt\_c} - \text{sqrt\_a}}, \qquad L = \min(L_0, L_1) $$ Whichever side binds determines the ratio actually consumed; the other side may have leftover. ## Single-tick swap step A swap proceeds in **steps**. Each step either (a) consumes all available input within the current tick range without crossing a tick, or (b) moves the price exactly to the next initialized tick. Given current state `(sqrt_c, L)` and a swap **up** (token0 in, token1 out, `sqrt_price` increases), the distance to the next initialized tick is `sqrt_t`. Inside this micro-interval the relationship between input and price is: $$ \Delta\text{amount0} = L \cdot \left( \frac{1}{\text{sqrt\_c}} - \frac{1}{\text{sqrt\_t}} \right) = \frac{L \cdot (\text{sqrt\_t} - \text{sqrt\_c})}{\text{sqrt\_c} \cdot \text{sqrt\_t}} $$ and $$ \Delta\text{amount1} = L \cdot (\text{sqrt\_t} - \text{sqrt\_c}) $$ The program does one of two things: * **Does the entire input fit?** If the input remaining (after fee) is less than `Δamount0` to reach `sqrt_t`, solve for the new `sqrt_c'` exactly: $$ \text{sqrt\_c}' = \frac{L \cdot \text{sqrt\_c}}{L + \Delta\text{input} \cdot \text{sqrt\_c}} $$ (for an exact-input `token0 → token1` swap). The swap completes in this step without crossing a tick. * **Input exceeds `Δamount0`?** Set `sqrt_c' = sqrt_t`, cross the tick (apply `liquidity_net`), decrement remaining input by `Δamount0`, increment output by `Δamount1`, and repeat. For the opposite direction (`token1 → token0`, price going down), the formulas have `sqrt_c` and `sqrt_t` swapped and the inversion in the other slot. The full Rust implementation lives in `raydium-clmm/programs/amm/src/libraries/swap_math.rs`. The logic there matches Uniswap v3's `SwapMath.computeSwapStep` one-for-one. ## Fees on each step Trade fees are taken off the **input** amount in each step, same convention as CPMM: ``` step_fee_amount = ceil(step_input * trade_fee_rate / 1_000_000) step_net_input = step_input - step_fee_amount protocol_portion = floor(step_fee_amount * protocol_fee_rate / 1_000_000) fund_portion = floor(step_fee_amount * fund_fee_rate / 1_000_000) lp_portion = step_fee_amount - protocol_portion - fund_portion ``` The LP portion is split across the currently-in-range liquidity by updating the global fee-growth accumulator: $$ \text{fee\_growth\_global}_{\text{in}} \mathrel{+}= \text{lp\_portion} \cdot \frac{2^{64}}{L} $$ — i.e., it is denominated in *fees per unit of liquidity*, Q64.64, so that a position of size `L_i` that stayed in range across this swap will later read back `L_i · Δfee_growth_global / 2^{64}` owed tokens. The protocol and fund portions accrue to `PoolState.protocol_fees_token_{0,1}` and `PoolState.fund_fees_token_{0,1}` respectively, identical to CPMM. They are swept by `CollectProtocolFee` / `CollectFundFee`. ## Fee growth outside and inside The tricky part of CLMM fee accounting: a position earns fees only while the pool's price is **inside** its range. The pool tracks cumulative fees globally; the position needs to know the cumulative fees *while inside its specific range*. The solution is a **tick-based** accumulator. Each tick stores: ``` fee_growth_outside_0_x64 fee_growth_outside_1_x64 ``` At the moment of tick initialization: * If the pool's price is **above** this tick (`tick_current >= this_tick`), `fee_growth_outside = fee_growth_global`. (Everything earned so far is "outside" — i.e., below — this tick, relative to the current price.) * Else `fee_growth_outside = 0`. When the price crosses a tick, the program **flips** that tick's `fee_growth_outside`: $$ \text{fee\_growth\_outside} \gets \text{fee\_growth\_global} - \text{fee\_growth\_outside} $$ The invariant this preserves: for any tick `t`, `fee_growth_outside(t)` equals the fees that accrued while `tick_current` was on the opposite side of `t`. **Fee growth inside a range `[tick_lower, tick_upper]`** is then derived: ``` if tick_current >= tick_upper: fee_growth_below = fee_growth_outside(tick_lower) fee_growth_above = fee_growth_global - fee_growth_outside(tick_upper) elif tick_current >= tick_lower: fee_growth_below = fee_growth_outside(tick_lower) fee_growth_above = fee_growth_outside(tick_upper) else: fee_growth_below = fee_growth_global - fee_growth_outside(tick_lower) fee_growth_above = fee_growth_outside(tick_upper) fee_growth_inside = fee_growth_global - fee_growth_below - fee_growth_above ``` This is the Uniswap-v3 fee-growth formula, unchanged. ## What a position stores and what it reads A `PersonalPositionState` stores `fee_growth_inside_0_last_x64` and `fee_growth_inside_1_last_x64`: the `fee_growth_inside` values at the last time the position was touched. At any subsequent touch (increase, decrease, collect), the program: 1. Computes the current `fee_growth_inside_{0,1}_x64` using the formula above. 2. Computes `Δ = fee_growth_inside_now − fee_growth_inside_last` (modular-subtraction on u128). 3. Adds `Δ × position.liquidity / 2^{64}` to `tokens_fees_owed_{0,1}`. 4. Updates `fee_growth_inside_last` to the new value. Tokens actually move out of the vaults only on `CollectFees` / `DecreaseLiquidity`, against `tokens_fees_owed`. ## Rewards Each of the pool's up to 3 reward streams uses the same growth-inside machinery, in its own `reward_growth_global_x64` accumulator. At emission time: $$ \text{reward\_growth\_global} \mathrel{+}= \text{emission\_per\_second} \cdot \Delta t \cdot \frac{2^{64}}{L} $$ — emissions scale inversely with active liquidity, so a denser pool pays each position proportionally less per second, but over more positions total. The per-position reward owed is $$ \text{reward\_owed} = (\text{reward\_growth\_inside}_{\text{now}} - \text{reward\_growth\_inside}_{\text{last}}) \cdot L / 2^{64} $$ and is claimed via `CollectReward`. See [`products/clmm/fees`](/products/clmm/fees). ## Worked example: exact-input swap Suppose: * `tick_spacing = 60` * `sqrt_price_x64 = 1 × 2^{64}` — price = 1.0, so `tick_current = 0`. * Active liquidity `L = 1_000_000 × 2^{64}`. * Next initialized tick above: `t = 60` (sqrt\_price\_b ≈ `1.003004 × 2^{64}`). * Trade fee rate: 500 (0.05%). User: `SwapBaseInput` exact-input 1,000 token0. Step 1 — fees: ``` trade_fee = ceil(1000 * 500 / 1_000_000) = 1 step_net_input = 999 ``` Step 2 — does 999 fit within the current tick range? ``` Δ to next tick (amount0): L · (sqrt_t - sqrt_c) / (sqrt_c * sqrt_t) ≈ 1_000_000 · (1.003004 − 1) / (1 · 1.003004) ≈ 2995.5 token0 ``` `999 < 2995.5`, so the entire input fits without crossing the tick. Step 3 — new price: ``` sqrt_c' = L · sqrt_c / (L + Δin · sqrt_c) = 1_000_000 · 1 / (1_000_000 + 999 · 1) ≈ 0.999001 ``` i.e., `sqrt_c'` slightly below `sqrt_c`. Note that the formula above is for a `token1 → token0` swap. The example here is `token0 → token1`, which drives the price **up**, not down — so we use the corresponding form for `token0 in`: ``` sqrt_c' = sqrt_c + Δin / L = 1 + 999 / 1_000_000 = 1.000999 ``` (this matches the expected swap direction for `token0 → token1`: `sqrt_c` rises along with the price.) Step 4 — amount out: ``` Δout token1 = L · (sqrt_c' − sqrt_c) = 1_000_000 · 0.000999 = 999.00 ``` After accounting for rounding, the user receives ≈ 999 token1. The fee (1 token0) is split between LP, protocol, and fund by `trade_fee_rate × protocol_fee_rate / 1e6` (and similar for fund); the LP portion flows into `fee_growth_global_0_x64`. ## Limit-order matching during swap When a swap step crosses a tick that holds open limit orders, those orders consume swap input **before** the LP curve does, at the tick's exact price. The matching is FIFO within the tick by `order_phase` cohort. ### Per-cohort state on `TickState` ``` order_phase : u64 monotonic cohort id orders_amount : u64 input-token total in the current (newest) cohort part_filled_orders_remaining : u64 remaining input of the cohort that swap is currently filling unfilled_ratio_x64 : u128 Q64.64 fill ratio for the partially-filled cohort ``` The two-cohort layout exists because new orders may be opened on a tick *while* an older cohort is still being filled. Newly-opened orders join `orders_amount` and inherit the next `order_phase`; they cannot fill until the previous cohort is fully consumed. ### Matching step Pseudo-code for the matching that happens at each tick crossing during a swap: ``` fn match_limit_orders_at_tick(tick, swap_input_remaining, sqrt_p): # 1. Try to fill the partially-filled cohort first. if tick.part_filled_orders_remaining > 0: consume = min(tick.part_filled_orders_remaining, swap_input_remaining) # Update the unfilled-ratio for that cohort. tick.unfilled_ratio_x64 *= (1 - consume / tick.part_filled_orders_remaining) tick.part_filled_orders_remaining -= consume swap_input_remaining -= consume if tick.part_filled_orders_remaining == 0: tick.unfilled_ratio_x64 = 0 if swap_input_remaining == 0: return # 2. Promote the active cohort. if tick.orders_amount > 0: tick.part_filled_orders_remaining = tick.orders_amount tick.orders_amount = 0 tick.order_phase += 1 tick.unfilled_ratio_x64 = ONE_X64 # Recurse with the freshly-promoted cohort. return match_limit_orders_at_tick(tick, swap_input_remaining, sqrt_p) return # tick has no more limit orders ``` Output tokens going to the limit-order owners are **not** transferred per swap. They sit virtually in the pool's output vault until the order owner calls `SettleLimitOrder` (or `DecreaseLimitOrder`). The pool simply tracks how much of the cohort is now filled via `unfilled_ratio_x64`. Each `LimitOrderState` stores its own `(order_phase, unfilled_ratio_x64)` snapshot at open time, so settlement reduces to: ``` filled_amount = total_amount × (1 − tick_now.unfilled_ratio_x64 / order.unfilled_ratio_x64) if tick_now.order_phase > order.order_phase else 0 output_amount = price_at(tick_index) × filled_amount # adjusted for direction ``` This O(1) settlement is the whole point of the cohort design — a tick can fill arbitrarily many orders without per-order gas. ### Interaction with the LP curve In a swap step, limit-order matching happens **at** the tick (zero `Δsqrt_price`); LP curve consumption happens **between** ticks. The order is therefore: 1. Cross tick `t_cross` (apply LP `liquidity_net` change first, since this is how Uniswap-V3 does it). 2. Fill any limit orders sitting at `t_cross`. 3. Continue along the LP curve to the next initialized tick or to `swap_input` exhaustion. Limit orders thus give traders *more* effective liquidity at exactly the order's tick price (a price-improvement effect), at the cost of LPs not earning fees on that portion of the swap volume — the limit-order portion of the trade is fee-free for the swapper, since the limit-order placer is acting as a maker. The dynamic-fee surcharge (if enabled) still applies to the LP portion of the same swap. ## Dynamic fee derivation `PoolState.dynamic_fee_info` carries the volatility state. Each swap step computes the per-step fee rate as: $$ \text{fee\_rate}_{\text{total}} = \text{trade\_fee\_rate}_{\text{config}} + \underbrace{\frac{\text{dynamic\_fee\_control} \cdot (\text{vol\_acc} \cdot \text{tick\_spacing})^2} {D_{\text{ctrl}} \cdot S_{\text{vol}}^2}}_{\text{dynamic surcharge}} $$ where: * $D_{\text{ctrl}} = 100{,}000$ — `DYNAMIC_FEE_CONTROL_DENOMINATOR` * $S_{\text{vol}} = 10{,}000$ — `VOLATILITY_ACCUMULATOR_SCALE` * `vol_acc` is the per-swap accumulator after the update rule below * `tick_spacing` is from `PoolState.tick_spacing` The result is clamped at $100{,}000 / 10^6 = 10\%$. ### Accumulator update Two rules are applied each swap, in order: **Decay.** The reference floor decays based on time since last update: $$ \text{vol\_ref} = \begin{cases} 0 & \text{if } \Delta t > \text{decay\_period} \\ \text{vol\_acc}_{\text{prev}} \cdot \dfrac{\text{reduction\_factor}}{10{,}000} & \text{if } \text{filter\_period} < \Delta t \le \text{decay\_period} \\ \text{vol\_ref}_{\text{prev}} & \text{if } \Delta t \le \text{filter\_period} \end{cases} $$ **Accumulate.** The new accumulator is the reference plus tick-distance traversed since the previous reference index: $$ \text{vol\_acc} = \min\left( \text{vol\_ref} + \left| t_{\text{ref}} - t_{\text{now}} \right| \cdot S_{\text{vol}}, \text{max\_vol\_acc} \right) $$ `tick_spacing_index_reference` ($t_{\text{ref}}$) is in tick-spacing-units, not raw ticks: $t_{\text{ref}} = \lfloor \text{tick\_current} / \text{tick\_spacing} \rfloor$. ### Why parabolic in tick distance Squaring the accumulator means the fee rises as the *square* of how far price has walked away from its reference point. Empirically this matches the variance scaling of price under random-walk pressure: a 2× tick excursion implies 4× the implied volatility, so charges 4× the surcharge. The `dynamic_fee_control` parameter calibrates the absolute level. The `filter_period` window prevents tiny sub-second oscillations (e.g., MEV bots sandwiching) from inflating the accumulator. The `decay_period` window prevents a single past spike from charging fees indefinitely after the market has calmed. ## Numerical robustness * All intermediate products go through `u128` or `u256`-shaped arithmetic. CLMM uses `U128Sqrt` helpers and `FullMath::mulDiv` patterns directly ported from Uniswap v3. * Division rounding is chosen per-step to enforce the invariant `k' ≥ k` locally. `SwapBaseInput` rounds output **down**; `SwapBaseOutput` rounds input **up**. * Tick crossings that drop `PoolState.liquidity` to zero are allowed (the price can traverse a "liquidity hole") but the swap simply advances to the next initialized tick without consuming input, charging no fee. * Overflow guard: `sqrt_price_x64` is kept in the inclusive range `[MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64]` corresponding to `[MIN_TICK, MAX_TICK]`. A swap that would push past either bound reverts with `SqrtPriceLimitOverflow`. ## Where to go next * [`products/clmm/ticks-and-positions`](/products/clmm/ticks-and-positions) for how the tick map participates in the walk. * [`products/clmm/fees`](/products/clmm/fees) for the fee/reward side of the math in detail. * [`algorithms/clmm-math`](/algorithms/clmm-math) for the derivations behind `L = sqrt(x · y)` and the range-vs-liquidity formulas. Sources: * [`raydium-io/raydium-clmm` — `libraries/swap_math.rs`, `libraries/tick_math.rs`](https://github.com/raydium-io/raydium-clmm) * "Uniswap v3 Core" whitepaper, §6–7 # CLMM overview Source: https://docs.raydium.io/products/clmm/overview What Raydium's CLMM is, how concentrated liquidity differs from a constant-product pool, and when to pick it over CPMM or AMM v4. ## One-paragraph summary CLMM — **C**oncentrated **L**iquidity **M**arket **M**aker — is Raydium's Uniswap-v3-style AMM. Instead of spreading a liquidity provider's deposit across the entire price curve, CLMM lets LPs deposit into a **specific price range**. Inside that range, every dollar of deposit is many times more productive than it would be in a CPMM pool; outside the range, the deposit earns nothing and sits as a single-sided balance. The program tracks liquidity per-**tick** (a discretized price bucket), prices pool state by the square root of the price encoded as a Q64.64 fixed-point number (`sqrt_price_x64`), and mints an **NFT** for each LP position rather than a fungible LP token. ## What's new The latest CLMM release ships three additions on top of the Uniswap-v3-style core. They are opt-in at pool-creation time and backwards-compatible with existing pools and positions: * **Limit orders.** LPs can now park a single-tick order at a specific price and have the swap path fill it FIFO when the swap crosses that tick. Orders settle into the owner's ATA at the limit price; an off-chain keeper (`limit_order_admin`) can sweep filled orders without the owner being online. See [Instructions → OpenLimitOrder / SettleLimitOrder](/products/clmm/instructions) and [Math → Limit-order matching during swap](/products/clmm/math). * **Single-sided fee (`CollectFeeOn`).** Pools can be configured to take swap fees from the input side (legacy behaviour, mode `0`), or always from `token_0` (`1`), or always from `token_1` (`2`). Useful when one side of the pair is the canonical accounting token (e.g., USDC). See [Fees → Single-sided fee](/products/clmm/fees). * **Dynamic fee.** Pools can opt into a volatility-tracking fee surcharge that rises with rapid tick movement and decays over time. Calibrated by a per-tier `DynamicFeeConfig` and a per-pool `DynamicFeeInfo`. See [Fees → Dynamic fee](/products/clmm/fees) and [Math → Dynamic fee derivation](/products/clmm/math). A new instruction, `CreateCustomizablePool`, exposes all three knobs at pool-creation time. The classic `CreatePool` continues to work for default-fee pools without limit orders or dynamic fees. ## What CLMM gives you * **Capital efficiency.** A stablecoin-stablecoin LP concentrating liquidity in a ±0.1% band around parity can earn 100×+ the fees per dollar of TVL compared to a CPMM pool of the same pair. * **Position-level fee accounting.** Fees accrue per-position, not per-LP-mint. Two positions on the same pool earn different fee amounts based on their ranges and the path the price has taken. * **Multiple fee tiers per pair.** A pair can have several CLMM pools, each bound to a different `AmmConfig` with its own trade-fee rate and tick-spacing. The web UI and routers surface whichever tier has the most liquidity at the current price. * **Incentivizable directly on the pool.** Up to three reward token streams can be attached to a pool; positions collect rewards pro-rata based on the seconds × in-range-liquidity they contribute. See [`products/clmm/fees`](/products/clmm/fees). * **NFT positions.** Each position is a non-fungible token with mint equal to a deterministic PDA. Transferring the NFT transfers the position; wallets and UIs can display positions the same way they display collectibles. * **Token-2022 support** on both sides of the pair, with the same extension restrictions as CPMM. ## What CLMM is not * **Not set-it-and-forget-it.** A range set while SOL is \$160 will earn nothing if SOL moves to \$80, unless you actively adjust. CLMM rewards active LPs; passive LPs should stay with CPMM. * **Not zero-cost to open.** Each new tick-array the position crosses must be initialized, costing rent. Wide ranges are cheaper; narrow ones are not. * **Not a CLOB.** Unlike AMM v4, CLMM has no OpenBook dependency. All liquidity sits on the tick map. * **Not a superset of CPMM.** A CLMM position spanning `[tick_min, tick_max]` at maximum range behaves *similarly* to CPMM, but with different gas costs, a different fee accounting model, and no fungible LP token. If you want a simple fungible-LP pool, use CPMM. ## How CLMM differs from CPMM and AMM v4 | Dimension | AMM v4 | CPMM | CLMM | | ----------------- | --------------------- | --------------------------------- | ------------------------------ | | Curve | Constant product | Constant product | Concentrated (tick-based) | | LP share | Fungible LP mint | Fungible LP mint | Per-position NFT | | Liquidity lives… | Across all prices | Across all prices | In a user-chosen range | | Fee tiers | Fixed 0.25% | Per `AmmConfig` (e.g., 0.25%, 1%) | Per `AmmConfig` × tick-spacing | | Active management | Not applicable | Not applicable | **Required** | | Fee accounting | Pool-level | Pool-level | Per-position | | Reward farms | Separate Farm program | Separate Farm program | Built-in (up to 3 rewards) | | Token-2022 | No | Yes | Yes | | On-chain oracle | No | `observation` ring | `observation` array per pool | ## Mental model Think of a CLMM pool as three overlaid data structures: 1. **A continuous curve in `sqrt_price` space.** The pool's price is represented as `sqrt_price_x64`, a Q64.64 fixed-point. Swaps walk along this curve; within a tick boundary, the math is standard concentrated-liquidity AMM math (see [`algorithms/clmm-math`](/algorithms/clmm-math)). 2. **A discrete tick map.** Prices are quantized into ticks — integer powers of `1.0001`. Each tick has a known `sqrt_price`. Positions reference their endpoints as integer tick indices. Tick indices are grouped into fixed-size **tick arrays** for storage. 3. **Per-position fee and reward bookkeeping.** Each position stores the global `fee_growth_inside` at the time of its last update. When the LP touches the position (open, close, adjust, collect), the program subtracts the stored value from the current global to compute what's owed. This is the Uniswap-v3 `feeGrowthInside0X128 / feeGrowthInside1X128` pattern. Every user action decomposes into state transitions on these three structures: * **Open position:** pick tick range, deposit tokens, mint NFT, insert liquidity into the tick map within the range, initialize any previously empty tick-arrays. * **Increase / decrease liquidity:** adjust the amount stored in the NFT's associated position account and in the tick map; collect accumulated fees at the same time. * **Swap:** walk from the current `sqrt_price_x64` in the direction of trade, consuming active liquidity until the input is exhausted or the next initialized tick is reached; cross the tick and pick up or drop liquidity on the new side. * **Collect fees / rewards:** compute `fee_growth_inside_now − fee_growth_inside_last` × `position_liquidity` for each side and each reward stream; transfer out. The pool is otherwise indifferent to which positions are open. Two LPs in the same range see the same fee-growth path, scaled by their individual `liquidity` amounts. ## When to choose CLMM Pick CLMM when: * You are providing liquidity to a **stable or mean-reverting pair** (USDC/USDT, jitoSOL/SOL, wBTC/BTC) and want to concentrate near parity. * You are a **market maker** willing to monitor price and rebalance. * You specifically need **per-pool incentive emissions** without standing up a separate farm. * You need **per-position accounting** for your own LP product (vault, structured product, etc.). Prefer [CPMM](/products/cpmm) when: * You are launching a new token with unknown price discovery. * You want a single fungible LP token you can stake, lock, or compose with. * You want a passive LP experience. Prefer [AMM v4](/products/amm-v4) when: * You specifically need the hybrid-CLOB depth AMM v4 places on OpenBook. * You are migrating existing AMM v4 integrations and are not opening new positions. ## Positions are NFTs A CLMM position is represented on-chain by two accounts: * A **position NFT mint** with supply 1. * A **personal-position state** account keyed to the NFT mint, holding the position's ticks, liquidity, and last-observed fee-growth values. Transferring the NFT transfers the position — the personal-position account's authority is the NFT owner. This is the same pattern Uniswap v3 pioneered, implemented in Solana's account model. A detailed treatment is in [`products/clmm/ticks-and-positions`](/products/clmm/ticks-and-positions). Older CLMM releases also created a `ProtocolPositionState` account per `(pool, tick_lower, tick_upper)` to aggregate liquidity for that range. **Newer releases no longer create or use it** — the field still appears on `OpenPosition` / `IncreaseLiquidity` / `DecreaseLiquidity` account lists as an `UncheckedAccount` for ABI compatibility, but the program does not read or write it. Aggregate range bookkeeping lives on the tick endpoints (`liquidity_gross`, `liquidity_net`) directly. ## Where to go next * [Accounts](/products/clmm/accounts) — the pool, config, tick-array, and position account layouts. * [Ticks and positions](/products/clmm/ticks-and-positions) — the tick map, tick-spacing, tick-array sizing, NFT-based positions. * [Math](/products/clmm/math) — `sqrt_price_x64`, swap step-through, `fee_growth_inside` derivation. * [Instructions](/products/clmm/instructions) — `OpenPosition`, `IncreaseLiquidity`, `Swap`, `CollectRewards`, the limit-order family, and `CreateCustomizablePool`. * [Fees and rewards](/products/clmm/fees) — the per-position fee model, single-sided fee modes, dynamic fee, and the three reward slots. * [Code demos](/products/clmm/code-demos) — open / adjust / swap / collect / limit-order / customizable-pool walkthroughs in TypeScript. Sources: * [Raydium CLMM source — `raydium-io/raydium-clmm`](https://github.com/raydium-io/raydium-clmm) * Uniswap v3 whitepaper ("Uniswap v3 Core") for the math that CLMM directly inherits * [`reference/program-addresses`](/reference/program-addresses) for canonical program IDs # Ticks and positions Source: https://docs.raydium.io/products/clmm/ticks-and-positions How CLMM discretizes price into ticks, how tick-arrays compress storage, and the rules governing where a position can live. ## Why ticks exist CLMM's liquidity is concentrated into price ranges. To make ranges tractable on-chain, prices are quantized into integer **ticks**, where each tick is a constant multiple of the last: $$ \text{price}(i) = 1.0001^{\,i} $$ A tick corresponds to a 0.01% price move, or \~1 basis point. The mapping is: | Tick index `i` | Price multiplier | | -------------------- | ------------------- | | `0` | `1.0000` | | `100` | `1.0100` (≈ +1.00%) | | `-100` | `0.9900` (≈ −0.99%) | | `10000` | `2.7181` (≈ e) | | `MAX_TICK = 443636` | ≈ `1.84e19` | | `MIN_TICK = -443636` | ≈ `5.42e-20` | `MIN_TICK` and `MAX_TICK` are chosen so that `sqrt_price_x64` fits in a `u128` at both ends. Every pool enforces that `tick_lower >= MIN_TICK` and `tick_upper <= MAX_TICK`. In practice the web UI clamps the range to something much narrower to prevent users from locking liquidity into unreachable ticks. ## Tick spacing A pool's `AmmConfig` fixes a **tick spacing** — the only ticks a position is allowed to use as endpoints. If `tick_spacing = 60`, only ticks `…, −120, −60, 0, 60, 120, …` are valid. An attempt to open a position with endpoint `31` reverts with `InvalidTickIndex`. Common published spacings: | Fee tier | `trade_fee_rate` | Tick spacing | Coarsest price step per tick position | | -------- | ---------------- | ------------ | ------------------------------------- | | 0.01% | `100` | 1 | 0.01% | | 0.05% | `500` | 10 | 0.10% | | 0.25% | `2500` | 60 | 0.60% | | 1.00% | `10000` | 120 | 1.21% | The coarser the spacing, the fewer tick arrays to initialize, the cheaper to open a wide position, and the blurrier the price boundary. Volatile pairs typically live in 120-spacing tiers; stables live in 1-spacing tiers. ## Tick arrays The pool does not store per-tick state in separate accounts. Instead, `TICK_ARRAY_SIZE` adjacent ticks (60 in the current Raydium CLMM) are packed into a single `TickArrayState`. The array's first tick is its `start_tick_index`, and it covers exactly `TICK_ARRAY_SIZE * tick_spacing` integer-tick units. For `tick_spacing = 60` and `TICK_ARRAY_SIZE = 60`: * Each tick array spans `60 × 60 = 3600` integer ticks. * `start_tick_index` is a multiple of 3600: `…, -7200, -3600, 0, 3600, 7200, …`. A position endpoint `t = 2040` at `tick_spacing = 60` lives in the tick array with `start_tick_index = 0`. A position endpoint `t = 4200` lives in the array with `start_tick_index = 3600`. ### When an array is created A tick array is lazy: the **first** position that references any tick inside it initializes the array, paying the rent. Swaps do not initialize tick arrays — they skip over uninitialized arrays using the bitmap. The SDK's open-position flow inspects the chosen range, computes the list of tick arrays it touches, and adds `init_tick_array` instructions in the same transaction as `OpenPosition` if any are missing. ### Tick arrays are not closed Once a tick array has been initialised, it persists for the life of the pool. The program does **not** expose a path to close a tick array, even after `initialized_tick_count` returns to zero. There is no rent recovery for tick arrays; the rent paid by the first position to touch an array is locked into that account permanently. This is a deliberate trade-off: re-using an existing tick array is free for every subsequent position, so a heavily-traded pool only pays the rent cost once per `(pool, start_tick_index)` slot regardless of churn. ### The bitmap Finding "the next initialized tick to the left/right of the current tick" has to be fast — a swap may cross many ticks. The pool stores a 1-bit-per-tick-array bitmap inline in `PoolState` for the range ±1,024 arrays around tick 0. Outside that range (full-range positions, exotic setups), `TickArrayBitmapExtension` provides the overflow. A swap walks the bitmap: `lowest_set_bit_above(tick_current_array_index)` gives the next array with an initialized tick on the side the swap is crossing toward. Within that array, a similar bit-scan locates the next initialized tick. ## `liquidity_gross` and `liquidity_net` Every **initialized** tick stores two liquidity values: * **`liquidity_gross`** — the sum of `L` over all positions that reference this tick as either endpoint. When `liquidity_gross` hits zero, the tick becomes uninitialized and can be removed from the bitmap. * **`liquidity_net`** — the *signed* change to pool-level `liquidity` when the price crosses this tick **moving upward** (left-to-right in tick space). If this tick is the lower bound of a position with size `L`, it contributes `+L`; if it is the upper bound of that position, it contributes `−L`. Worked example: two positions on the same pool. * Position A: `tick_lower = -120`, `tick_upper = 0`, liquidity `L_A = 100`. * Position B: `tick_lower = -60`, `tick_upper = 60`, liquidity `L_B = 50`. Tick-by-tick state: | Tick | Touched by | `liquidity_gross` | `liquidity_net` | | ------ | ---------- | ----------------- | --------------- | | `-120` | A lower | 100 | +100 | | `-60` | B lower | 50 | +50 | | `0` | A upper | 100 | −100 | | `60` | B upper | 50 | −50 | Pool-level `liquidity` for different `tick_current` values: * `tick_current = -180`: `liquidity = 0` (before any position) * `tick_current = -90`: `liquidity = 100` (inside A only) * `tick_current = -30`: `liquidity = 150` (inside A and B) * `tick_current = 30`: `liquidity = 50` (inside B only) * `tick_current = 90`: `liquidity = 0` (past both) On every tick cross during a swap, the program adds `liquidity_net` (possibly negative) to `PoolState.liquidity`. This is the exact Uniswap-v3 mechanism. ## Positions as NFTs A Raydium CLMM position is an NFT. Opening a position mints a brand-new mint with supply 1 into the caller's wallet, and the mint's authority is the CLMM program. The program keys position ownership to **whoever holds a balance in an ATA of that mint** at CPI time. Consequences: * **Positions are transferable.** A wallet can sell or airdrop a position by transferring the NFT. The new holder can then call `CollectRewards`, `IncreaseLiquidity`, etc. * **Positions are addressable outside CLMM.** Marketplaces and wallets display positions like other NFTs. The SDK sets a reasonable `name`/`symbol` on the mint metadata. * **A position's PDA is derived from the NFT mint.** You can find the `PersonalPositionState` without knowing who currently holds it. ### Token-2022 positions Newer CLMM pools can mint positions under Token-2022 instead of classic SPL Token. The program exposes two parallel open instructions — `OpenPosition` and `OpenPositionWithToken22Nft` — with identical semantics beyond which token program owns the NFT mint. Wallet and marketplace compatibility differs; Raydium's UI tracks both. ## Allowed-range rules At `OpenPosition` time the program enforces: 1. `tick_lower < tick_upper`. 2. `tick_lower % tick_spacing == 0` and `tick_upper % tick_spacing == 0`. 3. `MIN_TICK <= tick_lower` and `tick_upper <= MAX_TICK`. 4. The caller has supplied the tick arrays containing `tick_lower` and `tick_upper` — either already initialized or via an `init_tick_array` in the same transaction. 5. The bitmap extension account, if this position extends into the extension range. If any check fails the instruction reverts with `InvalidTickIndex`, `NotApproved`, or `InsufficientLiquidity` depending on which constraint. See [`reference/error-codes`](/reference/error-codes). ## "In-range" vs "out-of-range" A position is **in range** when `tick_lower <= tick_current < tick_upper`. Only in-range positions contribute to `PoolState.liquidity` and therefore only they earn swap fees. An out-of-range position: * Holds 100% of *one* token (the one its range has walked past). Specifically, if `tick_current < tick_lower`, the position holds only token1 (it has already been "sold" into by the price moving away); if `tick_current >= tick_upper`, it holds only token0. * Does **not** earn swap fees. * **Does** continue to accrue rewards if the pool's reward streams emit to out-of-range liquidity — but Raydium's default behavior is "emit only to in-range", matching the Uniswap v3 convention. See [`products/clmm/fees`](/products/clmm/fees). LPs managing CLMM positions spend most of their attention keeping positions in range as price moves. ## Common integration pitfalls * **Off-spacing endpoints.** Code that computes a tick from a target price must snap to a multiple of `tick_spacing` before passing it to `OpenPosition`. The SDK helpers (`TickUtils.getTickWithPriceAndTickspacing`) do this; home-grown math often does not. * **Missing tick arrays.** Opening a wide position may require initializing several tick arrays; forgetting to pass them as writable accounts reverts. The SDK's `openPositionFromBase` returns the list for you. * **Stale tick after a swap.** `tick_current` can cross many ticks in one swap. If your UX shows a "current tick" from one RPC call and then opens a position in a later one, the relative position vs the live price can be off by dozens of ticks. Re-fetch right before signing. * **Position NFTs with extra metadata.** If you build a wallet that recognizes Raydium positions, detect them by their mint authority (= the CLMM program's PDA), not by a hardcoded metadata field. ## Where to go next * [Math](/products/clmm/math) — the swap step-through and fee-growth derivation that tick boundaries participate in. * [Accounts](/products/clmm/accounts) — the `TickArrayState` and `PositionState` layouts. * [Fees and rewards](/products/clmm/fees) — how in-range-ness gates fee accrual. * [`algorithms/clmm-math`](/algorithms/clmm-math) — the shared derivation of the concentrated-liquidity formulas. Sources: * [`raydium-io/raydium-clmm` — `tick_array`, `tick`, `position` modules](https://github.com/raydium-io/raydium-clmm) * "Uniswap v3 Core" whitepaper, §6 (ticks), §7 (fee growth) # CPMM accounts Source: https://docs.raydium.io/products/cpmm/accounts Every PDA and shared account a CPMM pool touches: PoolState, AmmConfig, authority, LP mint, token vaults, and the observation ring buffer. Program ID and PDA seeds for CPMM are listed canonically in [`reference/program-addresses`](/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. | Account | Seed(s) | Owner | Purpose | | ------------- | ------------------------------------------------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `authority` | `"vault_and_lp_mint_auth_seed"` | CPMM | The signer for every vault move and every LP mint/burn. Shared across **all** CPMM pools. | | `poolState` | `"pool"`, `ammConfig`, `token0Mint`, `token1Mint` **or** any signer-provided random keypair | CPMM | The pool's state struct — mint pair, vault balances, LP supply, fee accrual, observation pointer. The CPMM `Initialize` instruction accepts either the canonical PDA derived from the four seeds **or** an arbitrary keypair signed by the creator. The random-keypair path exists to defeat a front-running attack where an adversary watches mempool and races to occupy the canonical PDA before the legitimate creator. | | `lpMint` | `"pool_lp_mint"`, `poolState` | SPL Token | The pool's LP token. Supply = total LP outstanding. Mint authority = the CPMM authority PDA. | | `vault0` | `"pool_vault"`, `poolState`, `token0Mint` | SPL Token / Token-2022 | Holds the pool's balance of token0. Owned by the authority PDA. | | `vault1` | `"pool_vault"`, `poolState`, `token1Mint` | SPL Token / Token-2022 | Holds the pool's balance of token1. Owned by the authority PDA. | | `observation` | `"observation"`, `poolState` | CPMM | Ring buffer of price samples used for the TWAP. Written on every swap. | And the shared config: | Account | Seed(s) | Owner | Purpose | | ----------- | ---------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `ammConfig` | `"amm_config"`, `index: u16` | CPMM | Holds the trade/protocol/fund/creator fee rates and admin keys. One per "fee tier". Poolstate binds to one at creation and cannot change later. | ## Deriving a pool from nothing but two mints ```ts theme={null} import { PublicKey } from "@solana/web3.js"; import BN from "bn.js"; const CPMM_PROGRAM_ID = new PublicKey( "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C" ); // mainnet — see reference/program-addresses function u16ToBytes(n: number): Buffer { const b = Buffer.alloc(2); b.writeUInt16BE(n); return b; } // token0 < token1 by byte order. Getting this wrong yields a valid PDA // that points at a nonexistent pool. function sortMints(a: PublicKey, b: PublicKey): [PublicKey, PublicKey] { return Buffer.compare(a.toBuffer(), b.toBuffer()) < 0 ? [a, b] : [b, a]; } export function deriveCpmmAccounts( mintA: PublicKey, mintB: PublicKey, ammConfigIndex = 0, ) { const [token0Mint, token1Mint] = sortMints(mintA, mintB); const [ammConfig] = PublicKey.findProgramAddressSync( [Buffer.from("amm_config"), u16ToBytes(ammConfigIndex)], CPMM_PROGRAM_ID, ); const [authority] = PublicKey.findProgramAddressSync( [Buffer.from("vault_and_lp_mint_auth_seed")], CPMM_PROGRAM_ID, ); const [poolState] = PublicKey.findProgramAddressSync( [ Buffer.from("pool"), ammConfig.toBuffer(), token0Mint.toBuffer(), token1Mint.toBuffer(), ], CPMM_PROGRAM_ID, ); const [lpMint] = PublicKey.findProgramAddressSync( [Buffer.from("pool_lp_mint"), poolState.toBuffer()], CPMM_PROGRAM_ID, ); const [vault0] = PublicKey.findProgramAddressSync( [Buffer.from("pool_vault"), poolState.toBuffer(), token0Mint.toBuffer()], CPMM_PROGRAM_ID, ); const [vault1] = PublicKey.findProgramAddressSync( [Buffer.from("pool_vault"), poolState.toBuffer(), token1Mint.toBuffer()], CPMM_PROGRAM_ID, ); const [observation] = PublicKey.findProgramAddressSync( [Buffer.from("observation"), poolState.toBuffer()], CPMM_PROGRAM_ID, ); return { ammConfig, authority, poolState, lpMint, token0Mint, token1Mint, vault0, vault1, observation, }; } ``` **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`](https://github.com/raydium-io/raydium-cp-swap) source. The fields below are the ones you will read from an integration. ### `PoolState` ```rust theme={null} // programs/cp-swap/src/states/pool.rs pub struct PoolState { pub amm_config: Pubkey, // binds this pool to an AmmConfig pub pool_creator: Pubkey, // who ran initialize pub token_0_vault: Pubkey, // == vault0 PDA pub token_1_vault: Pubkey, // == vault1 PDA pub lp_mint: Pubkey, pub token_0_mint: Pubkey, pub token_1_mint: Pubkey, pub token_0_program: Pubkey, // SPL Token or Token-2022 program pub token_1_program: Pubkey, pub observation_key: Pubkey, // == observation PDA pub auth_bump: u8, pub status: u8, // bitmask: deposit | withdraw | swap pub lp_mint_decimals: u8, pub mint_0_decimals: u8, pub mint_1_decimals: u8, pub lp_supply: u64, // mirrors lp_mint supply pub protocol_fees_token_0: u64, pub protocol_fees_token_1: u64, pub fund_fees_token_0: u64, pub fund_fees_token_1: u64, pub open_time: u64, // unix; swaps rejected before this pub recent_epoch: u64, // Creator-fee state (added after the original layout): pub creator_fee_on: u8, // 0=BothToken, 1=OnlyToken0, 2=OnlyToken1 pub enable_creator_fee: bool, pub padding1: [u8; 6], pub creator_fees_token_0: u64, pub creator_fees_token_1: u64, pub padding: [u64; 28], } ``` 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` ```rust theme={null} pub struct AmmConfig { pub bump: u8, pub disable_create_pool: bool, pub index: u16, // matches the seed pub trade_fee_rate: u64, // e.g., 2500 = 0.25% pub protocol_fee_rate: u64, // fraction of trade fee to protocol pub fund_fee_rate: u64, // fraction of trade fee to fund pub create_pool_fee: u64, // paid once at init (in SOL or token) pub protocol_owner: Pubkey, // can call CollectProtocolFee pub fund_owner: Pubkey, // can call CollectFundFee pub creator_fee_rate: u64, // optional pool-creator fee rate (1/1_000_000 of volume) pub padding: [u64; 15], } ``` 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`](/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`](/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`. ```rust theme={null} pub struct Permission { pub authority: Pubkey, // who is allowed to call InitializeWithPermission pub padding: [u64; 8], } ``` 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`](/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`. ```rust theme={null} // OBSERVATION_NUM is hardcoded in the program to 100. pub const OBSERVATION_NUM: usize = 100; pub struct Observation { pub block_timestamp: u64, pub cumulative_token_0_price_x32: u128, // Q32.32, top 64 bits left for overflow pub cumulative_token_1_price_x32: u128, } pub struct ObservationState { pub initialized: bool, pub observation_index: u16, // circular index pub pool_id: Pubkey, pub observations: [Observation; OBSERVATION_NUM], // 100 entries pub last_update_timestamp: u64, // timestamp of the most recent append pub padding: [u64; 3], } ``` 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`](/products/clmm/accounts). ## Account lifecycle | Event | Accounts created | Accounts destroyed | | -------------------- | -------------------------------------------------------- | ------------------ | | `Initialize` | `poolState`, `lpMint`, `vault0`, `vault1`, `observation` | — | | `Deposit` | — (may create user LP ATA) | — | | `Withdraw` | — | — | | `Swap` | — (may create user destination ATA) | — | | `CollectProtocolFee` | — | — | | `CollectFundFee` | — | — | | `UpdatePoolStatus` | — | — | 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 * **Instruction account lists** (which of the above are writable/signer for each instruction): [`products/cpmm/instructions`](/products/cpmm/instructions). * **Fee-accrual semantics**: [`products/cpmm/fees`](/products/cpmm/fees). * **Swap math / observation update rule**: [`products/cpmm/math`](/products/cpmm/math). * **Canonical seeds / program IDs**: [`reference/program-addresses`](/reference/program-addresses). Sources: * [`raydium-io/raydium-cp-swap` — PoolState, AmmConfig, ObservationState](https://github.com/raydium-io/raydium-cp-swap) # CPMM code demos Source: https://docs.raydium.io/products/cpmm/code-demos End-to-end TypeScript examples for every CPMM operation — create pool, swap, deposit, withdraw, collect fees — plus a minimal Rust CPI skeleton. **Version banner.** All TypeScript demos target `@raydium-io/raydium-sdk-v2@0.2.42-alpha` against Solana mainnet-beta, verified 2026-04. The Rust CPI skeleton targets `raydium-cp-swap` on the `master` branch, Anchor `0.30.x`. Program IDs are pulled via constants from [`reference/program-addresses`](/reference/program-addresses). ## Prerequisites ```bash theme={null} npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token bn.js decimal.js ``` Every demo on this page mirrors a file in [`raydium-sdk-V2-demo/src/cpmm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/cpmm); the GitHub link sits next to each section. Bootstrap follows the demo repo's `config.ts.template` ([source](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template)): ```ts theme={null} import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js"; import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; import fs from "node:fs"; const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta")); const owner = Keypair.fromSecretKey( new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))), ); const raydium = await Raydium.load({ owner, connection, cluster: "mainnet", disableFeatureCheck: true, blockhashCommitment: "finalized", }); ``` The `Raydium` instance is the SDK's facade — every demo below uses it. It lazily fetches token lists and fee configs from `api-v3.raydium.io`; you can seed it with your own data in offline environments. ## Create a CPMM pool Source: [`src/cpmm/createCpmmPool.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/createCpmmPool.ts) ```ts theme={null} import { PublicKey } from "@solana/web3.js"; import BN from "bn.js"; import { getCpmmPdas, CREATE_CPMM_POOL_PROGRAM, CREATE_CPMM_POOL_FEE_ACC } from "@raydium-io/raydium-sdk-v2"; const mintA = new PublicKey("So11111111111111111111111111111111111111112"); // wSOL const mintB = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC // 1. Pick a fee config. index=0 is the 0.25% tier. const feeConfigs = await raydium.api.getCpmmConfigs(); const feeConfig = feeConfigs.find((c) => c.index === 0)!; // 2. Pull mint metadata so the SDK can handle Token-2022 extensions. const mintAInfo = await raydium.token.getTokenInfo(mintA); const mintBInfo = await raydium.token.getTokenInfo(mintB); // 3. Build the transaction. const { execute, extInfo } = await raydium.cpmm.createPool({ programId: CREATE_CPMM_POOL_PROGRAM, poolFeeAccount: CREATE_CPMM_POOL_FEE_ACC, mintA: mintAInfo, mintB: mintBInfo, mintAAmount: new BN(1_000_000_000), // 1 SOL (assuming 9 decimals) mintBAmount: new BN( 160_000_000), // 160 USDC (at 160/SOL) startTime: new BN(0), // open immediately feeConfig, associatedOnly: false, ownerInfo: { useSOLBalance: true }, txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Pool created at", extInfo.address.poolId.toBase58()); console.log("Tx:", txId); ``` A few things the SDK quietly takes care of: * Sorting the mints into token0/token1 order before deriving the PDA. * Paying the one-time `create_pool_fee` to `poolFeeAccount`. * Creating the caller's associated token accounts if missing. * Choosing the right token program (SPL Token vs Token-2022) per side. After confirmation you can fetch the live pool state with: ```ts theme={null} const { poolKeys, poolInfo, rpcData } = await raydium.cpmm.getPoolInfoFromRpc( extInfo.address.poolId, ); ``` ## Swap (base-input) Source: [`src/cpmm/swap.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/swap.ts) ```ts theme={null} import { CurveCalculator } from "@raydium-io/raydium-sdk-v2"; const poolId = new PublicKey(""); // 1. Load current pool state directly from an RPC (not from the API). const { poolInfo, poolKeys, rpcData } = await raydium.cpmm.getPoolInfoFromRpc(poolId); const inputMint = new PublicKey(poolInfo.mintA.address); // swap A → B const amountIn = new BN(100_000_000); // 0.1 SOL const slippage = 0.005; // 0.5% // 2. Quote locally. The SDK's CurveCalculator mirrors on-chain math, // including Token-2022 transfer fees on either side. const baseIn = inputMint.equals(new PublicKey(poolInfo.mintA.address)); const swapResult = CurveCalculator.swap( amountIn, baseIn ? rpcData.baseReserve : rpcData.quoteReserve, baseIn ? rpcData.quoteReserve : rpcData.baseReserve, rpcData.configInfo!.tradeFeeRate, ); const minimumAmountOut = swapResult.destinationAmountSwapped.muln(1 - slippage * 100).divn(100); // 3. Build and send. const { execute } = await raydium.cpmm.swap({ poolInfo, poolKeys, inputAmount: amountIn, swapResult, slippage, baseIn, txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Swap tx:", txId); ``` Note: the SDK **always** re-fetches the pool state from an RPC inside `getPoolInfoFromRpc`. Do not quote off `api-v3.raydium.io` for a transaction you are about to sign — a quote that is one block stale can slip into `ExceededSlippage` at land time. ## Swap (base-output) Source: [`src/cpmm/swapBaseOut.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/swapBaseOut.ts) ```ts theme={null} const amountOutWanted = new BN(15_000_000); // 15 USDC const slippage = 0.005; const baseIn = false; // B is input, A is output? depends on your direction const swapResult = CurveCalculator.swapBaseOutput( amountOutWanted, rpcData.baseReserve, rpcData.quoteReserve, rpcData.configInfo!.tradeFeeRate, ); const maxAmountIn = swapResult.sourceAmountSwapped.muln(1 + slippage * 100).divn(100); const { execute } = await raydium.cpmm.swap({ poolInfo, poolKeys, inputAmount: maxAmountIn, fixedOut: true, amountOut: amountOutWanted, baseIn, slippage, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` ## Deposit liquidity Source: [`src/cpmm/deposit.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/deposit.ts) ```ts theme={null} const lpAmount = new BN(100_000); // desired LP mint amount const slippage = 0.01; const { execute } = await raydium.cpmm.addLiquidity({ poolInfo, poolKeys, lpAmount, slippage, baseIn: true, // quote from mintA side txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` The SDK converts `lpAmount` into `needed_token_0` and `needed_token_1` using the pool's current reserves, inflates each by `1 + slippage` for the instruction's `maximum_*` arguments, and builds the ATA creations if necessary. ## Withdraw liquidity Source: [`src/cpmm/withdraw.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/withdraw.ts) ```ts theme={null} const lpAmount = new BN(100_000); // LP to burn const slippage = 0.01; const { execute } = await raydium.cpmm.withdrawLiquidity({ poolInfo, poolKeys, lpAmount, slippage, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` ## Collect protocol/fund/creator fees Source: [`src/cpmm/collectCreatorFee.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/collectCreatorFee.ts), [`src/cpmm/collectAllCreatorFee.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/cpmm/collectAllCreatorFee.ts) These instructions are admin- or creator-gated and typically invoked from a signer held by the Raydium multisig or the pool creator. The SDK surfaces them as raw builders: ```ts theme={null} import { makeCollectProtocolFeeInstruction, makeCollectFundFeeInstruction, makeCollectCreatorFeeInstruction, } from "@raydium-io/raydium-sdk-v2"; // The PDAs and authority were set at pool creation; see reference/program-addresses // for the canonical seeds. The SDK exposes helpers if you prefer. ``` Off-chain you can read accrued fees directly from `PoolState`: ```ts theme={null} const pool = await raydium.cpmm.getRpcPoolInfo(poolId); console.log("Accrued protocol fee token0:", pool.protocolFeesToken0.toString()); console.log("Accrued protocol fee token1:", pool.protocolFeesToken1.toString()); ``` ## Rust CPI skeleton If you want to invoke CPMM from your own Anchor program — for example, a vault that swaps on behalf of its depositors — the CPI context looks like this. Account ordering follows [`products/cpmm/instructions`](/products/cpmm/instructions). ```rust theme={null} // Cargo.toml // raydium-cp-swap = { git = "https://github.com/raydium-io/raydium-cp-swap" } // anchor-spl = "0.30" use anchor_lang::prelude::*; use anchor_spl::token_interface::{TokenAccount, TokenInterface, Mint}; use raydium_cp_swap::cpi::accounts::Swap; use raydium_cp_swap::cpi; use raydium_cp_swap::program::RaydiumCpSwap; #[derive(Accounts)] pub struct ProxySwap<'info> { #[account(mut)] pub payer: Signer<'info>, /// CHECK: validated by the CPMM program pub authority: UncheckedAccount<'info>, /// CHECK: pub amm_config: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub pool_state: UncheckedAccount<'info>, #[account(mut)] pub input_token_account: InterfaceAccount<'info, TokenAccount>, #[account(mut)] pub output_token_account: InterfaceAccount<'info, TokenAccount>, #[account(mut)] pub input_vault: InterfaceAccount<'info, TokenAccount>, #[account(mut)] pub output_vault: InterfaceAccount<'info, TokenAccount>, pub input_token_program: Interface<'info, TokenInterface>, pub output_token_program: Interface<'info, TokenInterface>, pub input_token_mint: InterfaceAccount<'info, Mint>, pub output_token_mint: InterfaceAccount<'info, Mint>, #[account(mut)] /// CHECK: ring buffer pub observation_state: UncheckedAccount<'info>, pub cpmm_program: Program<'info, RaydiumCpSwap>, } pub fn proxy_swap_base_input( ctx: Context, amount_in: u64, minimum_amount_out: u64, ) -> Result<()> { let cpi_accounts = Swap { payer: ctx.accounts.payer.to_account_info(), authority: ctx.accounts.authority.to_account_info(), amm_config: ctx.accounts.amm_config.to_account_info(), pool_state: ctx.accounts.pool_state.to_account_info(), input_token_account: ctx.accounts.input_token_account.to_account_info(), output_token_account: ctx.accounts.output_token_account.to_account_info(), input_vault: ctx.accounts.input_vault.to_account_info(), output_vault: ctx.accounts.output_vault.to_account_info(), input_token_program: ctx.accounts.input_token_program.to_account_info(), output_token_program: ctx.accounts.output_token_program.to_account_info(), input_token_mint: ctx.accounts.input_token_mint.to_account_info(), output_token_mint: ctx.accounts.output_token_mint.to_account_info(), observation_state: ctx.accounts.observation_state.to_account_info(), }; let cpi_ctx = CpiContext::new( ctx.accounts.cpmm_program.to_account_info(), cpi_accounts, ); cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out) } ``` If your CPI signs as a PDA (e.g., you manage a vault on behalf of depositors), swap `CpiContext::new` for `CpiContext::new_with_signer` and pass your seeds. ## Common pitfalls A short checklist before opening a support ticket: * **Sorted mints.** If your derived `poolState` PDA does not match the on-chain pool, you probably forgot to sort the mints. * **Stale API quote.** Never pass a reserve value from `api-v3.raydium.io` into `CurveCalculator.swap`. Fetch from an RPC. * **Wrong token program.** A Token-2022 mint's vault is owned by the Token-2022 program, not by SPL Token. Always use the pool's `token_0_program` / `token_1_program` fields. * **Slippage under-denominated for transfer-fee mints.** If either side of the pool is a Token-2022 transfer-fee mint, your `minimum_amount_out` must be denominated in what the user actually receives, not in what the vault sends. * **`NotApproved` on a swap.** Check `PoolState.status` — the admin may have paused swaps on that pool. See [`products/cpmm/instructions`](/products/cpmm/instructions) for the status bitmask. ## Where to go next * [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — full SDK reference. * [`sdk-api/rest-api`](/sdk-api/rest-api) — quote and pool-metadata endpoints. * [`user-flows/create-cpmm-pool`](/user-flows/create-cpmm-pool) — the non-code walkthrough of the same flow. * [`integration-guides/aggregator`](/integration-guides/aggregator) — if you are routing CPMM as part of a multi-hop path. Sources: * [Raydium SDK v2 — `@raydium-io/raydium-sdk-v2`](https://github.com/raydium-io/raydium-sdk-V2) * [Raydium SDK v2 demos — `raydium-sdk-v2-demo`](https://github.com/raydium-io/raydium-sdk-V2-demo) * [`raydium-io/raydium-cp-swap`](https://github.com/raydium-io/raydium-cp-swap) # CPMM fees Source: https://docs.raydium.io/products/cpmm/fees How CPMM charges a trade fee and a separate creator fee, splits the trade fee between LPs, the protocol treasury, and the fund multisig, and who collects what, when. ## Two independent fees, four destinations CPMM levies **two separately-rated fees** on every swap: 1. **Trade fee** — charged at `AmmConfig.trade_fee_rate` and split between three destinations: * **LP share** — stays in the vault and grows `k`. Claimed implicitly by burning LP tokens. * **Protocol share** — accrued to `PoolState.protocol_fees_token*`; swept by the `protocol_owner` via `CollectProtocolFee`. * **Fund share** — accrued to `PoolState.fund_fees_token*`; swept by the `fund_owner` via `CollectFundFee`. 2. **Creator fee** (optional, per-pool) — charged at `AmmConfig.creator_fee_rate` **independently** of the trade fee, accrued to `PoolState.creator_fees_token*`, swept by `pool_state.pool_creator` via `CollectCreatorFee`. Active only when the pool was created with `enable_creator_fee = true`. The creator fee is **not** a slice of the trade fee. The two rates are added together when the fee is taken on the swap input, but each remains its own bucket — the protocol and fund shares are always derived from `trade_fee` only, never from `creator_fee`. A pool with `creator_fee_rate = 1000` (0.10%) and `trade_fee_rate = 2500` (0.25%) charges a combined 0.35% of the input on a creator-fee-on-input swap, of which the creator keeps the 0.10% and the trade-fee bucket gets the 0.25%. The trade-fee rates (`trade_fee_rate`, `protocol_fee_rate`, `fund_fee_rate`) and the `creator_fee_rate` all live on `AmmConfig`. The per-pool `enable_creator_fee` flag and the `creator_fee_on` mode (which side of the trade the creator fee is taken from) live on `PoolState`. See [`products/cpmm/accounts`](/products/cpmm/accounts). ## Rates and units All rates are `u64`s denominated in units of `1 / FEE_RATE_DENOMINATOR` where `FEE_RATE_DENOMINATOR = 1_000_000`. * **`trade_fee_rate`** is a fraction of **swap volume**. `2500` ⇒ 0.25% of the relevant side (input or output, depending on `creator_fee_on` — see "Which side of the trade the fees are taken from" below). * **`creator_fee_rate`** is a fraction of **swap volume**, taken **separately** from the trade fee. `1000` ⇒ 0.10% of the relevant side. * **`protocol_fee_rate`** and **`fund_fee_rate`** are fractions of the **trade fee**, not of volume. `120_000` ⇒ 12% of the trade fee. Default parameters for `AmmConfig[index=0]` (the "standard" 0.25% pool) on mainnet, for reference: | Field | Value | Effective percent | | -------------------- | ------------- | --------------------------------------- | | `trade_fee_rate` | `2500` | 0.25% of volume (trade-fee bucket) | | `protocol_fee_rate` | `120000` | 12% of trade fee ≈ **0.030% of volume** | | `fund_fee_rate` | `40000` | 4% of trade fee ≈ **0.010% of volume** | | `creator_fee_rate` | `0` (default) | 0% (separate bucket) | | → LP share effective | | **0.210% of volume** | So on a \$1,000 swap against `AmmConfig[0]` with `enable_creator_fee = false`: \$2.50 total trade fee, of which \$2.10 stays with LPs, \$0.30 goes to protocol, \$0.10 to the fund. The creator bucket is 0 because creator fee is disabled. If the same pool had `enable_creator_fee = true` and `creator_fee_rate = 1000` (0.10%), the user pays an additional \$1.00 to the creator bucket — taken on the same side of the trade configured by `creator_fee_on` — for \$3.50 of total fees. The trade-fee bucket and its protocol/fund splits are unchanged. Confirm the current mainnet values against `GET https://api-v3.raydium.io/main/cpmm-config` — rates are admin-mutable and should be read fresh rather than hardcoded. ## The split, in code ```rust theme={null} // Paraphrased from raydium-cp-swap/programs/cp-swap/src/curve/{calculator,fees}.rs. // The actual code branches on `is_creator_fee_on_input`; both branches preserve the // invariant that creator_fee is its own rate, never a slice of trade_fee. const FEE_RATE_DENOMINATOR_VALUE: u64 = 1_000_000; pub struct FeeBreakdown { pub amount_in_after_fees: u64, // input minus all fees taken on the input side pub amount_out_after_fees: u64, // output minus any creator fee taken on the output side pub trade_fee: u64, // → split into LP / protocol / fund buckets below pub protocol_fee: u64, // share of trade_fee pub fund_fee: u64, // share of trade_fee pub creator_fee: u64, // independent bucket (input or output side) } fn take_fees_on_input( amount_in: u64, trade_rate: u64, creator_rate: u64, protocol_rate: u64, fund_rate: u64, ) -> (u64 /* trade_fee */, u64 /* creator_fee */, u64 /* amount_in_after_fees */) { // The two rates are added so we round once on the combined fee, then split // proportionally — this is purely a rounding/efficiency trick. The split // honours the rates exactly: creator_fee/trade_fee == creator_rate/trade_rate. let total_fee = ((amount_in as u128) * ((trade_rate + creator_rate) as u128)) .div_ceil(FEE_RATE_DENOMINATOR_VALUE as u128) as u64; let creator_fee = ((total_fee as u128) * (creator_rate as u128) / ((trade_rate + creator_rate) as u128)) as u64; let trade_fee = total_fee - creator_fee; (trade_fee, creator_fee, amount_in - total_fee) } fn take_creator_fee_on_output(amount_out_swapped: u64, creator_rate: u64) -> (u64, u64) { // When creator_fee_on routes the creator fee to the output side, // trade_fee is taken on input as a single rate, and creator_fee // is computed against the curve output. let creator_fee = ((amount_out_swapped as u128) * (creator_rate as u128)) .div_ceil(FEE_RATE_DENOMINATOR_VALUE as u128) as u64; (amount_out_swapped - creator_fee, creator_fee) } fn split_trade_fee( trade_fee: u64, protocol_rate: u64, fund_rate: u64, ) -> (u64 /* protocol_fee */, u64 /* fund_fee */) { let protocol_fee = (trade_fee as u128 * protocol_rate as u128 / FEE_RATE_DENOMINATOR_VALUE as u128) as u64; let fund_fee = (trade_fee as u128 * fund_rate as u128 / FEE_RATE_DENOMINATOR_VALUE as u128) as u64; (protocol_fee, fund_fee) } ``` Notes: * The total fee on input rounds **up** so the pool never undercharges. * The sub-splits of `trade_fee` (protocol, fund) round **down** so their sum never exceeds `trade_fee`; the remainder is the LP share. * `lp_share = trade_fee − protocol_fee − fund_fee` (creator\_fee is **not** subtracted here because it is its own bucket). * The creator fee is taken from input or output depending on `PoolState.creator_fee_on` (see next section). The rate is unchanged either way. ## Which side of the trade the fees are taken from CPMM has a per-pool `creator_fee_on` setting (`BothToken` / `OnlyToken0` / `OnlyToken1`) that determines whether the creator fee is taken from the **input** side or the **output** side of a given swap. The runtime helper `is_creator_fee_on_input(direction)` collapses that to a boolean per swap: | `creator_fee_on` | Swap `0 → 1` | Swap `1 → 0` | | ------------------ | ------------ | ------------ | | `BothToken` (`0`) | input side | input side | | `OnlyToken0` (`1`) | input side | output side | | `OnlyToken1` (`2`) | output side | input side | When the creator fee is on the **input** side, both the trade fee and the creator fee are deducted from `amount_in` before the curve runs. Quote math: take the combined `trade_rate + creator_rate` off the input. When the creator fee is on the **output** side, only the trade fee is deducted from `amount_in`; the curve produces an unfee'd output, then the creator fee is deducted from that output. Quote math: take `trade_rate` off the input; take `creator_rate` off the output. Trade fee itself is always taken on the input side (the standard Uniswap-V2 pattern). Only the creator fee can land on output. ## How "accrued" fees interact with the curve An important subtlety: **the protocol, fund, and creator fees stay physically in the vault** until their respective `Collect*` instruction is called. But they are excluded from the curve's view of the vault balance. A concrete picture after one swap: ``` raw_vault_0_balance = getTokenAccountBalance(vault_0).amount = lp_entitled + protocol_fees_token0 + fund_fees_token0 + creator_fees_token0 curve_x = raw_vault_0_balance − (protocol_fees_token0 + fund_fees_token0 + creator_fees_token0) ``` The program uses `curve_x` (and the analogous `curve_y`) when enforcing `k' ≥ k`. This is how the non-LP fees reach their destinations **without** inflating the LP share of the pool. Consequences you should design around: * **Quoting off raw balances is wrong.** If you build a quoter off `getTokenAccountBalance`, you will consistently overstate the price the pool will honor. Always subtract accrued fees, or simulate via `SwapBaseInput` / the API. * **`CollectProtocolFee` does not move the price.** It moves tokens out of the vault *and* zeros the `protocol_fees_token*` counters, so `curve_x` and `curve_y` are unchanged. * **LP fees do not accrue to a counter.** They are implicit in the vault balance. LP's entitlement to accumulated LP fees is exercised by burning LP tokens (i.e., via `Withdraw`) — there is no `CollectLpFee`. ## Interaction with Token-2022 transfer fees Token-2022 transfer fees are applied by the **mint**, not by CPMM. They act on every token transfer — swap, deposit, withdrawal, and the `Collect*` sweeps. CPMM's trade-fee math is computed against the amount that **actually landed in the vault**, i.e., net of the input mint's transfer fee (if any). So in the worst case a user pays three distinct taxes on an input-exact swap: 1. The input mint's transfer fee on `amount_in` (to the mint's fee authority). 2. The pool's `trade_fee` on the remainder (split per above). 3. The output mint's transfer fee on `amount_out` (to the mint's fee authority). The SDK's quoter accounts for all three so `minimum_amount_out` is denominated in what the user actually receives. If you are writing your own quoter, mirror that behavior, or your slippage checks will be systematically too generous. See [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees) for the detailed derivation. ## Creator fee The creator fee is optional and per-pool. The **rate** lives on `AmmConfig.creator_fee_rate`; the **enable flag** and the **side** (`creator_fee_on`) live on `PoolState`: * **Enabled at pool creation.** `Initialize` sets `enable_creator_fee = false` by default; pools created via `InitializeWithPermission` (used by LaunchLab graduations and other gated paths) can pass `enable_creator_fee = true` and choose `creator_fee_on`. * **Rate is shared with the fee tier.** The rate itself is `AmmConfig.creator_fee_rate`, the same value across every pool bound to that config. Each pool then decides whether to charge it (`enable_creator_fee`) and which side of the swap to charge it on (`creator_fee_on`). When `enable_creator_fee = false`, the pool's effective creator-fee rate is zero regardless of the config value (see `PoolState::adjust_creator_fee_rate` in the source). * **Independent of trade fee.** The creator fee never reduces the LP / protocol / fund shares — it is its own rate, applied separately, accrued in its own counters. * **Swept via `CollectCreatorFee`**, signed by `PoolState.pool_creator`. * **Cannot be re-enabled or re-routed after creation.** A pool initialized with `enable_creator_fee = false` will never charge a creator fee; one initialized with a particular `creator_fee_on` cannot switch sides. Creator fees are the mechanism behind Raydium's "Burn & Earn" pattern: LP tokens are locked under the [LP Lock](/reference/program-addresses) program so the creator cannot withdraw liquidity, but can still claim `CollectCreatorFee` indefinitely. ## Collection operational flow | Signer | Instruction | Source counters zeroed | Typical cadence | | --------------------------- | -------------------- | -------------------------- | ---------------------- | | `amm_config.protocol_owner` | `CollectProtocolFee` | `protocol_fees_token{0,1}` | Weekly or programmatic | | `amm_config.fund_owner` | `CollectFundFee` | `fund_fees_token{0,1}` | Weekly or programmatic | | `pool_state.pool_creator` | `CollectCreatorFee` | `creator_fees_token{0,1}` | Anytime | Protocol and fund owners are the Raydium multisig on mainnet; see [`security/admin-and-multisig`](/security/admin-and-multisig). The creator signer is the account that ran `Initialize`. ## Changing a fee tier Fee rates can be changed by the admin via `UpdateAmmConfig` (see [`products/cpmm/instructions`](/products/cpmm/instructions)). Changes take effect on the **next swap** for every pool bound to that `AmmConfig` — there is no migration, because pools load the config each swap. What the admin cannot do: * Move a pool from one `AmmConfig` to another. * Retroactively reprice already-accrued fees. * Collect fees without the `protocol_owner` / `fund_owner` signer. ## Reading fees from a running pool ```ts theme={null} // Off-chain: current accrued fees in each bucket const pool = await connection.getAccountInfo(poolStatePda); const decoded = PoolState.decode(pool.data); console.log( "Protocol accrued:", decoded.protocolFeesToken0.toString(), decoded.protocolFeesToken1.toString(), ); console.log( "Fund accrued:", decoded.fundFeesToken0.toString(), decoded.fundFeesToken1.toString(), ); console.log( "Creator accrued:", decoded.creatorFeesToken0.toString(), decoded.creatorFeesToken1.toString(), ); // Off-chain: effective rates today. // trade_fee_rate, creator_fee_rate are fractions of volume (denominator 1e6). // protocol_fee_rate, fund_fee_rate are fractions of the *trade fee* (same denominator). const config = await fetch("https://api-v3.raydium.io/main/cpmm-config") .then((r) => r.json()); const tier = config.data.find((t) => t.index === decoded.ammConfigIndex); const tradeFeeOfVolume = tier.tradeFeeRate / 1_000_000; const protocolOfTradeFee = tier.protocolFeeRate / 1_000_000; const fundOfTradeFee = tier.fundFeeRate / 1_000_000; const lpOfTradeFee = 1 - protocolOfTradeFee - fundOfTradeFee; console.log("LP fee effective:", (tradeFeeOfVolume * lpOfTradeFee * 100).toFixed(4), "%"); console.log("Protocol fee effective:", (tradeFeeOfVolume * protocolOfTradeFee * 100).toFixed(4), "%"); console.log("Fund fee effective:", (tradeFeeOfVolume * fundOfTradeFee * 100).toFixed(4), "%"); console.log( "Creator fee effective:", decoded.enableCreatorFee ? ((tier.creatorFeeRate / 1_000_000) * 100).toFixed(4) + " %" : "0 % (disabled on this pool)", ); ``` ## Comparison with CLMM and AMM v4 See [`reference/fee-comparison`](/reference/fee-comparison) for a side-by-side matrix. Summary: * **AMM v4** uses a fixed 0.25% trade fee with a different LP/protocol split and no fund fee. * **CLMM** fees are per-tick-spacing tier, accrued per-position (not per-pool), and claimed via `DecreaseLiquidity` or `CollectFees`. ## Where to go next * [`products/cpmm/math`](/products/cpmm/math) — where the trade-fee deduction plugs into the curve. * [`products/cpmm/instructions`](/products/cpmm/instructions) — the `Collect*` instruction account lists. * [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees) — how to combine a pool trade fee with a mint transfer fee correctly. Sources: * [`raydium-io/raydium-cp-swap` — `states/config.rs`, `states/pool.rs`, `states/curve.rs`](https://github.com/raydium-io/raydium-cp-swap) * [Raydium API — `GET /main/cpmm-config`](https://api-v3.raydium.io/main/cpmm-config) # CPMM (Standard AMM) Source: https://docs.raydium.io/products/cpmm/index Pure constant-product AMM. No OpenBook dependency. Token-2022 compatible. The recommended program for new constant-product pools. ## What it is CPMM is Raydium's standard constant-product AMM. It is the successor to AMM v4 and has no OpenBook dependency, lower gas, first-class Token-2022 support (including transfer-fee tokens), and a cleaner account layout. **Program ID:** see [reference/program-addresses](/reference/program-addresses). **Token-2022:** supported, including transfer-fee, permanent delegate, and default-account-state extensions. See [algorithms/token-2022-transfer-fees](/algorithms/token-2022-transfer-fees) for how transfer fees affect swap math. ## Chapter contents What CPMM is, why it was built, and how it differs from AMM v4. PoolState, AmmConfig, LP mint, token vaults, observation (oracle) account. Seeds, field layouts. xy=k invariant, SwapBaseIn vs. SwapBaseOut math, oracle observation update rule, transfer-fee adjustment. Initialize, Deposit, Withdraw, SwapBaseInput, SwapBaseOutput, CollectFundFee, CollectProtocolFee, UpdatePoolStatus. LP fee, protocol fee, fund fee, creator fee; how fees are collected and withdrawn. Create pool, deposit, withdraw, swap — in TypeScript (raydium-sdk-v2) and Rust CPI. ## When to read this * You are creating a new constant-product pool. * You need an AMM that supports Token-2022 tokens. * You are routing and want to include CPMM liquidity. # CPMM instructions Source: https://docs.raydium.io/products/cpmm/instructions Every instruction on the CPMM program, with its arguments, account list, pre/post conditions, and the errors it can return. This page is the authoritative instruction reference. For code that actually composes these instructions, see [`products/cpmm/code-demos`](/products/cpmm/code-demos). For error-code meanings see [`reference/error-codes`](/reference/error-codes). ## Instruction summary | Discriminator name | Who signs | What it does | | -------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Initialize` | pool creator | Create a new CPMM pool from two mints and an `AmmConfig`. Permissionless; anyone can call it. Hardcodes `enable_creator_fee = false` on the new pool. The `pool_state` account can be either the canonical PDA or a fresh random keypair (see Initialize accounts). | | `InitializeWithPermission` | payer + a holder of a `Permission` PDA | Permissioned variant of `Initialize`. The caller (`payer`) must own a `Permission` PDA derived from their own pubkey. Used by platforms that need gated pool creation (e.g. LaunchLab graduations). Lets the caller pin `creator_fee_on` (`BothToken` / `OnlyToken0` / `OnlyToken1`) and forces `enable_creator_fee = true` on the new pool. The `creator` field on `pool_state` is set to a separately-passed `creator` account, not the payer. Same canonical-PDA-or-random-keypair flexibility for `pool_state` as `Initialize`. | | `Deposit` | LP | Add liquidity in both tokens; receive LP tokens. | | `Withdraw` | LP | Burn LP tokens; receive both underlying tokens pro-rata. | | `SwapBaseInput` | swapper | Exact-input swap (`amount_in` in, ≥ `minimum_amount_out` out). | | `SwapBaseOutput` | swapper | Exact-output swap (≤ `maximum_amount_in` in, `amount_out` out). | | `CollectProtocolFee` | `protocol_owner` (from `AmmConfig`) | Sweep accrued protocol fees from vaults. | | `CollectFundFee` | `fund_owner` (from `AmmConfig`) | Sweep accrued fund fees from vaults. | | `CollectCreatorFee` | `pool_creator` | Sweep accrued creator fees (if creator fee was enabled). | | `UpdatePoolStatus` | `admin` | Pause / resume specific operations on a pool via a bitmask. | | `UpdateAmmConfig` | `admin` | Change fee rates or the protocol/fund owner on an `AmmConfig`. | | `CreateAmmConfig` | `admin` | Create a new fee tier (new `AmmConfig` account). | | `CreatePermissionPda` | `admin` | Mint a `Permission` PDA that allows a specific authority to call `InitializeWithPermission`. | | `ClosePermissionPda` | `admin` | Revoke a previously-issued `Permission` PDA. | 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** ``` init_amount_0: u64 init_amount_1: u64 open_time: u64 // Unix timestamp; swaps rejected before this ``` **Accounts** (W = writable, S = signer) | # | Name | W | S | Notes | | -- | -------------------------- | - | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | `creator` | W | S | Pays rent; recorded as `pool_state.pool_creator`. | | 2 | `amm_config` | | | The chosen fee tier. | | 3 | `authority` | | | CPMM global authority PDA. | | 4 | `pool_state` | W | S\* | `init`ed here. **Either** the canonical PDA `["pool", amm_config, token_0_mint, token_1_mint]` **or** a fresh random keypair — when not the canonical PDA, the program requires `pool_state` to sign (`require_eq!(pool_account_info.is_signer, true)`). The random-keypair path lets a creator dodge front-running attempts on the canonical PDA. The downstream PDAs (`lp_mint`, `vaults`, `observation_state`) are derived from `pool_state.key()` either way. | | 5 | `token_0_mint` | | | Sorted: `token_0_mint < token_1_mint`. | | 6 | `token_1_mint` | | | | | 7 | `lp_mint` | W | | `init`ed here. Authority set to `authority`. | | 8 | `creator_token_0` | W | | Source ATA for `init_amount_0`. | | 9 | `creator_token_1` | W | | Source ATA for `init_amount_1`. | | 10 | `creator_lp_token` | W | | Destination for LP (created if missing). | | 11 | `token_0_vault` | W | | `init`ed here. Owned by `authority`. | | 12 | `token_1_vault` | W | | | | 13 | `create_pool_fee` | W | | Destination ATA for the `create_pool_fee` paid by the creator. | | 14 | `observation_state` | W | | `init`ed here. | | 15 | `token_program` | | | SPL Token (for LP mint). | | 16 | `token_0_program` | | | SPL Token or Token-2022. | | 17 | `token_1_program` | | | SPL Token or Token-2022. | | 18 | `associated_token_program` | | | | | 19 | `system_program` | | | | | 20 | `rent` | | | | `*` `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`](/products/cpmm/accounts#vaults-and-token-2022). A small per-mint allow-list inside the program bypasses the check for case-by-case onboarding. * `creator` has at least `init_amount_0` and `init_amount_1` in the respective ATAs. * `amm_config.disable_create_pool == false`. **Postconditions** * `pool_state` exists with `lp_supply = sqrt(init_amount_0 * init_amount_1) − LOCKED_LP`. * The LP starter of `LOCKED_LP` (`100` lamports of LP token) is permanently locked in the pool — `pool_state.lp_supply` records `liquidity − 100` while `100` LP units remain outside circulation, preventing the pool from being fully drained and dividing by zero. * `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`](/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** ``` lp_token_amount: u64 // how many LP tokens to mint to the LP maximum_token_0: u64 maximum_token_1: u64 ``` **Accounts** | # | Name | W | S | | -- | -------------------- | - | - | | 1 | `owner` | | S | | 2 | `authority` | | | | 3 | `pool_state` | W | | | 4 | `owner_lp_token` | W | | | 5 | `token_0_account` | W | | | 6 | `token_1_account` | W | | | 7 | `token_0_vault` | W | | | 8 | `token_1_vault` | W | | | 9 | `token_program` | | | | 10 | `token_program_2022` | | | | 11 | `vault_0_mint` | | | | 12 | `vault_1_mint` | | | | 13 | `lp_mint` | W | | **Math** ``` needed_token_0 = ceil(lp_token_amount * vault_0 / lp_supply) needed_token_1 = ceil(lp_token_amount * vault_1 / lp_supply) require(needed_token_0 <= maximum_token_0, "ExceededSlippage") require(needed_token_1 <= maximum_token_1, "ExceededSlippage") ``` No change to `k`'s proportionality — both vaults 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** ``` lp_token_amount: u64 minimum_token_0: u64 minimum_token_1: u64 ``` **Accounts** | # | Name | W | S | | -- | -------------------- | - | - | | 1 | `owner` | | S | | 2 | `authority` | | | | 3 | `pool_state` | W | | | 4 | `owner_lp_token` | W | | | 5 | `token_0_account` | W | | | 6 | `token_1_account` | W | | | 7 | `token_0_vault` | W | | | 8 | `token_1_vault` | W | | | 9 | `token_program` | | | | 10 | `token_program_2022` | | | | 11 | `vault_0_mint` | | | | 12 | `vault_1_mint` | | | | 13 | `lp_mint` | W | | (Identical to `Deposit`; `lp_mint` is writable because the LP tokens are burned.) **Math** ``` out_token_0 = floor(lp_token_amount * vault_0 / lp_supply) out_token_1 = floor(lp_token_amount * vault_1 / lp_supply) require(out_token_0 >= minimum_token_0, "ExceededSlippage") require(out_token_1 >= minimum_token_1, "ExceededSlippage") ``` **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** ``` amount_in: u64 minimum_amount_out: u64 ``` **Accounts** | # | Name | W | S | | -- | ---------------------- | - | - | | 1 | `payer` | | S | | 2 | `authority` | | | | 3 | `amm_config` | | | | 4 | `pool_state` | W | | | 5 | `input_token_account` | W | | | 6 | `output_token_account` | W | | | 7 | `input_vault` | W | | | 8 | `output_vault` | W | | | 9 | `input_token_program` | | | | 10 | `output_token_program` | | | | 11 | `input_token_mint` | | | | 12 | `output_token_mint` | | | | 13 | `observation_state` | W | | 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`](/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** ``` max_amount_in: u64 amount_out: u64 ``` **Accounts** — same as `SwapBaseInput`. **Math** — inverse curve with ceiling, see [`products/cpmm/math`](/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** | # | Name | W | S | | | -- | --------------------------- | - | - | --------------------------------------- | | 1 | `owner` | | S | Must match `amm_config.protocol_owner`. | | 2 | `authority` | | | | | 3 | `pool_state` | W | | | | 4 | `amm_config` | | | | | 5 | `token_0_vault` | W | | | | 6 | `token_1_vault` | W | | | | 7 | `vault_0_mint` | | | | | 8 | `vault_1_mint` | | | | | 9 | `recipient_token_0_account` | W | | | | 10 | `recipient_token_1_account` | W | | | | 11 | `token_program` | | | | | 12 | `token_program_2022` | | | | **Effect** ``` transfer pool_state.protocol_fees_token0 from vault_0 to recipient_0 transfer pool_state.protocol_fees_token1 from vault_1 to recipient_1 pool_state.protocol_fees_token0 = 0 pool_state.protocol_fees_token1 = 0 ``` No change to the curve's effective balances (accrued fees were already excluded). **Common error** — `NotApproved` if signer is not `protocol_owner`. ## `CollectFundFee` Same shape as `CollectProtocolFee` but signed by `fund_owner` and zeroing the `fund_fees_*` counters. ## `CollectCreatorFee` Same shape again, signed by `pool_state.pool_creator`. Only emits transfers if the pool was initialized with a non-zero creator-fee rate. ## `UpdatePoolStatus` Pause or resume individual operations on a pool. The `status` field is a bitmask: | Bit | Flag | Effect when set | | --- | ------------------- | ------------------------------------------ | | 0 | `DEPOSIT_DISABLED` | `Deposit` rejects with `NotApproved`. | | 1 | `WITHDRAW_DISABLED` | `Withdraw` rejects. | | 2 | `SWAP_DISABLED` | `SwapBaseInput` / `SwapBaseOutput` reject. | **Arguments** ``` status: u8 // new bitmask ``` **Accounts** | # | Name | W | S | | | - | ------------ | - | - | --------------------------------------------- | | 1 | `authority` | | S | Must match the admin key on the CPMM program. | | 2 | `pool_state` | W | | | The admin key is the upgrade authority on the CPMM program — in practice, the Raydium multisig. See [`security/admin-and-multisig`](/security/admin-and-multisig). ## `CreateAmmConfig` Create a new fee tier. **Arguments** ``` index: u16 trade_fee_rate: u64 protocol_fee_rate: u64 fund_fee_rate: u64 create_pool_fee: u64 ``` **Accounts** | # | Name | W | S | | | - | ---------------- | - | - | -------------- | | 1 | `owner` | W | S | Admin. | | 2 | `amm_config` | W | | `init`ed here. | | 3 | `system_program` | | | | **Preconditions** * No existing `AmmConfig` with the same `index`. * `protocol_fee_rate + fund_fee_rate <= FEE_RATE_DENOMINATOR_VALUE`. ## `UpdateAmmConfig` Change fee rates or ownership on an existing `AmmConfig`. Takes a `param: u8` (discriminator for which field to update) and a `value: u64`. The value semantics per param are in the source; commonly: * `param = 0` → `trade_fee_rate` * `param = 1` → `protocol_fee_rate` * `param = 2` → `fund_fee_rate` * `param = 3` → `new_protocol_owner` (pass `Pubkey` bytes as a reinterpret) * `param = 4` → `new_fund_owner` * `param = 5` → `create_pool_fee` * `param = 6` → `disable_create_pool` 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. ## State-change matrix | Instruction | `lp_supply` | Vault balances | Accrued-fee fields | `observation` | | -------------------------- | ----------- | ----------------------- | ------------------------------------------------------------------ | ----------------------- | | `Initialize` | + init LP | + `init_amount_{0,1}` | 0 | init | | `InitializeWithPermission` | + init LP | + `init_amount_{0,1}` | 0 | init | | `Deposit` | + | + both | — | — | | `Withdraw` | − | − both | — | — | | `SwapBaseInput` | — | + in, − out | + `trade_fee` split into protocol/fund; + `creator_fee` if enabled | + (if interval elapsed) | | `SwapBaseOutput` | — | + in, − out | + `trade_fee` split into protocol/fund; + `creator_fee` if enabled | + (if interval elapsed) | | `CollectProtocolFee` | — | − (by protocol buckets) | `protocol_*` → 0 | — | | `CollectFundFee` | — | − (by fund buckets) | `fund_*` → 0 | — | | `CollectCreatorFee` | — | − (by creator buckets) | `creator_*` → 0 | — | | `UpdatePoolStatus` | — | — | — | — | ## Where to go next * [`products/cpmm/code-demos`](/products/cpmm/code-demos) — runnable TypeScript samples for the above. * [`reference/error-codes`](/reference/error-codes) — the complete Anchor error table. * [`products/cpmm/fees`](/products/cpmm/fees) — the fee-accrual model that `CollectProtocolFee` / `CollectFundFee` / `CollectCreatorFee` drain. Sources: * [`raydium-io/raydium-cp-swap` — instructions](https://github.com/raydium-io/raydium-cp-swap/tree/master/programs/cp-swap/src/instructions) * [Raydium SDK v2 — `@raydium-io/raydium-sdk-v2`](https://github.com/raydium-io/raydium-sdk-V2) # CPMM math Source: https://docs.raydium.io/products/cpmm/math The constant-product invariant, SwapBaseInput vs SwapBaseOutput, Token-2022 transfer-fee handling, and how the observation account is updated. ## The invariant CPMM maintains the classic constant-product invariant on its two vaults: $$ x \cdot y = k $$ where `x` is the vault0 balance **after** any Token-2022 transfer fees on receipt, and similarly for `y`. Every swap must leave `k' ≥ k` after accounting for trade fees credited to the LP (the protocol, fund, and creator buckets are *not* counted toward `k` — they sit in the vault but are excluded from the curve view, see [Fees on the curve](#fees-on-the-curve) below). `k` therefore grows monotonically over time as LPs accrue fees. LP shares are priced by the pool's reserves, not by `k`: $$ \text{LP price in token0} = \frac{x}{\text{lpSupply}}, \qquad \text{LP price in token1} = \frac{y}{\text{lpSupply}} $$ Burning `ΔLP` LP tokens returns exactly `ΔLP × x / lpSupply` of token0 and `ΔLP × y / lpSupply` of token1. Neither the curve nor `k` moves on deposit or withdrawal — only swaps change the price. ## Fee model on the swap path CPMM applies **two independently-rated fees** on every swap: * The **trade fee** is taken on the input side, charged at `AmmConfig.trade_fee_rate`. It is then split into LP, protocol, and fund shares (the LP share stays in the vault and grows `k`; the protocol and fund shares are extracted from vault accounting). * The **creator fee** (active only when `enable_creator_fee == true`) is charged at `AmmConfig.creator_fee_rate`. It is taken on the **input** side or the **output** side depending on `PoolState.creator_fee_on` and the swap direction (see [`products/cpmm/fees`](/products/cpmm/fees#which-side-of-the-trade-the-fees-are-taken-from)). It is its own bucket — never a slice of the trade fee. Let: * `FEE_RATE_DENOMINATOR = 1_000_000` * `trade_fee_rate` — from `AmmConfig`, e.g., `2500` = 0.25% of the relevant volume side * `creator_fee_rate` — from `AmmConfig`, e.g., `1000` = 0.10% of the relevant volume side * `protocol_fee_rate`, `fund_fee_rate` — denominated in units of `1/FEE_RATE_DENOMINATOR` **of the trade fee**, not of volume When the creator fee is on the input side: ``` total_input_fee = ceil(amount_in * (trade_fee_rate + creator_fee_rate) / FEE_RATE_DENOMINATOR) creator_fee = floor(total_input_fee * creator_fee_rate / (trade_fee_rate + creator_fee_rate)) trade_fee = total_input_fee - creator_fee amount_in_after_fees = amount_in - total_input_fee ``` When the creator fee is on the output side: ``` trade_fee = ceil(amount_in * trade_fee_rate / FEE_RATE_DENOMINATOR) amount_in_after_fees = amount_in - trade_fee amount_out_curve = curve_output(amount_in_after_fees, ...) creator_fee = ceil(amount_out_curve * creator_fee_rate / FEE_RATE_DENOMINATOR) amount_out = amount_out_curve - creator_fee ``` In both cases the trade fee is split the same way: ``` protocol_fee = floor(trade_fee * protocol_fee_rate / FEE_RATE_DENOMINATOR) fund_fee = floor(trade_fee * fund_fee_rate / FEE_RATE_DENOMINATOR) lp_fee = trade_fee - protocol_fee - fund_fee // creator_fee is NOT subtracted here ``` The `protocol_fee + fund_fee + creator_fee` amount is held in the vaults but tracked separately on the pool state (`protocol_fees_token*`, `fund_fees_token*`, `creator_fees_token*`). When the constant-product invariant checks `k' ≥ k`, it uses vault balances **minus** all three accrued-but-unswept fees — so LPs capture only `lp_fee`. See [`products/cpmm/fees`](/products/cpmm/fees) for the collection instructions and the worked numerical examples. ## SwapBaseInput (input-exact) "The user gives us exactly `amount_in` of the input mint and receives at least `minimum_amount_out` of the output mint." Ignoring Token-2022 for a moment: ``` amount_in_after_trade_fee = amount_in - trade_fee amount_out = y − (x * y) / (x + amount_in_after_trade_fee) ``` By algebra: $$ \text{amount\_out} = \frac{y \cdot \Delta x_{\text{net}}}{x + \Delta x_{\text{net}}} $$ where `Δx_net = amount_in_after_trade_fee`. The program then updates the vault accounting such that the portion of `trade_fee` owed to protocol/fund/creator sits in "accrued" buckets (not included in the curve's next `x`), while the LP share does join `x` for the next swap. ### Token-2022 on the input side If the input mint has a transfer-fee extension, the **mint** deducts its fee on the transfer from user → vault. So the vault actually receives `amount_in − transfer_fee_in`. The CPMM program therefore computes: ``` amount_actually_received = amount_in − transfer_fee_in(amount_in) trade_fee = ceil(amount_actually_received * trade_fee_rate / FEE_RATE_DENOMINATOR) amount_in_after_trade_fee = amount_actually_received − trade_fee ``` and runs the curve against `amount_in_after_trade_fee`. This matters because **the curve price is computed off the net amount that landed in the vault**, not off the user's headline amount. ### Token-2022 on the output side If the output mint has a transfer fee, the pool sends `amount_out` from its vault to the user. The **mint** will then skim its fee on the way out, so the user receives `amount_out − transfer_fee_out(amount_out)`. The program computes `amount_out` from the curve as usual, but it is the integrator's responsibility to convert the pool's "vault send" number into a "user receive" number when showing quotes. ### Slippage check After computing `amount_out`: ``` require(amount_out >= minimum_amount_out, "AmountSpecifiedLessThanMinimum") ``` If the output mint charges a transfer fee, the SDK applies the transfer fee *before* setting `minimum_amount_out` so the slippage constant is denominated in what the user will actually receive, not in what the vault sends. ## SwapBaseOutput (output-exact) "The user will receive exactly `amount_out` of the output mint and is willing to pay up to `maximum_amount_in` of the input mint." Inverting the curve for `Δx_net`: $$ \Delta x_{\text{net}} = \left\lceil \frac{x \cdot \text{amount\_out}}{y - \text{amount\_out}} \right\rceil $$ The ceiling is important — it guarantees `k' ≥ k` after integer truncation. Then: ``` // Work backwards from the net in to the gross in. // fee is applied on the gross, so: // net = gross − ceil(gross * rate / D) // ≈ gross * (D − rate) / D // inverting with ceiling in the right places: gross_needed = ceil(Δx_net * D / (D − trade_fee_rate)) ``` On Token-2022 input, wrap with: ``` gross_needed_before_mint_fee = inflate_for_transfer_fee(gross_needed, input_mint) ``` so the user pays enough that after the mint's transfer-fee deduction the pool still receives `gross_needed`. ### Slippage check ``` require(gross_needed_before_mint_fee <= maximum_amount_in, "AmountSpecifiedExceedsMaximum") ``` ## Worked example Pool state, ignoring Token-2022: * `x = 1_000_000_000_000` (1,000,000.000000 of token0, 6 decimals) * `y = 2_000_000_000_000` (2,000,000.000000 of token1, 6 decimals) * `AmmConfig`: `trade_fee_rate = 2500`, `protocol_fee_rate = 120_000`, `fund_fee_rate = 40_000`, `creator_fee_rate = 0` User: `SwapBaseInput` with `amount_in = 1_000_000_000` (1,000.000000 of token0). Creator fee is disabled (`enable_creator_fee = false`). ``` trade_fee = ceil(1_000_000_000 * 2500 / 1_000_000) = 2_500_000 protocol_fee = floor(2_500_000 * 120_000 / 1_000_000) = 300_000 fund_fee = floor(2_500_000 * 40_000 / 1_000_000) = 100_000 lp_fee = 2_500_000 − 300_000 − 100_000 = 2_100_000 creator_fee = 0 // disabled amount_in_after_trade_fee = 1_000_000_000 − 2_500_000 = 997_500_000 amount_out = y − (x * y) / (x + Δx_net) = 2_000_000_000_000 − (1_000_000_000_000 * 2_000_000_000_000) / (1_000_000_000_000 + 997_500_000) ≈ 1_995_015_009 new_vault0_raw = x + amount_in = 1_001_000_000_000 new_vault1 = y − amount_out ≈ 1_998_004_984_991 // Of the 1_000_000_000 received in vault0, 400_000 is "accrued fee" // (protocol + fund) that the curve should exclude: curve_x = new_vault0_raw − (protocol_fees_token0 + fund_fees_token0) = 1_001_000_000_000 − 400_000 = 1_000_999_600_000 k' = curve_x * new_vault1 ≈ 2.000_002_501_E24 ≥ k = 2.0E24 ✓ ``` If the same pool had `enable_creator_fee = true` with `creator_fee_rate = 1000` (0.10%) on the input side, the program would charge `total_input_fee = ceil(1_000_000_000 * 3500 / 1_000_000) = 3_500_000`, then split it as `creator_fee = 1_000_000` and `trade_fee = 2_500_000`. The protocol/fund/LP arithmetic on `trade_fee` is unchanged from the example above — the creator fee is its own bucket, accrued to `creator_fees_token0` and excluded from `curve_x` along with the protocol and fund buckets. If the input mint has a 1% Token-2022 transfer fee, the vault receives `990_000_000` tokens instead of `1_000_000_000`, and every subsequent calculation uses that net amount. ## Observation update rule On every swap, the program evaluates whether to push a new observation into the ring buffer: ``` let since_last = now − observations[head].block_timestamp; if since_last >= MIN_OBSERVATION_INTERVAL { let price0 = (vault1 << 32) / vault0; // Q32.32-ish let price1 = (vault0 << 32) / vault1; let head' = (head + 1) % OBSERVATION_NUM; observations[head'] = Observation { block_timestamp: now, cumulative_token0_price_x32: observations[head].cumulative_token0_price_x32 + price0 * since_last, cumulative_token1_price_x32: observations[head].cumulative_token1_price_x32 + price1 * since_last, }; head = head'; } ``` Two properties: * **Cumulative price, not spot price.** A single observation is not a price. To get a TWAP from time `t0` to `t1`, read the observations closest to each end and compute `(cumulative(t1) − cumulative(t0)) / (t1 − t0)`. * **Samples are rate-limited.** Back-to-back swaps in the same slot may share one observation. Reading an observation immediately after a swap can therefore look stale by one slot — this is normal. More in [`products/clmm/accounts`](/products/clmm/accounts). ## Fees on the curve This is the subtle part and worth calling out. The curve arithmetic works against the **net** vault balances — i.e., raw SPL balance minus accrued protocol, fund, and creator fees (all three are independent buckets — see [`products/cpmm/fees`](/products/cpmm/fees)). A concrete picture: ``` raw_vault_balance = what an RPC getTokenAccountBalance returns accrued_fees = protocol_fees_token{0,1} + fund_fees_token{0,1} + creator_fees_token{0,1} curve_balance = raw_vault_balance − accrued_fees invariant = curve_balance0 * curve_balance1 ``` Consequences for integrators: * **Do not quote from raw balances.** Subtract the accrued-fee fields first, or call `SwapBaseInput` as a simulation and take its return. * **`CollectProtocolFee` moves tokens out of the vault.** After collection, `raw_vault_balance` drops but `curve_balance` is unchanged; the pool's price does not move. This is deliberate. ## Precision and overflow * All curve arithmetic uses `u128` intermediates to prevent overflow on `x * y`. * Division rounds toward zero except for `SwapBaseOutput`'s `Δx_net`, which rounds up, and fee computation, which rounds up on the `trade_fee` and down on the sub-splits. These rounding directions are chosen so the invariant never decreases due to integer truncation. * Pools with extreme vault ratios (billions : 1) may hit precision floors on small trades; the program returns `ZeroTradingTokens` in that case. See [`reference/error-codes`](/reference/error-codes). ## Where to go next * [`products/cpmm/fees`](/products/cpmm/fees) — the full fee tier and collection semantics. * [`products/cpmm/instructions`](/products/cpmm/instructions) — the instructions that invoke this math. * [`algorithms/constant-product`](/algorithms/constant-product) — the derivation and edge cases of `x · y = k` shared across AMM v4 and CPMM. Sources: * [`raydium-io/raydium-cp-swap` — swap math in `states/curve.rs`](https://github.com/raydium-io/raydium-cp-swap) * Raydium audit reports linked in [`security/audits`](/security/audits) # CPMM overview Source: https://docs.raydium.io/products/cpmm/overview What Raydium's CPMM (Standard AMM) is, what makes it different from AMM v4, and when to pick it. ## One-paragraph summary CPMM — **C**onstant **P**roduct **M**arket **M**aker, officially the "Standard AMM" in Raydium's UI — is a native Solana implementation of the classic `x · y = k` AMM. No OpenBook order book, no Serum legacy, no external dependencies beyond SPL Token and Token-2022. Every pool is a triple of (two token vaults, one LP mint) governed by a program-owned authority PDA, priced by the product of the vault balances. It is the AMM Raydium recommends for all new constant-product pools, and it is what the `/pools/create` endpoint and the web UI's "Create pool" flow target by default. ## What CPMM gives you * **Token-2022 with a vetted extension allow-list.** CPMM does **not** accept arbitrary Token-2022 mints. The program enforces a whitelist of safe extensions at pool creation: `TransferFeeConfig`, `MetadataPointer`, `TokenMetadata`, `InterestBearingConfig`, and `ScaledUiAmount`. Any other extension on the mint causes `Initialize` to reject with `NotSupportMint` — unless the mint itself is on a small hard-coded mint allow-list maintained in the program (used to onboard specific mints case-by-case). Transfer fees in particular affect swap math and are applied on the correct side of the trade — see [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees). * **Predictable fees.** Each pool references an `AmmConfig` selected at creation. The config carries a trade-fee rate (split between LPs, protocol, and fund) and a separate, independent creator-fee rate. The creator fee is its own bucket — never a slice of the trade fee. Pools opt into charging it at creation. Defaults and the full split math are in [`products/cpmm/fees`](/products/cpmm/fees). * **On-chain TWAP via an observation ring buffer.** Every swap updates an `observation` account. External contracts can read a cumulative-price observation to compute a TWAP without a custom oracle. * **Flat account layout.** A pool is fully described by six PDAs (authority, pool state, LP mint, two vaults, observation). No per-market OpenBook account, no event queue, no request queue. Transactions are cheaper in both compute and account count than AMM v4. * **Burn-and-earn compatible.** LP tokens can be locked under the [LP Lock](/reference/program-addresses) program so the pool creator can keep claiming fees without retaining the right to withdraw liquidity. Used for "permanent" liquidity launches. ## What CPMM is not * **Not concentrated.** Liquidity is spread evenly across the whole price range, like Uniswap v2. If you need capital-efficient market making — i.e., concentrating liquidity near the current price — use [CLMM](/products/clmm). * **Not hybrid.** Unlike [AMM v4](/products/amm-v4), CPMM pools do not place resting orders on an OpenBook market. Routing across CPMM pools happens through the [AMM Routing](/reference/program-addresses) program, not through a CLOB. * **Not launchable for arbitrary curves.** The curve is hard-coded to constant product. If you want a bonding curve for a token launch, use [LaunchLab](/products/launchlab), which graduates to a CPMM pool when it fills. ## How CPMM differs from AMM v4 | Dimension | AMM v4 | CPMM | | ------------------------- | ------------------------------------------------------------------------------- | --------------------------------- | | Curve | Constant product | Constant product | | OpenBook dependency | Inert (originally placed orders on an OpenBook market; integration deactivated) | No | | Token-2022 support | No (SPL Token only) | **Yes** (including transfer fees) | | Account count per V2 swap | \~9 | \~11 | | Compute units per swap | \~80k–120k (V2 path) | \~60k–100k | | TWAP oracle | No native oracle account | `observation` ring buffer | | New pool creation today | Not the default (program still accepts it) | Default | | Status | Fully operational | Active, recommended | A deeper treatment of the migration story is in [`protocol-overview/versions-and-migration`](/protocol-overview/versions-and-migration). ## Mental model A CPMM pool is a program-owned object holding three balances: `vault0` (token0), `vault1` (token1), and the supply of the LP mint. The LP mint's supply tracks depositors' claim on the pool; the token vaults hold the actual assets. Everything else — the authority PDA, the observation account, the fee-config pointer — is bookkeeping to make that three-variable relationship tradable, fee-collecting, and observable. Every user-facing operation collapses to a well-defined state transition: * **Deposit:** transfer token0 and token1 in, mint LP to user, no change to price. * **Withdraw:** burn LP from user, transfer token0 and token1 out in the pool's current ratio, no change to price. * **Swap:** transfer one token in, transfer the other out; the ratio moves along the `x · y = k` curve (minus fees); the observation account samples the new price. Fee collection (`CollectProtocolFee`, `CollectFundFee`) is a separate transaction signed by the respective authority; it does not happen on every swap. The math is spelled out in [`products/cpmm/math`](/products/cpmm/math) and the instruction set in [`products/cpmm/instructions`](/products/cpmm/instructions). ## When to choose CPMM Pick CPMM when: * You are launching a new token or a new pair and do not have strong opinions about which range will see trading. * One or both of the tokens uses Token-2022 extensions. * You want a simple fee-per-trade model over a dynamic, tick-based one. * You are integrating and want to route through Raydium without taking on the complexity of CLMM positions. Prefer [CLMM](/products/clmm) when: * The pair is stable or highly correlated (stablecoin-stablecoin, LST-SOL) and you want to concentrate liquidity around parity. * You are a market-making team willing to actively manage ranges for higher fee APR per dollar of TVL. Prefer [AMM v4](/products/amm-v4) when: * You are migrating existing AMM v4 tooling and are not creating a new pool. (Note: AMM v4's OpenBook hybrid mode is no longer active — that's not a reason to choose AMM v4 anymore.) ## Where to go next * [Accounts](/products/cpmm/accounts) — the six PDAs of a CPMM pool and how to derive them. * [Math](/products/cpmm/math) — `SwapBaseInput` vs `SwapBaseOutput`, Token-2022 transfer-fee handling, observation updates. * [Instructions](/products/cpmm/instructions) — the complete instruction surface with account lists. * [Fees](/products/cpmm/fees) — the four-way fee split and how to collect. * [Code demos](/products/cpmm/code-demos) — runnable TypeScript snippets for create / swap / deposit / withdraw. Sources: * [Raydium CP-Swap source — `raydium-io/raydium-cp-swap`](https://github.com/raydium-io/raydium-cp-swap) * [`reference/program-addresses`](/reference/program-addresses) for canonical program IDs # Farm / Staking accounts Source: https://docs.raydium.io/products/farm-staking/accounts FarmState, UserStake / Ledger, reward vaults, and the authority PDAs — layouts and PDA seeds for farm v3, v5, and v6. **Three programs, three schemas.** Farm v3, v5, and v6 are separate programs with separate state layouts. This page documents each side by side. In practice most new integrations target v6; v3 and v5 are read-only for most integrators (staking already happened there long ago and the pools are in wind-down). ## Account inventory (per farm, any version) | Account | Owner | Purpose | | --------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------- | | `FarmState` | Farm program (v3/v5/v6) | Root state: staking mint, total staked, reward streams. | | `farm_authority` | Farm program | PDA that owns the staking vault and the reward vaults. | | `staking_vault` | SPL Token | Holds staked LP (or whatever the staking mint is). | | `reward_vault_{i}` | SPL Token | Holds the undistributed budget for reward stream `i`. One per stream. | | `UserStake` (v3/v5) / `UserLedger` (v6) | Farm program | Per-`(farm, user)` ledger: staked amount + snapshot of reward-per-share per stream. | The SDK returns the full set on `raydium.farm.getFarmById`. For arbitrary third-party farms, the API endpoint `GET https://api-v3.raydium.io/main/farms/info?ids=` also returns them. ## `FarmState` layout — v6 v6 is the current version. Its account structure is the most general. ```rust theme={null} // programs/farm_v6/src/state/farm.rs (abridged) pub struct FarmState { pub state: u64, // bitfield: 0 = live, 1 = paused, ... pub nonce: u64, // bump for farm_authority pub creator: Pubkey, // original creator (can add rewards / transfer admin) pub staking_mint: Pubkey, pub staking_vault: Pubkey, pub staking_token_program: Pubkey, // SPL Token or Token-2022 pub lp_mint_decimals: u8, pub reward_period_len: u64, // minimum duration between SetRewards edits pub reward_period_min: u64, pub total_staked: u64, pub reward_info_count: u8, pub _reserved: [u8; ...], pub reward_infos: [RewardInfo; 5], // up to 5 reward streams } pub struct RewardInfo { pub reward_state: u8, // 0 = unused, 1 = running, 2 = ended pub open_time: u64, // start_time pub end_time: u64, pub last_update_time: u64, pub emission_per_second_x64: u128, // Q64.64 fixed-point rate pub reward_total_emissioned: u64, // sum of emissions so far pub reward_claimed: u64, // sum paid out to stakers pub reward_vault: Pubkey, pub reward_mint: Pubkey, pub reward_sender: Pubkey, // who deposited the budget; can top up pub reward_token_program: Pubkey, // SPL or Token-2022 pub reward_per_share_x64: u128, // Q64.64 counter } ``` Integrator-facing fields: * **`staking_mint`, `staking_vault`** — what gets staked and where it sits. * **`total_staked`** — current total. Required to compute APR: `reward_per_second × 86400 / total_staked`. * **`reward_infos[i].emission_per_second_x64`** — the Q64.64 rate. Divide by `2^64` for the true per-second token count. * **`reward_infos[i].open_time` / `end_time`** — for UI "X days left" displays. * **`reward_infos[i].reward_per_share_x64`** — the counter the `UserLedger` debts off. ## `FarmState` layout — v5 ```rust theme={null} pub struct FarmStateV5 { pub state: u64, pub nonce: u64, pub lp_vault: Pubkey, // aka staking_vault pub reward_vaults: [Pubkey; 2], pub owner: Pubkey, pub reward_mints: [Pubkey; 2], pub reward_total_emissioned: [u64; 2], pub reward_claimed: [u64; 2], pub reward_per_second: [u64; 2], // integer, not fixed-point pub reward_open_time: [u64; 2], pub reward_end_time: [u64; 2], pub reward_per_share: [u128; 2], // Q56.8 or similar; check program source pub total_staked: u64, pub last_slot: u64, // v5 updates per-slot on mainnet pub _reserved: [u8; 256], } ``` Differences from v6: * **Per-slot, not per-second.** v5's update loop runs on slots rather than on the wall clock. The SDK normalizes this into "per-second" for the UI but on-chain the unit is slots. * **Integer emission rate.** `reward_per_second` is `u64`. This caps the minimum expressible rate at 1 unit per second, which is too coarse for low-emission streams on 9-decimal mints. v6 fixed this with the Q64.64 rate. * **No `reward_sender`**. On v5 the owner is the implicit sender; only `owner` can top up. ## `FarmState` layout — v3 ```rust theme={null} pub struct FarmStateV3 { pub state: u64, pub nonce: u64, pub lp_vault: Pubkey, pub reward_vault: Pubkey, // single reward pub owner: Pubkey, pub reward_mint: Pubkey, pub reward_total_emissioned: u64, pub reward_claimed: u64, pub reward_per_slot: u64, pub reward_per_share: u128, pub total_staked: u64, pub last_slot: u64, pub _reserved: [u8; 256], } ``` Single-reward. Slot-based. The oldest program generation, kept alive for the RAY-USDC and SOL-USDC farms that predate v5. ## `UserLedger` (v6) / `UserStake` (v5/v3) Per-user state, one account per `(farm, user)` pair. Seeded PDA: ```ts theme={null} // v6 const [ledgerPda] = PublicKey.findProgramAddressSync( [farmId.toBuffer(), user.toBuffer(), Buffer.from("user_stake_info_v2")], FARM_V6_PROGRAM_ID, ); // v5 const [stakePda] = PublicKey.findProgramAddressSync( [farmId.toBuffer(), user.toBuffer(), Buffer.from("user_stake_info")], FARM_V5_PROGRAM_ID, ); // v3 const [stakePda] = PublicKey.findProgramAddressSync( [farmId.toBuffer(), user.toBuffer(), Buffer.from("staker_info")], FARM_V3_PROGRAM_ID, ); ``` (Seed strings are the values actually used by the SDK; program versions have historically varied them. Verify against the v6 source for anything security-critical.) ```rust theme={null} // v6 pub struct UserLedger { pub version: u64, pub farm_id: Pubkey, pub owner: Pubkey, pub deposited: u64, // current stake pub reward_debts: [u128; 5], // snapshot of reward_per_share_x64 × deposited } ``` The per-stream debt is the accounting offset described in the overview: ``` pending_for_stream_i = deposited × reward_per_share_x64[i] / 2^64 − reward_debts[i] ``` After each `Deposit`, `Withdraw`, or `Harvest`, the debt is reset to the current `deposited × reward_per_share_x64[i] / 2^64`. ## Authority PDAs ```ts theme={null} // v6 const [farmAuthorityV6] = PublicKey.findProgramAddressSync( [farmId.toBuffer()], FARM_V6_PROGRAM_ID, ); // v5 const [farmAuthorityV5] = PublicKey.findProgramAddressSync( [farmId.toBuffer()], FARM_V5_PROGRAM_ID, ); // v3 const [farmAuthorityV3] = PublicKey.findProgramAddressSync( [farmId.toBuffer()], FARM_V3_PROGRAM_ID, ); ``` All three versions derive the farm authority per-farm with a single seed. This PDA is the authority on the staking vault and on each reward vault. It signs every transfer the farm makes. ## Vaults Staking and reward vaults are standard SPL Token accounts whose `owner` is the farm authority PDA. Addresses are stored on `FarmState` — do not re-derive; read from state. Freeze authorities must be disabled on the staking mint for v5/v6 (the program checks). Token-2022 notes: * **v3**: SPL Token only. * **v5**: SPL Token only. * **v6**: Supports Token-2022 on both staking and reward mints, gated on `staking_token_program` / `reward_token_program` fields. Transfer fees on Token-2022 reward mints are charged on emit (vault → user). ## Observation and APR Farms do not store APR on-chain. To compute: ``` annualized_rewards_value_usd = reward_per_second × 86400 × 365 × reward_usd_price tvl_usd = total_staked / 10^decimals × staking_mint_usd_price apr = annualized_rewards_value_usd / tvl_usd ``` Consumers typically pull `staking_mint_usd_price` from the pool the LP belongs to (via `api-v3.raydium.io/pools/info/ids`) and `reward_usd_price` from any price oracle. ## Where to go next * [`products/farm-staking/instructions`](/products/farm-staking/instructions) — per-version instruction reference. * [`products/farm-staking/code-demos`](/products/farm-staking/code-demos) — staking, harvesting, and creating farms via the SDK. * [`reference/program-addresses`](/reference/program-addresses) — all three program IDs. Sources: * [Raydium SDK v2 `Farm` module](https://github.com/raydium-io/raydium-sdk-V2) * Farm v6 program source is not currently published as a standalone repo; the IDL is bundled in the SDK at [`raydium-io/raydium-sdk-V2`](https://github.com/raydium-io/raydium-sdk-V2) under `src/raydium/farm/`. # Farm / Staking code demos Source: https://docs.raydium.io/products/farm-staking/code-demos TypeScript examples for creating a farm, staking LP, harvesting rewards, and topping up reward budgets against Raydium's farm v6 program. **Version banner.** All demos target `@raydium-io/raydium-sdk-v2@0.2.42-alpha` against Solana mainnet-beta, verified 2026-04. The SDK dispatches v3 / v5 / v6 internally based on the farm's program owner; examples below assume a v6 farm. See [`reference/program-addresses`](/reference/program-addresses) for the three program IDs. ## Setup Demos here mirror files in [`raydium-sdk-V2-demo/src/farm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/farm). Bootstrap follows the demo repo's [`config.ts.template`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template): ```ts theme={null} import { Connection, Keypair, clusterApiUrl, PublicKey } from "@solana/web3.js"; import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; import BN from "bn.js"; import fs from "node:fs"; const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta")); const owner = Keypair.fromSecretKey( new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))), ); const raydium = await Raydium.load({ owner, connection, cluster: "mainnet", disableFeatureCheck: true, blockhashCommitment: "finalized", }); export const txVersion = TxVersion.V0; ``` ## Fetch a farm by id ```ts theme={null} const farmId = new PublicKey(""); const farm = await raydium.farm.getFarmById({ farmId }); console.log("Staking mint:", farm.symbolMint.toBase58()); console.log("Total staked:", farm.totalStaked.toString()); console.log("Rewards:"); for (const r of farm.rewardInfos) { console.log( " -", r.mint.toBase58(), "rate(1e-" + r.decimals + "/s):", r.perSecond.toString(), "open:", new Date(r.openTime * 1000).toISOString(), "end: ", new Date(r.endTime * 1000).toISOString(), ); } ``` `getFarmById` pulls `FarmState` off-chain, decodes per program version, and normalizes the fixed-point emission rate into a plain `Decimal` per second. ## Stake LP tokens Source: [`src/farm/stake.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/stake.ts) ```ts theme={null} const amount = new BN(1_000_000_000); // 1 LP (assuming 9 decimals) const { execute } = await raydium.farm.deposit({ farmInfo: farm, amount, txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Deposit tx:", txId); ``` The SDK handles the pre-settle of any pending rewards, so if this wallet already has stake in this farm, the instruction will pay out accumulated rewards to the user's ATAs in the same transaction. ## Claim-only (harvest) Source: [`src/farm/harvest.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/harvest.ts) ```ts theme={null} const { execute } = await raydium.farm.harvestAllRewards({ farmInfoList: [farm], txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` `harvestAllRewards` accepts a list — for UIs showing a portfolio view, batch the call. Each farm is claimed in a separate instruction within one transaction (subject to the 1232-byte size limit; for >\~6 farms, split into multiple transactions). For a single farm on v6, you can also use the explicit `Harvest` path: ```ts theme={null} const { execute } = await raydium.farm.withdraw({ farmInfo: farm, amount: new BN(0), // 0 = harvest-only path, even on v6 txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` On v3 and v5 the `amount: 0` idiom is required; the SDK dispatches it correctly. ## Unstake Source: [`src/farm/unstake.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/unstake.ts) ```ts theme={null} const amount = new BN(500_000_000); // 0.5 LP const { execute } = await raydium.farm.withdraw({ farmInfo: farm, amount, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` ## Create a v6 farm Source: [`src/farm/createAmmFarm.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/createAmmFarm.ts) and [`editAmmFarm.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/farm/editAmmFarm.ts) ```ts theme={null} import { CurveCalculator } from "@raydium-io/raydium-sdk-v2"; // optional, for APR planning const stakingMint = new PublicKey(""); // e.g. a CPMM LP mint const rewardMint = new PublicKey(""); // e.g. your project's token const now = Math.floor(Date.now() / 1000); const openTime = now + 60 * 60; // start in 1h const duration = 60 * 60 * 24 * 30; // 30 days const endTime = openTime + duration; const totalBudget = new BN(1_000_000).mul(new BN(10).pow(new BN(9))); // 1M reward tokens (9 dec) const perSecond = totalBudget.div(new BN(duration)); // integer, SDK lifts to Q64.64 const { execute, extInfo } = await raydium.farm.create({ programId: /* v6 program ID, from reference/program-addresses */, poolInfo: { lpMint: { address: stakingMint, decimals: 9, programId: /* SPL Token */ } as any }, rewardInfos: [ { mint: rewardMint, openTime: new BN(openTime), endTime: new BN(endTime), perSecond, rewardSender: owner.publicKey, // who pays the initial budget mintProgramId: /* SPL Token or Token-2022 */, }, ], txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Farm:", extInfo.farmId.toBase58(), "createTx:", txId); ``` Key points: * `perSecond` is the integer emission rate per second. The SDK packs it into Q64.64 before sending. For a fractional rate, scale and adjust duration. * The full budget (`perSecond × duration`) must be present in your reward ATA — `create` moves it into the reward vault atomically. * You can seed up to 5 rewards in one `create` call. The account list grows by `(reward_mint, reward_vault, sender_ata, token_program)` per extra stream; stay aware of the 1232-byte transaction size limit. For 4+ rewards, create with 1–2 and use `AddReward` in follow-up transactions. ## Top up an existing reward stream ```ts theme={null} const additionalDays = 30; const extraSeconds = 60 * 60 * 24 * additionalDays; const extraBudget = perSecond.mul(new BN(extraSeconds)); const { execute } = await raydium.farm.setRewards({ farmInfo: farm, rewardInfos: [ { rewardMint: rewardMint, newEndTime: new BN(farm.rewardInfos[0].endTime + extraSeconds), newPerSecond: perSecond, // keep same rate payer: owner.publicKey, }, ], txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` `setRewards` extends `end_time` and transfers the delta budget. The instruction cannot shorten a stream, cannot lower `per_second` on a live stream, and cannot change the reward mint. To swap mints, wait for `end_time` and use `AddReward` on a freed slot (if any), or create a new farm. ## Restart a finished stream ```ts theme={null} const { execute } = await raydium.farm.restartRewards({ farmInfo: farm, newRewardInfo: { rewardMint: rewardMint, openTime: new BN(Math.floor(Date.now() / 1000) + 60 * 60), endTime: new BN(Math.floor(Date.now() / 1000) + 60 * 60 + 60 * 60 * 24 * 14), perSecond: new BN("1000000000"), payer: owner.publicKey, }, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` Valid only when the target slot's `reward_state == 2` (ended). The caller must be the slot's `reward_sender` (v6) or the farm owner (v5). ## Rust CPI Unlike AMM v4, the v6 farm program ships with an Anchor crate (`raydium_farm_v6`) published alongside the frontend and SDK sources. A minimal `Deposit` sketch: ```rust theme={null} use anchor_lang::prelude::*; use raydium_farm_v6::{self, cpi::accounts::Deposit as RaydiumDeposit}; #[derive(Accounts)] pub struct ProxyFarmDeposit<'info> { /// CHECK: pub farm_program: UncheckedAccount<'info>, #[account(mut)] pub user: Signer<'info>, #[account(mut)] /// CHECK: pub user_ledger: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub farm_state: UncheckedAccount<'info>, /// CHECK: pub farm_authority: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub staking_vault: UncheckedAccount<'info>, #[account(mut)] /// CHECK: pub user_staking_ata: UncheckedAccount<'info>, // Followed in remaining_accounts by (reward_vault_i, user_reward_ata_i) pairs // for each live reward stream on the farm. pub token_program: UncheckedAccount<'info>, pub system_program: Program<'info, System>, } pub fn proxy_deposit<'info>( ctx: Context<'_, '_, '_, 'info, ProxyFarmDeposit<'info>>, amount: u64, ) -> Result<()> { let cpi_ctx = CpiContext::new( ctx.accounts.farm_program.to_account_info(), RaydiumDeposit { user: ctx.accounts.user.to_account_info(), user_ledger: ctx.accounts.user_ledger.to_account_info(), farm_state: ctx.accounts.farm_state.to_account_info(), farm_authority: ctx.accounts.farm_authority.to_account_info(), staking_vault: ctx.accounts.staking_vault.to_account_info(), user_staking_ata: ctx.accounts.user_staking_ata.to_account_info(), token_program: ctx.accounts.token_program.to_account_info(), system_program: ctx.accounts.system_program.to_account_info(), }, ).with_remaining_accounts(ctx.remaining_accounts.to_vec()); raydium_farm_v6::cpi::deposit(cpi_ctx, amount) } ``` The `remaining_accounts` slice must match the farm's active reward slots 1-for-1 (pairs of `reward_vault_i`, `user_reward_ata_i` in index order). Omitting or misordering these produces a silent mis-accounting — the program will transfer the wrong amount. ## Pitfalls * **Forgetting to claim before withdrawing.** Harmless — `Withdraw` settles pending rewards first. But if your UI shows "claim" separately from "withdraw", the user may think there is still something to claim after a `Withdraw`. There is not; everything accrued up to that point was paid out. * **`total_staked = 0` during emissions.** Emissions accrued while nothing was staked are forfeited (the `reward_per_share` update formula divides by 0 and the program skips the update). For programs with scheduled `open_time`, run a "seed stake" at open\_time to avoid this. * **Token-2022 transfer fees.** On v6 farms with Token-2022 reward mints, the transfer fee applies on emit (vault → user). Factor this into APR quotes. * **Small `per_second` on v5.** v5's `u64` rate means any `per_second < 1` token-unit per second (on mints with ≥9 decimals this is often the desired rate) cannot be expressed — the stream rate rounds to 0 and the farm emits nothing. Use v6. ## Where to go next * [`products/farm-staking/instructions`](/products/farm-staking/instructions) — underlying instruction reference. * [`products/clmm/fees`](/products/clmm/fees) — compare to CLMM's native reward streams. * [`user-flows/migrate-amm-v4-to-cpmm`](/user-flows/migrate-amm-v4-to-cpmm) — often paired with spinning up a new CPMM farm. Sources: * [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2) * Farm v6 IDL bundled in [`raydium-io/raydium-sdk-V2`](https://github.com/raydium-io/raydium-sdk-V2) under `src/raydium/farm/`. # Farm / Staking Source: https://docs.raydium.io/products/farm-staking/index Reward distribution program for LP tokens and single-asset staking. ## What it is The Farm / Staking program distributes reward tokens to accounts that stake an input token (typically an LP token from AMM v4 / CPMM / CLMM, but also supports single-asset staking). Each farm can emit up to multiple reward mints on independent schedules. **Program ID:** see [reference/program-addresses](/reference/program-addresses). Multiple farm versions are in production (v3, v5, v6) and differ in reward slots and admin primitives — the [versions-and-migration](/protocol-overview/versions-and-migration) page documents which to use for which deployments. ## Chapter contents Conceptual model: stake accounts, per-second emission rate, reward-per-stake accounting. Farm, UserStake (ledger), reward vaults, admin PDAs. Seeds and field layouts per farm version. CreateFarm, Deposit, Withdraw, Harvest, AddReward, SetRewards, RestartRewards, WithdrawReward. Create a farm, stake LP tokens, harvest, and top up reward vaults. ## When to read this * You are setting up incentives for your project's LP. * You are building a staking UI or aggregator that displays farm APRs. # Farm / Staking instructions Source: https://docs.raydium.io/products/farm-staking/instructions CreateFarm, Deposit, Withdraw, Harvest, AddReward / SetRewards / RestartRewards, WithdrawReward — the full instruction surface across farm v3, v5, and v6. Farm instructions are version-specific. A `Deposit` on v6 is not callable on a v5 farm and vice versa. The SDK dispatches by reading the farm's program owner; for on-chain CPI you must choose the right program ID up front. ## Instruction inventory | Purpose | v3 | v5 | v6 | | ---------------------------------------- | --------------------- | ------------------------ | --------------------- | | Create a farm | `CreateFarm` | `CreateFarm` | `CreateFarm` | | Add a user ledger (may be implicit) | `CreateUserLedger` | `CreateAssociatedLedger` | Implicit in `Deposit` | | Stake | `Deposit` | `Deposit` | `Deposit` | | Unstake | `Withdraw` | `Withdraw` | `Withdraw` | | Claim rewards only | N/A (use `Deposit 0`) | N/A (use `Deposit 0`) | `Harvest` | | Add a reward stream after creation | N/A | `AddReward` | `AddReward` | | Edit an existing reward stream | N/A | `SetRewards` | `SetRewards` | | Restart a reward after end\_time | N/A | `RestartRewards` | `RestartRewards` | | Withdraw unclaimed reward budget (admin) | N/A | `WithdrawReward` | `WithdrawReward` | On v3 and v5, the canonical way to claim rewards without changing stake is to call `Deposit` with `amount = 0`. The program treats this as a pure settlement. v6 introduced an explicit `Harvest` for clarity. The SDK abstracts all of these behind `raydium.farm.deposit({ ... })` etc. The sections below document the underlying account lists for integrators who need to build instructions manually (aggregators, monitoring tools, SDK extensions). ## `CreateFarm` (v6) Spin up a new v6 farm. **Arguments** ``` reward_info_count: u8 // number of reward streams at creation (1..=5) reward_infos: [ { open_time: u64, end_time: u64, emission_per_second_x64: u128, // Q64.64 mint: Pubkey, // reward mint token_program: Pubkey, // SPL or Token-2022 } ] ``` **Accounts** (abridged, for `reward_info_count = 1`) | # | Name | W | S | Notes | | -- | -------------------------- | - | - | -------------------------------------------------------------- | | 1 | `creator` | W | S | Pays rent, owns the farm. | | 2 | `farm_state` | W | | New `FarmState` account. | | 3 | `farm_authority` | | | PDA `[farm_id]`. | | 4 | `staking_mint` | | | | | 5 | `staking_vault` | W | | Created as the authority's ATA or a PDA vault. | | 6 | `staking_token_program` | | | | | 7 | `reward_mint` | | | | | 8 | `reward_vault` | W | | Will receive the initial budget. | | 9 | `reward_token_program` | | | | | 10 | `reward_sender_ata` | W | | Creator's ATA on the reward mint; drained by this instruction. | | 11 | `system_program` | | | | | 12 | `token_program` | | | | | 13 | `associated_token_program` | | | | | 14 | `rent` | | | | **Preconditions** * `open_time > now`, `end_time > open_time`. * `creator` ATAs hold at least `emission_per_second_x64 × (end_time − open_time) / 2^64` of the reward mint. * `staking_mint` has no freeze authority, or freeze authority is disabled. **Postconditions** * `FarmState` initialized, `total_staked = 0`. * Reward vault(s) funded with the full stream budget. * The creator's reward ATA is drained by that amount. ## `Deposit` (v6) Stake `amount` of the staking mint. **Arguments** ``` amount: u64 ``` **Accounts** | # | Name | W | S | | -------- | -------------------------- | - | - | | 1 | `user` | W | S | | 2 | `user_ledger` | W | | | 3 | `farm_state` | W | | | 4 | `farm_authority` | | | | 5 | `staking_vault` | W | | | 6 | `user_staking_ata` | W | | | 7..(7+n) | `reward_vault_{i}` | W | | | ... | `user_reward_ata_{i}` | W | | | last−2 | `system_program` | | | | last−1 | `token_program` | | | | last | `associated_token_program` | | | If the `user_ledger` does not exist, the SDK prepends a `CreateAccount`-style ix; the v6 program can also lazily create it given the system program account. Remaining accounts pattern: for each live reward, append `(reward_vault, user_reward_ata)` so the settlement can pay out. **Effect** 1. Refresh `reward_per_share_x64[i]` for each live reward stream using the lazy update formula. 2. Compute `pending_i = user_ledger.deposited × reward_per_share_x64[i] / 2^64 − user_ledger.reward_debts[i]`. 3. Transfer `pending_i` from `reward_vault_{i}` to `user_reward_ata_{i}`. 4. Transfer `amount` staking mint from `user_staking_ata` to `staking_vault`. 5. Update `user_ledger.deposited += amount` and re-snapshot `reward_debts[i]`. 6. Update `farm_state.total_staked += amount`. **Preconditions** * `amount > 0` for a true stake (v6 forbids `amount = 0` — use `Harvest` for claim-only). * `user_staking_ata` holds at least `amount`. * Each live reward vault holds at least the pending owed to this user. ## `Withdraw` (v6) Unstake `amount`. **Arguments** ``` amount: u64 ``` **Accounts** — identical to `Deposit`. **Effect** — same settlement as `Deposit`, then move staking mint back to the user: `staking_vault → user_staking_ata`. `total_staked` and `user_ledger.deposited` both decrease. **Preconditions** * `amount ≤ user_ledger.deposited`. * Farm is not paused. ## `Harvest` (v6) Claim pending rewards without changing stake. **Arguments** — none. **Accounts** — same as `Deposit`, no staking-side movement. **Effect** — refresh `reward_per_share_x64[i]`, pay out `pending_i`, re-snapshot `reward_debts[i]`. No change to `total_staked` or `deposited`. ## `AddReward` (v5/v6) Add a new reward stream to an existing farm that has an unused slot. **Arguments** ``` reward_info: { open_time: u64, end_time: u64, emission_per_second_x64: u128, mint: Pubkey, token_program: Pubkey, } ``` **Preconditions** * A free slot exists (`reward_info_count < 5` on v6, `< 2` on v5). * `open_time ≥ now` (may be in the future) or `open_time < now` is allowed only if the program version permits it — v6 does, v5 does not. **Postconditions** * The new stream is initialized at index `reward_info_count`, `reward_info_count++`. * The reward vault is credited with the full stream budget from the caller's ATA. **Common error** — `RewardAlreadyExists` if the mint collides with an existing slot. ## `SetRewards` (v5/v6) Extend or top up an existing reward stream. Cannot change the mint; cannot shorten `end_time`; cannot lower `emission_per_second_x64` once running. **Arguments** ``` reward_index: u8 new_open_time: u64, new_end_time: u64, new_emission_per_second_x64: u128, ``` **Preconditions** * The stream is still running (`reward_state == 1`). * `new_end_time ≥ current end_time`. * The additional budget required `(new_emission × new_duration − already_emissioned)` is present in the sender's ATA and is transferred to the reward vault by the instruction. On v5, the equivalent call is `SetRewards` with a smaller argument set (no per-second changes on live streams). ## `RestartRewards` (v5/v6) Restart a stream after its `end_time` has passed. Conceptually the same as `AddReward` for a mint that already has a slot. **Arguments** — identical shape to `AddReward` at that index. **Preconditions** * `reward_state == 2` (ended). * Caller is `reward_sender` of the slot (v6) or farm `owner` (v5). ## `WithdrawReward` (v5/v6) Admin sweep of unclaimed reward vault balance after a stream has ended and all stakers have had a chance to harvest. **Arguments** ``` reward_index: u8 ``` **Preconditions** * Stream is ended (`reward_state == 2`). * `reward_total_emissioned == reward_claimed + vault_balance` (nothing is currently owed). **Effect** — moves the remainder to `reward_sender_ata`. The program does not prevent withdrawing while stakers still have pending claims; the admin is expected to harvest on behalf of lagging stakers first (or let them harvest). If you sweep early, users lose access to their unclaimed rewards. **Do not call this early.** ## v5 variations * `Deposit` / `Withdraw` have the same shape as v6 but use up to 2 reward slots and `reward_per_share` is `u128` (fixed-point with a different radix). * `CreateAssociatedLedger` is a required separate call before the first `Deposit`; v6 merged it. * `AddReward` is available, `Harvest` is not (use `Deposit 0`). ## v3 variations * Single reward stream. No `AddReward`, no second slot. * `Deposit 0` is the only way to claim. * `CreateUserLedger` must be called before the first `Deposit`. ## State-change matrix | Instruction | `total_staked` | `user.deposited` | `reward_per_share` | Reward vaults | | ---------------- | -------------- | ---------------- | ------------------ | ------------------- | | `CreateFarm` | 0 | — | 0 | funded by creator | | `Deposit(n)` | +n | +n | refreshed | −pending (paid out) | | `Withdraw(n)` | −n | −n | refreshed | −pending | | `Harvest` | — | — | refreshed | −pending | | `AddReward` | — | — | — | +new budget | | `SetRewards` | — | — | — | +delta budget | | `RestartRewards` | — | — | — | +budget | | `WithdrawReward` | — | — | — | −remainder | ## Where to go next * [`products/farm-staking/code-demos`](/products/farm-staking/code-demos) — TypeScript examples. * [`products/farm-staking/accounts`](/products/farm-staking/accounts) — `FarmState` / `UserLedger` / `UserStake` layouts. * [`reference/error-codes`](/reference/error-codes) — farm error enum. Sources: * [Raydium SDK v2 `Farm` module](https://github.com/raydium-io/raydium-sdk-V2) * Raydium farm program source (v3 / v5 / v6) # Farm / Staking overview Source: https://docs.raydium.io/products/farm-staking/overview How Raydium's farm program distributes reward tokens to stakers — the per-share accounting, the farm-version generations (v3, v5, v6), and how farms relate to but are distinct from the AMM / CLMM pools they sit alongside. ## What a farm is A **farm** is a standalone on-chain program that distributes one or more **reward mints** to accounts that stake a **staking mint**. The staking mint is almost always an LP token issued by CPMM, AMM v4, or a legacy pair pool, but single-asset farms (staking SOL, RAY, or a project token directly) are supported and used for a small number of long-running programs. Key properties: * **Emission-based, not fee-based.** Unlike CLMM's built-in reward streams, farm rewards are not tied to swap volume or pool activity. A farm's budget is deposited up front by the creator and emits at a constant per-second rate until it runs out. * **Independent of the pool.** The pool does not know the farm exists. Moving LP between wallets does not notify the farm; a user must actively `Withdraw` from the farm first. Likewise, `Withdraw` from the pool does not withdraw from the farm. * **Per-user, per-reward ledger.** Each staker has a `UserStake` (or "Ledger") account per farm that tracks their staked amount and their snapshot of the reward-per-share counter for each of the farm's reward streams. * **Multi-reward.** Farm v5 supports up to 2 rewards; v6 supports up to 5. Each reward has its own vault, per-second rate, start time, and end time. ## The three live versions Raydium has shipped three farm program versions. All are live, and each carries its own PDA scheme and instruction set. Integrators should treat them as three distinct programs that share a conceptual model. | Version | Program ID location | Max rewards | Notable differences | | ------- | ----------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------- | | **v3** | see [`reference/program-addresses`](/reference/program-addresses) | 1 | Earliest schema. Oldest farms (RAY-USDC, SOL-USDC) still route through here. | | **v5** | see [`reference/program-addresses`](/reference/program-addresses) | 2 | Added second reward slot and `AddReward`-style top-ups. | | **v6** | see [`reference/program-addresses`](/reference/program-addresses) | 5 | Current version. Expanded slots, cleaner admin, supports CLMM-position farming via a v6-specific adapter (rare in practice). | The SDK exposes `raydium.farm` as a single facade — version is inferred from the farm account's owner. When building on-chain integrations you must dispatch manually. ## Reward-per-share accounting The farm program uses the standard "master-chef" pattern seen across DeFi yield contracts: ``` reward_per_share := total_rewards_distributed_by_stream / total_staked pending(user) := user.staked × (reward_per_share − user.reward_debt) ``` * **`reward_per_share`** is stored on the farm account as a fixed-point counter (Q64.64 in v5+, Q56.8 in v3). It only grows. * **`user.reward_debt`** is the snapshot of `reward_per_share` at the user's last interaction. It is not a debt the user owes; it is an offset used to compute future accruals. * On `Deposit` and `Withdraw`, the farm first settles pending rewards (crediting `user.pending_reward` or sending directly to the user's ATA, depending on version), then updates `user.reward_debt` to the current counter. * On `Harvest`, the farm pays out `pending_reward` and snapshots `reward_debt` again. The per-second emission rate enters the accounting via a lazy update: ``` elapsed := min(now, reward.end_time) − reward.last_update_time new_emissions := reward.per_second × elapsed if total_staked > 0: reward_per_share += new_emissions / total_staked reward.last_update_time := now ``` Lazy: no instruction is emitted "every second." The counter is refreshed every time anyone touches the farm (`Deposit`, `Withdraw`, `Harvest`, admin update). Farms with no activity accrue a growing gap that is closed on the next interaction. ## Staking mint vs reward mint **The staking mint is held in escrow, not burned.** When a user stakes 100 LP, the farm moves 100 LP from the user's ATA into the farm's staking vault. On `Withdraw`, the farm moves 100 LP back. The farm never calls the pool. **Reward mints are paid out from vaults pre-funded by the creator.** When a creator spins up a farm, they deposit the full reward budget (say, 1,000,000 RAY + 500,000 USDC) into the two reward vaults. The farm program does not mint new tokens; it simply distributes what is in the vault over the stream's duration. If the vault is drained before `end_time`, emissions halt. ## Emission schedules Each reward stream has three time parameters: * **`start_time`** — the UNIX timestamp at which emissions begin. Before this, no accrual. * **`end_time`** — the timestamp at which emissions stop. After this, `reward_per_share` no longer grows from this stream. * **`per_second`** — the emission rate while `start_time ≤ now < end_time`. A reward can be extended (push `end_time` forward, top up the vault) via `AddReward` / `SetRewards` on v5 / v6. It can be restarted after `end_time` via `RestartRewards`. It cannot be shortened without admin cooperation. ## What farms are not * **Not a fee distributor.** CPMM and CLMM collect trade fees directly into pool state. Farms do not touch pool fees. The only path from pool fees to a token holder is LP-redemption or CLMM's `CollectFee`. * **Not automatic.** LP must be explicitly staked to earn farm rewards. LP holders who leave their tokens in their wallet earn nothing from the farm. * **Not fungible.** Each `UserStake` account is tied to one `(farm, user)` pair. You cannot transfer your stake to another wallet without unstaking first. * **Not compatible with CLMM positions directly.** Farm v6 introduced a CLMM adapter, but in practice CLMM pools use their own built-in reward streams (see [`products/clmm/fees`](/products/clmm/fees)) rather than farm emissions. ## When farms are the right tool Use a farm when you want to: * Incentivize LP for one of your project's CPMM or AMM v4 pools with an external token (your project token, a partner's token, etc.). * Run a staking program on a single-asset mint (classic "stake RAY, earn RAY") without deploying your own contract. * Layer additional rewards on top of an existing pool without needing admin access to that pool. Use CLMM's built-in reward streams instead when your pool is CLMM. They have identical economics but participate in the position's fee-growth-inside accounting (in-range positions earn pro rata, out-of-range positions do not) and do not require users to move their position NFT. ## Chapter contents * [`accounts`](/products/farm-staking/accounts) — full on-chain state layout per version. * [`instructions`](/products/farm-staking/instructions) — every farm instruction with its account list and pre/postconditions. * [`code-demos`](/products/farm-staking/code-demos) — TypeScript examples for staking, harvesting, and creating a new farm. ## Where to go next * [`products/clmm/fees`](/products/clmm/fees) — compare farm emissions to CLMM's built-in reward model. * [`reference/program-addresses`](/reference/program-addresses) — the three farm program IDs. * [`protocol-overview/versions-and-migration`](/protocol-overview/versions-and-migration) — when to use each farm version. Sources: * Farm v6 IDL bundled in the SDK: [`raydium-io/raydium-sdk-V2`](https://github.com/raydium-io/raydium-sdk-V2) under `src/raydium/farm/`. * [Raydium SDK v2 `Farm` module](https://github.com/raydium-io/raydium-sdk-V2) # Products Source: https://docs.raydium.io/products/index Quick comparison of Raydium product surfaces and when to use each one. Raydium is a set of product surfaces built around independent Solana programs and integrations. Start here to choose the right product, then use the product pages for accounts, math, instructions, fees, and code examples. ## Product comparison | Product | Market type | Token-2022 | OpenBook dependency | When to use | | ------------------ | ------------------------------------------ | ---------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | **AMM v4** | Constant-product (xy=k) | No | Inert (originally yes; OpenBook integration deactivated) | Existing pools and integrations that depend on the v4 market layout. New pools default to CPMM. | | **CPMM** | Constant-product (xy=k) | Yes | No | New constant-product pools. Supports transfer-fee tokens. Lower gas than v4. | | **CLMM** | Concentrated liquidity (sqrt price, ticks) | Yes | No | Stable pairs, tight-range market making, capital-efficient LP positions. | | **Farm / Staking** | Reward distribution | Partial | No | Incentivizing LP positions or single-asset staking. | | **Perps** | Perpetual futures | N/A | No | Leveraged trading through Raydium Perps, powered by Orderly Network. | | **Stable AMM** | Stablecoin-correlated AMM | No | Inert AMM v4-style wiring | Existing stable pools and integrations; not the default for new pools. | | **AMM Routing** | Multi-pool routing | Depends on routed pool | Depends on routed pool | Composing multiple Raydium AMM pools in one route. | LaunchLab has its own `Launch tools` tab because token launches need a dedicated user and builder flow. ## Product references The original Raydium constant-product AMM. Originally hybrid against OpenBook; that integration has been deactivated, so it operates today as a pure AMM. Pure constant-product AMM, no orderbook. Supports Token-2022 transfer fees. Recommended for new constant-product pools. Concentrated-liquidity AMM. Liquidity is provided into price ranges; positions earn fees only when price is in range. Reward distribution program for LP tokens and single-asset staking. Supports multiple reward mints and custom schedules. Perpetual futures: leverage, order types, collateral, funding, and API integration notes. Stablecoin-correlated pools and legacy Stable AMM account/instruction references. Multi-pool routing account, math, instruction, fee, and code references. # LaunchLab accounts Source: https://docs.raydium.io/products/launchlab/accounts PoolState (per-launch root), the base and quote vaults, the authority PDA, and the small graph of per-launch accounts. Protocol-level GlobalConfig and platform-level PlatformConfig live on their own pages. This page documents the **per-launch** account graph: the `PoolState` (the root state account for one launch), its two vaults, the authority PDA, and the post-graduation references it gains when the launch settles. For the protocol-level config that bounds every launch, see [`products/launchlab/global-config`](/products/launchlab/global-config). For the per-platform overlay, see [`products/launchlab/platform-config`](/products/launchlab/platform-config). For vesting accounts (`VestingSchedule` on `PoolState`, `VestingRecord` per beneficiary), see [`products/launchlab/vesting`](/products/launchlab/vesting). ## Account inventory | Account | Owner | Purpose | | --------------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------- | | [`GlobalConfig`](/products/launchlab/global-config) | LaunchLab program | Protocol-level rules: fees, supply floors, migration wallets. One per `(curve_type, index)`. | | [`PlatformConfig`](/products/launchlab/platform-config) | LaunchLab program | Per-platform overlay: branding, platform fee, NFT split at graduation, curve-shape whitelist. | | `PoolState` | LaunchLab program | Per-launch root state: mints, vaults, curve params, sold counters, fee counters, vesting schedule, graduation status. | | `authority` | LaunchLab program | Single PDA at seed `[b"vault_auth_seed"]` that owns vaults across all launches and signs the post-graduation CPI. | | `base_vault` | SPL Token / Token-2022 | Per-launch vault holding the unsold base tokens. | | `quote_vault` | SPL Token | Per-launch vault holding accumulated quote tokens. | | [`VestingRecord`](/products/launchlab/vesting) | LaunchLab program | Per-beneficiary cliff + linear-unlock record. Optional. | | `creator_fee_vault` | SPL Token | Per-creator + per-quote-mint vault holding accrued creator fees, swept by `ClaimCreatorFee`. | | `platform_fee_vault` | SPL Token | Per-platform + per-quote-mint vault holding accrued platform fees, swept by `ClaimPlatformFeeFromVault`. | | (post-graduation) `cpmm_pool_state` *or* `amm_pool_state` | CPMM / AMM v4 program | The pool created by `MigrateToCpswap` / `MigrateToAmm`. | | (post-graduation) Fee Key NFT | LP-Lock program | Wraps the creator's slice of LP at CPMM graduation; entitles the holder to `ClaimCreatorFee` on the CPMM pool. | The SDK's `raydium.launchpad.getLaunchById` returns `PoolState` plus a flag indicating whether the launch has graduated; if it has, the post-migration pool ID is included. ## `PoolState` The per-launch root state. Field names below match the on-chain Rust struct (`states/pool.rs`); some values are simplified for readability — consult the source for the exact memory layout. ```rust theme={null} // states/pool.rs (abridged) pub struct PoolState { pub epoch: u64, // recent_epoch tracker pub auth_bump: u8, pub status: u8, // PoolStatus enum pub base_decimals: u8, pub quote_decimals: u8, pub migrate_type: u8, // 0 = AMM v4, 1 = CPMM pub supply: u64, // total base tokens (no decimals) pub total_base_sell: u64, // base supply allocated to the curve pub virtual_base: u64, // virtual-reserve seed pub virtual_quote: u64, // virtual-reserve seed pub real_base: u64, // base sold so far pub real_quote: u64, // quote raised so far (excl. fees) pub total_quote_fund_raising: u64, // graduation threshold in quote pub quote_protocol_fee: u64, // accrued, pending CollectFee pub platform_fee: u64, // accrued, pending ClaimPlatformFee pub migrate_fee: u64, // accrued, pending CollectMigrateFee pub vesting_schedule: VestingSchedule, pub global_config: Pubkey, pub platform_config: Pubkey, pub creator: Pubkey, pub base_mint: Pubkey, pub quote_mint: Pubkey, pub base_vault: Pubkey, pub quote_vault: Pubkey, pub amm_creator_fee_on: u8, // 0 = QuoteToken, 1 = BothToken (CPMM-only) pub token_program_flag: u8, // bit flags for SPL Token vs Token-2022 pub padding: [u8; 54], } #[derive(Default, Debug, Clone)] pub struct VestingSchedule { pub total_locked_amount: u64, pub cliff_period: u64, pub unlock_period: u64, pub start_time: u64, // set at graduation pub allocated_share_amount: u64, // running sum across VestingRecords } ``` `PoolStatus` values (from the Anchor IDL): ``` 0 = Funding — accepting buys / sells; below quote_reserve_target 1 = Migrate — quote_reserve_target reached; awaiting MigrateTo* 2 = Migrated — MigrateTo* already executed; vesting clock running ``` Integrator-facing fields: * **`status`** — three values, monotone (Funding → Migrate → Migrated). Reads always safe; writes gated. * **`real_base`**, **`real_quote`** — current curve state. Combined with `virtual_base` / `virtual_quote` they are sufficient to compute spot price without touching the vaults. See [`bonding-curve`](/products/launchlab/bonding-curve). * **`total_base_sell`** vs `real_base` — "progress toward graduation" ratio for UIs. * **`migrate_type`** — selects whether `MigrateToAmm` or `MigrateToCpswap` is the valid graduation path. Token-2022 launches must use CPMM. * **`amm_creator_fee_on`** — only meaningful when graduating to CPMM. Picks `creator_fee_on = OnlyQuoteToken` (`0`) or `BothToken` (`1`) on the post-graduation CPMM pool. *Despite the name, this enum effectively also drives the migration target — `BothToken` is paired with `MigrateToAmm` in current operational practice; `QuoteToken` with `MigrateToCpswap`.* See [`creator-fees`](/products/launchlab/creator-fees). * **`quote_protocol_fee` / `platform_fee` / `migrate_fee`** — three independent fee counters. Each has its own claim instruction; see [`instructions`](/products/launchlab/instructions). * **`vesting_schedule`** — present on every `PoolState` but inactive when `total_locked_amount == 0`. See [`vesting`](/products/launchlab/vesting) for the full lifecycle. ## The authority PDA LaunchLab uses a **single** authority PDA across all launches, derived with no per-launch seed: ```ts theme={null} const LAUNCHLAB_PROGRAM_ID = new PublicKey(""); // see reference/program-addresses const [authority, bump] = PublicKey.findProgramAddressSync( [Buffer.from("vault_auth_seed")], LAUNCHLAB_PROGRAM_ID, ); ``` That single PDA is: * The authority on every launch's `base_vault` and `quote_vault`. * The `mint_authority` on each launch's `base_mint` (pre-graduation). * The signer on the post-graduation CPI to AMM v4 / CPMM (`MigrateTo*`). * The signer on `ClaimVestedToken` transfers out of the base vault. The `mint_authority` is revoked immediately after `MigrateToAmm` / `MigrateToCpswap` so the supply is permanently fixed. Two additional PDAs gate the fee vaults: ```ts theme={null} const [creatorFeeVaultAuthority] = PublicKey.findProgramAddressSync( [Buffer.from("creator_fee_vault_auth_seed")], LAUNCHLAB_PROGRAM_ID, ); const [platformFeeVaultAuthority] = PublicKey.findProgramAddressSync( [Buffer.from("platform_fee_vault_auth_seed")], LAUNCHLAB_PROGRAM_ID, ); ``` These sign the transfer out of the corresponding fee vaults during `ClaimCreatorFee` and `ClaimPlatformFeeFromVault`. ## Base mint Created inline by `Initialize` with: * `mint_authority = authority` (revoked at graduation). * `freeze_authority = None`. * `supply = supply`, entirely minted into `base_vault`. * `decimals` chosen by the creator at `Initialize` (commonly 6). Because the full supply is pre-minted, `base_mint.supply` is constant for the life of the launch. Curve buys move tokens from `base_vault` to the buyer, but do not call `mint_to`. `Initialize` / `InitializeV2` create SPL Token launches. The dedicated `InitializeWithToken2022` instruction lets the base mint be a Token-2022 mint (with optional `TransferFeeConfig`); the quote mint is still SPL Token. Token-2022 launches must graduate to a CPMM pool because AMM v4 only supports SPL Token vaults. ## Vaults Both `base_vault` and `quote_vault` are standard SPL Token accounts owned by the LaunchLab `authority` PDA. Addresses are stored on `PoolState` and can also be derived: ```ts theme={null} const [baseVault] = PublicKey.findProgramAddressSync( [Buffer.from("pool_vault"), poolState.toBuffer(), baseMint.toBuffer()], LAUNCHLAB_PROGRAM_ID, ); const [quoteVault] = PublicKey.findProgramAddressSync( [Buffer.from("pool_vault"), poolState.toBuffer(), quoteMint.toBuffer()], LAUNCHLAB_PROGRAM_ID, ); ``` (Verify the exact seed prefixes from the source's `Initialize` accounts struct before relying on a derivation in production.) ## Fee vaults Two PDAs aggregate fees across launches: * **Creator fee vault** — PDA at seeds `[creator, quote_mint]`. Every launch that earns the same creator fees on the same quote mint pours into the same vault. The creator sweeps it via `ClaimCreatorFee`. * **Platform fee vault** — PDA at seeds `[platform_config, quote_mint]`. Every launch routed through the same platform that uses the same quote mint pours into the same vault. The platform's `platform_fee_wallet` sweeps it via `ClaimPlatformFeeFromVault`. There is also a per-launch sweep variant (`ClaimPlatformFee`) that pulls from the launch's `quote_vault` directly without going through the aggregated vault. The aggregated-vault pattern is what lets a high-volume creator or platform amortize the rent cost of fee accumulation across many launches. ## Quote vault ↔ `real_quote` `quote_vault.balance` and `PoolState.real_quote` should stay in sync. They can drift by at most the sum of the three pending fee counters (`quote_protocol_fee`, `platform_fee`, `migrate_fee`), which sit in the vault but belong to the fee counters and not the curve reserve. The curve math always uses `real_quote`, never the raw vault balance. Pre-graduation invariant: ``` quote_vault.balance == real_quote + quote_protocol_fee + platform_fee + migrate_fee ``` ## Lifecycle account transitions | Event | Status | `real_base` | `real_quote` | Post-graduation pool | | --------------------------------------- | ---------------------------- | ----------- | ----------------------- | ----------------------------------------------- | | `Initialize` | Funding | 0 | 0 | — | | `BuyExactIn` / `BuyExactOut` | Funding | `+base_out` | `+quote_in_after_fee` | — | | `SellExactIn` / `SellExactOut` | Funding | `−base_in` | `−quote_out_before_fee` | — | | `MigrateToAmm` / `MigrateToCpswap` | Funding → Migrate → Migrated | (frozen) | (frozen) | created, LP split per `PlatformConfig` | | `ClaimCreatorFee` / `ClaimPlatformFee*` | any | — | — | drains a fee vault | | `CreateVestingAccount` | Funding | — | — | bumps `vesting_schedule.allocated_share_amount` | | `ClaimVestedToken` | Migrated only | — | — | drains `base_vault` | ## Where to go next * [`products/launchlab/bonding-curve`](/products/launchlab/bonding-curve) — the math behind `real_base` ↔ `real_quote`. * [`products/launchlab/instructions`](/products/launchlab/instructions) — per-instruction account lists. * [`products/launchlab/global-config`](/products/launchlab/global-config) — protocol-level binding. * [`products/launchlab/platform-config`](/products/launchlab/platform-config) — platform overlay. * [`products/launchlab/vesting`](/products/launchlab/vesting) — locked supply mechanics. * [`products/cpmm/accounts`](/products/cpmm/accounts) — what the post-graduation `cpmm_pool_state` looks like. Sources: * `raydium-launch/programs/launchpad/src/states/pool.rs` — `PoolState`, `PoolStatus`, `VestingSchedule`, `AmmCreatorFeeOn`. * `raydium-launch/programs/launchpad/src/lib.rs` — PDA seed constants (`AUTH_SEED`, `CREATOR_FEE_VAULT_AUTH_SEED`, `PLATFORM_FEE_VAULT_AUTH_SEED`). * [Raydium SDK v2 `launchpad` module](https://github.com/raydium-io/raydium-sdk-V2). # LaunchLab bonding curve Source: https://docs.raydium.io/products/launchlab/bonding-curve The curve formulas LaunchLab supports, closed-form buy and sell cost, graduation threshold derivation, and a worked numeric walk-through of a launch from first buy to graduation. LaunchLab supports three curve shapes selected at `Initialize`: **constant-product** (the most common, virtual-reserve form of the standard `x · y = k` curve), **linear-price**, and **fixed-price**. The graduation threshold formula is shared across all three. This page walks through the constant-product math in detail; the linear and fixed forms are summarised at the end. ## Parameters stored on `LaunchState` | Field | Meaning | | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `curve_type` | `0` = constant-product (virtual-reserves), `1` = fixed-price, `2` = linear-price. | | `base_supply_max` | Total base tokens the curve can ever mint. | | `base_supply_graduation` | Base tokens that must be sold to reach graduation. Usually `0.8 × base_supply_max`; the remaining 20% becomes the post-graduation pool's initial LP. | | `quote_reserve_target` | Quote amount that triggers graduation. Derived at `Initialize` from the curve params + `base_supply_graduation`. | | `virtual_base` / `virtual_quote` | Virtual-reserve seeds for the constant-product curve. | | `migrate_type` | Selects the graduation target: AMM v4 vs CPMM. See [`instructions`](/products/launchlab/instructions). | | `fees.buy_numerator / buy_denominator` | Buy-side fee, e.g. `100 / 10_000 = 1.00%`. | | `fees.sell_numerator / sell_denominator` | Sell-side fee. Often same as buy. | | `fees.protocol_share`, `fees.creator_share`, `fees.lp_share` | Split of the above, summing to denominator. | Field names in the Rust struct match the `PoolState` fields described in [`accounts`](/products/launchlab/accounts); units above are conceptual. ## Constant-product curve with virtual reserves (`curve_type = 0`) The default and most-used curve. Pump-style launches all use this form. The curve pretends there is a **virtual quote reserve** `V_q` and a **virtual base reserve** `V_b` from the start (stored as `virtual_quote` and `virtual_base` on `PoolState`), so the effective pool looks like a CPMM with those reserves. Buys follow `x · y = k` math: ``` (V_q + real_quote_in_after_fee) × (V_b + real_base_remaining − base_out) = V_q × V_b ``` solved for `base_out`: ``` base_out = (V_b + real_base_remaining) × quote_in_after_fee / (V_q + real_quote_in_after_fee) ``` Effective price at base-sold `s`: ``` price(s) = (V_q + real_quote_in(s)) / (V_b + real_base_remaining(s)) ``` The same `x · y = k` invariant LaunchLab applies pre-graduation is then literally the CPMM (or AMM v4) curve post-graduation, so the graduation handoff is mechanically seamless: the marginal price at `base_sold = base_supply_graduation` equals the price the post-graduation pool opens at with `(quote_vault, base_vault_remaining)` as its reserves. ## Fixed-price curve (`curve_type = 1`) A flat-price curve. Every buy/sell happens at a constant price, configurable at `Initialize`: ``` price(s) = virtual_quote / virtual_base (constant for all s) ``` Useful for fair launches where the team wants uniform pricing for all participants regardless of when they buy. Graduation triggers when `base_supply_graduation` has been sold (the linear-cost relationship makes `quote_reserve_target` straightforward to derive). ## Linear-price curve (`curve_type = 2`) Price increases linearly with `base_sold`: ``` price(s) = a · s (a = slope, derived from virtual_base / virtual_quote) ``` Integrated cost: ``` cost(s_0, s_1) = a · (s_1² − s_0²) / 2 ``` Quadratic in `base_sold` — early buyers pay close to zero, late buyers pay substantially more, with the marginal price always rising at a fixed slope. The on-chain implementation lives in `curve/linear_price.rs`. ## Curve-shape comparison ``` price │ linear (steep tail) linear (curve_type = 2) │ ╱ │ ╱ │ ╱ const-product (curve_type = 0) │ ╱ ╱ │ ╱ ╱ │ ╱ ╱ │ ╱ ╱ │╱_____╱_______________________ fixed-price (curve_type = 1) └──────────────────────────────── base_sold 0 S_grad S_max ``` ## Graduation threshold `quote_reserve_target` is computed at `Initialize` as the quote required to drive `base_sold` from 0 to `base_supply_graduation`: ``` quote_reserve_target = cost(0, base_supply_graduation) × (1 + buy_fee_rate) ^^^^^^^^^^^^^^^^^ approximate; exact form matches the fee rounding used on Buy. ``` A launch graduates as soon as `quote_vault.balance ≥ quote_reserve_target`. Because buys come in at discrete sizes, the actual balance at graduation can slightly exceed the target — the surplus becomes extra quote-side liquidity in the resulting CPMM pool. ## Worked example — a quadratic launch Parameters: * `base_supply_max = 1_000_000_000` (1 billion base tokens, 6 decimals) * `base_supply_graduation = 800_000_000` (80% sold triggers graduation) * `k = 40` (price scale) * Fees: 1% buy, 1% sell, split `lp:creator:protocol = 60:20:20`. **Initial price** (`s = 0`): `0` (pure quadratic starts at zero). **Price at 50% sold** (`s = 500_000_000`): ``` price = 40 × (500e6 / 1e9)² = 40 × 0.25 = 10 (quote per base, 6 decimals) ``` **Price at graduation** (`s = 800_000_000`): ``` price = 40 × (800e6 / 1e9)² = 40 × 0.64 = 25.6 ``` **Quote required to reach graduation** (integrated cost): ``` cost(0, 800_000_000) = (40 / (3 × 1e18)) × ((800e6)³ − 0) = (40 / 3e18) × 5.12e26 ≈ 6.827e9 ``` So ≈ 6.827 quote-native units (in whichever 6-decimal quote mint is configured, e.g. \~6,827 USDC if the quote is USDC). **Fee applied on top**: ``` quote_reserve_target ≈ 6.827e9 × 1.01 ≈ 6.895e9 (6,895 USDC) ``` **First buy** of `10 USDC`: * Virtual state: `s = 0`, `quote_vault = 0`. * Subtract fee: `quote_after_fee = 10 × 0.99 = 9.9`. * Solve `(40 / (3e18)) × s³ = 9.9` ⇒ `s ≈ 6.22e6` base tokens bought. * 1% fee (`0.1 USDC`) split: lp `0.06`, creator `0.02`, protocol `0.02`. The lp share stays in `quote_vault`; the other two route to their respective accrual counters. **Buy at 75% sold** (approaching graduation): Same 10 USDC buys far less base now because the curve is steep. A Newton solve at `s₀ = 750e6` with `quote_in_after_fee = 9.9` gives roughly `∆s ≈ 0.4e6` — a \~15× reduction in base per USDC compared to the first buy. ## Fee mechanics during the curve phase On every `Buy`: ``` gross_fee = ceil(quote_in_gross × buy_numerator / buy_denominator) lp_share = gross_fee × fees.lp_share / fees.total_share protocol_share = gross_fee × fees.protocol_share / fees.total_share creator_share = gross_fee × fees.creator_share / fees.total_share ``` * `lp_share` is left in `quote_vault`. This is what makes the effective curve tighter (more quote reserve against the same base supply). * `protocol_share` increments `LaunchState.state_data.protocol_fees_quote`. * `creator_share` increments `LaunchState.state_data.creator_fees_quote`. On `Sell` the same split applies but the fee is taken from the outbound `quote_out`. Both counters are swept via `CollectFees` (admin or creator, each to their own counter). ## Precision * Base-side amounts: `u64`. * Quote-side amounts: `u64`. * Intermediate cubes / products: `u128`. * Newton solves for "buy exact quote" and "sell exact quote" iterate in `u128` fixed-point with a configurable max iteration count (default 10). Failure mode is `NotConverged` — rare outside of near-graduation edge cases. ## Handoff to CPMM When `Graduate` fires: ``` cpmm_quote_reserve = quote_vault − swept_protocol_fees − swept_creator_fees cpmm_base_reserve = base_vault // i.e. base_supply_max − base_sold cpmm_initial_price = cpmm_quote_reserve / cpmm_base_reserve ``` For the quadratic curve, `cpmm_initial_price` is `price(base_sold)` mechanically (it is the marginal curve price at the moment of handoff). The CPMM pool opens at exactly that price, so an observer switching from the curve UI to the CPMM UI sees no jump. ## Where to go next * [`products/launchlab/accounts`](/products/launchlab/accounts) — the `LaunchState` fields storing these parameters. * [`products/launchlab/instructions`](/products/launchlab/instructions) — `Buy`, `Sell`, `Graduate` account lists. * [`algorithms/constant-product`](/algorithms/constant-product) — the CPMM math the post-graduation pool uses. Sources: * [Raydium SDK v2 `LaunchLab` module](https://github.com/raydium-io/raydium-sdk-V2) * Raydium LaunchLab program source # LaunchLab GlobalConfig Source: https://docs.raydium.io/products/launchlab/global-config Protocol-level configuration accounts that bound every LaunchLab launch — fee rates, supply minimums, lock-rate ceilings, the migration wallets that gate graduation, and the per-curve indexing scheme. `GlobalConfig` is the **protocol-level** configuration account. There can be many of them — keyed by `(curve_type, index)` — and every launch picks exactly one at `Initialize` time. The values on the chosen `GlobalConfig` then become hard limits for that launch's parameters. For platform-level customization (per-platform fee rate, allowed curve shapes, NFT splits at graduation), see [`products/launchlab/platform-config`](/products/launchlab/platform-config). ## What it is A `GlobalConfig` is a singleton-per-`(curve_type, index)` PDA that fixes the protocol-side rules every launch must obey: * The kind of bonding curve allowed (`curve_type`). * The trade fee rate the curve charges on every buy and sell. * The migration fee charged at graduation. * Floors on supply, lock rate, sell rate, and migrate rate. * The quote mint (typically wrapped SOL or USDC) — the asset users buy with. * Three protocol-side wallets: the protocol-fee owner, the migrate-fee owner, and the two migration-control wallets that authorize graduation to AMM v4 / CPMM. * An optional flag (`requires_platform_auth`) that gates which platforms may use this config. `GlobalConfig` accounts are created and updated by the LaunchLab program admin (the hardcoded admin pubkey shared across Anchor-based Raydium programs — see [`reference/program-addresses`](/reference/program-addresses)). ## Layout ```rust theme={null} // states/config.rs pub const GLOBAL_CONFIG_SEED: &str = "global_config"; #[account] pub struct GlobalConfig { pub epoch: u64, // recent_epoch tracker pub curve_type: u8, // 0 = ConstantProduct, 1 = FixedPrice, 2 = LinearPrice pub index: u16, // discriminator within a curve_type pub migrate_fee: u64, // SOL paid on graduation pub trade_fee_rate: u64, // 1/1_000_000 of volume pub max_share_fee_rate: u64, // 100 bps cap on platform share fee pub min_base_supply: u64, // floor on Initialize supply (no decimals) pub max_lock_rate: u64, // ceiling on vesting locked / supply pub min_base_sell_rate: u64, // floor on base_supply_graduation / supply pub min_base_migrate_rate: u64, // floor on tokens that seed the post-graduation pool pub min_quote_fund_raising: u64, // floor on quote_reserve_target (with decimals) pub quote_mint: Pubkey, pub protocol_fee_owner: Pubkey, // claims protocol fees via CollectFee pub migrate_fee_owner: Pubkey, // claims migration fees via CollectMigrateFee pub migrate_to_amm_wallet: Pubkey, // can sign MigrateToAmm pub migrate_to_cpswap_wallet:Pubkey, // can sign MigrateToCpswap pub requires_platform_auth: u8, // 0/1 flag pub padding_alignment: [u8; 7], pub padding: [u64; 15], } ``` PDA derivation: ```ts theme={null} const [globalConfig] = PublicKey.findProgramAddressSync( [ Buffer.from("global_config"), Buffer.from(quoteMint.toBytes()), Buffer.from([curveType]), u16ToBytes(index), ], LAUNCHLAB_PROGRAM_ID, ); ``` (Verify the exact seed order from the on-chain account before signing — the program's `create_config` instruction is the source of truth.) ## Field semantics ### `curve_type` and `index` Together these key a `GlobalConfig` uniquely. There is one `GlobalConfig` per `(curve_type, index)` pair: * `curve_type = 0` — Constant-product virtual-reserve curve. Default and most-used. * `curve_type = 1` — Fixed-price curve. * `curve_type = 2` — Linear-price curve. `index` is a `u16` that lets the admin publish multiple configurations per curve type (e.g., one with a tighter fee, one with a higher minimum quote raise). At `Initialize` the launch supplies a `(curve_type, index)` pair and the program loads the matching `GlobalConfig`. ### Fees * `trade_fee_rate` — denominated in `1/1_000_000` of trade volume. Applied to every buy and sell on the curve. The `protocol_fee_owner` claims its share via `CollectFee`. * `migrate_fee` — a flat fee in lamports (or quote units, depending on configuration) charged once at graduation. Claimed by `migrate_fee_owner` via `CollectMigrateFee`. * `max_share_fee_rate` — initialized to `10_000` (100 bps). Caps the **platform's** share fee rate from the binding `PlatformConfig` (see [`platform-config`](/products/launchlab/platform-config)). The program enforces `trade_fee_rate + max_share_fee_rate < RATE_DENOMINATOR_VALUE`. ### Supply and rate floors These floors bound what curve-shape parameters a launch can pick at `Initialize`. If the creator's `CurveParams` violate any of them, `Initialize` reverts with `InvalidInput`. * `min_base_supply` — the minimum `supply` (no decimals) the curve can declare. Defaults to `10_000_000`. * `max_lock_rate` — denominated in `1/1_000_000`; default `300_000` (30%). Bounds vesting via `total_locked_amount <= supply * max_lock_rate / 1_000_000`. * `min_base_sell_rate` — denominated in `1/1_000_000`; default `200_000` (20%). Bounds `base_supply_graduation / supply` from below. * `min_base_migrate_rate` — denominated in `1/1_000_000`; default `200_000` (20%). Bounds the number of tokens left over to seed the post-graduation pool. * `min_quote_fund_raising` — minimum `quote_reserve_target` a launch may declare (with decimals). Defaults to `30_000_000_000` units of the quote mint. ### Quote mint and protocol wallets * `quote_mint` — the asset users buy with. Most launches use wrapped SOL (`So111…112`) or USDC (`EPjF…Dt1v`). One `GlobalConfig` is bound to one quote mint; launches that need a different quote target a different `(curve_type, index)`. * `protocol_fee_owner` — pubkey that signs `CollectFee` and claims the accrued protocol fees on every launch bound to this config. Stored on-chain; admin can rotate it via `UpdateConfig`. * `migrate_fee_owner` — pubkey that signs `CollectMigrateFee`. ### Migration wallets The two graduation paths require different signers: * `migrate_to_amm_wallet` — must sign `MigrateToAmm`. Used for launches whose `migrate_type = 0` (graduate to AMM v4 + OpenBook). * `migrate_to_cpswap_wallet` — must sign `MigrateToCpswap`. Used for `migrate_type = 1` (graduate to CPMM). Token-2022 launches always take this path. These are typically held by the Raydium-operated graduation crank, so graduation lands shortly after the curve hits the threshold rather than waiting for the creator to call it. ### `requires_platform_auth` A `u8` flag (`0` = anyone may use this config; non-zero = only platforms with a valid `PlatformGlobalAccess` PDA may launch against it). When set, every `Initialize` against this `GlobalConfig` must include a matching `PlatformGlobalAccess` account proving the platform has been pre-authorized via `CreatePlatformGlobalAccess` (an admin-only instruction). See [`platform-config`](/products/launchlab/platform-config) for the platform-side mechanics. ## Defaults at initialization When `CreateConfig` is called, the program seeds many fields with hardcoded defaults: ```rust theme={null} self.max_share_fee_rate = 10_000; // 100 bps self.min_base_supply = 10_000_000; // no decimals self.max_lock_rate = 300_000; // 30% self.min_base_migrate_rate = 200_000; // 20% self.min_base_sell_rate = 200_000; // 20% self.min_quote_fund_raising = 30_000_000_000; self.requires_platform_auth = 0; ``` The admin sets `curve_type`, `index`, `migrate_fee`, `trade_fee_rate`, `quote_mint`, and the four wallet pubkeys explicitly at create time; subsequent values can be tuned via `UpdateConfig`. ## How a launch picks a `GlobalConfig` At `Initialize`, the creator passes: * `(curve_type, index)` to select which `GlobalConfig` PDA to load. * `CurveParams` describing the curve shape (`supply`, `total_base_sell`, `total_quote_fund_raising`, `migrate_type`). * `VestingParams` describing locked supply. * `MintParams` for the base mint. The program enforces: * `curve_type` matches `global_config.curve_type`. * `supply >= global_config.min_base_supply`. * `total_locked_amount <= supply * max_lock_rate / 1_000_000`. * `total_base_sell >= supply * min_base_sell_rate / 1_000_000`. * `(supply − total_base_sell − total_locked_amount) >= supply * min_base_migrate_rate / 1_000_000` — i.e., enough tokens left over to seed the post-graduation pool. * `total_quote_fund_raising >= min_quote_fund_raising`. * If `requires_platform_auth != 0`, a valid `PlatformGlobalAccess` PDA is included. After `Initialize`, the launch's `PoolState` stores the `global_config` pubkey directly, so the binding is permanent. ## Update path `UpdateConfig` is admin-only and takes a `(param: u8, value: u64)` pair. Each `param` value selects which field to mutate: | `param` | Field changed | | ------- | ------------------------ | | 0 | `migrate_fee` | | 1 | `trade_fee_rate` | | 2 | `max_share_fee_rate` | | 3 | `min_base_supply` | | 4 | `max_lock_rate` | | 5 | `min_base_sell_rate` | | 6 | `min_base_migrate_rate` | | 7 | `min_quote_fund_raising` | Wallet rotations (`protocol_fee_owner`, `migrate_fee_owner`, `migrate_to_amm_wallet`, `migrate_to_cpswap_wallet`) take a separate `Pubkey` argument shape; check the `update_config` source for the exact dispatch table before composing an admin transaction. `requires_platform_auth` is toggled via `set_requires_platform_auth(bool)`. ## Reading a `GlobalConfig` from a client ```ts theme={null} import { Raydium } from "@raydium-io/raydium-sdk-v2"; // Fetch by PDA derivation: const [configPda] = PublicKey.findProgramAddressSync( [ Buffer.from("global_config"), quoteMint.toBytes(), Buffer.from([curveType]), u16ToBytes(index), ], LAUNCHLAB_PROGRAM_ID, ); const config = await raydium.launchpad.getGlobalConfig(configPda); console.log(config.tradeFeeRate, config.minQuoteFundRaising); ``` For a UI listing all available configs, walk the SDK's `getAllGlobalConfigs` helper or query the LaunchLab API endpoint that mirrors them; the on-chain account count is small (single digits in practice). ## Pointers * [`products/launchlab/platform-config`](/products/launchlab/platform-config) — per-platform overlay that constrains curve shape further. * [`products/launchlab/accounts`](/products/launchlab/accounts) — full account graph for a launch. * [`products/launchlab/instructions`](/products/launchlab/instructions) — `Initialize` and update instructions. * [`reference/program-addresses`](/reference/program-addresses) — admin pubkey, program ID, shared authorities. Sources: * `raydium-launch/programs/launchpad/src/states/config.rs` — `GlobalConfig` struct and `initialize`. * `raydium-launch/programs/launchpad/src/lib.rs` — `create_config`, `update_config`. # LaunchLab Source: https://docs.raydium.io/products/launchlab/index Bonding-curve token-launch venue that graduates into a Raydium AMM pool once funding targets are met. ## What it is LaunchLab is Raydium's token-launch program. A project deploys a token against a bonding curve; buyers trade against the curve until a funding threshold is reached, at which point the remaining curve supply and collected quote tokens are migrated into a Raydium AMM pool (CPMM or CLMM) and trading continues there. **Program ID:** see [reference/program-addresses](/reference/program-addresses). ## Chapter contents Lifecycle of a launch: pre-graduation trading, graduation trigger, post-graduation AMM pool. The curve formula(s) supported by LaunchLab, worked price examples, and how the graduation threshold is computed. LaunchConfig, LaunchState, vaults, authority PDAs; account states through the launch lifecycle. Initialize, Buy, Sell, Graduate, CollectFees, SetParams. Includes migration-to-pool instruction. End-to-end: deploy a launch, buy/sell, trigger graduation, verify resulting pool. Third-party Platform PDAs — how external teams run their own branded launch environment on top of LaunchLab. Pre-migration vault, post-migration Fee Key NFT, the 0.1% token-share referral, and how splits are configured per platform. ## When to read this * You are launching a new token on Raydium. * You are building a launch-tracking UI or aggregator. * You need to understand price formation pre-graduation vs. post-graduation. # LaunchLab overview Source: https://docs.raydium.io/products/launchlab/overview How Raydium's bonding-curve launch venue works end-to-end: pre-graduation trading against a deterministic curve, the funding threshold that triggers graduation, and the automatic migration into a standard CPMM pool. ## The problem LaunchLab solves Before LaunchLab, launching a new token on Raydium required the creator to seed an AMM pool with both sides of the pair up front — meaning the team had to provide the quote-side liquidity (SOL or USDC) out of pocket. This favored well-funded projects and gated access to the launchpad mechanism on initial capital. LaunchLab replaces that opening step with a **bonding curve**: the token is deployed against a curve priced in a quote mint (typically SOL or USDC). Buyers acquire the token by sending quote to the curve, which atomically mints or releases base-token units at a price determined by the curve formula and current supply. No pre-seeded liquidity required. Once the curve collects enough quote to match the liquidity formula for a real AMM pool, it **graduates**: the program creates a CPMM pool on mainnet seeded with the curve's base reserve and quote reserve, and from that point trading moves to the AMM. ## Lifecycle ``` ┌───────────────────────┐ creator ──▶ │ Initialize (Launch) │ ▸ mints the base token, funds the curve vaults, │ │ sets curve params and graduation threshold └───────────┬────────────┘ │ ▼ ┌───────────────────────┐ repeated N times │ Buy / Sell │ ◀─── traders interact with the curve └───────────┬────────────┘ │ curve quote_reserve │ crosses threshold ▼ ┌───────────────────────┐ ▸ snapshots curve state │ Graduate │ ▸ deploys CPMM pool seeded with curve reserves │ │ ▸ locks / burns remaining base supply per policy └───────────┬────────────┘ │ ▼ ┌───────────────────────┐ │ CPMM pool live │ ▸ standard CPMM behavior going forward └───────────────────────┘ ``` Every launch passes through this sequence exactly once. `Buy` and `Sell` are the only user-callable instructions in the middle phase; `Graduate` is permissionless (anyone can call it once the threshold is crossed) but in practice the SDK auto-calls it inside the transaction that crosses the threshold. ## Two fixed parties A LaunchLab state has two distinguished accounts: * **The base mint** — the token being launched. Its mint authority is held by the LaunchLab program until graduation; post-graduation, it is revoked. * **The quote mint** — the collateral. Always a mainstream mint (SOL / USDC / RAY). The launch config picks one at `Initialize`; it cannot be changed. Plus two vaults: * **`base_vault`** — holds the portion of the base supply that has been pre-minted to the curve but not yet sold. Decreases as users buy. * **`quote_vault`** — accumulates the quote paid in by buyers. Increases as users buy. This is the balance that is checked against the graduation threshold. ## Pricing model LaunchLab supports multiple curve formulas (see [`bonding-curve`](/products/launchlab/bonding-curve)). The most common is a **quadratic** bonding curve analogous to the Pump.fun / Curve.fi / Bancor lineage: ``` price(s) = k × (s / S_max)² (or similar — exact formula is curve-config dependent) ``` where `s` is the amount of base already sold to users and `S_max` is the curve's maximum supply. Price rises monotonically with each buy and falls with each sell. Because the program computes the AMM-integrated cost exactly, a buy of any size returns the correct integrated amount; there is no per-trade slippage beyond the curve's natural convexity. ## Graduation A launch graduates when `quote_vault.balance ≥ graduation_threshold`. The threshold is set at `Initialize` and is typically chosen so that at graduation the curve's implied price matches the price the AMM pool will open at with the collected reserves. Concretely: ``` threshold ≈ S_graduate × price(S_graduate) × f ``` where `S_graduate` is the base amount already sold, `price(S_graduate)` is the curve's marginal price at that point, and `f` is a small factor to account for the fee line (1–2%). At graduation: 1. The program snapshots `(base_vault_remaining, quote_vault)`. 2. It calls CPMM `CreatePool` CPI with these two reserves, minting the initial LP to a program-owned authority (usually burned / locked per policy). 3. It revokes the base mint's mint authority (so no more base tokens can ever be minted). 4. `LaunchState.status` flips to `Graduated`. Post-graduation, `Buy` and `Sell` reject. Trading continues on the resulting CPMM pool, which is indistinguishable from any other Raydium CPMM pool. ## Fees During the curve phase, each `Buy` and `Sell` incurs a fee split between: * **Curve LP side** — increases the curve's implied `k`, which benefits later buyers (tighter price). * **Protocol** — accrues to LaunchLab admin, collected via `CollectFees`. * **Creator** — optional, configurable at `Initialize`. Some launches direct a share to the creator as an ongoing revenue stream. Default rates are documented on [`bonding-curve`](/products/launchlab/bonding-curve). The exact split is stored on `LaunchState.fees` and can differ per launch. Post-graduation fees follow the CPMM config the pool was created with (typically `AmmConfig[0]`, the 0.25% tier). ## Who holds the LP after graduation? LaunchLab supports several post-graduation LP policies: * **Burn** — LP is minted to a dead address. The pool becomes permanent; no one can remove liquidity. * **Lock** — LP is minted to a time-locked escrow that releases to the creator after a vesting period. * **Creator-received immediately** — used only for permissioned launches. The policy is set at `Initialize` and cannot be changed. Most open launches use **burn** — once the pool exists, its liquidity is there forever. ## Important invariants * **The base mint is inflation-free after graduation.** Its `mint_authority` is revoked; `freeze_authority` was never set. * **Token-2022 is supported via `initialize_with_token_2022`.** Standard `Initialize` / `InitializeV2` create SPL Token launches; the dedicated `InitializeWithToken2022` instruction creates a launch whose base mint is Token-2022 (with optional `TransferFeeConfig`). Token-2022 launches must graduate to a CPMM pool — they cannot graduate to AMM v4, which is SPL Token-only. * **Curve state is monotonic in one direction.** `base_sold` only rises during Buy, `quote_vault` only rises during Buy (falls during Sell — which symmetrically reduces `base_sold`). The program never lets the curve go negative. * **Graduation is a one-way gate.** Even if post-graduation trading pushes the AMM pool price back below the graduation price, the launch does not revert to the curve. ## When to use LaunchLab This page describes protocol mechanics only. Nothing here constitutes financial, legal, or investment advice. Token launches carry significant financial risk. Consult appropriate professionals before launching a token that involves public fundraising. * You are launching a new token with no prior market. * You want the market to determine the opening CPMM price rather than pre-declaring it. * You want to allow anyone — including the team itself — to buy in at the same curve-determined prices, rather than pre-allocating to insiders at a discount. Do **not** use LaunchLab for: * Existing tokens with established markets (use `CreatePool` on CPMM directly). * Launches where you need precise control over the opening AMM price (you can approximate it with careful curve config, but the mechanism is still curve-driven). * Tokens that require Token-2022 extensions LaunchLab does not allowlist (the launch program rejects extensions like `TransferHook` and `PermanentDelegate` even on the Token-2022 path). ## Chapter contents * [`bonding-curve`](/products/launchlab/bonding-curve) — the curve formula, cost and proceeds math, graduation threshold derivation. * [`accounts`](/products/launchlab/accounts) — `LaunchConfig`, `LaunchState`, vaults, authority PDAs. * [`instructions`](/products/launchlab/instructions) — `Initialize`, `Buy`, `Sell`, `Graduate`, `CollectFees`, `SetParams`. * [`code-demos`](/products/launchlab/code-demos) — end-to-end TypeScript examples. ## Where to go next * [`products/cpmm/overview`](/products/cpmm/overview) — what happens after a launch graduates. * [`user-flows/create-cpmm-pool`](/user-flows/create-cpmm-pool) — alternative path for tokens that do not need a curve. * [`reference/program-addresses`](/reference/program-addresses) — LaunchLab program ID. Sources: * [Raydium SDK v2 `LaunchLab` module](https://github.com/raydium-io/raydium-sdk-V2) (IDL under `src/raydium/launchpad/`). * LaunchLab program source is not currently published as a standalone repo. The IDL bundled with the SDK above is the canonical interface. # LaunchLab PlatformConfig Source: https://docs.raydium.io/products/launchlab/platform-config Per-platform configuration that customizes branding, fee splits, NFT migration shares, vesting allocation, and the whitelist of permitted curve shapes for any launch routed through that platform. `PlatformConfig` is the **platform-level** overlay that sits on top of [`GlobalConfig`](/products/launchlab/global-config). Where `GlobalConfig` defines the protocol-wide rules ("trade fee is 1%, supply must be at least 10M, only this wallet can graduate"), `PlatformConfig` is what each launch platform — pump.fun, Raydium's own UI, third-party launchpads — uses to add their fee, claim their slice of post-graduation LP, restrict which curve shapes their launches can pick, and surface their branding (name, website, image) on-chain. ## What it is A `PlatformConfig` account owns four cross-cutting concerns for a platform: 1. **Branding** — name, website, image link, all stored inline so any explorer or aggregator can display the platform that launched a token. 2. **Platform fee** — an extra trade fee (`fee_rate`) on top of the protocol's `trade_fee_rate`. Accrues to the platform's `platform_fee_wallet`. Capped at 100 bps by `GlobalConfig.max_share_fee_rate`. 3. **NFT migration split** — three integers (`platform_scale`, `creator_scale`, `burn_scale`) that sum to `RATE_DENOMINATOR_VALUE = 1_000_000` and partition the post-graduation LP into a piece minted to the platform NFT wallet, a piece to the creator NFT wallet, and a piece burned (Burn & Earn). Only meaningful when graduation targets CPMM (`migrate_type = 1`). 4. **Curve-parameter whitelist** — a `Vec` listing exactly which `(supply, total_base_sell, total_quote_fund_raising, migrate_type, migrate_cpmm_fee_on, vesting_params...)` combinations are permitted on this platform. If the vector is empty or all entries are invalid, **any** combination is allowed; otherwise launches must match one of the entries exactly. PDA derivation: ```ts theme={null} const [platformConfigPda] = PublicKey.findProgramAddressSync( [ Buffer.from("platform_config"), platformAdmin.toBuffer(), // platform's owning pubkey ], LAUNCHLAB_PROGRAM_ID, ); ``` (See `create_platform_config` in the source for the canonical seed list.) ## Layout ```rust theme={null} // states/platform_config.rs pub const PLATFORM_CONFIG_SEED: &str = "platform_config"; pub const NAME_SIZE: usize = 64; pub const WEB_SIZE: usize = 256; pub const IMG_SIZE: usize = 256; pub const MAX_CREATOR_FEE_RATE: u64 = 5000; // 50 bps (denominator 1_000_000) pub const MAX_TRANSFER_FEE_RATE: u16 = 500; // 5% (denominator 10_000) pub const MAX_CURVE_PARAMS: usize = 10; #[account] pub struct PlatformConfig { pub epoch: u64, pub platform_fee_wallet: Pubkey, // signs ClaimPlatformFee pub platform_nft_wallet: Pubkey, // receives the platform NFT slice at CPMM graduation pub platform_scale: u64, // share of LP minted to platform NFT pub creator_scale: u64, // share of LP minted to creator NFT pub burn_scale: u64, // share of LP burned via Burn & Earn pub fee_rate: u64, // platform's trade fee (1/1_000_000) pub name: [u8; 64], // utf-8 padded with zeros pub web: [u8; 256], pub img: [u8; 256], pub cpswap_config: Pubkey, // CPMM AmmConfig that the post-grad pool will bind to pub creator_fee_rate: u64, // creator-side fee taken pre-graduation pub transfer_fee_extension_auth: Pubkey, // for Token-2022 launches: who inherits transfer-fee authorities post-graduation pub platform_vesting_wallet: Pubkey, pub platform_vesting_scale: u64, // platform's slice of total_locked_amount pub platform_cp_creator: Pubkey, // optional creator-of-record on the post-graduation CPMM pool pub padding: [u8; 108], pub curve_params: Vec, // whitelist of permitted curve shapes } ``` `platform_scale + creator_scale + burn_scale` must equal `1_000_000` (validated by `MigrateNftInfo::check`). Common splits seen in production: * `(0, 100_000, 900_000)` — 90% LP burned, 10% to creator. Standard pump-style fair launch. * `(50_000, 100_000, 850_000)` — small platform slice (5%), 10% creator, 85% burn. * `(0, 0, 1_000_000)` — full burn, no NFT mints. Strict "no insiders" launches. ## Branding fields `name`, `web`, and `img` are inline byte arrays padded with zeros up to their size constants. To read them as strings, slice up to the first `\0`: ```ts theme={null} function readString(bytes: Uint8Array): string { const end = bytes.indexOf(0); return Buffer.from(end === -1 ? bytes : bytes.subarray(0, end)).toString("utf-8"); } ``` The constants are deliberately generous (`name: 64`, `web: 256`, `img: 256`) so platforms can include enough metadata for explorers and aggregators without touching off-chain storage. Anything that exceeds these sizes reverts at `CreatePlatformConfig` with `InvalidInput`. ## Fee mechanics A swap on a curve bound to a `PlatformConfig` charges three layered fees: ``` trade_fee = amount_in × global_config.trade_fee_rate / 1_000_000 platform_fee = amount_in × platform_config.fee_rate / 1_000_000 creator_fee = amount_in × platform_config.creator_fee_rate / 1_000_000 amount_after_fee = amount_in − trade_fee − platform_fee − creator_fee ``` * `trade_fee` accrues to the protocol's `protocol_fee_owner` (claimed via `CollectFee`). * `platform_fee` accrues to a per-platform vault (claimed via `ClaimPlatformFee` or `ClaimPlatformFeeFromVault`; see [`instructions`](/products/launchlab/instructions)). * `creator_fee` accrues to a per-creator vault keyed by the creator's pubkey + quote mint (claimed via `ClaimCreatorFee`). `creator_fee_rate` is capped by `MAX_CREATOR_FEE_RATE = 5000` (50 bps). `fee_rate` (the platform fee) is capped at `10000` (100 bps) by `GlobalConfig.max_share_fee_rate`. ## NFT migration split (CPMM-only) When a launch graduates to CPMM (`migrate_type = 1`, signed by `migrate_to_cpswap_wallet`), the migration instruction splits the LP tokens minted by `CPMM::InitializeWithPermission` three ways: ``` lp_to_platform = lp_total × platform_scale / 1_000_000 → platform_nft_wallet lp_to_creator = lp_total × creator_scale / 1_000_000 → creator NFT (Fee Key) lp_to_burn = lp_total × burn_scale / 1_000_000 → Burn & Earn lock program ``` The platform and creator slices are wrapped as **NFTs** by the LP-Lock program (`LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE`) — the holder of the NFT is entitled to claim accrued CPMM fees indefinitely without being able to withdraw the underlying liquidity. See [`products/launchlab/creator-fees`](/products/launchlab/creator-fees) for the post-graduation Fee Key flow. The burn slice is sent to the Lock program with `is_burn = true` so the LP tokens are permanently inaccessible — they secure the pool's price floor without ever paying fees back to anyone. When `migrate_type = 0` (graduate to AMM v4), the NFT split fields are ignored and the entire LP is locked / burned according to a separate AMM v4-side flow. ## Curve-parameter whitelist `curve_params: Vec` is the platform's mechanism for restricting which curve shapes its launches can pick. If the vector is non-empty and at least one entry is valid, the program enforces at `Initialize` that the launch's parameters match at least one entry exactly. ```rust theme={null} #[derive(AnchorSerialize, AnchorDeserialize, Clone)] pub struct PlatformCurveParam { pub epoch: u64, pub index: u8, // ordinal within this platform's whitelist pub global_config: Pubkey, // which GlobalConfig this entry applies to pub bonding_curve_param: BondingCurveParam, pub padding: [u64; 50], } #[derive(AnchorSerialize, AnchorDeserialize, Clone)] pub struct BondingCurveParam { pub migrate_type: u8, // 0 = AMM v4, 1 = CPMM. u8::MAX = wildcard pub migrate_cpmm_fee_on: u8, // 0 = quote-only, 1 = both. u8::MAX = wildcard pub supply: u64, // 0 = wildcard pub total_base_sell: u64, // 0 = wildcard pub total_quote_fund_raising: u64, // 0 = wildcard pub total_locked_amount: u64, // u64::MAX = wildcard pub cliff_period: u64, // u64::MAX = wildcard pub unlock_period: u64, // u64::MAX = wildcard } ``` Each field has a sentinel value that means **wildcard** (any value matches): `u64::MAX` for the `u64` fields, `u8::MAX` for the `u8` fields, `0` for the supply / sell / fund-raising fields. A `BondingCurveParam` with all sentinels is "allow anything" — equivalent to the empty-whitelist behaviour. The matching algorithm at `Initialize`: 1. Filter `curve_params` to entries whose `global_config` matches the launch's chosen `GlobalConfig`. 2. If the filtered list is empty, allow any params (the platform did not whitelist anything for this `GlobalConfig`). 3. If every entry in the filtered list has `all_is_invalid()` (every field is the wildcard), allow any params. 4. Otherwise iterate entries; for each entry, check the launch's params against every non-wildcard field. If all non-wildcard fields match, accept and return. 5. If no entry matched, revert with `InvalidInput`. This lets a platform say "we only allow the standard 1B-supply / 800M-sold / 30k-USDC-raise / no-vesting shape" by writing a single entry with concrete values for those four fields and wildcards everywhere else. Or a stricter platform might enumerate three or four discrete shapes, one per supported launch tier. `MAX_CURVE_PARAMS = 10` caps the whitelist size. ## `PlatformGlobalAccess` — authorizing a platform When a `GlobalConfig` has `requires_platform_auth = 1`, every `Initialize` against it must include a `PlatformGlobalAccess` PDA proving the platform has been pre-authorized: ```rust theme={null} // states/platform_global_access.rs pub const PLATFORM_GLOBAL_ACCESS_SEED: &str = "platform_global_access"; #[account] pub struct PlatformGlobalAccess { pub bump: u8, pub global_config: Pubkey, pub platform_config: Pubkey, pub padding: [u64; 8], } ``` PDA seeds: `[b"platform_global_access", global_config, platform_config]`. The protocol admin creates one of these per `(GlobalConfig, PlatformConfig)` pair via `CreatePlatformGlobalAccess` and revokes it via `ClosePlatformGlobalAccess`. Without this account, a launch cannot bind to that `GlobalConfig` from the gated platform. ## Read path ```ts theme={null} const platformConfig = await raydium.launchpad.getPlatformConfig(platformConfigPda); console.log("Platform:", readString(platformConfig.name)); console.log("Fee rate:", platformConfig.feeRate, "(/1M)"); console.log("NFT split:", platformConfig.platformScale, platformConfig.creatorScale, platformConfig.burnScale, ); console.log("Curve whitelist size:", platformConfig.curveParams.length); ``` For a UI showing "where did this token launch from", `PoolState.platform_config` points at the originating `PlatformConfig` directly — fetch it once and cache the branding. ## Update path | Instruction | Who signs | What changes | | --------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CreatePlatformConfig` | platform admin (one-time) | Initializes the account with `PlatformParams`. | | `UpdatePlatformConfig` | platform admin | Generic dispatch keyed by a `param: u8`; mutates one field per call. Branding fields, fee rates, vesting wallet, and the various wallets are all settable through this. | | `UpdatePlatformCurveParam` | platform admin | Add or replace one `PlatformCurveParam` entry by `(global_config, index)`. | | `RemovePlatformCurveParam` | platform admin | Clear one entry (sets it to all-sentinel = wildcard). | | `ClaimPlatformFee` | `platform_fee_wallet` | Sweep the per-pool platform fee from `PoolState.quote_vault`. | | `ClaimPlatformFeeFromVault` | `platform_fee_wallet` | Sweep the per-platform fee vault (PDA at `[platform_config, quote_mint]`). | Wallet rotations (`platform_fee_wallet`, `platform_nft_wallet`, `platform_vesting_wallet`, `platform_cp_creator`, `transfer_fee_extension_auth`, `cpswap_config`) all go through `UpdatePlatformConfig`. Read the source's `update_platform_config` dispatch table for the exact `param` codes. ## Common pitfalls * **Whitelist sentinels mis-set.** A `BondingCurveParam` with `total_locked_amount = 0` *is not* a wildcard — it matches launches that explicitly opt out of vesting. The wildcard for that field is `u64::MAX`. The same trap exists for `cliff_period` and `unlock_period`. Use `clear()` (which the program exposes) to set sentinels correctly. * **NFT-split rounding.** The three scales must sum to exactly `1_000_000`. Off-by-one errors at `CreatePlatformConfig` revert; off-by-one at runtime would mint or burn one extra LP unit, which is what the strict-equality check is there to prevent. * **Platform vesting double-allocation.** If `platform_vesting_scale > 0`, the platform must call `CreatePlatformVestingAccount` once after the launch's fundraising ends; if it forgets, that share remains unallocated and dormant forever (the launch's `total_locked_amount` budget is consumed but the platform never claims). * **`platform_cp_creator` ambiguity.** When set to `Pubkey::default()`, the launch creator is recorded as the post-graduation CPMM pool's `pool_creator`; when set to a real key, that key is recorded instead. This affects who can call `CPMM::CollectCreatorFee` later. Decide at platform-config creation time which model you want. ## Pointers * [`products/launchlab/global-config`](/products/launchlab/global-config) — protocol-side rules a launch must satisfy. * [`products/launchlab/vesting`](/products/launchlab/vesting) — `platform_vesting_scale` mechanics. * [`products/launchlab/creator-fees`](/products/launchlab/creator-fees) — Fee Key NFT, Burn & Earn. * [`products/launchlab/platforms`](/products/launchlab/platforms) — platform integrator's how-to. Sources: * `raydium-launch/programs/launchpad/src/states/platform_config.rs` — `PlatformConfig`, `PlatformParams`, `MigrateNftInfo`, `PlatformCurveParam`, `BondingCurveParam`, `is_valid_curve_param`. * `raydium-launch/programs/launchpad/src/states/platform_global_access.rs` — `PlatformGlobalAccess`. * `raydium-launch/programs/launchpad/src/lib.rs` — `create_platform_config`, `update_platform_config`, `update_platform_curve_param`, `remove_platform_curve_param`, `create_platform_global_access`, `close_platform_global_access`, `claim_platform_fee`, `claim_platform_fee_from_vault`. # Perps code demos Source: https://docs.raydium.io/products/perps/code-demos Programmatic access to Raydium Perps via Orderly Network's API: deposit USDC, register the trading account, place / cancel / amend orders, fetch positions, subscribe to a market WebSocket, and withdraw. **Raydium Perps is a white-labeled deployment on Orderly Network.** The order book, matching engine, and account state all live on Orderly. The Raydium SDK v2 (`@raydium-io/raydium-sdk-v2`) does **not** cover perps — for programmatic access, use Orderly's REST + WebSocket API directly. The snippets below show the most common flows; the canonical reference is at [orderly.network/docs](https://orderly.network/docs/build-on-omnichain/evm-api/introduction). **Version banner.** * Backend: Orderly Network REST + WebSocket API * Snippet schema verified against Orderly's API as of 2026-04 * Solana cluster for on-chain deposits: `mainnet-beta` * Signing: Solana ed25519 over the Orderly EIP-712-style payload (Orderly uses an EIP-712 schema even for non-EVM chains; see Orderly docs for the latest field list) Orderly's API surface evolves; check [orderly.network/docs](https://orderly.network/docs/build-on-omnichain/evm-api/introduction) before copying these snippets into production. ## What's on this page The flows below cover the integrator-relevant lifecycle: 1. Account setup — deposit USDC and register the account with Orderly. 2. Authenticated REST calls — request signing for order placement, cancellation, and account queries. 3. Trading — placing market / limit orders, cancelling, fetching positions and fills. 4. Market data — subscribing to the orderbook and trade WebSocket. 5. Withdrawal — initiating a withdrawal back to the wallet. These snippets target Node.js + TypeScript with `@solana/web3.js` and `tweetnacl` for Ed25519 signing. They are **starting points** — Orderly's API surface is broad and changes faster than this page; always check Orderly's live docs before shipping production code. ## Setup ```ts theme={null} import { Connection, Keypair, PublicKey, clusterApiUrl } from "@solana/web3.js"; import nacl from "tweetnacl"; import bs58 from "bs58"; import fs from "node:fs"; // 1. Solana wallet — owns USDC, signs deposit/withdrawal transactions. const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta")); const owner = Keypair.fromSecretKey( new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))), ); // 2. Orderly trading key — separate Ed25519 keypair used to sign API requests. // NOT the Solana wallet. Generate once, keep secret, reuse across sessions. const orderlyKey = nacl.sign.keyPair(); // ed25519 const orderlyPubB58 = "ed25519:" + bs58.encode(orderlyKey.publicKey); // 3. Orderly base URL. Raydium uses Orderly's mainnet host. const ORDERLY_BASE = "https://api.orderly.org"; const BROKER_ID = "raydium"; // Raydium's broker namespace on Orderly const CHAIN_ID = "solana"; // for cross-chain account registration ``` The Orderly trading key is **not** your wallet keypair. It's a request-signing key that you register against your wallet on first use; you can rotate it without touching funds. Treat it as a session credential. ## Account registration Before placing any orders, register the wallet with Orderly: ```ts theme={null} import { encodeUserSettlement } from "./eip712-helpers"; // see Orderly docs for the exact payload // 1. Request a registration nonce from Orderly. const nonceResp = await fetch(`${ORDERLY_BASE}/v1/registration_nonce`).then(r => r.json()); const registrationNonce = nonceResp.data.registration_nonce; // 2. Sign a registration payload with the Solana wallet (EIP-712-style on Solana // is implemented as a structured message; Orderly's SDK provides the encoder). const payload = encodeUserSettlement({ brokerId: BROKER_ID, chainId: CHAIN_ID, registrationNonce, timestamp: Date.now(), }); const walletSig = nacl.sign.detached(Buffer.from(payload), owner.secretKey); // 3. Register, including the Orderly Ed25519 trading key. const reg = await fetch(`${ORDERLY_BASE}/v1/register_account`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: payload, signature: bs58.encode(walletSig), userAddress: owner.publicKey.toBase58(), orderlyKey: orderlyPubB58, }), }).then(r => r.json()); console.log("Account ID:", reg.data.account_id); ``` Account IDs are deterministic per `(broker_id, wallet_address)` pair — the registration is idempotent. If a wallet has already registered with Raydium's broker, the call returns the same account ID without creating a new one. ## Deposit USDC Deposits move USDC from the wallet ATA to Orderly's settlement vault. They are on-chain Solana transactions: ```ts theme={null} // Build the deposit ix using Orderly's Solana program (vault program ID is // published in their docs; pull it dynamically rather than hard-coding). const vaultProgramId = new PublicKey(""); const depositIx = await buildOrderlyDepositIx({ vaultProgramId, user: owner.publicKey, brokerId: BROKER_ID, amountUsdc: BigInt(100_000_000), // 100 USDC (6 decimals) }); const tx = new Transaction().add(depositIx); const sig = await connection.sendTransaction(tx, [owner]); await connection.confirmTransaction(sig, "confirmed"); console.log("Deposit tx:", sig); ``` After \~30 seconds, Orderly's relayer indexes the deposit and the balance shows up under the account's free margin. Query `/v1/client/holding` to confirm: ```ts theme={null} const holdingResp = await orderlyAuthGet("/v1/client/holding"); console.log("Balances:", holdingResp.data.holding); ``` (`orderlyAuthGet` is defined below — every authenticated call goes through it.) ## Request signing helper Every authenticated REST call to Orderly carries an Ed25519 signature over `(timestamp + method + path + body)`: ```ts theme={null} async function orderlyAuthRequest( method: "GET" | "POST" | "PUT" | "DELETE", path: string, body?: unknown, ): Promise { const ts = Date.now().toString(); const json = body ? JSON.stringify(body) : ""; const msg = `${ts}${method}${path}${json}`; const sig = nacl.sign.detached(Buffer.from(msg), orderlyKey.secretKey); const resp = await fetch(ORDERLY_BASE + path, { method, headers: { "Content-Type": "application/json", "orderly-account-id": /* the registered account_id */ "", "orderly-key": orderlyPubB58, "orderly-signature": bs58.encode(sig), "orderly-timestamp": ts, }, body: json || undefined, }); return resp.json(); } const orderlyAuthGet = (p: string) => orderlyAuthRequest("GET", p); const orderlyAuthPost = (p: string, b: object) => orderlyAuthRequest("POST", p, b); const orderlyAuthDel = (p: string) => orderlyAuthRequest("DELETE", p); ``` Replay protection: requests with a `timestamp` more than 5 seconds off the server clock are rejected. Sync your clock (NTP) and avoid signing requests in advance. ## Place a market order ```ts theme={null} const marketResp = await orderlyAuthPost("/v1/order", { symbol: "PERP_SOL_USDC", order_type: "MARKET", side: "BUY", order_quantity: 1.0, // 1 SOL of position reduce_only: false, }); if (marketResp.success) { console.log("Order ID:", marketResp.data.order_id); } else { console.error("Reject:", marketResp.message); } ``` Market orders execute immediately. The response returns the resulting `order_id` plus a status. Fills come over the WebSocket (see below); the REST response itself does not block until fully filled. ## Place a limit order with Post-Only ```ts theme={null} const limitResp = await orderlyAuthPost("/v1/order", { symbol: "PERP_SOL_USDC", order_type: "LIMIT", side: "SELL", order_quantity: 0.5, order_price: 140.50, // flag combinations: // post_only: true makes this a maker-only order — cancels if it would cross. // reduce_only / time_in_force are independently settable. post_only: true, }); console.log(limitResp); ``` For `IOC` / `FOK`, set `time_in_force: "IOC"` or `"FOK"`. See [`products/perps/order-types`](/products/perps/order-types) for the semantics of each flag. ## Cancel an order ```ts theme={null} // By order ID await orderlyAuthDel(`/v1/order?order_id=${orderId}&symbol=PERP_SOL_USDC`); // Cancel ALL orders on a symbol await orderlyAuthDel(`/v1/orders?symbol=PERP_SOL_USDC`); ``` A cancel is acknowledged synchronously but the actual cancellation may race with a fill. Always reconcile by polling `/v1/orders` or watching the WebSocket — assuming a cancel succeeded without confirmation can lead to duplicate or unintended positions. ## Fetch open positions ```ts theme={null} const posResp = await orderlyAuthGet("/v1/positions"); for (const p of posResp.data.rows) { console.log( p.symbol, "size:", p.position_qty, "entry:", p.average_open_price, "unrealized:", p.unsettled_pnl, ); } ``` A negative `position_qty` is a short, positive is a long. `position_qty == 0` means the position is closed but the row may still show until the next cleanup. ## Fetch fill history ```ts theme={null} const fills = await orderlyAuthGet( "/v1/trades?symbol=PERP_SOL_USDC&start_t=" + (Date.now() - 86_400_000) ); for (const t of fills.data.rows) { console.log(t.executed_timestamp, t.side, t.executed_quantity, "@", t.executed_price); } ``` Time arguments are millisecond Unix timestamps. The default page size is 25 rows; use `page` and `size` query params to paginate. ## WebSocket: market data ```ts theme={null} import WebSocket from "ws"; const ws = new WebSocket(`wss://ws.orderly.org/ws/stream/${accountId}`); ws.on("open", () => { // Public market data: orderbook deltas + trades for one symbol ws.send(JSON.stringify({ id: "ob1", topic: "orderbook@PERP_SOL_USDC" })); ws.send(JSON.stringify({ id: "tr1", topic: "trade@PERP_SOL_USDC" })); }); ws.on("message", (raw) => { const msg = JSON.parse(raw.toString()); if (msg.topic?.startsWith("orderbook@")) { // depth diff: { bids: [[price, qty], ...], asks: [[price, qty], ...] } applyOrderbookDelta(msg.data); } else if (msg.topic?.startsWith("trade@")) { console.log("trade:", msg.data); } }); ``` For the **private** stream (your fills, position updates, balance changes), the WebSocket has to be authenticated. Send a `subscribe` payload signed the same way as REST requests, scoped to your account ID. Orderly's docs have the exact payload shape; it changes occasionally, so don't hard-code a particular schema here. ## Withdraw USDC ```ts theme={null} // 1. Request a withdrawal. const wRes = await orderlyAuthPost("/v1/withdraw_request", { token: "USDC", chain_id: CHAIN_ID, amount: 50.0, // human units receiver: owner.publicKey.toBase58(), }); console.log("Withdrawal request id:", wRes.data.withdraw_id); ``` Orderly relays the withdrawal on-chain to the receiver address. There is a flat **1 USDC** withdrawal fee (see [`products/perps/fees`](/products/perps/fees)). The on-chain transfer happens within 1–2 minutes under normal conditions; expect longer during congestion. ## Pitfalls * **Don't reuse the trading key across environments.** A single Orderly trading key registered against your wallet is associated with one Solana mainnet account. If you also need devnet or staging, generate a separate key for each. * **Time sync.** Orderly's clock-skew tolerance is tight (±5s). On long-running services, NTP drift will eventually break signing. Re-sync periodically. * **WebSocket reconnects.** The public WS occasionally drops connections during Orderly upgrades. Implement exponential backoff and resubscribe on reopen. * **Rate limits.** REST calls are tier-rate-limited per account. Bulk-cancel via `cancel_all` rather than looping `cancel`-by-id when you have >5 orders to cancel. * **Position direction is implicit.** A `BUY` order on `PERP_SOL_USDC` opens or extends a long; a `SELL` opens or extends a short — but if you're already long, a `SELL` *reduces* (and may flip) the position because Raydium Perps is one-way mode. Always check current position before placing an order if the direction matters. * **Funding and liquidations are separate from order flow.** Funding payments and liquidations show up as separate event streams; they are not "orders". Subscribe to the relevant private WS topics if you need to observe them. ## Where to go next * [`products/perps/trading-basics`](/products/perps/trading-basics) — the conceptual primer on perpetual mechanics. * [`products/perps/order-types`](/products/perps/order-types) — the semantics of each order type and flag. * [`products/perps/collateral`](/products/perps/collateral) — supported collateral assets and per-chain limits. * [`products/perps/fees`](/products/perps/fees) — maker/taker schedules and the withdrawal fee. Sources: * [Orderly Network developer documentation](https://orderly.network/docs/build-on-omnichain/evm-api/introduction) — canonical reference for the API surface used above. Raydium Perps consumes this directly. * [Orderly TypeScript SDK](https://github.com/OrderlyNetwork/orderly-sdk-js) — wraps the same REST/WebSocket layer with typed helpers; useful if you want to skip writing the signing layer yourself. # Collateral Source: https://docs.raydium.io/products/perps/collateral Supported collateral assets, per-chain global caps, per-user deposit caps, and how USDC-settled P&L interacts with non-USDC collateral. Collateral parameters are set by Orderly Network and change periodically. The tables below are a snapshot; always fetch live values from Orderly's API before quoting or sizing. Orderly's canonical reference: [orderly.network/docs/introduction/trade-on-orderly/multi-collateral](https://orderly.network/docs/introduction/trade-on-orderly/multi-collateral). ## Settlement vs. collateral * **Settlement currency: USDC.** All P\&L, funding payments, and liquidation accounting happen in USDC regardless of what you deposited. * **Collateral assets: multiple.** USDT, SOL, ETH, BNB, WBTC, YUSD, USD1 (and USDC itself) can be deposited. Non-USDC collateral is held at its current market value with a haircut and counted toward your margin in USDC-equivalent terms. Practical consequence: if you deposit SOL and SOL price halves, your margin ratio deteriorates **even if your perp positions haven't moved**. For production use this is a meaningful risk that does not exist when you deposit USDC. ## Global caps (shared across all users) These limits apply to the **entire protocol**, not per-account. When the global cap for an asset is reached, new deposits of that asset (on that chain) are rejected until others withdraw. Quantities are denominated in the asset's own units (SOL in SOL, ETH in ETH, USDT in USDT, etc.), not in USDC equivalent. | Collateral | Chain | Max (global, in asset units) | | ---------- | -------- | ---------------------------- | | USDT | Arbitrum | 2,000,000 USDT | | ETH | Arbitrum | 1,000 ETH | | ETH | Base | 500 ETH | | BNB | BSC | 10,000 BNB | | USDT | BSC | 4,000,000 USDT | | USD1 | BSC | 1,000,000 USD1 | | YUSD | BSC | 500,000 YUSD | | ETH | Ethereum | 500 ETH | | USDT | Ethereum | 3,000,000 USDT | | USD1 | Ethereum | 1,000,000 USD1 | | YUSD | Ethereum | 500,000 YUSD | | WBTC | Ethereum | 80 WBTC | | SOL | Solana | 40,000 SOL | ## Per-user caps These limits apply **per individual wallet / perps account**. | Collateral | Max per account (in asset units) | | ---------- | -------------------------------- | | USDT | 500,000 USDT | | ETH | 100 ETH | | SOL | 2,000 SOL | | BNB | 500 BNB | | YUSD | 50,000 YUSD | | WBTC | 5 WBTC | | USD1 | 500,000 USD1 | USDC does not have a listed per-user cap because it is the settlement asset; effective cap is whatever the platform / Orderly-side limit allows at the time. ## Deposit and withdrawal flow **Deposit.** A user signs an on-chain transaction on the source chain that moves the asset to Orderly's vault. Orderly's relayer acknowledges the deposit and credits the Solana perps account ledger. Cross-chain deposits (e.g. ETH from Arbitrum) take longer than native SOL deposits because the relayer must wait for source-chain finality. **Withdrawal.** The user signs an off-chain request; Orderly debits the ledger and emits an on-chain transfer on the destination chain. A **flat 1 USDC fee per withdrawal** applies (set by Orderly, not Raydium). Withdrawals fail if they would drop margin ratio below the required threshold — you cannot withdraw collateral that is backing an open position. ## Margin accounting For a cross-margin account with mixed collateral: ``` margin_usdc_equivalent = sum over collateral assets ( asset_balance × asset_price_usd × (1 − haircut_fraction) ) free_margin = margin_usdc_equivalent − initial_margin_used margin_ratio = margin_usdc_equivalent / maintenance_margin_required ``` * `asset_price_usd` comes from Orderly's price oracle, not from a Solana pool. * `haircut_fraction` protects against collateral price crashes. USDC is typically 0%; volatile collateral like SOL or ETH carries a material haircut. * `maintenance_margin_required` is the sum over open positions of each position's maintenance-margin requirement. ## Implications for integrators * **Do not model collateral as USDC-equivalent without the haircut** — the effective buying power can be materially lower. * **Monitor source-chain confirmation.** Cross-chain deposits aren't spendable until Orderly acknowledges them; a user who just deposited ETH from Ethereum may still see "insufficient margin" for several minutes. * **Respect the per-user caps.** Automated deposit logic that doesn't check the cap will fail intermittently when the user approaches the limit. * **USDC is the only "flat" collateral.** For any position sizing system that cannot handle FX-like effects between collateral and settlement, insist on USDC-only deposits. ## Where to go next * [Trading basics](/products/perps/trading-basics) * [Order types](/products/perps/order-types) * [Fees](/products/perps/fees) Sources: * Orderly Network multi-collateral reference. * Raydium Perps UI collateral panel at perps.raydium.io. # Perps fees Source: https://docs.raydium.io/products/perps/fees Maker 0 bps, volume-tiered taker schedule, Orderly's 1 bps cut, and the flat 1 USDC withdrawal fee. ## Fee schedule Raydium Perps uses Orderly Network's fee model: * **Maker (resting limit orders that get hit): 0 bps** — zero maker fee, no rebate. * **Taker (market orders and crossing limit orders): volume-tiered**, starting at 4.5 bps and scaling down with 30-day trading volume. * **Orderly infrastructure fee:** 1 bps on taker volume, already included in the taker bps figure below (Orderly receives that portion of the fee). * **Withdrawal fee: 1 USDC flat per withdrawal**, charged by Orderly at settlement time. ## Taker fee tiers (30-day volume) | Tier | 30-day volume | Taker (bps) | | ---- | -------------------- | ----------- | | 1 | \< \$500k | 4.5 | | 2 | ≥ \$500k and \< \$2M | 4.0 | | 3 | ≥ \$2M and \< \$5M | 3.5 | | 4 | ≥ \$5M and \< \$25M | 3.0 | | 5 | ≥ \$25M and \< \$50M | 2.5 | | 6 | ≥ \$50M and \< \$80M | 2.25 | | 7 | ≥ \$80M | 2.0 | Volume is measured across **all** Orderly markets the account trades on, not just Raydium Perps. ## Maker-taker in practice Because the maker fee is 0, market makers on Raydium Perps pay nothing to rest liquidity. Takers pay the full bps shown above. A few implications: * **A pure market-making strategy** pays no venue fees on successful fills. The only running cost is capital lockup and inventory risk. * **A directional trader that exclusively uses market orders** pays full taker bps; at tier 1 this is 0.45% per round-trip (0.225% entry + 0.225% exit), which eats quickly into a leveraged strategy's expected return. * **Post-only limit orders** can reduce taker exposure to zero at the cost of execution uncertainty — you only fill when the market comes to your price. ## Funding payments Funding is **not** a venue fee; it is a peer-to-peer transfer between longs and shorts intended to keep the perp price anchored to the spot reference. Funding charges are a real P\&L effect but they flow from trader to trader, not to the venue. Rates are set by Orderly's funding-rate formula and accrue at fixed intervals (commonly every hour). Check the live rate per market before opening a position, especially on markets where the perp is trading at a persistent premium or discount. ## Liquidation fees When the liquidation engine closes positions, it charges a liquidation fee to the account being liquidated. The fee funds Orderly's **insurance fund**, which backstops clawback scenarios. The fee is not owed to Raydium. Exact liquidation-fee parameters are Orderly-side and vary by market; they are disclosed in the Orderly margin-engine documentation. ## Comparison with spot AMM fees For context, Raydium's spot venues charge: * **AMM v4:** 25 bps per trade (22 LP + 3 protocol). See [`products/amm-v4/fees`](/products/amm-v4/fees). * **CPMM:** 25 bps default, configurable per `AmmConfig`. See [`products/cpmm`](/products/cpmm). * **CLMM:** tier-dependent, 1 / 5 / 25 / 100 bps. See [`products/clmm`](/products/clmm). **Spot trading costs approximately 55–125× more in basis points than a tier-1 taker perp trade** — but the products are not substitutes: spot gives you actual asset ownership, perps give you leveraged synthetic exposure with funding overhead. ## Integrator-facing notes * Fee rates change. The tier thresholds above are a 2026-04 snapshot — fetch live tiers from Orderly's fee endpoint before displaying them to a user. * Withdrawal fees are denominated in USDC; they come out of the withdrawable balance. A withdrawal of 100 USDC results in 99 USDC actually received. * Funding accruals are reported per-interval in Orderly's API; integrate them into your P\&L display or traders will be confused about position-level returns that don't match (unrealized\_pnl − entry\_fee − exit\_fee). ## Where to go next * [Trading basics](/products/perps/trading-basics) * [Order types](/products/perps/order-types) * [Collateral](/products/perps/collateral) * [`reference/fee-comparison`](/reference/fee-comparison) — spot-side fee matrix. Sources: * Orderly Network fee schedule. * Raydium Perps published fee tiers at perps.raydium.io. # Raydium Perps Source: https://docs.raydium.io/products/perps/index Gasless central-limit-order-book perpetuals on Solana (perps.raydium.io), powered by Orderly Network. Up to 100× leverage, multi-collateral, cross-margin, one-way mode. Raydium Perps is a separate product from the spot AMMs (AMM v4 / CPMM / CLMM). It lives at **perps.raydium.io**, uses its own order book (not Raydium pools), and routes order flow through Orderly Network's shared CLOB infrastructure. Traders retain self-custody of funds at all times. ## What it is **Raydium Perpetual** is a gasless central-limit-order-book (CLOB) perpetuals protocol. Positions are funded from an SPL wallet on Solana but the matching engine and order book are operated by [Orderly Network](https://orderly.network/), which pools liquidity across a multi-chain venue set (Solana, Ethereum, Base, Arbitrum, BSC). Key properties: * **Gasless trading.** Order creation, modification, and cancellation do not require on-chain transactions on Solana — signing is local and free. Only deposits and withdrawals touch-chain. * **Up to 100× leverage**, depending on the market. * **Multi-chain collateral** — you can deposit USDC / SOL / USDT / ETH / BNB / WBTC / YUSD / USD1 from their native chain; all P\&L settles in USDC. * **Cross-margin only.** All collateral in an account is shared across every open position. * **One-way mode.** You cannot hold a long and a short on the same market simultaneously; an opposite-side order reduces or flips the existing position. * **Self-custody.** Funds remain in your wallet's perps account; there is no Raydium-held custodian wallet. * **CEX-grade order types.** Market, Limit, Stop (market / limit), Scale, plus IOC / FOK / Post-Only / Reduce-Only flags. ## Why a separate product The Raydium AMMs (spot) and Raydium Perps (perpetual futures) share a brand and a UI entry point but almost nothing at the protocol level: | | Raydium spot AMMs | Raydium Perps | | ---------------------------------------------------------------------------- | -------------------------------- | ---------------------------------------------------------- | | Venue | On-chain pools on Solana | Off-chain CLOB operated by Orderly | | Matching engine | AMM curve (CPMM / CLMM / AMM v4) | Central limit order book | | Settlement | On-chain at swap time | Off-chain; deposits/withdrawals on-chain | | Price discovery | AMM curve + arbitrage | Order book + external prices | | Token universe | Any SPL / Token-2022 mint | Listed perp markets only (BTC, ETH, SOL, and other majors) | | Program IDs in [`reference/program-addresses`](/reference/program-addresses) | Yes | No — see Orderly's contracts | | Raydium SDK v2 coverage | Full | Not covered | | Raydium bug bounty | Yes | Routed through Orderly | The rest of this docs site focuses almost entirely on the spot AMMs and the programs in the Raydium org's GitHub. Perps is documented at product-surface level — order types, fees, collateral, restrictions — so readers understand what it is and when to use it, but the deep protocol reference lives on Orderly's side. ## Restricted jurisdictions Raydium Perps is **not available** to residents of the US, Afghanistan, Belarus, the Central African Republic, DRC, DPRK (North Korea), the Crimea / Donetsk / Luhansk regions of Ukraine, Cuba, Iran, Libya, Somalia, Sudan, South Sudan, Syria, Yemen, Zimbabwe, or any other jurisdiction in which the Raydium Protocol is prohibited. Integrators embedding the perps UI or API must respect this list. ## Chapter contents What perps are, going long vs short, leverage, cross-margin, liquidation risk. Market, Limit, Stop (market / limit), Scale, plus IOC / FOK / Post-Only / Reduce-Only flags. Supported assets, per-chain global limits, per-user limits, USDC-settled P\&L. Maker 0 bps, volume-tiered taker fees, withdrawal fee, Orderly's cut. ## Integration notes * The spot Raydium SDK (`@raydium-io/raydium-sdk-v2`) does **not** cover perps. For programmatic access use [Orderly's SDKs](https://orderly.network/docs/build-on-omnichain/evm-api/introduction) (TypeScript, REST, WebSocket). * There is no "route a swap through a perp position" primitive. A client wanting both spot and perp exposure builds the two transactions independently. * Orderly's risk parameters (leverage caps, liquidation thresholds, collateral limits) change over time. Always fetch the live parameters from Orderly's API before quoting risk to a user. ## Where to go next * [Trading basics](/products/perps/trading-basics) * [Order types](/products/perps/order-types) * [Collateral](/products/perps/collateral) * [Fees](/products/perps/fees) Sources: * Orderly Network developer docs (Raydium Perps is a white-labeled Orderly deployment). * On-chain USDC settlement vault (for deposits/withdrawals). # Order types Source: https://docs.raydium.io/products/perps/order-types The five order shapes Raydium Perps supports — Market, Limit, Stop (market / limit), Scale — plus the four order flags: IOC, FOK, Post-Only, Reduce-Only. ## The five order types **Market.** Buys or sells immediately at the best available prices in the order book. Matches against resting orders until your size is filled. If the book cannot fill the full amount, the unfilled remainder is canceled (not rested). Use for immediate execution at the cost of slippage. **Limit.** Buys or sells at a specific price you choose (the **limit price**) or better. A limit order rests on the book until matched or canceled. Use when price, not speed, is the priority. **Stop Market.** Inactive until the market prints your **trigger (stop) price**. When triggered, it is submitted as a market order and fills at the next available prices if sufficient liquidity is available. Use when immediate execution is more important than price control. **Stop Limit.** Inactive until the market prints your trigger price. When triggered, it is submitted as a limit order at your specified limit price and will only fill if the book reaches that price. Use when you want a stop that never executes worse than a floor/ceiling, accepting that it may not fill at all in a gap move. **Scale.** Splits a single larger order into multiple smaller limit orders distributed across a price range. Use for gradual entry or exit rather than punching the whole size through at one price. ## The four order flags These can be attached to Limit or Stop-Limit orders to change execution semantics: **IOC — Immediate-or-Cancel.** Fills as much as possible instantly at the limit price (or better). Any unfilled portion is canceled rather than resting on the book. Use when you want immediate execution with a price ceiling. **FOK — Fill-or-Kill.** Either fills the **entire** order instantly at the limit price (or better), or cancels with no partial fill. Use when partial fills are worse than no fill — e.g., the position only makes sense at full size. **Post-Only.** Ensures the order adds liquidity (maker side). If it would match against a resting order on placement (i.e. it would be a taker), it is canceled instead of crossing. Use when you want maker execution rather than taker execution. **Reduce-Only.** Prevents the order from *increasing* your position. It can only close or reduce an existing position in that market. If you have no position, or the order would increase it, the order does not execute. Use for automated take-profit / stop-loss logic where you never want to accidentally flip into the opposite direction. ## Combining type + flags Not every combination is allowed. The common ones: | Order | Flag | Typical use | | ----------- | ----------- | ------------------------------------------------ | | Limit | — | Resting maker quote | | Limit | Post-Only | Strict maker quote (no accidental take) | | Limit | IOC | Aggressive take with price cap | | Limit | FOK | All-or-nothing take | | Limit | Reduce-Only | Exit at a price | | Market | Reduce-Only | Emergency close at any price | | Stop Market | Reduce-Only | Stop-loss that prioritizes exit without flipping | | Stop Limit | Reduce-Only | Stop-loss with price floor, no flip | | Scale | Post-Only | Passive ladder entry | Post-Only and Reduce-Only can be combined on the same order. ## Fee implications Order type and flags interact with fee tier: * **Market orders always pay taker.** No way to get maker rebates on a market order. * **Limit orders** pay maker if they rest on the book and get matched later; pay taker if they match immediately on placement. * **Post-Only** targets maker fees by rejecting any placement that would cross. * **Stop orders** pay the fee class of whatever they convert into when triggered (stop-market → taker; stop-limit → maker unless it crosses). Volume-based taker tiers and the flat 0 bps maker fee are documented in [fees](/products/perps/fees). ## Where to go next * [Trading basics](/products/perps/trading-basics) — leverage, cross-margin, liquidation. * [Collateral](/products/perps/collateral) — deposit types and limits. * [Fees](/products/perps/fees) — maker / taker schedule. Sources: * Orderly Network order-type documentation. * Raydium Perps trading interface at perps.raydium.io. # Trading basics Source: https://docs.raydium.io/products/perps/trading-basics Perpetual futures primer: going long / short, leverage mechanics, cross-margin, one-way mode, liquidation and slippage risk. This page is an on-ramp for readers new to perps. If you already trade perpetuals on another venue, skip to [order types](/products/perps/order-types). ## What a perpetual future is A **perpetual future** ("perp") is a leveraged derivative contract that tracks the price of an underlying asset but has no expiry date. You never hold the underlying — your position is a bookkeeping entry in the venue's ledger that gains or loses value with the reference price. Two practical consequences: * You can **go long** (profit if price rises) or **go short** (profit if price falls). Spot only offers the long direction without borrowing. * You can keep a position open indefinitely as long as you maintain enough collateral. There is no settlement date that forces closure. ## Leverage Leverage is the ratio between your exposure (notional value of the position) and the collateral committed to it. Example: BTC at 90,000 USDC. * Spot: 1 BTC costs 90,000 USDC of capital. * Perp at **10× leverage**: the same 1 BTC exposure can be opened with \~9,000 USDC of collateral, with fees added on top. * Perp at **50× leverage**: \~1,800 USDC. * Raydium Perps supports up to 100× on select markets. Leverage multiplies both directions. A 1% adverse move at 50× leverage is a 50% hit to the collateral backing that position. ## Cross-margin Raydium Perps operates in **cross-margin** mode only: every unit of collateral in your perps account is shared across every open position. There is no "isolate this position" toggle. Implications: * A profitable position can subsidize a losing one, delaying liquidation. * Conversely, a large loss in one market can eat into collateral that was supporting other positions and cascade-liquidate them. * If you want segregated risk between strategies, use **different wallets** — each wallet has its own perps account. ## One-way mode You cannot simultaneously hold a long and a short on the same market. Placing an opposite-side order against an existing position will either reduce it or flip its direction. Example: long 1 BTC-PERP. Placing a market sell of 0.3 BTC-PERP reduces the position to 0.7 BTC long. Placing a market sell of 2 BTC-PERP closes the long and opens a 1 BTC short. To hold genuinely uncorrelated long and short exposure on the same underlying, use two different wallets. ## Liquidation Each open position carries a **maintenance-margin requirement** — the minimum collateral-to-notional ratio that keeps the position alive. If your cross-margin ratio drops below that threshold (price moves against you, funding charges accrue, or another position's P\&L eats your buffer), the liquidation engine: 1. Partially or fully closes positions at market, starting with the largest risk contributor. 2. Charges a liquidation fee that goes to the insurance fund. 3. Stops once your margin ratio is back above threshold, or all positions are closed. Liquidations in fast markets can fill well beyond the nominal liquidation price — a volatile market can blow through multiple price bands before the engine finishes. ## Funding rate Perps stay anchored to the spot reference price through a **funding rate**: a periodic payment between longs and shorts. When the perp trades above spot, longs pay shorts; below spot, shorts pay longs. The exact formula, interval, and cap are Orderly parameters; fetch them live from the market-data endpoint rather than hardcoding. Funding is settled against your USDC balance (not your collateral asset), even if your collateral is SOL or ETH. ## Slippage Market orders in volatile conditions can fill at materially worse prices than the last-traded price suggests. Slippage risk goes up with: * Thinner order books (illiquid markets or low-conviction sides). * Larger order size relative to book depth. * Wider bid-ask spreads, typical during news events or overnight hours. Rule of thumb: a market order sized at more than 20–30% of the opposite side's top-of-book depth should be split or routed as scale / limit orders instead. ## Risk checklist before opening a position * Is my leverage appropriate for this market's volatility? (BTC ≠ a small-cap altcoin perp.) * What's my liquidation price? Most UIs display it; confirm it before confirming the order. * Is funding currently against my direction? A persistently expensive funding rate can erode a position faster than the price move earns it back. * Am I sizing on the assumption of zero slippage? Budget 10–50 bps slippage on entries into thin markets. ## Where to go next * [Order types](/products/perps/order-types) — every order shape the venue supports. * [Collateral](/products/perps/collateral) — what you can deposit and the per-asset caps. * [Fees](/products/perps/fees) — maker / taker schedule plus withdrawal fees. Sources: * Orderly Network trading documentation (underlying venue). * Raydium Perps product surface at perps.raydium.io. # Routing accounts Source: https://docs.raydium.io/products/routing/accounts How accounts are passed per hop on the AMM Routing program: user ATAs for every intermediate token, then the per-pool account block for each leg of the route. ## Account layout: user ATAs all the way through Every enabled swap variant routes intermediate tokens through **user-controlled ATAs**. The user owns the input ATA, every intermediate ATA, and the final output ATA. There is no shared / router-owned intermediate token account in the active surface. Properties: * User owns one ATA per intermediate token. * User provides every ATA in the accounts list. * Each intermediate ATA must already exist (initialize it with `CreateSyncNative` for wSOL, or via the SPL Associated Token Account program for any other mint, before routing). * The router transfers out of one ATA and into the next pool's vault on each hop. * Each intermediate ATA ends each route with the same balance it started with — the route consumes whatever the previous hop produced. Example flow for route `USDC → SOL → STEP`: ``` Accounts list: [ USDC_input_ata (user, signer), SOL_intermediate_ata (user), STEP_output_ata (user), token_program, amm_program_1, *amm1_accounts, // hop 1: USDC → SOL amm_program_2, *amm2_accounts, // hop 2: SOL → STEP ] Hop 1: USDC_input_ata → AMM1 → SOL_intermediate_ata Hop 2: SOL_intermediate_ata → AMM2 → STEP_output_ata ``` ## Per-hop account layout Each hop's accounts are passed consecutively. The router identifies the child program by reading the first account in each hop's block (the program ID), then dispatches to the correct handler. For each hop, the router expects accounts grouped as: ``` [ program_id, // Identifies which pool program (AMM v4, CPMM, CLMM, Stable) *child_accounts, // All accounts required by that pool's swap instruction ] ``` The child accounts vary by pool type: ### AMM v4 hop Approximately 18 accounts: pool, authority, vaults, mints, OpenBook market accounts (kept on the account list for backwards compatibility even though AMM v4's OpenBook integration is no longer active), token programs. See [`products/amm-v4/accounts`](/products/amm-v4/accounts) for the full list. ### CPMM hop Approximately 11–13 accounts: pool state, authority, vaults (2), mints (2), token programs, system program, associated token program. See [`products/cpmm/accounts`](/products/cpmm/accounts). ### CLMM hop Approximately 15+ accounts: pool, tick arrays, vaults, mints, observation state, signer, token programs. See [`products/clmm/accounts`](/products/clmm/accounts). ### Stable hop Similar to AMM v4. See [`products/stable/accounts`](/products/stable/accounts). ## Token flow and ATA ownership * The caller signs with `user_input_ata`. * The caller **must own** all input, intermediate, and output ATAs. The router will reject the transaction if any intermediate ATA's owner is not the signer. * The caller's `user_input_ata` balance must be sufficient for the first hop's input (`amount_in` for tag 0 / 8, or `maximum_amount_in` for tag 1 / 9). * Each intermediate ATA must already exist on-chain. If it does not, create it ahead of time — typically via the [SPL Associated Token Account program](https://spl.solana.com/associated-token-account), or with `CreateSyncNative` (tag 5) for a wSOL ATA. ## The CreateSyncNative instruction If you need to route through wrapped SOL and do not want to manually create and sync a wSOL ATA, use [`CreateSyncNative`](/products/routing/instructions#createsyncnative-tag-5) (tag 5): ``` CreateSyncNative(amount) ``` This creates a wSOL ATA under the caller's wallet, transfers `amount` of SOL into it via the System Program, and syncs it in one instruction. Useful for initializing a fresh wSOL ATA before routing. ## The CloseTokenAccount instruction After a route completes you may want to close any intermediate ATA — most commonly a wSOL ATA — to reclaim rent. Use [`CloseTokenAccount`](/products/routing/instructions#closetokenaccount-tag-6) (tag 6): ``` CloseTokenAccount ``` The token account must have a zero token balance before close; the router will not auto-empty it for you. ## Where to go next * [`products/routing/instructions`](/products/routing/instructions) — argument shapes and account-list order per instruction. * [`products/routing/code-demos`](/products/routing/code-demos) — building a route in TypeScript. * [`reference/program-addresses`](/reference/program-addresses) — child program IDs. # Routing code demos Source: https://docs.raydium.io/products/routing/code-demos TypeScript examples: SDK-based multi-hop swap through the router, and raw instruction building. ## Version info * **SDK:** `@raydium-io/raydium-sdk-v2@0.2.42-alpha` * **Network:** mainnet-beta * **Router program ID:** `routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS` * **Verified:** April 2026 ## Example 1: SDK-based routing Source: [`src/trade/routeSwap.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/trade/routeSwap.ts) The Raydium SDK abstracts route building. Use the SDK's trade functions to compose a multi-hop route and execute it through the router automatically. ### Setup ```typescript theme={null} import { Raydium, Router, Token, TokenAmount, toApiV3Token, toFeeConfig, TxVersion, } from "@raydium-io/raydium-sdk-v2"; import { Connection, Keypair, PublicKey, } from "@solana/web3.js"; import { NATIVE_MINT, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"; const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); const wallet = Keypair.fromSecretKey(/* your key bytes */); const raydium = await Raydium.load({ connection, owner: wallet, // the SDK will sign with this Keypair cluster: "mainnet", disableFeatureCheck: true, blockhashCommitment: "finalized", }); // The router needs a chain-time anchor for CLMM hops. await raydium.fetchChainTime(); ``` ### Build a multi-hop swap Routing in `@raydium-io/raydium-sdk-v2` is exposed on `raydium.tradeV2`. The end-to-end shape — fetching pool data, computing routes, ranking by output, and building the swap transaction — is shown below; this matches the canonical example in [`raydium-sdk-V2-demo/src/trade/routeSwap.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/trade/routeSwap.ts). ```typescript theme={null} // 1. Choose input/output mints and amount. const inputMint = NATIVE_MINT; // wSOL const outputMint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC const inputAmount = "8000000"; // 0.008 SOL (lamports) // 2. Fetch all pool basic info. Cache this aggressively — it's a large RPC fan-out. const poolData = await raydium.tradeV2.fetchRoutePoolBasicInfo(); // 3. Enumerate every viable route across AMM v4, CPMM, CLMM, and Stable AMM. const routes = raydium.tradeV2.getAllRoute({ inputMint, outputMint, ...poolData, }); // 4. Hydrate routes with the live RPC state (reserves, ticks, mint info). const { routePathDict, mintInfos, ammPoolsRpcInfo, ammSimulateCache, clmmPoolsRpcInfo, computeClmmPoolInfo, computePoolTickData, computeCpmmData, } = await raydium.tradeV2.fetchSwapRoutesData({ routes, inputMint, outputMint, }); // 5. Compute output amounts for every candidate route. Result is sorted descending by output. const swapRoutes = raydium.tradeV2.getAllRouteComputeAmountOut({ inputTokenAmount: new TokenAmount( new Token({ mint: inputMint.toBase58(), decimals: mintInfos[inputMint.toBase58()].decimals, isToken2022: mintInfos[inputMint.toBase58()].programId.equals(TOKEN_2022_PROGRAM_ID), }), inputAmount, ), directPath: routes.directPath.map( (p) => ammSimulateCache[p.id.toBase58()] || computeClmmPoolInfo[p.id.toBase58()] || computeCpmmData[p.id.toBase58()], ), routePathDict, simulateCache: ammSimulateCache, tickCache: computePoolTickData, mintInfos, outputToken: toApiV3Token({ ...mintInfos[outputMint.toBase58()], programId: mintInfos[outputMint.toBase58()].programId.toBase58(), address: outputMint.toBase58(), freezeAuthority: undefined, mintAuthority: undefined, extensions: { feeConfig: toFeeConfig(mintInfos[outputMint.toBase58()].feeConfig) }, }), chainTime: Math.floor(raydium.chainTimeData?.chainTime ?? Date.now() / 1000), slippage: 0.005, // 0.5% epochInfo: await raydium.connection.getEpochInfo(), }); const targetRoute = swapRoutes[0]; if (!targetRoute) throw new Error("no swap routes were found"); // 6. Resolve the pool keys for the chosen route. const poolKeys = await raydium.tradeV2.computePoolToPoolKeys({ pools: targetRoute.poolInfoList, ammRpcData: ammPoolsRpcInfo, clmmRpcData: clmmPoolsRpcInfo, }); // 7. Build, sign, and execute the swap transaction(s). const { execute, transactions } = await raydium.tradeV2.swap({ routeProgram: Router, txVersion: TxVersion.V0, swapInfo: targetRoute, swapPoolKeys: poolKeys, ownerInfo: { associatedOnly: true, checkCreateATAOwner: true, }, computeBudgetConfig: { units: 600_000, microLamports: 465_915, }, }); // `sequentially: true` is important — the first transaction may need to create // intermediate ATAs that subsequent transactions rely on. const { txIds } = await execute({ sequentially: true }); console.log("txIds:", txIds); ``` ### Expected behavior The SDK handles: * Route discovery across AMM v4, CPMM, CLMM, and Stable AMM. * Account derivation (pool states, vaults, observation accounts, ATA pre-creation). * Instruction packing for the router program (`Router`) when the route is multi-hop, or a direct pool swap when a single pool already gives the best price. * Slippage enforcement via the `slippage` parameter on `getAllRouteComputeAmountOut`. `raydium.tradeV2.swap` may return more than one `transaction` — the first commonly initializes intermediate ATAs and the second performs the swap itself. Always pass `sequentially: true` to `execute()` so they confirm in order. *** ## Example 2: Raw instruction building (Rust-like pseudocode) If you need finer control or are building a program that CPIs into the router, construct instructions manually. The example below uses **tag 8 (`SwapBaseIn`)** — the recommended Current variant — and routes through user-owned ATAs end to end. ### Scenario: USDC → SOL (CPMM) → mSOL (CPMM) #### Step 1: Derive the user's ATAs ```typescript theme={null} import { getAssociatedTokenAddressSync } from "@solana/spl-token"; import { PublicKey } from "@solana/web3.js"; const ROUTER_PROGRAM_ID = new PublicKey( "routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS" ); const USDC_MINT = new PublicKey( "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ); const SOL_MINT = new PublicKey( "So11111111111111111111111111111111111111112" ); const MSOL_MINT = new PublicKey( "mSoL1MK4LCEDcTYVhPnD6DHK4PMGQ8WyXDEMXbnQAaW" ); // User input / intermediate / output ATAs — all owned by the caller. const user_usdc_ata = getAssociatedTokenAddressSync(USDC_MINT, wallet.publicKey); const user_sol_ata = getAssociatedTokenAddressSync(SOL_MINT, wallet.publicKey, true); // wSOL: allowOwnerOffCurve const user_msol_ata = getAssociatedTokenAddressSync(MSOL_MINT, wallet.publicKey); // If the user's intermediate / output ATAs do not yet exist, create them up // front via the Associated Token Account program (or use CreateSyncNative for // the wSOL ATA on tag 5). ``` #### Step 2: Gather accounts for each hop Hop 1 is USDC/SOL on CPMM. Hop 2 is SOL/mSOL on CPMM. ```typescript theme={null} // Hop 1: USDC/SOL swap on CPMM const CPMM_PROGRAM_ID = new PublicKey( "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C" ); // Fetch pool state for USDC/SOL const usdc_sol_pool = await connection.getParsedAccountInfo( USDC_SOL_POOL_STATE ); // Pool state, authority, vaults, mints, token programs, etc. // (see products/cpmm/accounts for the full 11-account list) const hop1_accounts = [ CPMM_PROGRAM_ID, // Identifies this as CPMM USDC_SOL_POOL_STATE, CPMM_AUTHORITY, USDC_SOL_VAULT_TOKEN0, USDC_SOL_VAULT_TOKEN1, USDC_MINT, SOL_MINT, LP_MINT, OBSERVATION_STATE, TOKEN_PROGRAM, ASSOCIATED_TOKEN_PROGRAM, SYSTEM_PROGRAM, ]; // Hop 2: SOL/mSOL swap on CPMM const sol_msol_pool = await connection.getParsedAccountInfo( SOL_MSOL_POOL_STATE ); const hop2_accounts = [ CPMM_PROGRAM_ID, // Identifies this as CPMM SOL_MSOL_POOL_STATE, CPMM_AUTHORITY, SOL_MSOL_VAULT_TOKEN0, SOL_MSOL_VAULT_TOKEN1, SOL_MINT, MSOL_MINT, LP_MINT_2, OBSERVATION_STATE_2, TOKEN_PROGRAM, ASSOCIATED_TOKEN_PROGRAM, SYSTEM_PROGRAM, ]; ``` #### Step 3: Build the instruction ```typescript theme={null} import BN from "bn.js"; // Arguments const amount_in = new BN(1_000_000_000); // 1000 USDC (6 decimals) const minimum_amount_out = new BN(500_000_000_000); // 500 mSOL (9 decimals) // No CLMM hops, so limit_prices is empty const limit_prices: BN[] = []; // Pack arguments let data = Buffer.alloc(1024); let offset = 0; // Tag 8: SwapBaseIn (Current variant; empty limit_prices is allowed) data[offset++] = 8; // amount_in (u64) data.writeBigUInt64LE(BigInt(amount_in.toString()), offset); offset += 8; // minimum_amount_out (u64) data.writeBigUInt64LE(BigInt(minimum_amount_out.toString()), offset); offset += 8; // limit_prices (VecDeque) — empty (no CLMM hops in this route). // Tag 8 accepts an empty deque without error. Write zero entries (no u128s). data = data.slice(0, offset); // Construct accounts list — every intermediate ATA is user-owned. const accounts = [ { pubkey: user_usdc_ata, isSigner: true, isWritable: true }, { pubkey: user_sol_ata, isSigner: false, isWritable: true }, { pubkey: user_msol_ata, isSigner: false, isWritable: true }, ...hop1_accounts.map((pk, i) => ({ pubkey: pk, isSigner: false, isWritable: i === 0 ? false : true, // program ID is not writable })), ...hop2_accounts.map((pk, i) => ({ pubkey: pk, isSigner: false, isWritable: i === 0 ? false : true, })), ]; const instruction = new TransactionInstruction({ programId: ROUTER_PROGRAM_ID, keys: accounts, data, }); ``` #### Step 4: Send transaction ```typescript theme={null} const { blockhash } = await connection.getLatestBlockhash(); const tx = new VersionedTransaction( new TransactionMessage({ instructions: [instruction], payerKey: wallet.publicKey, recentBlockhash: blockhash, }).compileToV0Message() ); tx.sign([wallet]); const sig = await connection.sendTransaction(tx); await connection.confirmTransaction(sig); ``` *** ## Example 3: Error handling Common errors and how to recover: ### ExceededSlippage The output was less than `minimum_amount_out`. Retry with higher slippage tolerance or re-quote the route. ```typescript theme={null} try { const sig = await connection.sendTransaction(tx); } catch (error) { if (error.message.includes("ExceededSlippage")) { console.log( "Route price moved. Re-quote and rebuild transaction." ); } } ``` ### SqrtPriceX64 (CLMM) A CLMM hop's price drifted outside the `limit_prices` bounds. Update the bounds and retry. ### InvalidOwner An intermediate or output ATA is not owned by the caller. The router validates ownership on every slot; ensure each ATA you pass was derived from the user's wallet (not from any other authority). *** ## Tips and best practices ### Pre-create intermediate ATAs Before routing through a new intermediate token for the first time, create the user's ATA so the route does not fail validation: ```typescript theme={null} import { createAssociatedTokenAccountInstruction } from "@solana/spl-token"; const createAtaIx = createAssociatedTokenAccountInstruction( wallet.publicKey, // payer user_sol_ata, // ATA to create wallet.publicKey, // owner — always the user SOL_MINT, ); // Bundle createAtaIx into the same transaction as the first route, or send it // once up front; the rent (~0.002 SOL) is reclaimable later via tag 6 // (CloseTokenAccount) once the ATA is empty. ``` For wSOL specifically, prefer [`CreateSyncNative`](/products/routing/instructions#createsyncnative-tag-5) (tag 5) — it creates the ATA, transfers the SOL, and syncs in one instruction. ### Quote before executing Always query the pools and compute expected output before building the instruction: ```typescript theme={null} // Pseudocode: get the current pool state and compute price const pool1State = await connection.getAccountInfo(USDC_SOL_POOL_STATE); const amount_out_hop1 = computeSwapOutput( amount_in, pool1State, USDC_MINT, SOL_MINT ); const pool2State = await connection.getAccountInfo(SOL_MSOL_POOL_STATE); const amount_out_hop2 = computeSwapOutput( amount_out_hop1, pool2State, SOL_MINT, MSOL_MINT ); const minimum_amount_out = amount_out_hop2 * 0.99; // 1% slippage ``` ### Use the newer instruction variants (8–9) Tags 8 and 9 (`SwapBaseIn` and `SwapBaseOut`) are more forgiving with `limit_prices`. Prefer them over the legacy variants if you don't need CLMM price validation. ```typescript theme={null} // Tag 8: SwapBaseIn with optional limit_prices data[offset++] = 8; // ... rest of packing; limit_prices deque can be empty ``` *** ## Where to go next * [`products/routing/instructions`](/products/routing/instructions) — full instruction reference. * [`products/routing/accounts`](/products/routing/accounts) — account derivation details. * [`products/cpmm/code-demos`](/products/cpmm/code-demos) — CPMM swap examples for comparison. * [`reference/program-addresses`](/reference/program-addresses) — canonical program IDs and devnet addresses. Sources: * [`@raydium-io/raydium-sdk-v2` on npm](https://www.npmjs.com/package/@raydium-io/raydium-sdk-v2) * [`raydium-io/raydium-sdk-V2`](https://github.com/raydium-io/raydium-sdk-V2) (open source) * [`raydium-io/raydium-sdk-V2-demo`](https://github.com/raydium-io/raydium-sdk-V2-demo) — official end-to-end demos including `routeSwap.ts` * AMM Routing program source is not publicly available; verify the program ID against the live API and the on-chain bytecode # Routing fees Source: https://docs.raydium.io/products/routing/fees The router takes no fee. Each hop pays its pool's fee. Compute cost scales linearly with route depth. ## Router fee The AMM Routing program charges **no fee of its own**. It is a pure orchestrator. All fees are paid directly to the pools you route through: * Each AMM v4 hop pays the AMM v4 fee (0.25% split). * Each CPMM hop pays the CPMM fee (configurable, typically 0.25% split). * Each CLMM hop pays the CLMM fee (configurable per pool). * Each stable hop pays the stable-swap fee (like AMM v4, typically 0.25% split). ## Total effective fees on a route If you route through N pools, your total fee is the sum of all N hops' fees, compounded. **Example:** Route: USDC → SOL (0.25% fee) → STEP (0.25% fee) ``` Input: 1000 USDC Hop 1 (USDC/SOL): 0.25% fee Fee: 2.5 USDC Output to hop 2: ~997.5 USDC of buying power Hop 2 (SOL/STEP): 0.25% fee Fee: ~2.49 STEP-worth Output: ~995 STEP-worth Total cost: ~0.5% (not exactly 0.5%, since fees compound) ``` See the respective pool's fee documentation for exact splits: * [`products/amm-v4/fees`](/products/amm-v4/fees) * [`products/cpmm/fees`](/products/cpmm/fees) * [`products/clmm/fees`](/products/clmm/fees) (if available) * [`products/stable/fees`](/products/stable/fees) (if available) ## Compute cost The router's compute cost is **linear in the number of hops**: * **Fixed overhead:** \~5k CU for the router's dispatch logic and account validation. * **Per-hop cost:** \~10k–50k CU depending on the pool type and complexity: * CPMM: \~20k–30k CU * AMM v4: \~40k–60k CU (includes OpenBook validation) * CLMM: \~50k–100k CU (tick-math heavy) * Stable: \~30k–40k CU * **Total transaction budget:** 1.4M CU on mainnet. A route with 10 CPMM hops would cost \~305k CU, leaving room for other operations. See the individual pool's documentation for exact CU costs. ## Account initialization overhead Every enabled swap variant routes intermediate tokens through user-controlled ATAs. On each intermediate hop, you must pre-create and provide a user-owned ATA: * **If the ATA doesn't exist:** ATA program init costs \~5k CU + \~0.00203928 SOL rent per ATA. Initialize it with the SPL Associated Token Account program (or, for wSOL, with [`CreateSyncNative`](/products/routing/instructions#createsyncnative-tag-5) tag 5). * **If the ATA already exists:** no extra overhead. For a 5-hop route with 4 intermediate tokens, you would need 4 new ATAs if starting fresh = \~20k CU + \~0.00816 SOL. You can recover the rent of any temporary ATA — most commonly a wSOL ATA used for a single route — by closing it with [`CloseTokenAccount`](/products/routing/instructions#closetokenaccount-tag-6) (tag 6) once it has zero balance. ## Where to go next * [`products/routing/instructions`](/products/routing/instructions) — which instruction to use for your route shape. * [`products/routing/code-demos`](/products/routing/code-demos) — examples of quoting routes before execution. * [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev) — broader fee and MEV strategy. # AMM Routing Source: https://docs.raydium.io/products/routing/index A program that chains swaps across multiple pools (AMM v4, CPMM, CLMM, Stable) in a single transaction, with user-controlled intermediate ATAs. ## What it is The AMM Routing program is a CPI router that executes multi-hop swaps across Raydium's AMM pools in one on-chain transaction. Instead of bundling N child-program CPIs in your client code, you can invoke the router once and pass the route as a list of accounts. The router dispatches each hop to the correct pool program (AMM v4, CPMM, CLMM, or Stable) and chains the output of one hop as the input to the next. **Program ID:** see [`reference/program-addresses`](/reference/program-addresses). **Devnet:** `DRaybByLpbUL57LJARs3j8BitTxVfzBg351EaMr5UTCd`. **Account model:** every intermediate token flows through a **user-owned ATA**. The user signs with their input ATA and provides each intermediate ATA in the accounts list. The recommended swap entrypoints are tag 8 (`SwapBaseIn`) and tag 9 (`SwapBaseOut`), which accept an empty `limit_prices` deque when no CLMM hop is involved. ## Chapter contents What the router does, why it exists, and when you should use it vs. client-side stitching. User-ATA layout, per-hop account block, and how the router dispatches by reading the program ID slot. The router does no math. Each hop prices off its own curve. Slippage compounds over hops; `limit_prices` for CLMM. Reference for the six enabled instruction variants: exact-input / exact-output Current (8 / 9) and Legacy (0 / 1) swaps, plus the wSOL utilities (5 / 6). No router fee. Each hop pays its underlying program's fee. CU cost scales linearly with hop count. TypeScript examples using the SDK and raw instruction building. ## When to read this * You need to execute a route with 2+ hops in one transaction. * You are implementing a program that CPIs into the router. * You are an aggregator deciding between client-side stitching and on-chain routing. ## Where to go next * [`products/routing/overview`](/products/routing/overview) — the design and motivation. * [`products/routing/instructions`](/products/routing/instructions) — the full instruction reference. * [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev) — broader routing strategy. * [`integration-guides/aggregator`](/integration-guides/aggregator) — when to use the router in an aggregator context. Sources: * [`reference/program-addresses`](/reference/program-addresses) for the canonical program ID * `raydium-io/raydium-programs/raydium-route` for the source code # Routing instructions Source: https://docs.raydium.io/products/routing/instructions Reference for the AMM Routing instructions enabled on mainnet — six variants covering exact-input / exact-output swaps and the wSOL utilities. This page is the authoritative instruction reference for the AMM Routing program. For code examples, see [`products/routing/code-demos`](/products/routing/code-demos). For error meanings, see [`reference/error-codes`](/reference/error-codes). ## Instruction summary | Tag | Discriminator | Exact | Variant | | --- | ---------------------------- | ------ | ------- | | 0 | `SwapBaseInWithUserAccount` | Input | Legacy | | 1 | `SwapBaseOutWithUserAccount` | Output | Legacy | | 5 | `CreateSyncNative` | — | Utility | | 6 | `CloseTokenAccount` | — | Utility | | 8 | `SwapBaseIn` | Input | Current | | 9 | `SwapBaseOut` | Output | Current | **Legend:** * **Exact:** which amount is fixed by the caller (Input = exact-input `amount_in`; Output = exact-output `amount_out`). * **Variant:** Legacy instructions require a non-empty `limit_prices` deque even if no CLMM hop is in the route. Current instructions (8 / 9) treat an empty `limit_prices` as "no checks", which is the recommended path for new code. All swap variants route intermediate tokens through **user-controlled ATAs** — the user owns the input ATA, every intermediate ATA, and the output ATA. For new integrations, use **tag 8 (`SwapBaseIn`)** or **tag 9 (`SwapBaseOut`)** unless you have a specific reason to call a Legacy variant. ## Current swap instructions (recommended) These are the entry points new code should use. Argument structure is the same as the Legacy variants but `limit_prices` may be empty. ### `SwapBaseIn` (tag 8) Exact-input multi-hop swap. The caller fixes `amount_in`; the router executes hop-by-hop and asserts that the final amount lands at or above `minimum_amount_out`. **Arguments** ``` amount_in: u64 minimum_amount_out: u64 limit_prices: VecDeque // optional; empty deque means no per-hop CLMM price check ``` **Accounts** ``` [ W S, // signer; balance >= amount_in W, // one per intermediate hop ... W, W, , , // identifies which AMM family hop 1 is W, ... , , W, ... , ... [repeat per hop] ] ``` The exact account list per hop depends on the underlying AMM program (AMM v4 / CPMM / CLMM / Stable). The router CPIs into each in turn and validates the program ID matches one of the four supported programs. **Pre-conditions** * Caller signs with `user_input_ata`. * `user_input_ata.amount >= amount_in`. * Each intermediate user ATA exists and is owned by the caller. * If any hop is CLMM and you want price-bound enforcement, supply one `limit_prices` entry per CLMM hop. **Post-conditions** * `user_input_ata` balance decreased by `amount_in`. * `user_output_ata` balance increased by ≥ `minimum_amount_out`. * Each intermediate ATA is left with zero net change (the route consumes whatever it produced one hop earlier). **Common errors** * `ExceededSlippage` — final output \< `minimum_amount_out`. * `InvalidInput` — empty route, malformed accounts, or unsupported `pool_program`. * `SqrtPriceX64` — a CLMM hop's price moved outside the supplied `limit_prices` bound (only when `limit_prices` is non-empty). *** ### `SwapBaseOut` (tag 9) Exact-output multi-hop swap. The caller fixes `amount_out`; the router asserts that the actual input does not exceed `maximum_amount_in`. **Arguments** ``` maximum_amount_in: u64 amount_out: u64 limit_prices: VecDeque // optional; empty deque means no per-hop CLMM price check ``` **Accounts** — same structure as tag 8. **Pre-conditions** * Caller signs with `user_input_ata`; balance `>= maximum_amount_in` (worst case). * Each intermediate and the output ATA exist. **Post-conditions** * `user_input_ata` decreased by the actual amount needed (≤ `maximum_amount_in`). * `user_output_ata` increased by exactly `amount_out`. **Common errors** * `ExceededSlippage` — required input exceeds `maximum_amount_in`. * `InvalidInput`, `SqrtPriceX64` — as for tag 8. *** ## Legacy swap instructions These older variants are still callable on the live program and are documented here for completeness. Prefer tag 8 / tag 9 for new code; both Legacy variants below require a non-empty `limit_prices` deque even when no CLMM hop is involved, which makes them awkward to use. ### `SwapBaseInWithUserAccount` (tag 0) Exact-input multi-hop swap, identical in shape to tag 8 but with the stricter `limit_prices` requirement. **Arguments** ``` amount_in: u64 minimum_amount_out: u64 limit_prices: VecDeque // required, non-empty ``` **Accounts** — same shape as `SwapBaseIn` (tag 8). All intermediate slots must be ATAs owned by the caller. **Pre-conditions** * Caller signs with `user_input_ata`. * `user_input_ata.amount >= amount_in`. * All intermediate user ATAs exist and are owned by the caller. * `limit_prices` is non-empty (one entry per CLMM hop; pad with placeholder values if no CLMM hop is involved). **Post-conditions** * `user_input_ata` balance decreased by `amount_in`. * `user_output_ata` balance increased by ≥ `minimum_amount_out`. **Common errors** * `ExceededSlippage`. * `InvalidInput` — empty `limit_prices` is rejected on this Legacy variant. * `SqrtPriceX64`. *** ### `SwapBaseOutWithUserAccount` (tag 1) Exact-output swap, the Legacy counterpart to `SwapBaseOut` (tag 9). **Arguments** ``` maximum_amount_in: u64 amount_out: u64 limit_prices: VecDeque // required, non-empty ``` **Accounts** — same shape as tag 0 / tag 9. **Pre-conditions** * Caller signs with `user_input_ata`. * `user_input_ata.amount >= maximum_amount_in`. * All intermediate user ATAs exist and are owned by the caller. * `limit_prices` is non-empty. **Post-conditions** * `user_input_ata` decreased by the actual amount needed (≤ `maximum_amount_in`). * `user_output_ata` increased by exactly `amount_out`. **Common errors** * `ExceededSlippage`. * `InvalidInput`. * `SqrtPriceX64`. *** ## Utility instructions ### `CreateSyncNative` (tag 5) Create (if missing) and sync a wSOL ATA in one step. Convenient when wrapping SOL inline alongside a swap. **Arguments** ``` amount: u64 // SOL to wrap (lamports) ``` **Accounts** ``` [ W, // ATA for wSOL; created if missing W S, // signer; SOL is debited from here , , , , ] ``` **Effect** * Creates `user_wsol_ata` if it does not yet exist. * Transfers `amount` lamports from the signer's native SOL balance to the ATA. * Calls `SyncNative` on the ATA so its token balance reflects the new lamports. **Common errors** * `InvalidOwner` — `user_wsol_ata`'s owner is not the signer. *** ### `CloseTokenAccount` (tag 6) Close a token account and return its rent to the destination wallet. Pairs with `CreateSyncNative`: after a wSOL-leg swap, call `CloseTokenAccount` to recover the rent that backed the wSOL ATA. **Arguments** — none. **Accounts** ``` [ W, W, S, , ] ``` **Effect** * Closes `token_account_to_close`. * Transfers the rent-exempt lamport balance (\~0.00203928 SOL on mainnet for a vanilla SPL Token account) to `destination_for_rent`. * The token account must have zero token balance. **Common errors** * `InvalidOwner` — caller is not the ATA owner. * Token account balance is non-zero. *** ## Where to go next * [`products/routing/code-demos`](/products/routing/code-demos) — building each of these instructions in TypeScript. * [`products/routing/accounts`](/products/routing/accounts) — per-AMM dispatch keys and per-hop account layout. * [`reference/error-codes`](/reference/error-codes) — full `RouteError` list. # Routing math Source: https://docs.raydium.io/products/routing/math The router performs no pricing math itself; each hop delegates to its pool program's curve. Multi-hop slippage compounds; CLMM hops use limit_prices. ## The router does no math The routing program **does not implement any pricing logic**. It is a pure orchestrator: it accepts a route, passes accounts to child programs, and chains token flows. Each hop prices off its own pool program's curve: * **AMM v4 hops:** use the constant-product formula (`x · y = k`) with OpenBook hybrid pricing. See [`products/amm-v4/math`](/products/amm-v4/math). * **CPMM hops:** use the constant-product formula with configurable fee tiers. See [`products/cpmm/math`](/products/cpmm/math). * **CLMM hops:** use concentrated-liquidity tick math. See [`algorithms/clmm-math`](/algorithms/clmm-math). * **Stable hops:** use the stable-swap curve for like-kind assets. See [`products/stable/math`](/products/stable/math). The router's only involvement is: 1. Calling each pool's swap instruction via CPI. 2. Collecting the output amount. 3. Passing it as the input amount to the next hop. 4. Checking the final output against the caller's slippage limit. ## Slippage compounding On a multi-hop route, slippage at each hop compounds. A small slippage on hop 1 becomes a larger slippage on hop 2 because the volume into hop 2 is already reduced. **Example:** ``` Route: USDC → SOL → STEP Pool 1 (USDC / SOL): Input: 1000 USDC Slippage: 1% (spot would give 0.5 SOL, but you get 0.495 SOL) Output: 0.495 SOL Pool 2 (SOL / STEP): Input: 0.495 SOL (already reduced) Slippage: 1% (spot would give 495 STEP, but you get 490 STEP) Output: 490 STEP Total effective slippage on USDC → STEP: 1.99%, not 1% + 1% = 2%. ``` When you provide `minimum_amount_out`, the router checks your final output against this global limit. Each hop also checks its own swap against its local fee structure, but the router does not re-quote mid-route—you must pre-compute the route and bake in enough slippage tolerance. ## CLMM hops and limit\_prices For each hop into a CLMM pool, the router checks that the pool's current `sqrt_price_x64` is within a specified bound. The bounds are passed as a `VecDeque` called `limit_prices`: * One `sqrt_price_x64` per CLMM hop in the route. * `sqrt_price_x64` is the tick-based price representation used by CLMM. See [`algorithms/clmm-math`](/algorithms/clmm-math) for the definition. * The router enforces: ``` sqrt_price_lower <= pool.sqrt_price_x64 <= sqrt_price_upper ``` for each CLMM hop, rejecting the swap if the price is out of bounds. ### Instruction variants and limit\_prices * **`SwapBaseInWithUserAccount`, `SwapBaseOutWithUserAccount` (Legacy, tags 0 and 1):** the `limit_prices` VecDeque is **required**. An empty deque is rejected with an error if any hop is a CLMM pool. You must provide one price per CLMM hop, in order. * **`SwapBaseIn`, `SwapBaseOut` (Current, tags 8 and 9):** the `limit_prices` VecDeque is **optional**. An empty deque is silently ignored; no price check is performed. New code should use these. ### Building limit\_prices For a route with M CLMM hops, the deque should contain exactly M entries. Order them by hop: ``` limit_prices = [ sqrt_price_for_first_clmm_hop, sqrt_price_for_second_clmm_hop, ... ] ``` ### When to check limit\_prices The `sqrt_price_x64` is a snapshot of the pool's current price. It changes continuously as swaps execute. You should: 1. Fetch the pool's current state from on-chain. 2. Compute acceptable bounds (e.g., ±0.5% of the current price). 3. Encode those bounds into `limit_prices`. 4. Include the bounds in your router instruction. If the pool's price drifts beyond your bounds before the transaction lands, the router will reject it. ## Fee handling Each pool charges its own fee according to its configuration: * **AMM v4:** 0.25% (fixed) split between LP, protocol, and fund. * **CPMM:** configurable per `AmmConfig` (default 0.25%, split varies by tier). * **CLMM:** configurable per pool, taken off the input amount. * **Stable:** like AMM v4, 0.25% split. The router **takes no fee of its own**. All fee handling is delegated to each child pool. The output from hop N already has that hop's fee deducted. See the individual pool's fee documentation: * [`products/amm-v4/fees`](/products/amm-v4/fees) * [`products/cpmm/fees`](/products/cpmm/fees) * [`products/clmm/fees`](/products/clmm/fees) (if available) * [`products/stable/fees`](/products/stable/fees) (if available) ## Multi-hop accounting example Suppose you route USDC → SOL → STEP across two constant-product pools, each with a 0.25% fee: ``` Input: 1000 USDC Pool 1 (USDC/SOL): Fee taken: ceil(1000 * 0.25%) = 2.5 USDC Net input to curve: 997.5 USDC Curve output (before slippage): 0.5 SOL Slippage margin: assume 1%, so you get ~0.495 SOL Pool 2 (SOL/STEP): Input: 0.495 SOL Fee taken: ceil(0.495 * 0.25%) ≈ 0.001 SOL Net input to curve: 0.494 SOL Curve output: ~494 STEP Slippage margin: 1%, so you get ~489 STEP Final output: ~489 STEP ``` The router verifies: ``` 489 >= minimum_amount_out // specified by caller ``` If false, the entire route fails atomically. ## Precision considerations Like all Solana programs, the router uses integer arithmetic: * All amounts are `u64` (lamports or token smallest units). * Curve calculations use `u128` intermediates where needed to avoid overflow. * Rounding conventions depend on the child program. The router does not re-round. If a hop produces a zero amount due to extreme price ratios (e.g., swapping 1 lamport on a 1B:1 pool), the router propagates that zero to the next hop, which may then reject it as insufficient. See the individual pool's error codes. ## Where to go next * [`products/amm-v4/math`](/products/amm-v4/math) — constant-product math. * [`products/cpmm/math`](/products/cpmm/math) — CPMM constant-product with Token-2022. * [`algorithms/clmm-math`](/algorithms/clmm-math) — concentrated liquidity pricing. * [`products/stable/math`](/products/stable/math) — stable-swap curve. * [`products/routing/code-demos`](/products/routing/code-demos) — examples of quoting before routing. # Routing overview Source: https://docs.raydium.io/products/routing/overview What the AMM Routing program does, why it exists as a separate program, and when to use it instead of client-side instruction stitching. ## One-paragraph summary The AMM Routing program bundles multi-hop swaps into a single on-chain transaction that chains liquidity across pools. You provide a route (a list of pools and intermediate mints) and one instruction with slippage parameters; the router executes all N hops in order, moving output from one pool into the input of the next. No separate on-chain router logic is needed for the price calculation—each hop's fee and curve are handled by its own pool program via CPI—but the router orchestrates account passing and token movement. ## Why a separate router program? Raydium clients and aggregators can always stitch multi-hop swaps together in the client without using the router: build N swap instructions (one per pool) and submit them in a single transaction. Why, then, have a dedicated router program? ### Reasons to use the router 1. **CPI from other programs.** If your own program needs to invoke a route as part of a larger transaction (e.g., a liquidity manager that swaps fees for a target token), CPIing into the router is cleaner than bundling N child CPIs and managing all their accounts in your contract. 2. **Atomic account state.** Every hop's account list is validated in one instruction context. If an intermediate pool's state is corrupted or a limit-price assertion fails, the entire route fails atomically with no partial settlement. 3. **Single instruction composition.** SDKs and frontends can represent a multi-hop route as one logical operation, not as N separate instructions that happen to be consecutive. ### Client-side stitching is still the default For most applications, building separate `Swap` instructions for each pool and submitting them in order is simpler, more composable, and equally valid. The Raydium SDK's `Trade.makeSwapTransaction` and similar flows do exactly this for most routes. The router is an alternative, not a replacement. Use it when: * You are implementing a program that needs to route as part of a larger atomic operation. * You are building an aggregator that wants a single "submit this route" operation. ## How it works A router instruction carries: * **Swap args**: exact input (`amount_in`, `minimum_amount_out`) or exact output (`maximum_amount_in`, `amount_out`). * **Route specification**: a list of `program_id` + child-program accounts for each hop, in order. The router reads the first account in each hop group to determine which program to invoke. * **Limit prices** (for CLMM): a `VecDeque` of `sqrt_price_x64` bounds. Only used for hops into CLMM pools; empty deque is an error for older instruction variants. The router then: 1. **Executes the first hop:** transfers `amount_in` (or calculates the required input for exact-output) to the first pool's input vault, invokes that pool's swap, and collects the output. 2. **Chains subsequent hops:** for each hop N, uses the output from hop N−1 as the input to hop N. 3. **Enforces slippage:** at each CLMM hop, checks `sqrt_price` against the corresponding `limit_price`; at the final hop, checks total output against the global `minimum_amount_out`. Intermediate tokens can flow either through **user-controlled ATAs** (one per hop, slower but transparent) or through a **shared PDA-derived account** (one address for all hops, faster, opaque). ## Delegation of pricing and fees The router **does not compute prices itself**. Each hop delegates to the child program's curve: * **AMM v4**: uses the constant-product formula with OpenBook hybrid pricing. * **CPMM**: uses the constant-product formula with the configured fee tier. * **CLMM**: uses the concentrated-liquidity math with tick-based pricing. * **Stable**: uses the stable-swap curve for like-kind tokens. Fees are charged by each pool according to its own configuration. The router takes no fee of its own. ## When to avoid the router * **Low hop count (1–2 hops).** The account-passing overhead is minimal; just use two separate swap instructions. * **Non-Raydium pools.** The router only knows about the four Raydium pool types. For routes that cross external programs, stitch instructions in your client. * **Conditional routing.** If you need to branch on prices or pool states mid-route, on-chain routing is less flexible than client-side composition. ## Mental model Think of the router as a **transaction-packing utility**. It takes your route specification and packs it into one instruction, one transaction, one compute budget. Each hop internally CPIs into its pool program and handles the curve math there. The router's job is to pass accounts correctly, move tokens between hops, and check slippage. ## Where to go next * [`products/routing/accounts`](/products/routing/accounts) — the route authority PDA and the shared-account pattern. * [`products/routing/instructions`](/products/routing/instructions) — the full instruction API (all 10 variants). * [`products/routing/code-demos`](/products/routing/code-demos) — examples of building routes in TypeScript and raw Rust. * [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev) — broader strategy for multi-hop routing. # Stable AMM accounts Source: https://docs.raydium.io/products/stable/accounts AmmInfo, ModelDataInfo, DataElement, Fees, vault layout, and OpenBook accounts. The full account inventory of a Stable AMM pool. Stable AMM shares the pool-side account structure of AMM v4 (AmmInfo, vaults, authority) and additionally requires a `ModelDataInfo` account that stores the lookup table. This page covers both. ## Inventory A Stable AMM pool binds to exactly one OpenBook market. The full inventory mirrors AMM v4 closely: | Category | Account | Owner | Role | | -------- | ------------------------------------ | -------------- | ----------------------------------------------------------------------------- | | Pool | `AmmInfo` | Stable program | Pool state, references to vaults, OpenBook, and model-data account. | | Pool | `amm_authority` | Stable program | Program-owned PDA that signs vault moves. Shared across all Stable AMM pools. | | Pool | `amm_open_orders` | OpenBook | The pool's OpenBook `OpenOrders` account. | | Pool | `amm_target_orders` | Stable program | Pool-side grid for limit orders. | | Pool | `pool_coin_token_account` | SPL Token | Pool's coin-side vault. | | Pool | `pool_pc_token_account` | SPL Token | Pool's pc-side vault. | | Pool | `lp_mint` | SPL Token | Fungible LP mint. | | Model | **`model_data_account`** | Stable program | **The lookup table: 50,000 × DataElement.** | | Market | `serum_market` | OpenBook | OpenBook market. | | Market | `serum_bids`, `serum_asks` | OpenBook | Bid/ask queues. | | Market | `serum_event_queue` | OpenBook | Event queue. | | Market | `serum_coin_vault`, `serum_pc_vault` | SPL Token | OpenBook market-level vaults. | | Market | `serum_vault_signer` | OpenBook | Market-level vault signer. | ## `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. ```rust theme={null} // 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, // OpenBook OpenOrders pub serum_market: Pubkey, pub serum_program: Pubkey, 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. ```rust theme={null} // 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:** 1. **`InitModelData`** creates the account and sets `status = Initialized`, `multiplier = `, `valid_data_count = 0`. 2. **`UpdateModelData`** (invoked up to 5 times per transaction) populates elements via: * Input: array of `(index: u64, DataElement)` pairs. * Writes each to `elements[index]`. * Increments `valid_data_count` if `index >= valid_data_count`. 3. **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. ```rust theme={null} 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 Same as AMM v4. 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. ## 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 * **Instruction account lists**: [`products/stable/instructions`](/products/stable/instructions). * **How interpolation uses the table**: [`products/stable/math`](/products/stable/math). * **Fee structure and WithdrawPnl**: [`products/stable/fees`](/products/stable/fees). * **OpenBook account derivation**: OpenBook program docs. Sources: * [`reference/program-addresses`](/reference/program-addresses) # Stable AMM code demos Source: https://docs.raydium.io/products/stable/code-demos How to identify a Stable AMM pool from the API, swap through it via the Liquidity module (it handles version=5 pools natively), and use the off-chain stable-curve helpers. **Version banner.** * SDK: `@raydium-io/raydium-sdk-v2@0.2.42-alpha` * Cluster: Solana `mainnet-beta` * Stable AMM program ID: `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` (see [`reference/program-addresses`](/reference/program-addresses)) * Last verified: 2026-04 The SDK's `liquidity` module handles Stable AMM pools natively. Stable pools surface as **`version: 5`** (or `pooltype: "StablePool"`) on `ApiV3PoolInfoStandardItem`; the same `addLiquidity` / `removeLiquidity` / swap helpers work for them as for AMM v4 (`version: 4`) constant-product pools — the SDK detects the variant and emits the correct instructions automatically. The off-chain stable-curve math lives in `src/raydium/liquidity/stable.ts`. ## Setup ```bash theme={null} npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token ``` ```ts theme={null} import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js"; import BN from "bn.js"; import bs58 from "bs58"; const connection = new Connection(clusterApiUrl("mainnet-beta")); const owner = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY!)); const raydium = await Raydium.load({ connection, owner, cluster: "mainnet", // Optional: load the stable curve model layout if you intend to call quoting // helpers from `liquidity/stable.ts` directly. Pool-level swap / add / remove // do this lazily for you, so most callers can skip this step. }); // One-time: prefetch the on-chain model data layout used by the off-chain // stable-curve helpers. Only needed if you call getStablePrice / getDxByDyBaseIn / // getDyByDxBaseIn directly. addLiquidity / removeLiquidity / swap don't need this. await raydium.liquidity.initLayout(); ``` ## Identifying a Stable pool Two equivalent signals on `ApiV3PoolInfoStandardItem`: ```ts theme={null} const isStable = pool.version === 5 || pool.pooltype.includes("StablePool"); // the SDK uses this string check internally // Alternatively, by program ID: const STABLE_AMM_PROGRAM_ID = "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h"; const isStableByProgram = pool.programId === STABLE_AMM_PROGRAM_ID; ``` Both AMM v4 (`version: 4`, constant-product) and Stable AMM (`version: 5`) flow through the same `LiquidityModule` API on the SDK. Internally the module dispatches to: * `InstructionType.AmmV4AddLiquidity` / `AmmV4RemoveLiquidity` for v4 pools * `InstructionType.AmmV5AddLiquidity` / `AmmV5RemoveLiquidity` for v5 (Stable) pools The pool's `programId` (returned with the pool keys) tells the SDK which program to CPI into; you do not need to hardcode it. ## Find a pool by mint pair ```ts theme={null} import { PublicKey } from "@solana/web3.js"; // Two common mints to use as an example const mintA = new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"); // USDT const mintB = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC const pools = await raydium.api.fetchPoolByMintPair({ mint1: mintA.toBase58(), mint2: mintB.toBase58(), }); const stablePool = pools.find( (p) => p.version === 5 || p.pooltype.includes("StablePool"), ); if (!stablePool) { throw new Error("No Stable pool exists for this mint pair"); } console.log("Stable pool id:", stablePool.id); console.log("Stable pool programId:", stablePool.programId); console.log("TVL:", stablePool.tvl); ``` If the mint pair has both a v4 (constant-product) pool and a v5 (stable) pool, the response includes both — pick the one your flow needs, or hand them to the [AMM Routing](/products/routing) program and let it pick the best route. ## Swap through a Stable pool The `LiquidityModule.swap` flow is the same shape as for v4 pools — just hand it a v5 pool object: ```ts theme={null} import { Percent, TokenAmount, toToken } from "@raydium-io/raydium-sdk-v2"; const inputAmount = new TokenAmount(toToken(stablePool.mintA), 1_000_000); // 1 USDT const slippage = new Percent(50, 10_000); // 0.5% // Compute expected output using the SDK's stable-curve helpers internally. const { amountOut, minAmountOut } = raydium.liquidity.computeAmountOut({ poolInfo: stablePool, amountIn: inputAmount, mintIn: stablePool.mintA.address, mintOut: stablePool.mintB.address, slippage, }); console.log("Expected out:", amountOut.toSignificant()); console.log("Minimum out:", minAmountOut.toSignificant()); // Build & sign the swap transaction. const { transaction, execute } = await raydium.liquidity.swap({ poolInfo: stablePool, amountIn: inputAmount.raw, amountOut: minAmountOut.raw, fixedSide: "in", txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Stable swap tx:", txId); ``` The SDK reads the pool's `programId` from the pool keys and dispatches into the Stable AMM program. No special `programId` argument is needed. ## Add and remove liquidity `addLiquidity` and `removeLiquidity` work identically across v4 and v5 pools: ```ts theme={null} import { Percent, TokenAmount, toToken } from "@raydium-io/raydium-sdk-v2"; const amountInA = new TokenAmount(toToken(stablePool.mintA), 100_000_000); // 100 USDT const slippage = new Percent(50, 10_000); // 0.5% // Compute the matching B amount the curve requires for this size of A. const { anotherAmount, minAnotherAmount } = raydium.liquidity.computePairAmount({ poolInfo: stablePool, amount: amountInA.toSignificant(), baseIn: true, slippage, }); const { execute } = await raydium.liquidity.addLiquidity({ poolInfo: stablePool, amountInA, amountInB: anotherAmount, otherAmountMin: minAnotherAmount, fixedSide: "a", txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Add-liquidity tx:", txId); ``` Internally the SDK emits `InstructionType.AmmV5AddLiquidity` because `pooltype.includes("StablePool")` is true. The corresponding `removeLiquidity` flow is symmetric — feed in `lpAmount` and the minimum amounts you will accept on each side. ## Off-chain quote helpers (stable.ts) For server-side quoting or backtesting, the SDK exposes the underlying stable-curve math: ```ts theme={null} import { getStablePrice, getDxByDyBaseIn, getDyByDxBaseIn, } from "@raydium-io/raydium-sdk-v2"; // You must call initLayout() once before using these (loads the on-chain // `ModelDataInfo` PDA into the SDK's StableLayout cache). await raydium.liquidity.initLayout(); const modelData = raydium.liquidity.stableLayout; // Spot price at the pool's current reserves. const price = getStablePrice(modelData, /* x */, /* y */, /* withFee */); console.log("Spot price:", price); // Quote: given dx in, how much dy out (no fee applied here)? const dyOut = getDyByDxBaseIn(modelData, /* x */, /* y */, /* dx */); // Quote: given dy out target, how much dx in needed? const dxIn = getDxByDyBaseIn(modelData, /* x */, /* y */, /* dy */); ``` These are pure functions — no RPC, no signing. The on-chain `ModelDataInfo` is fetched once by `initLayout()` and cached in `raydium.liquidity.stableLayout`. Pass current reserves (`x`, `y`) and the helpers compute by binary-searching the lookup table and linearly interpolating between the two surrounding `DataElement` rows. See [`products/stable/math`](/products/stable/math) for the underlying algorithm. ## Routing through AMM Routing (multi-hop / best-price) If you do not want to pick a venue yourself, the [AMM Routing](/products/routing) program will consider every Raydium AMM (v4 / CPMM / CLMM / Stable) and route through whichever combination is best: ```ts theme={null} const route = await raydium.tradeV2.fetchRoutes({ inputMint: mintA, outputMint: mintB, amount: new BN(1_000_000), slippage, }); // route.routes[0].poolType tells you which programs the best route uses; // "Stable" appears here whenever a Stable pool is part of the optimal path. console.log(route.routes[0]); const { execute } = await raydium.tradeV2.swap({ inputMint: mintA, outputMint: mintB, inputAmount: new BN(1_000_000), swapResult: route.routes[0], slippage, txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); ``` This is the recommended path for production swappers and aggregators — you never need to manually decide whether a Stable pool exists or whether it is the better venue today. ## Recommendations 1. **For end-user swaps**, prefer the [`tradeV2`](/products/routing) routing flow. It handles every Raydium pool type including Stable. 2. **For pool-specific operations** (LP add / remove on a known Stable pool), use the `LiquidityModule` directly — it auto-detects v5 pools. 3. **For off-chain quoting / analytics**, call `getStablePrice` / `getDyByDxBaseIn` / `getDxByDyBaseIn` after `initLayout()`. No RPC traffic per quote after the model data is cached. 4. **Do not hand-encode raw `SwapBaseIn` instructions.** The Stable AMM program (forked from AMM v4) expects 17–19 OpenBook accounts for V1 swap entrypoints, with the `model_data_account` slotted in among them. The SDK's pre-built helpers handle every account and ordering correctly; rolling your own is error-prone. ## Where to go next * [Math](/products/stable/math) — how the lookup-table interpolation works. * [Instructions](/products/stable/instructions) — full instruction reference. * [AMM Routing](/products/routing) — multi-pool routing across AMM v4, CPMM, CLMM, Stable. Sources: * [`raydium-sdk-V2/src/raydium/liquidity/liquidity.ts`](https://github.com/raydium-io/raydium-sdk-V2/blob/master/src/raydium/liquidity/liquidity.ts) — module entry point; v4 / v5 dispatch. * [`raydium-sdk-V2/src/raydium/liquidity/stable.ts`](https://github.com/raydium-io/raydium-sdk-V2/blob/master/src/raydium/liquidity/stable.ts) — `StableLayout`, `getStablePrice`, `getDxByDyBaseIn`, `getDyByDxBaseIn`. # Stable AMM fees Source: https://docs.raydium.io/products/stable/fees Identical to AMM v4: 0.25% trade fee, 0.22% LP / 0.03% protocol split. No fund fee, no creator fee. ## The fee model Stable AMM uses **the same fee structure as AMM v4**. There is only one fee tier per pool (set at initialization); pools cannot be reconfigured into higher tiers. | Field | Default | Meaning | | --------------------------------------------------- | ------------- | ----------------------------------------------------------------------------- | | `swap_fee_numerator / swap_fee_denominator` | `25 / 10_000` | **Gross trade fee: 0.25%** of input volume. | | `trade_fee_numerator / trade_fee_denominator` | `25 / 10_000` | Same 0.25%, used by OpenBook integration for order pricing. | | `pnl_numerator / pnl_denominator` | `12 / 100` | **Protocol's share of the fee: 12%** — i.e., `0.25% × 12% = 0.03%` of volume. | | `min_separate_numerator / min_separate_denominator` | `4 / 10_000` | Internal rounding floor. | There is **no fund-fee** and **no creator-fee** line — these are post-AMM v4 inventions (CPMM/CLMM). Stable AMM predates that convention. ## How the split is computed On each swap: ``` gross_fee = ceil(amount_in * (swap_fee_numerator / swap_fee_denominator)) // e.g., 0.25% of amount_in pnl_portion = gross_fee * (pnl_numerator / pnl_denominator) // 12% of gross_fee lp_portion = gross_fee − pnl_portion // 88% of gross_fee ``` * **`lp_portion`** stays in the vault, inflates `k`, and benefits LPs on redemption. * **`pnl_portion`** increments `AmmInfo.out_put.need_take_pnl_coin` or `need_take_pnl_pc` (depending on the input token) and is swept by `WithdrawPnl`. Same **invariant-preserving trick** as CPMM: the PnL amount sits in the vault physically but is subtracted from the "effective reserves" used in the curve math, so removing it does not shift the price. ## PnL from OpenBook (same as AMM v4) When the pool's limit orders on OpenBook get filled, it can be on the taker side and earn or lose the market-maker/taker spread. These fills settle during `MonitorStep` and credit / debit the pool reserves. The program tracks them in `out_put.total_pnl_{coin,pc}` counters for analytics. This **OpenBook PnL is distinct from the 0.03% protocol fee**. It inflates the pool reserves and benefits LPs and protocol proportionally. Operational coupling to OpenBook is a reason why CPMM (order-book-independent) is now the default for new pools. ## Collection The Raydium multisig (or whoever controls `amm_admin`) calls `WithdrawPnl` to sweep: 1. Settles any pending OpenBook fills first (via internal crank logic). 2. Transfers `need_take_pnl_coin` and `need_take_pnl_pc` from vaults to admin-designated accounts. 3. Zeroes the counters. The operation does not move the curve. LPs see no price change. ## LP fee redemption No dedicated "collect fees" instruction. LP fees accumulate in vaults, inflating the reserves. LPs realize them by burning LP via `Withdraw`. The value of an LP token grows as reserves grow. ## Visualization: where \$1,000 of volume goes On a USDC-heavy `Swap` of \$1,000 against a default-parameter Stable pool: ``` Gross trade fee (0.25%): $2.50 LP share (0.22%): $2.20 → stays in pool, raises k PnL share (0.03%): $0.30 → need_take_pnl_pc, swept by WithdrawPnl User receives (minus curve): $997.50 ``` Compare to AMM v4 (identical) and CPMM (0.25% tier, no creator fee): CPMM gives LPs \$2.10, protocol \$0.30, fund \$0.10. ## Comparison table | | Stable AMM | AMM v4 | CPMM `index=0` | | --------- | ---------- | ------ | -------------- | | Trade fee | 0.25% | 0.25% | 0.25% | | LP | 0.22% | 0.22% | 0.21% | | Protocol | 0.03% | 0.03% | 0.03% | | Fund | None | None | 0.01% | | Creator | None | None | 0 by default | Full matrix: [`reference/fee-comparison`](/reference/fee-comparison). ## Integrator notes * **Quoting:** Always read `AmmInfo` from the chain; do not hardcode fees. In principle `SetParams` can change them, though the multisig has not changed the defaults. * **Curve vs. fees:** The 0.25% fee is independent of whether the curve is a formula (x·y=k in AMM v4) or a lookup table (Stable). Both apply the same 0.25% to the input amount. * **No rewards:** Stable pools do not support on-pool reward emissions. Ecosystem farms (Farm v3/v5/v6) handle staking elsewhere. ## Where to go next * [`products/stable/math`](/products/stable/math) — fee application in swap math. * [`products/stable/instructions`](/products/stable/instructions) — `WithdrawPnl` account list. * [`products/amm-v4/fees`](/products/amm-v4/fees) — deeper fee derivation for OpenBook path. * [`reference/fee-comparison`](/reference/fee-comparison) — side-by-side all products. Sources: * `raydium-stable/program/src/state.rs` (`Fees` struct) * On-chain `AmmInfo.fees` fields on live mainnet pools # Stable AMM Source: https://docs.raydium.io/products/stable/index AMM variant with interpolated price curves from lookup tables. Powers liquidity for stablecoin pairs and other assets with known price relationships. ## What it is Stable AMM is a specialized variant of the Raydium AMM designed for pairs with known, well-behaved price relationships — primarily stablecoin–stablecoin pairs (USDC-USDT, USDH-USDC) and collateralized-token pairs. Instead of a constant-product curve, it uses a **lookup table of (x, y, price) tuples** that the pool admin populates via `InitModelData` and `UpdateModelData` instructions. Price discovery happens through interpolation within the table, producing lower slippage for small swaps while remaining composable with OpenBook. Stable AMM shares the same **pool/OpenBook architecture as AMM v4**: same `MonitorStep` logic, same `Fees` structure, same fee model. The difference is purely the **pricing curve**: instead of x·y=k, you interpolate a pre-populated model. **Program ID:** see [reference/program-addresses](/reference/program-addresses). **Token-2022:** not supported. Classic SPL tokens only. **Liquidity:** thin. Most user-facing integrations reach Stable pools through the [AMM Routing program](/products/routing) for best-price routing across all Raydium AMMs. The SDK also supports Stable pools directly: pools surface as `version: 5` (`pooltype: "StablePool"`) on `ApiV3PoolInfoStandardItem`, and the standard `LiquidityModule` (`addLiquidity` / `removeLiquidity` / swap) auto-detects v5 and emits the right instructions — see [Code demos](/products/stable/code-demos). ## Chapter contents Why a separate program, the lookup-table model, comparison to AMM v4 and CPMM. AmmInfo, ModelDataInfo, DataElement fields, vault layout, fee structure. How interpolation produces prices, OpenBook integration, fee application identical to AMM v4. Initialize, InitModelData, UpdateModelData, Deposit, Withdraw, SwapBaseIn, SwapBaseOut, MonitorStep, and more. Same 0.25% / 0.22% LP / 0.03% protocol split as AMM v4. No fund fee, no creator fee. Detect a Stable pool, swap / add / remove liquidity through the SDK's `LiquidityModule` (handles v5 natively), and use the off-chain stable-curve helpers. ## When to read this * You are integrating a stablecoin or asset pair and find a Stable AMM pool with the best price. * You are building a routing engine and need to support Stable pools as a liquidity source. * You are maintaining liquidity in a Stable pool — learning the model-data update flow. * You are curious how AMM pricing can be made programmable via a lookup table. ## Key facts | Aspect | Value | | ------------------------- | ---------------------------------------------- | | **Mainnet program ID** | `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` | | **Devnet program ID** | `DRayDdXc1NZQ9C3hRWmoSf8zK4iapgMnjdNZWrfwsP8m` | | **Curve model** | Interpolated lookup table (not x·y=k) | | **OpenBook dependency** | Yes, same as AMM v4 | | **Trade fee** | 0.25% (same as AMM v4) | | **LP share of fee** | 0.22% | | **Protocol share of fee** | 0.03% | | **Creator fee** | None | | **Fund fee** | None | # Stable AMM instructions Source: https://docs.raydium.io/products/stable/instructions The full instruction set: Initialize, InitModelData, UpdateModelData, Deposit, Withdraw, SwapBaseIn, SwapBaseOut, MonitorStep, SetParams, WithdrawPnl, WithdrawSrm, PreInitialize, SimulateInfo. Stable AMM shares most of its instruction surface with AMM v4. The unique instructions are `InitModelData` and `UpdateModelData`, which populate and update the lookup table. All other operations (swap, deposit, withdraw, crank) follow the same pattern as AMM v4. ## Instruction inventory | Instruction | Category | Notes | | ----------------- | ----------- | -------------------------------------------------------- | | `Initialize` | Lifecycle | Create pool (requires pre-allocated model-data account). | | `PreInitialize` | Lifecycle | Legacy pre-allocation helper. | | `InitModelData` | Model setup | Create and initialize the lookup table. | | `UpdateModelData` | Model setup | Populate up to 5 table elements per call. | | `Deposit` | Liquidity | Add liquidity, receive LP. | | `Withdraw` | Liquidity | Burn LP, receive both sides. | | `SwapBaseIn` | Swap | Exact-input swap. | | `SwapBaseOut` | Swap | Exact-output swap. | | `MonitorStep` | Crank | Settle OpenBook, update orders. | | `SetParams` | Admin | Change pool parameters. | | `WithdrawPnl` | Admin | Sweep accrued protocol fees. | | `WithdrawSrm` | Legacy | Sweep SRM rebates (**legacy**). | | `SimulateInfo` | Diagnostic | Read-only quote helper. | ## `Initialize` Bootstrap a new Stable AMM pool bound to an existing OpenBook market and a pre-created `ModelDataInfo` account. **Arguments** ``` nonce: u8 open_time: u64 ``` **Accounts** (writable `W`, signer `S`) | # | Name | W | S | Notes | | ----- | ------------------------- | - | - | --------------------------------------------- | | 1 | `token_program` | | | SPL Token. | | 2 | `system_program` | | | | | 3 | `rent` | | | | | 4 | `amm` | W | | Pool's `AmmInfo` account. | | 5 | `amm_authority` | | | Program-wide PDA. | | 6 | `amm_open_orders` | W | | OpenBook `OpenOrders`. | | 7 | `lp_mint` | W | | Fungible LP token mint. | | 8 | `coin_mint` | | | | | 9 | `pc_mint` | | | | | 10 | `pool_coin_token_account` | W | | Pool's coin vault. | | 11 | `pool_pc_token_account` | W | | Pool's pc vault. | | 12 | `amm_target_orders` | W | | Grid for OpenBook orders. | | 13 | `model_data_account` | | | **The lookup table account.** | | 14 | `serum_program` | | | OpenBook program. | | 15 | `serum_market` | | | OpenBook market. | | 16 | `user_dest_lp_token` | W | | Creator's LP ATA (receives initial LP). | | 17 | `user_wallet` | W | S | Creator; pays rent, funds initial deposit. | | (opt) | `srm_token` | W | | SRM token account for fee discounts (legacy). | **Preconditions** * `model_data_account` must already be created and initialized by a prior `InitModelData`. * `lp_mint` must be empty (zero supply). * Vaults must exist and be owned by `amm_authority`. **Postconditions** * `AmmInfo` is initialized with all references. * `TargetOrders` is zeroed and ready for the first `MonitorStep`. * Initial LP tokens are minted and sent to `user_dest_lp_token`. * OpenBook orders have **not** been posted yet; the first `MonitorStep` posts them. ## `InitModelData` Create and initialize the `ModelDataInfo` account. Must be called once before `Initialize`. **Arguments** ``` multiplier: u64 // scale factor (e.g., 10^6) ``` **Accounts** (writable `W`, signer `S`) | # | Name | W | S | Notes | | - | -------------------- | - | - | ------------------------------------------ | | 1 | `model_data_account` | W | | The 50k-element table account. | | 2 | `amm_admin` | | S | Pool admin (must sign to prove authority). | **Preconditions** * `model_data_account` must be sufficiently large (\~1.2 MB for 50k × 24 bytes). * `model_data_account` must be owned by the Stable program. **Postconditions** * `status = Initialized`. * `multiplier` is set. * `valid_data_count = 0` (no elements populated yet; call `UpdateModelData` to add them). * `elements` array is zeroed. ## `UpdateModelData` Populate up to 5 table elements in a single call. Must be called after `InitModelData` but before swaps start using the table. **Arguments** ``` array_data: [UpdateModelData; 5] pub struct UpdateModelData { pub index: u64, pub data: DataElement, } ``` **Accounts** (writable `W`, signer `S`) | # | Name | W | S | Notes | | - | -------------------- | - | - | -------------------------------- | | 1 | `amm_admin` | | S | Signer (must be the pool admin). | | 2 | `model_data_account` | W | | The table account. | **Preconditions** * `amm_admin` must match `AmmInfo.amm_admin`. * Each index in `array_data` must be valid (within 50,000). * **Entries must be sorted** (not validated on-chain for speed): x ascending, y descending, price ascending. **Postconditions** * Elements are written to `model_data_account.elements[index]` for each input. * `valid_data_count` is updated to the max index written + 1. **Governance note:** There is no on-chain enforcement of sort order or price consistency. A malicious or careless admin can corrupt the table and cause incorrect quotes. In practice, the Raydium multisig controls this address. ## `Deposit` Add liquidity, receive LP tokens. **Arguments** ``` max_coin_amount: u64 max_pc_amount: u64 base_side: u64 // 0 = base on coin, 1 = base on pc ``` **Accounts** — like AMM v4, \~13 accounts. Must include the `model_data_account` as read-only. **Math** — standard pro-rata using the lookup table to compute the ratio. The SDK computes coin/pc pair for the desired LP amount and checks against max caps. ## `Withdraw` Burn LP, receive both sides pro-rata. **Arguments** ``` amount: u64 // LP tokens to burn ``` **Accounts** — like AMM v4, with `model_data_account` as read-only. **Preconditions** * `user_lp_token_account` holds at least `amount`. **Postconditions** * `amount` LP tokens are burned. * User receives coin and pc amounts according to the current pro-rata, adjusted for accrued fees. ## `SwapBaseIn` Exact-input swap using the lookup table for pricing. **Arguments** ``` amount_in: u64 minimum_amount_out: u64 ``` **Accounts** (\~17 total) | # | Name | W | S | Notes | | -- | ------------------------- | - | - | ---------------------------- | | 1 | `token_program` | | | | | 2 | `amm` | W | | | | 3 | `amm_authority` | | | | | 4 | `amm_open_orders` | W | | | | 5 | `amm_target_orders` | W | | | | 6 | `pool_coin_token_account` | W | | | | 7 | `pool_pc_token_account` | W | | | | 8 | `model_data_account` | | | **Read-only lookup table.** | | 9 | `serum_program` | | | | | 10 | `serum_market` | W | | | | 11 | `serum_bids` | W | | | | 12 | `serum_asks` | W | | | | 13 | `serum_event_queue` | W | | | | 14 | `serum_coin_vault` | W | | | | 15 | `serum_pc_vault` | W | | | | 16 | `serum_vault_signer` | | | | | 17 | `user_source_token` | W | | User's input token account. | | 18 | `user_dest_token` | W | | User's output token account. | | 19 | `user_owner` | | S | User (transaction signer). | **Preconditions** * `amm.status` allows swap. * `user_source_token` holds ≥ `amount_in`. **Postconditions** * User loses `amount_in`, gains `amount_out ≥ minimum_amount_out`. * Pool fees increment `need_take_pnl_*` counters. * OpenBook orders may settle if filled. **Math** — Lookup-table interpolation as described in [`products/stable/math`](/products/stable/math). ## `SwapBaseOut` Exact-output swap (inverse of `SwapBaseIn`). Same accounts, different math direction. **Arguments** ``` max_amount_in: u64 amount_out: u64 ``` ## `MonitorStep` Permissionless crank: settle OpenBook fills, update the limit-order grid. **Arguments** ``` plan_order_limit: u16 place_order_limit: u16 cancel_order_limit: u16 ``` **Accounts** (\~18 total) — same as AMM v4 `MonitorStep` plus the `model_data_account` as read-only. **Preconditions** * OpenBook account references must match the pool's bound market. **Postconditions** * Pending OpenBook fills are settled into the pool vaults. * New limit orders are posted to OpenBook based on the lookup-table curve. * `TargetOrders` is updated. ## `SetParams` Admin-only. Change pool parameters (status, state, fees, owner, model-data key, etc.). **Arguments** ``` param: u8 // which parameter to change (Status, State, Fees, etc.) value: Option // new value (if param is numeric) new_pubkey: Option // new address (if param is an account key) fees: Option // new fees (if param is Fees) ``` **Accounts** — varies by param. Always requires `amm_admin` as signer. **Common params:** * `param = 0` (Status) — change the operation bitmask. * `param = 9` (Fees) — change trade\_fee, pnl split, etc. * `param = 11` (ModelDataKey) — rebind the lookup table (rare, requires admin action). ## `WithdrawPnl` Admin-only. Sweep accrued protocol fees from `need_take_pnl_*` into designated PnL accounts. **Arguments** — none (state-driven). **Accounts** (\~14 total) | # | Name | W | S | Notes | | --- | ------------------------- | - | - | ------------------------------------ | | 1 | `token_program` | | | | | 2 | `amm` | W | | | | 3 | `amm_authority` | | | | | 4 | `amm_open_orders` | W | | | | 5 | `pool_coin_token_account` | W | | | | 6 | `pool_pc_token_account` | W | | | | 7 | `coin_pnl_dest` | W | | Admin's coin account (receives fee). | | 8 | `pc_pnl_dest` | W | | Admin's pc account (receives fee). | | 9 | `pnl_admin` | | S | Signer (must match pool ownership). | | 10+ | OpenBook accounts (\~4) | | | Settle pending fills first. | **Preconditions** * `pnl_admin` must be authorized. **Postconditions** * `need_take_pnl_coin` and `need_take_pnl_pc` are transferred to the admin's accounts. * Counters are zeroed. ## `WithdrawSrm` **Legacy** (**do not use on new pools**). Sweeps SRM fee-discount token rebates from early Serum-era pools. **Arguments** ``` amount: u64 ``` ## `SimulateInfo` Read-only quote helper for clients and the SDK. **Arguments** ``` param: u8 // PoolInfo, SwapBaseInInfo, SwapBaseOutInfo, RunCrankInfo swap_base_in_value: Option swap_base_out_value: Option ``` **Usage** — invoked via `simulateTransaction` to get a quote without executing a swap. ## Where to go next * [Accounts](/products/stable/accounts) — for account field layouts and sizes. * [Math](/products/stable/math) — for the lookup-table interpolation logic. * [Code demos](/products/stable/code-demos) — to see how to call these from the SDK. Sources: * `raydium-stable/program/src/instruction.rs` (enum and pack/unpack) * `raydium-stable/program/src/processor.rs` (execution logic) # Stable AMM math Source: https://docs.raydium.io/products/stable/math Binary search and linear interpolation to quote prices from the lookup table. Fee application identical to AMM v4. OpenBook integration and MonitorStep. ## The lookup-table curve Stable AMM replaces the formula x·y=k with a **sparse lookup table** of (x, y, price) tuples. When pricing a swap, the program: 1. Computes the pool's current ratio from reserves. 2. **Binary searches** the table to find the two entries that bracket that ratio. 3. **Linearly interpolates** between them to get an intermediate price. 4. Applies fees and returns the quote. This approach trades the determinism of a formula for **admin flexibility** in price shaping, and it's efficient enough to fit in Solana's compute budget. ## Table layout and binary search The `ModelDataInfo` holds up to 50,000 `DataElement` entries, indexed by the admin. Only the first `valid_data_count` are live. Each entry: ``` DataElement { x: u64, // X coordinate (coin-side amount, scaled) y: u64, // Y coordinate (pc-side amount, scaled) price: u64, // price = x/y, scaled by multiplier } ``` To find a price at the current pool reserves (x\_real, y\_real): 1. Compute the ratio: `target_ratio = (x_real * multiplier) / y_real`. 2. Binary search for entries where `(element.x * multiplier) / element.y` brackets `target_ratio`. 3. When a bracket `[min_idx, max_idx]` is found, interpolate. The program's binary search code spans \~150 lines in `state.rs::ModelDataInfo::get_mininum_range_by_xy_real`. The key invariant: **entries must be sorted** (x ascending, y descending, price ascending) for the search to work. ## Linear interpolation Once two table points bracket the ratio, interpolation computes an intermediate price and reserve pair: ``` target = (x_real * multiplier) / y_real [x1, y1, p1] = table[min_idx] [x2, y2, p2] = table[max_idx] // Interpolate price p = p1 + (p2 - p1) * (target - ratio1) / (ratio2 - ratio1) // Interpolate reserve x = x1 + (x2 - x1) * (target - ratio1) / (ratio2 - ratio1) y = y1 + (y2 - y1) * (target - ratio1) / (ratio2 - ratio1) ``` The result is a piecewise-linear curve that smoothly connects the table points. ## Scaling: the multiplier Pool reserves and prices are stored at different scales. The `multiplier` field on `ModelDataInfo` accounts for this. A common pattern: * Coin has 6 decimals, PC has 18 decimals. * Multiplier = 10^6 (or similar). * Table entries are stored at a reduced scale to fit u64 bounds. The program rescales on read/write via: ``` real_value = table_value * ratio / multiplier table_value = real_value * multiplier / ratio ``` ## Swap pricing: `SwapBaseIn` and `SwapBaseOut` ### SwapBaseIn (exact input) Given input amount `amount_in`: 1. Get current ratio from `(coin_vault, pc_vault)`. 2. Find bracketing table entries and interpolate to get the table-space ratio. 3. Convert input to table space: `dx_table = amount_in * multiplier / ratio`. 4. Query the table at the new X coordinate to find the new Y. 5. `dy_table = y_old - y_new`. 6. Convert back: `dy_real = dy_table * ratio / multiplier`. 7. Apply trade fee: `dy_output = dy_real - (dy_real * trade_fee_numerator / trade_fee_denominator)`. 8. Return `dy_output`. ### SwapBaseOut (exact output) Symmetric: given desired `amount_out`, solve for the required `amount_in`. Both paths settle filled OpenBook orders first (via internal `MonitorStep`-like logic), so the effective reserves reflect any fills from the previous block. ## Fee application Identical to AMM v4: see [`products/amm-v4/math`](/products/amm-v4/math) for the full derivation. ``` gross_fee = amount_in * (swap_fee_numerator / swap_fee_denominator) // e.g., 0.25% lp_portion = gross_fee - (gross_fee * pnl_numerator / pnl_denominator) // e.g., 0.22% pnl_portion = gross_fee * (pnl_numerator / pnl_denominator) // e.g., 0.03% ``` The `pnl_portion` goes to `need_take_pnl_*` and is swept by the admin via `WithdrawPnl`. The `lp_portion` stays in the vault, inflating `k` and benefiting LP token holders. ## MonitorStep and OpenBook Like AMM v4, `MonitorStep` is a **crank instruction** that: 1. Settles pending OpenBook order fills (moves tokens from vaults to the pool). 2. Updates `AmmInfo.target_orders` with a new grid of limit-order slots. 3. Posts the new grid to OpenBook. The grid is computed from the table: the program uses the lookup table to find price points and translates them into OpenBook orders. **Compute cost** of `MonitorStep`: \~150k–180k CU (similar to AMM v4). ## Summary: why this works The lookup table + interpolation design is **efficient and flexible**: * **Efficiency:** Binary search is O(log 50,000) ≈ 16 iterations, each \~ 300–500 CU. Interpolation is a few multiplies/divides. Total quoting cost is \~5k–15k CU, much cheaper than recomputing a formula on every swap. * **Flexibility:** The admin can encode any piecewise-linear curve. Stablecoin pairs get high density around 1:1; collateralized pairs get custom curves. * **OpenBook composability:** The same `MonitorStep` / `TargetOrders` logic from AMM v4 applies. Price discovery via the table feeds into order grid generation. For deep dives into the interpolation logic, see `raydium-stable/program/src/state.rs`, methods `get_data_by_x`, `get_data_by_y`, `get_dy_by_dx_base_in`, etc. ## Where to go next * [Accounts](/products/stable/accounts) — `ModelDataInfo` and `DataElement` field reference. * [Instructions](/products/stable/instructions) — `InitModelData`, `UpdateModelData` for populating the table. * [Fees](/products/stable/fees) — fee application and `WithdrawPnl`. * [`products/amm-v4/math`](/products/amm-v4/math) — for the OpenBook fee-inclusive order pricing logic. Sources: * `raydium-stable/program/src/state.rs` (interpolation and binary search implementations) * `raydium-stable/program/src/math.rs` (calculator utilities) # Stable AMM overview Source: https://docs.raydium.io/products/stable/overview Stable AMM replaces the constant-product curve with an interpolated lookup table, reducing slippage for correlated assets. Uses the same OpenBook integration as AMM v4. ## One-paragraph summary Stable AMM is a variant of Raydium's AMM that trades a curve formula for a **pre-populated lookup table**. Instead of using x·y=k, the pool stores a sparse array of (x, y, price) points and uses **binary search + linear interpolation** to quote prices. This design excels at stablecoin pairs and other assets with known price relationships: swaps between 1-to-1 pegged tokens have near-zero slippage. Like AMM v4, it ties to an OpenBook market and posts limit orders there. Liquidity is currently thin; most integrators access Stable pools through the [AMM Routing program](/products/routing). ## Why a lookup table instead of xy=k Constant-product AMMs incur high slippage on pairs with tight price bands. A USDC-USDT swap should cost almost nothing; on a constant-product pool, k=x·y forces a price move even for tiny volume. A lookup table lets the pool admin express the *actual* price relationship: * For stablecoins: density the table around 1:1 so micro-swaps cost \~0 slippage. * For collateralized pairs: encode the target ratio and let the grid shape the fee/incentive surface. The table is static between `UpdateModelData` calls (which the admin posts when the relationship changes), so the on-chain cost is just interpolation search — much cheaper than recomputing a formula. ## How it works: the model-data account The pool holds a `ModelDataInfo` account — a **50,000-element array** of `DataElement` structs. Each element holds: ``` DataElement { x: u64, // table X coordinate y: u64, // table Y coordinate price: u64, // price at (x, y) } ``` Only the first `valid_data_count` elements are populated; the rest are zeroed. On swap, the program: 1. **Computes a ratio** from the current pool reserves and uses binary search to find which two table elements bracket that ratio. 2. **Interpolates linearly** between the two bracketing points to get the quote price. 3. **Applies fees** (same 0.25% as AMM v4) and returns the result to the user. The `multiplier` field on the table accounts for the possibility that x and y are stored at a reduced scale (e.g., with 6 decimals instead of 18). Price discovery rescales accordingly. ## Comparison: Stable AMM vs. AMM v4 vs. CPMM | Dimension | Stable AMM | AMM v4 | CPMM | | ---------------------- | ------------------------------------------- | ------------------------------------ | ------------------------------ | | Curve | Lookup table + interpolation | Constant product (xy=k) | Constant product | | Primary use case | Stablecoins, pegged pairs | General pairs, legacy deep liquidity | General pairs, new deployments | | OpenBook dependency | **Yes** | **Yes** | No | | Token-2022 | No | No | Yes | | Slippage profile | Minimal at 1:1 | High at tight ratios | Moderate across range | | Admin-tunable curve | Yes (`UpdateModelData`) | No (`SetParams` only) | No | | Table size | \~50k elements × 24 bytes | N/A | N/A | | Compute per swap | \~5k–15k CU (binary search + interpolation) | \~150k–200k CU | \~60k–100k CU | | Account count per swap | \~17 (AMM + OpenBook) | \~18 (AMM + OpenBook) | \~11 | ## Mental model A Stable AMM pool is an **interpolated lookup-table AMM** whose vaults also escrow OpenBook limit orders, just like AMM v4. The key difference is that the price discovery curve is not hardcoded — it is a sparse array that the admin can populate and update. Operations are similar to AMM v4: **direct swap** (user ↔ pool), **deposit / withdraw** (LP ops), **crank** (`MonitorStep`), and **admin upkeep** (`UpdateModelData`, `SetParams`). ## When Stable AMM is the right choice * You operate a stablecoin or other correlated-asset pair and want tight, predictable pricing. * You have deep knowledge of your pair's price relationship and want to encode it directly as a curve. * You already have integrations for AMM v4 and simply need a different curve flavor. For a fresh, general-purpose pool with no tight-correlation requirement, [CPMM](/products/cpmm) is the simpler and more liquid default. ## Where to go next * [Accounts](/products/stable/accounts) — `AmmInfo`, `ModelDataInfo`, `DataElement` field reference. * [Math](/products/stable/math) — binary search, interpolation, and fee application. * [Instructions](/products/stable/instructions) — `InitModelData`, `UpdateModelData`, swap and LP instructions. * [Fees](/products/stable/fees) — the 0.25% split (identical to AMM v4). * [Code demos](/products/stable/code-demos) — routing and direct integration. Sources: * [`reference/program-addresses`](/reference/program-addresses) for the canonical program ID * [`products/amm-v4/overview`](/products/amm-v4/overview) for OpenBook integration details # Architecture Source: https://docs.raydium.io/protocol-overview/architecture The end-to-end architecture of Raydium: on-chain programs, shared PDAs and config accounts, and the off-chain surface that fronts them. **This page is the single canonical architecture diagram for the docs.** Every other chapter links back here rather than redrawing the system. Program IDs are not embedded in this page — they live in [`reference/program-addresses`](/reference/program-addresses) so they can be updated in exactly one place. ## What Raydium actually is Raydium is **not one program**. It is a set of independent on-chain Solana programs that share a common off-chain surface (REST API, TypeScript SDK, IDL registry) and a handful of conventions (authority PDAs, fee-config accounts, admin multisig). A user interaction — a swap, a deposit, a farm-harvest — routes into exactly one of those programs; the off-chain surface is what makes them feel like a single product. The on-chain footprint groups into four kinds of programs: 1. **AMM programs** — four separate pool programs, each with its own format and pricing math: * **AMM v4** — the original constant-product AMM. Originally a hybrid design that mirrored the curve onto an OpenBook (formerly Serum) market; the OpenBook integration has since been deactivated and pools now operate as pure AMMs against the curve. Still the deepest venue for many major pairs. * **CPMM** — a plain constant-product AMM (`x · y = k`) built natively on Solana, with first-class Token-2022 support. **The recommended program for new constant-product pools.** * **CLMM** — a concentrated-liquidity AMM in the Uniswap v3 style. Liquidity is provided into price ranges; fees accrue per-position; state is organized around ticks and a `sqrt_price_x64`. * **Stable AMM** — a thin-liquidity StableSwap-style program (forked from AMM v4 with a lookup-table pricing curve) that the router uses for stablecoin-correlated pairs. Not surfaced as a first-class create-pool option in the UI today. 2. **Reward distribution** — **Farm** (v3 / v5 / v6, with v6 as the active generation; v3/v5 are wind-down only). 3. **Token launch** — **LaunchLab**, a bonding-curve program. Successful launches **graduate** into either an AMM v4 pool or a CPMM pool depending on the launch's configuration, with the LP wrapped through the LP-Lock program. 4. **Liquidity primitives** — **AMM Routing** (the on-chain multi-pool router that CPIs into the four AMM programs in a single transaction) and **LP-Lock / Burn & Earn** (locks LP positions while keeping fee claims open). Everything else in the stack — the REST APIs, the Transaction API, the TypeScript SDK, the UI — is off-chain infrastructure that composes these programs on top of Solana and SPL Token / Token-2022. The Perps surface is a separate integration on top of Orderly Network and is not an on-chain Raydium program; it is excluded from this diagram. ## Canonical diagram ```mermaid theme={null} flowchart TB subgraph user ["User entry points"] UI["Raydium web UI
raydium.io"] AGG["External aggregators
Jupiter, 1inch, etc."] CUST["Custom apps
(SDK / direct CPI)"] end subgraph offchain ["Off-chain surface"] API["REST API
api-v3.raydium.io"] TXAPI["Transaction API
transaction-v1.raydium.io"] SDK["TypeScript SDK
@raydium-io/raydium-sdk-v2"] IDL["IDL registry
raydium-io/raydium-idl"] IDX["Indexers &
price feeds"] end subgraph onchain ["On-chain programs (Solana mainnet-beta)"] subgraph amms ["AMM programs"] direction TB AMM4["AMM v4
constant-product"] CPMM["CPMM
standard AMM"] CLMM["CLMM
concentrated liquidity"] STABLE["Stable AMM
lookup-table curve"] end subgraph support ["Support programs"] direction TB ROUTE["AMM Routing
multi-pool router"] FARM["Farm v6
(v3 / v5 wind-down)"] LAUNCH["LaunchLab
bonding-curve launches"] LOCK["LP-Lock /
Burn & Earn"] end end subgraph solana ["Solana platform primitives"] TOKEN["SPL Token
+ Token-2022"] OB["OpenBook v2
(CLOB; AMM v4 wiring inert)"] SYS["System / Rent /
Associated Token"] end %% Client paths UI --> SDK CUST --> SDK AGG --> TXAPI AGG --> ROUTE %% Off-chain composition SDK --> API SDK --> TXAPI SDK --> IDL SDK --> AMM4 SDK --> CPMM SDK --> CLMM SDK --> FARM SDK --> LAUNCH SDK --> LOCK TXAPI --> ROUTE TXAPI --> CPMM TXAPI --> CLMM API --> IDX IDX --> AMM4 IDX --> CPMM IDX --> CLMM IDX --> STABLE IDX --> LAUNCH %% Routing → AMM programs (CPI) ROUTE --> AMM4 ROUTE --> CPMM ROUTE --> CLMM ROUTE --> STABLE %% LaunchLab graduation paths LAUNCH -- "MigrateToAmm" --> AMM4 LAUNCH -- "MigrateToCpswap" --> CPMM LAUNCH --> LOCK %% LP-Lock wraps LP from CPMM / CLMM pools LOCK --> CPMM LOCK --> CLMM %% Programs → SPL Token / Token-2022 AMM4 --> TOKEN CPMM --> TOKEN CLMM --> TOKEN STABLE --> TOKEN FARM --> TOKEN LAUNCH --> TOKEN LOCK --> TOKEN %% AMM v4's historical OpenBook wiring is inert AMM4 -. "inert" .-> OB %% Programs → System / Rent / ATA AMM4 --> SYS CPMM --> SYS CLMM --> SYS STABLE --> SYS FARM --> SYS LAUNCH --> SYS LOCK --> SYS ROUTE --> SYS ``` Key invariants this diagram captures: * **AMM programs are peers.** CPMM does not call into CLMM; CLMM does not call into AMM v4; Stable AMM is its own program. A direct swap on one pool touches exactly one AMM program. The only program that composes multiple AMMs in a single transaction is **AMM Routing**, which CPIs into AMM v4 / CPMM / CLMM / Stable AMM as needed when a route crosses pool types. * **The SDK and the Transaction API are composition layers, not programs.** When the web UI or an aggregator builds a "swap through three pools" transaction, the SDK (client-side) or the Transaction API (server-side) stitches the instructions together using quotes fetched from the REST API. The chain sees a single Solana transaction with N instructions — no orchestrator program owns the whole flow. * **AMM v4's OpenBook wiring is inert.** AMM v4 was the only AMM ever bound to OpenBook, but the integration has been deactivated — pools no longer share liquidity to OpenBook, `MonitorStep` is no longer cranked, and an OpenBook outage has no impact on current swap traffic. The market accounts remain on the pool's `AmmInfo` for backwards compatibility but reference unused state. CPMM, CLMM, and Stable AMM never had a CLOB dependency. * **LaunchLab graduates into one of two AMM programs.** A successful launch calls `MigrateToAmm` (target: AMM v4) or `MigrateToCpswap` (target: CPMM) depending on its `migrate_type`; Token-2022 launches always migrate to CPMM. The post-graduation LP is split via `PlatformConfig` and the creator/platform slices are wrapped through the LP-Lock program as Fee Key NFTs (the Burn & Earn pattern). * **LP-Lock is a wrapper, not a fifth AMM.** It holds LP positions on behalf of creators under a PDA so the underlying fees can still be claimed without exposing the ability to withdraw liquidity. It composes over CPMM and CLMM pools. * **Off-chain surfaces complement each other.** The REST API is read-only with caching; the Transaction API builds ready-to-sign transactions server-side; the SDK builds them client-side. All three depend on the same IDL registry as the schema source of truth. ## Data flow: a CPMM swap, end to end To make the picture concrete, here is what happens when a user swaps USDC → RAY on a CPMM pool from the Raydium UI. (AMM v4 and CLMM differ in the accounts they need, not in the high-level shape.) 1. **Quote request (off-chain).** The UI calls `GET https://api-v3.raydium.io/compute/swap-base-in` with the input mint, output mint, amount, and a slippage tolerance. The API consults its indexer, picks a route (possibly through multiple pools), and returns a quote plus the list of program IDs, pool IDs, and fee accounts that the client will need. 2. **Transaction build (client + SDK).** The client passes the quote to `raydium-sdk-v2`. The SDK resolves every PDA it needs (authority PDA, pool state, observation, vaults — see [`products/cpmm/accounts`](/products/cpmm/accounts)), injects the user's associated token accounts (creating them with the Associated Token Program if missing), and emits an unsigned `Transaction`. 3. **Wallet sign.** The user's wallet signs the transaction. Nothing Raydium-specific here; this is the standard Solana wallet flow. 4. **On-chain execution.** The signed transaction hits the Raydium **CPMM program**, which (a) validates the pool state, (b) applies the constant-product curve with the pool's fee config, (c) moves tokens between the user's ATAs and the pool vaults via CPI into SPL Token / Token-2022, (d) updates the `observation` account for the TWAP, and (e) returns. 5. **Indexer ingestion.** The Solana RPC a few slots later exposes the program logs. Raydium's indexer parses them, updates the pool's reserves, 24h volume, and APR, and serves the updated values to the next `/pools/info/ids` request. All four steps 2–4 happen within a single Solana transaction. The API is only involved in **step 1** (quote) and **step 5** (indexing for next time). If the API is down, a client with a live SDK and a Solana RPC can still transact — it just has to compute the route itself. ## Shared infrastructure Several primitives are used by every product and are worth naming once so later chapters can refer to them without redefinition. Details live in [`protocol-overview/shared-infrastructure`](/protocol-overview/shared-infrastructure); this is the index. | Primitive | What it is | Where it is defined | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | **Authority PDA** | A program-owned signer that actually controls the token vaults. Users never hold vault authority. | Per-program; CPMM uses `vault_and_lp_mint_auth_seed` — see [`products/cpmm/accounts`](/products/cpmm/accounts). | | **Config accounts** | Per-program accounts holding fee rates, admin keys, and fund/creator destinations. Indexed by a `u16` in CPMM (`amm_config[index]`). | [`reference/program-addresses`](/reference/program-addresses) lists the API endpoints that return them. | | **Protocol/fund/creator fee split** | A single trade fee is split three (sometimes four) ways at settlement. Same pattern in CPMM and CLMM, different knobs. | [`reference/fee-comparison`](/reference/fee-comparison) | | **Observation account** | Ring buffer of price samples used for the TWAP. Written on every swap. | [`products/cpmm/accounts`](/products/cpmm/accounts), [`products/clmm/accounts`](/products/clmm/accounts) | | **REST API (`api-v3.raydium.io`)** | The single public read API for pool metadata, positions, farm state, and quote computation. | [`sdk-api/rest-api`](/sdk-api/rest-api) | | **IDL registry** | Anchor IDLs for every program, mirrored at [`github.com/raydium-io/raydium-idl`](https://github.com/raydium-io/raydium-idl). The SDK and CPI integrators deserialize against these. | [`sdk-api/anchor-idl`](/sdk-api/anchor-idl) | ## Off-chain surface: API vs SDK vs IDL These three are routinely confused. They do different things: * **REST API** (`api-v3.raydium.io`) is a **read-mostly, cached view** of on-chain state plus the **quote engine**. It tells you which pools exist, what their reserves are, how APRs look, and what the best route is for a swap. It does **not** build transactions. * **TypeScript SDK** (`@raydium-io/raydium-sdk-v2`) is a **transaction builder**. It knows every program's account layout and instruction format. It fetches fresh state from an RPC (not from the API) before composing an instruction, so it can sign accurate transactions. It talks to the API only when it needs a quote. * **IDL registry** is the **schema** both of the above depend on. If you are writing Rust CPIs into a Raydium program, the IDL is the contract; if you are writing a TS integration, you are using IDLs indirectly through the SDK. ## Where each chapter fits The diagram above recurs — in reduced form — throughout the docs. Here is where the full treatment of each piece lives so you can drill in: * **On-chain programs:** one chapter per product under [`products/`](/products). Each chapter follows the same template (overview → accounts → math → instructions → fees → code demos). * **Shared cross-program primitives:** [`protocol-overview/shared-infrastructure`](/protocol-overview/shared-infrastructure) and [`algorithms/`](/algorithms) for the math that recurs (constant-product, concentrated-liquidity, curve pricing). * **Off-chain surface:** [`sdk-api/`](/sdk-api) has the full SDK and REST API reference, plus [`sdk-api/anchor-idl`](/sdk-api/anchor-idl) and [`sdk-api/rust-cpi`](/sdk-api/rust-cpi). * **User-level flows (create a pool, swap, LP, claim rewards, launch a token):** [`user-flows/`](/user-flows). * **Integration patterns for other teams (aggregators, wallets, bots):** [`integration-guides/`](/integration-guides). * **Security surface, admin keys, known risks, audits:** [`security/`](/security). * **Versioned changes and the AMM v4 → CPMM / Farm v3 → v6 migration story:** [`protocol-overview/versions-and-migration`](/protocol-overview/versions-and-migration). ## Non-goals of this diagram A few deliberate omissions, so no one reads more into it than is there: * **No price oracles.** Raydium does not depend on Pyth, Switchboard, or any external oracle for its core AMM pricing. Quotes come from on-chain reserves. The `observation` account exists so **other** contracts can read a Raydium TWAP — Raydium itself does not need it. * **No on-chain token-voting program.** Admin actions such as fee-config updates and program upgrades are executed by a multisig. The multisig keys and rotation policy are in [`security/admin-and-multisig`](/security/admin-and-multisig). * **No bridges.** Raydium is Solana-native. Cross-chain flows are the integrator's problem and live outside this diagram. Sources: * [`reference/program-addresses`](/reference/program-addresses) for the canonical program IDs referenced throughout this page * [github.com/raydium-io/raydium-sdk-V2](https://github.com/raydium-io/raydium-sdk-V2) * [github.com/raydium-io/raydium-idl](https://github.com/raydium-io/raydium-idl) # Protocol Overview Source: https://docs.raydium.io/protocol-overview/index The end-to-end architecture of Raydium across all four product lines, the infrastructure they share, and how versions have evolved. ## Who this chapter is for Developers, researchers, and operators who want the 30,000-foot view before diving into a specific product. If you already know which product you need, skip to `/products`. ## Chapter contents A single diagram showing all four programs (AMM v4, CPMM, CLMM, Farm/LaunchLab), their on-chain state, off-chain infrastructure (API, SDK, indexers), and the user/aggregator entry points. Cross-program primitives: authority PDAs, admin/config accounts, fee collection, oracle usage, the REST API surface that fronts all products. Release history per product, deprecations, and the recommended migration paths (notably AMM v4 → CPMM, Farm v3/v5/v6). ## Writing brief * `architecture.mdx` should have ONE canonical diagram that every other chapter can reuse. Commit the source (Mermaid or draw\.io) alongside the SVG. * `shared-infrastructure.mdx` is where repeated patterns (e.g., the protocol/fund-fee collection pattern) are *defined once*. Product pages link here. * `versions-and-migration.mdx` is the chapter to check when something is deprecated — link to it from every page that uses a deprecated primitive. # Shared infrastructure Source: https://docs.raydium.io/protocol-overview/shared-infrastructure The primitives every Raydium program shares — authority PDAs, AmmConfig accounts, the protocol/fund/creator fee split, observation accounts, and the public REST API surface — defined once so per-product chapters can refer back here. Raydium's product programs are independent codebases, but they were designed against a shared set of conventions. This page is the canonical reference for those conventions. Per-product chapters describe how the conventions are *instantiated* in their accounts; this page describes the conventions themselves. ## What "shared" means here Three flavors of sharing run through the codebase: * **Convention sharing.** Every program uses the same PDA-derivation pattern, the same fee-split shape, and the same observation-account idea — but each implements them in its own program with its own seeds. * **Account sharing.** A handful of accounts are *literally* the same record across many pools (the global authority PDA in CPMM, the AmmConfig accounts). * **Off-chain sharing.** One REST API and one TypeScript SDK front all four programs. Integrators interact with one HTTP host and one NPM package regardless of which program they end up calling. The five primitives below cover everything that crosses program boundaries. ## 1. Authority PDAs Every Raydium program has exactly one PDA that owns its token vaults. Users never hold vault authority directly — the authority PDA is the only signer that can move funds out, and it only signs when a valid program instruction tells it to. The pattern is identical across products; the seeds differ: | Program | Authority PDA seeds | Notes | | --------------------- | --------------------------------------- | --------------------------------------------------------------- | | **AMM v4** | `[poolId]` | Per-pool authority. Same shape as v4's per-pool design. | | **CPMM** | `[b"vault_and_lp_mint_auth_seed"]` | **Single PDA shared by all CPMM pools.** No pool-specific seed. | | **CLMM** | `[b"pool_vault_and_lp_mint_auth_seed"]` | Single PDA shared across all CLMM pools. | | **Farm v3 / v5 / v6** | `[farmId]` | Per-farm. The PDA owns staking + reward vaults for that farm. | | **LaunchLab** | `[b"vault_auth", launchId]` | Per-launch. Owns base + quote vaults pre-graduation. | A few things follow from this: * For CPMM and CLMM, the authority PDA is **a global account** — every pool of that type uses it. If you are CPI-ing into CPMM you need it once, not per pool. * For per-pool / per-farm authorities, you derive the PDA from the pool/farm ID. The SDK does this in `getPoolKeys` / `getFarmKeys`; if you are integrating directly, you derive with `findProgramAddressSync`. * **Vault ownership cannot be changed.** Once a token account is created with the authority PDA as owner, only that PDA — invoked by the program — can transfer out. There is no admin override. For the exact seeds and ATA layouts per program, see [`products/cpmm/accounts`](/products/cpmm/accounts), [`products/clmm/accounts`](/products/clmm/accounts), [`products/amm-v4/accounts`](/products/amm-v4/accounts), [`products/farm-staking/accounts`](/products/farm-staking/accounts), [`products/launchlab/accounts`](/products/launchlab/accounts). ## 2. Admin and config accounts CPMM and CLMM share a config-account pattern called **`AmmConfig`**: a small global account, indexed by a `u16`, holding the fee rates and admin destinations that apply to a whole *fee tier*. Pools bind to a config at creation and never re-bind. ```rust theme={null} // CPMM AmmConfig (abridged — the CLMM version is structurally similar) pub struct AmmConfig { pub bump: u8, pub disable_create_pool: bool, // gate new pool creation in this tier pub index: u16, // tier index pub trade_fee_rate: u64, // fraction of trade going to fees pub protocol_fee_rate: u64, // fraction of trade fee to protocol pub fund_fee_rate: u64, // fraction of trade fee to fund pub create_pool_fee: u64, // one-time per-pool creation fee pub protocol_owner: Pubkey, // signer for CollectProtocolFee pub fund_owner: Pubkey, // signer for CollectFundFee pub padding: [u64; 16], } ``` **Rules of the road:** * **Fee tiers are global.** When a pool says "this is a 0.25% pool", it means it binds to the AmmConfig whose `trade_fee_rate` was 0.25% at creation time. There is no per-pool rate override. * **A config can be changed but pools don't follow.** If the config authority edits an AmmConfig, every existing pool bound to that config picks up the new rate immediately. This is a feature, not a bug; it is how protocol-level economic changes propagate without per-pool migrations. * **`disable_create_pool` is the deprecation lever.** When a fee tier is sunset, the protocol multisig sets this flag — existing pools keep working but no new pools can choose the tier. * **`protocol_owner` / `fund_owner`** are the *signers* for fee-collection calls. Setting them to a multisig is what gates fee withdrawal. They are NOT the destination addresses for the fees themselves; that is `protocol_fee_destination` / `fund_fee_destination` on the same account. AMM v4 has no `AmmConfig` — its fee parameters are per-pool, hardcoded at creation. Farm and LaunchLab have their own equivalents (`FarmConfig`, `LaunchConfig`) covered in their respective chapters. A complete table of who can change what is in [`security/admin-and-multisig`](/security/admin-and-multisig). Current user-facing fee splits are in [`ray/protocol-fees`](/ray/protocol-fees). ## 3. The protocol / fund / creator fee split Every CPMM and CLMM swap fee is split across up to four destinations on the way out: ``` total swap fee │ ┌───────────────┼───────────────┬──────────────┐ ▼ ▼ ▼ ▼ LP pool side Protocol Fund Creator (raises k) treasury multisig (LaunchLab pools) ``` Mechanically: 1. **Trade fee accrues into the pool.** The fee is removed from the input side of the swap and the *post-fee* amount is what the constant-product math sees. This is what "the LP earns the fee" means — `k` rises and so does the implied per-LP token value. 2. **Protocol/fund/creator portions are deducted from that LP-side accrual into per-pool counter accounts.** They sit on the pool state (`protocol_fees_token{0,1}`, `fund_fees_token{0,1}`, etc.) until somebody calls `CollectProtocolFee` / `CollectFundFee` / `CollectCreatorFee`. They do not leave the pool's vaults until then; from a swap's perspective they are still "in the pool". 3. **Collection moves them out.** The respective signer (the `protocol_owner` / `fund_owner` keys on AmmConfig, or the launch creator for LaunchLab pools) calls the collect instruction and the program transfers from the pool vault to a destination ATA. A few load-bearing observations: * **The split percentages are off the trade fee, not off the trade.** A 0.25% trade fee with a 12% protocol share means the protocol gets `0.25% × 12% = 0.03%` of the trade — not 12% of the trade. * **Creator fees only exist on LaunchLab-graduated pools.** Standard CPMM/CLMM pools have a 3-way split (LP / protocol / fund). LaunchLab adds a fourth slot routed to whoever launched the token, configured at `Initialize` and immutable. * **AMM v4 splits two ways only**, hardcoded per-pool: LP and protocol. No fund slot, no creator slot. * **Fund vs protocol** — both are protocol-treasury destinations, but they have different signers and different intended uses. `protocol` historically funds operations; `fund` is the longer-term treasury. The split between the two is itself a tunable. Specific rates are in [`reference/fee-comparison`](/reference/fee-comparison) and [`ray/protocol-fees`](/ray/protocol-fees). ## 4. Observation accounts (TWAP ring buffer) Both CPMM and CLMM maintain an **observation account** per pool — a fixed-size ring buffer of `(timestamp, cumulative_price)` samples that other contracts can use to derive a manipulation-resistant TWAP. ```rust theme={null} // CPMM ObservationState (abridged) pub struct ObservationState { pub initialized: bool, pub observation_index: u16, // next slot to overwrite pub pool_id: Pubkey, pub observations: [Observation; OBSERVATION_NUM], pub padding: [u64; 4], } pub struct Observation { pub block_timestamp: u64, pub cumulative_token_0_price_x32: u128, // sum of (price × dt) since init pub cumulative_token_1_price_x32: u128, } ``` How it works: * **Every swap calls `update_observation`.** The program reads the current price, multiplies by the elapsed seconds since the previous observation, and adds it into the cumulative counter. The new entry overwrites the oldest slot (ring-buffer style). * **TWAP over a window** = `(cumul[end] − cumul[start]) / (timestamp[end] − timestamp[start])`. Consumers pick two observations bracketing the desired window and divide. * **Raydium itself does not use the TWAP for pricing.** The AMM math reads the spot reserves directly. Observations are an externality — Raydium pays the cost of writing them so other contracts can read. * **AMM v4 has no observation account.** It is older than the ObservationState design; integrators wanting a v4 TWAP have to compute one off-chain from log history. Layout details and indexing math are in [`products/cpmm/accounts`](/products/cpmm/accounts) and [`products/clmm/accounts`](/products/clmm/accounts). ## 5. REST API + SDK + IDL The off-chain surface is a single trio used by every product: * **REST API** — `https://api-v3.raydium.io`. A read-mostly indexed view of all on-chain state plus a quote engine. One host, one schema. * **TypeScript SDK** — `@raydium-io/raydium-sdk-v2` on NPM. Builds and signs transactions for every program. Talks to the API for quotes/metadata, talks to a Solana RPC for pre-sign state refreshes. * **IDL registry** — Anchor IDLs for every published program live in the [`raydium-idl`](https://github.com/raydium-io/raydium-idl) repo (one JSON per program: CPMM, CLMM, LaunchLab). The TypeScript SDK consumes these IDLs internally; downstream Rust / Python clients regenerate from the same files. The boundary between them is sharp: | Piece | Reads from | Writes to | Stale tolerance | | -------- | --------------------------- | -------------------------------------- | ----------------------------------- | | REST API | Indexer (parses chain logs) | — (read-only for integrators) | A few seconds | | SDK | API + RPC | Builds transactions; doesn't broadcast | None — must re-fetch state pre-sign | | IDL | Program source | — | Versioned per program upgrade | A common mistake is to feed REST API output directly into a transaction. Don't — re-fetch the relevant pool/position state from a Solana RPC in the slot you are signing against. The SDK does this automatically for first-party flows; if you bypass the SDK you have to do it yourself. The full reference is in [`sdk-api/`](/sdk-api), with the IDL surface specifically in [`sdk-api/anchor-idl`](/sdk-api/anchor-idl). ## 6. Indexers and price feeds The REST API is fed by Raydium's own indexer, which subscribes to program logs from a fleet of Solana RPCs and writes denormalized records into a SQL store. Two consequences for integrators: * **The indexer is the only thing that "knows about" cross-program state.** Mapping a CPMM pool to its CLMM counterpart, computing a 24h-volume number across program versions, picking up a farm associated with an LP mint — all of that is indexer work. Programs themselves don't do it. * **Indexer downtime is API downtime.** If the API returns stale or empty data, the indexer is the suspect. The on-chain state is unaffected; integrators with their own RPC and SDK can keep transacting. Price feeds are a separate concern. The API publishes a `priceUsd` field on most pool responses; this is computed off-chain from a snapshot of the indexer's view of the pool reserves and a quoted reference price (USDC pools as the common pivot). It is good enough for UI; it is not safe to use as an on-chain oracle. Use the observation TWAP for that. ## What is not shared Worth listing explicitly, because new readers often assume more sharing than exists: * **Programs do not call each other.** A CPMM swap never CPIs into CLMM or AMM v4. The only program that composes multiple AMMs is the AMM Routing program — and that one is itself thin, just emitting CPIs into each AMM in sequence. * **No shared upgrade authority across programs.** Each on-chain program has its own program-upgrade key (a 3/4 multisig plus a 24h timelock). They are not linked. * **No shared state between farms and AMMs.** A farm doesn't know which LP it stakes is from a CPMM pool, a CLMM-position-NFT-mint, or an unrelated SPL token. The farm program treats the staking mint as opaque. * **No oracle dependency.** Pricing is on-chain reserves. There is no Pyth/Switchboard fallback; the AMM does not check an oracle before clearing. ## Pointers * [`protocol-overview/architecture`](/protocol-overview/architecture) — the canonical diagram showing how these pieces compose. * [`protocol-overview/versions-and-migration`](/protocol-overview/versions-and-migration) — how the conventions evolved across program versions. * [`security/admin-and-multisig`](/security/admin-and-multisig) — who controls the keys behind the AmmConfigs. * [`reference/fee-comparison`](/reference/fee-comparison) — fee-rate matrix per product. * [`reference/program-addresses`](/reference/program-addresses) — canonical program IDs. Sources: * [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2) — the source of truth for PDA seeds, account layouts, and IDL definitions. * [Raydium IDL registry](https://github.com/raydium-io/raydium-idl) — Anchor IDLs. * Per-product accounts pages cited inline above. # Versions and migration Source: https://docs.raydium.io/protocol-overview/versions-and-migration Release-version history for every Raydium program, the deprecation status of each generation, and the practical migration paths between them. Raydium has lived for five years. Several of its programs are on their third or fourth generation. This page is the operator's-eye view of "which program version do I use, what is the status of the older ones, and how do I get from A to B if I am running on the older version today?" ## Status at a glance | Program | Current | Deprecated | New deployments | Existing instances | | ------------- | ------------------- | ---------- | ---------------------------- | --------------------------------------- | | **AMM v4** | v4 (one generation) | No | Discouraged but accepted | Fully operational | | **CPMM** | v1 | — | **Recommended default** | Fully operational | | **CLMM** | v1 | — | Recommended for ranged LPs | Fully operational | | **Farm** | v6 | v3, v5 | v6 only | v3 + v5 in wind-down (read-only mostly) | | **LaunchLab** | v1 | — | Recommended for new launches | Fully operational | The single most consequential takeaway from this table: **AMM v4 is not deprecated**, and **CPMM is the new default** — but they coexist deliberately. AMM v4 pools have years of trading history and are not being forcibly migrated. The choice of which program to launch a new pool on is a recommendation, not a constraint. ## AMM v4 — status and trajectory AMM v4 is the original Raydium pool design: constant-product pricing (`x · y = k`). It launched as a hybrid AMM with an OpenBook (formerly Serum) orderbook integration that mirrored portions of the curve as limit orders on a bound market. **The OpenBook integration has since been deactivated** — pools no longer share liquidity to OpenBook and all swaps execute purely against the curve via the V2 swap entrypoints. AMM v4 today is, in practice, a pure constant-product AMM with the OpenBook accounts preserved as inert state. ### What's frozen * **No more new fee tiers.** The AMM v4 fee structure is per-pool and was set at deploy. New pools accept the same hardcoded \~0.25% trade fee, \~12% to protocol. * **No new feature work.** The team has not added new instructions to AMM v4 since CPMM became the new default. The program is in stewardship mode — bug fixes only, no scope expansion. * **No Token-2022 support.** AMM v4 was written before Token-2022 existed and the integration was never retrofitted. Token-2022 mints have to use CPMM (or CLMM, where appropriate). * **OpenBook integration deactivated.** Every AMM v4 pool is still bound to a corresponding OpenBook market account on-chain, but the pool no longer posts or maintains orders on that market. An OpenBook outage no longer affects AMM v4 swaps. ### What's still working * **Existing pools trade normally.** No state migration was forced; v4 pools created in 2021 are still the active venue for many high-volume pairs in 2026. * **LPs can deposit, withdraw, and harvest farm rewards as usual.** Migration to CPMM is opt-in. * **Aggregators still route through it.** Jupiter and the Raydium Trade API both index v4 pools as first-class venues. ### When to still use AMM v4 Honestly: rarely. The cases where v4 is the better answer are narrow: * The pair already has a deep, well-traded v4 pool and you want to add liquidity to existing depth rather than splitting a market. (OpenBook-integrated routing is no longer a reason to choose AMM v4 — that integration is off.) In every other case, **launch new pools on CPMM.** See [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type) for the full decision tree. ## CPMM — adoption curve and the v4 → CPMM migration CPMM (constant-product market maker, internal name `raydium-cp-swap`) was deployed in 2024 as a clean-room rewrite intended to be the new default constant-product pool. It is structurally the simplest of Raydium's programs: pure `x · y = k`, no orderbook, native Token-2022 support, smaller transaction footprint. ### What CPMM gets you over AMM v4 * **Better LP economics by default.** CPMM's default AmmConfig routes 100% of trade fees to LPs (with the protocol fee toggleable per-tier). AMM v4 hardcodes \~12% to protocol. * **Lower pool-creation cost.** No OpenBook market needed. Creation is one transaction, \~0.15 SOL of rent vs \~0.6 SOL for v4. * **Token-2022.** Transfer-fee mints, transfer-hook mints (with caveats), confidential transfers — all supported on CPMM, none on v4. * **Cleaner integrator surface.** CPMM has an Anchor-CPI-friendly published crate (`raydium-cp-swap`), a simpler account list, and a stable IDL. AMM v4 ships an IDL but never had a maintained Rust CPI crate. * **Smaller account list per swap.** \~10 accounts vs \~17 for v4 (which carries the OpenBook market accounts even when not hitting them). ### When migration is worth it For an actively traded pool, the LP-fee uplift alone usually justifies migration within a few months. The arithmetic: a pool earning 0.25% × \$X daily volume gives 0.03% to protocol on v4 (the missing 12%). On CPMM that returns to LPs. Over a year, that compounds meaningfully. For a low-volume pool, migration is more about future-proofing — better defaults, Token-2022 support if you ever need it, easier integrations. ### How migration works There is no in-place upgrade. Migration is a *create-new-pool, drain-old-pool, refill-new-pool* sequence. The full step-by-step is in [`user-flows/migrate-amm-v4-to-cpmm`](/user-flows/migrate-amm-v4-to-cpmm); the high-level shape: 1. Create a new CPMM pool for the same pair, on the same fee tier you want to preserve. 2. Coordinate LPs: announce a window during which the old pool is drained and the new pool seeded. 3. Each LP withdraws from the v4 pool and deposits into the new CPMM pool. 4. (Optional) Set up a CPMM-side farm to attract incentivized LPs to the new pool. 5. Watch volume migrate as aggregators reweight toward the deeper pool. The chain itself doesn't enforce any of this — Raydium's API and frontend simply favor whichever pool is deeper, and aggregators route through whichever is cheapest to the user. ## CLMM — single program, stable across versions CLMM is on its first program version. There has been no v2 — improvements have shipped as in-place upgrades to the same program ID (behind the 24h timelocked multisig), not as a new generation. That means there is **no CLMM migration story**: existing positions stay where they are, and the program's behavior may change subtly when an upgrade ships, but the account layouts and PDAs are stable. What has changed across CLMM upgrades: * **`SwapV2` instruction** added to support Token-2022 transfer-fee math correctly. Old `Swap` is still callable; new integrations should target `SwapV2`. * **Reward stream extensions** — the `RewardInfo` slot count was bumped (the original 3 → still 3 currently, but the reservation pattern was tightened). No data migration needed. * **Tick-array compaction** — internal optimization to reduce CU on swap-crossing-many-ticks. Externally invisible. The IDL lives in the dedicated [`raydium-idl`](https://github.com/raydium-io/raydium-idl/blob/master/raydium_clmm/raydium_clmm.json) repository (see [`sdk-api/anchor-idl`](/sdk-api/anchor-idl)). If you are running an older SDK against the current program, the worst case is missing the new instructions. ## Farm v3 → v5 → v6 Of all Raydium programs, Farm has the most explicit version history and the only forced migration path. The three generations are separate programs with separate program IDs and separate state layouts. ### Generations | Version | Released | Status | Key features | | ------- | -------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | **v3** | 2021 | **Wind-down.** Existing farms run; no new farms accepted. | Single reward stream. Slot-based emission. | | **v5** | Oct 2022 | **Wind-down.** Existing farms run; no new farms accepted. | Up to 2 reward streams. Slot-based emission. Integer `per_second`. | | **v6** | 2024 | **Current.** All new farms. | Up to 5 reward streams. Wall-clock emission. Q64.64 fixed-point `per_second`. Token-2022 staking + reward support. | ### Why three generations exist * **v3 → v5**: needed multiple concurrent reward streams (e.g., dual-incentive farms). v3's single-stream design couldn't support it without a redesign. * **v5 → v6**: v5's `u64` integer emission rate caps the minimum expressible rate at "1 token-unit per second." For a 9-decimal mint, that's 1 lamport/sec — far too coarse for low-emission programs. v6's Q64.64 fractional rate fixes this. v6 also lifted the slot-based update to wall-clock, and added Token-2022 support. ### What stays the same across generations * The "deposit LP, accrue per-share counter, claim on withdraw" accounting pattern is identical across v3/v5/v6. The math doesn't change; only the precision of the rate counter and the number of supported streams. * `UserStake` (v3/v5) and `UserLedger` (v6) are conceptually the same record, with different layouts. The SDK normalizes both. ### Migration path There is no in-place migration between farm versions. To move from v3/v5 to v6: 1. Wait for the existing farm's emissions to end (or run them down). 2. Stakers withdraw and claim pending rewards on the old farm. 3. The farm operator creates a new v6 farm against the same staking mint. 4. Stakers re-stake into the new farm. The on-chain reality is two unrelated farm accounts. A user with stake in both has two `UserLedger` (v6) / `UserStake` (v5) records. ### What "wind-down" means for v3 and v5 * The v3 and v5 programs are still deployed and callable. Existing farms can still distribute pending rewards and accept withdrawals. * The Raydium UI still surfaces v3 and v5 farms with active rewards; once a v3/v5 farm's `end_time` passes, the UI hides it from "active" but keeps it claimable. * The team will not create new v3/v5 farms. SDK helpers for "create farm" route to v6 only. * v3 and v5 are *receiving security upgrades but not feature work*. If a critical bug is found, it is fixed; if a feature could be useful, it is added to v6 instead. Full detail per version is in [`products/farm-staking/accounts`](/products/farm-staking/accounts) and [`products/farm-staking/instructions`](/products/farm-staking/instructions). ## LaunchLab — single program, evolving config LaunchLab is on its first program version. Like CLMM, improvements ship as in-place upgrades behind the 24h timelock — not as new generations. What has evolved through upgrades: * **Creator-fee slot.** Added so launches can route a portion of post-graduation CPMM trading fees to the original creator. See [`products/launchlab/creator-fees`](/products/launchlab/creator-fees). * **Curve-formula configurability.** Originally hardcoded quadratic; now the `LaunchConfig` selects from a small set of curve shapes. Existing LaunchLab launches are not affected by upgrades — once a launch is initialized, its parameters are frozen until graduation. ## Cross-program version compatibility A few cross-product compatibility notes integrators routinely hit: * **CLMM `SwapV2` is not the same instruction as `Swap`.** If your client speaks `Swap` only, it will silently mishandle Token-2022 transfer fees — the math is wrong by the fee amount. Update to `SwapV2`. * **Farm v6 staking with CLMM positions** is not supported the way LP-token staking is. CLMM positions are NFTs, not fungible LP tokens. CLMM has its own native reward mechanism instead — see [`products/clmm/fees`](/products/clmm/fees). * **CPMM pools backed by Token-2022 mints work in farms only on Farm v6.** v3 and v5 reject Token-2022 staking mints. * **AMM v4 pools never have Token-2022 LP mints.** If you see one, it's a fake — AMM v4 doesn't support that combination. ## Where to read more * [`introduction/history-and-milestones`](/introduction/history-and-milestones) — the chronological release timeline and why each version landed when it did. * [`user-flows/migrate-amm-v4-to-cpmm`](/user-flows/migrate-amm-v4-to-cpmm) — the operator runbook for the v4 → CPMM move. * [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type) — decision tree for new pool deployments. * [`products/farm-staking/accounts`](/products/farm-staking/accounts) — side-by-side schema for v3 / v5 / v6. * [`reference/changelog`](/reference/changelog) — what has changed in this documentation as program versions have evolved. Sources: * Per-product chapter pages cited inline above. * [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2) — version-aware dispatch logic confirms which program a given pool belongs to. * [`reference/program-addresses`](/reference/program-addresses) — canonical IDs per version. # Add or remove liquidity Source: https://docs.raydium.io/user-flows/add-remove-liquidity Deposit, withdraw, harvest farm rewards, and collect CLMM fees and rewards through the Raydium UI. Liquidity positions and rewards use different mechanics across Raydium products. **CPMM** positions use fungible LP tokens. **CLMM** positions use position NFTs with a selected price range. **Farm** rewards accrue while eligible LP tokens are staked. ## Before you start Check that you have: * The token pair you want to deposit. * Enough SOL for transaction fees and priority fees. * The correct pool on Raydium.io. * A clear understanding of whether the position is passive CPMM liquidity or actively managed CLMM liquidity. If you are creating a new pool, start with [Pool Creation](/user-flows/choosing-a-pool-type). ## Add liquidity to a CPMM pool 1. Open the pool page on Raydium. 2. Click **Add liquidity**. 3. Enter the amount of one token. The other amount auto-fills at the pool's current ratio. 4. Review the LP tokens you will receive and your expected pool share. 5. Confirm in Raydium, then approve in your wallet. The UI creates missing token accounts, accounts for supported Token-2022 transfer-fee mints, and wraps SOL when needed. ## Remove liquidity from a CPMM pool 1. Open the pool page or your portfolio position. 2. Click **Remove liquidity**. 3. Enter the LP token amount, or use the percentage slider. 4. Review the two token amounts you will receive. 5. Confirm and approve in your wallet. Small rounding dust can remain after withdrawal. This is normal for constant-product pools. ## Open or manage a CLMM position CLMM positions are NFTs. They have four common actions: * **Open position**: mint a new position NFT with an initial range and liquidity. * **Increase liquidity**: add more liquidity to an existing position. * **Decrease liquidity**: remove part of the liquidity while keeping the NFT. * **Close position**: remove all liquidity, collect pending fees and rewards, then burn the NFT. ## Open a CLMM position 1. Open the CLMM pool page. 2. Choose a lower price and upper price. 3. Enter the deposit amount. 4. Review whether the position is in range, partially in range, or out of range. 5. Confirm and approve. Your selected range determines the token ratio. If the current price is near the lower edge, the position may require mostly one token. If it is near the upper edge, it may require mostly the other token. ## Increase, decrease, or close a CLMM position * **Increase**: open the position card, click **Increase**, enter an amount, review the required token pair, and sign. * **Decrease**: click **Decrease**, choose a percentage or amount, review the estimated token return, and sign. * **Close**: decrease liquidity to zero, collect unclaimed fees and rewards, then close the position. Closing burns the position NFT and returns rent where applicable. If the current price is outside your selected range, the deposit may be single-sided. The UI warns before you sign a single-sided or out-of-range position. ## Harvest farm rewards Farm rewards accrue while your LP tokens are staked in an active farm. Harvesting transfers pending rewards to your wallet. It does not withdraw your stake. 1. Open [raydium.io/portfolio](https://raydium.io/portfolio). 2. Find your farm position. 3. Click **Harvest**. 4. Review pending amounts per reward stream. 5. Confirm and approve in your wallet. Farm deposits and withdrawals often settle pending rewards as part of the same action. Review the confirmation carefully because it may also harvest pending rewards. ## Collect CLMM fees and rewards CLMM positions earn swap fees in both pool mints. Fees accrue on the position and do not auto-compound. 1. Open your CLMM position from [raydium.io/portfolio](https://raydium.io/portfolio). 2. Click **Collect fees** or **Collect rewards**. 3. Review the token amounts. 4. Confirm and approve. If the UI offers **Collect all**, use it to collect fees and rewards together. To compound manually, collect fees, rebalance the two tokens if needed, then increase liquidity on the position. ## Timing and fee economics Every claim costs a Solana transaction fee and may include a priority fee during congestion. * For small pending amounts, wait until rewards are meaningful relative to the transaction fee. * For larger positions, weekly or monthly claims are often enough. * There is usually no need to claim every day unless you are actively compounding. ## Verification After a CPMM deposit: * Check that your wallet shows the LP token balance. * Check that Raydium's portfolio page shows the position. * Open the transaction signature and confirm the pool deposit succeeded. After opening or increasing a CLMM position: * Check that your portfolio shows the position NFT. * Confirm the lower and upper price range match what you selected. * Check whether the current price is in range. After harvesting or collecting: * Check that the transaction status is **Success**. * Confirm the reward or fee token balance increased in your wallet. * Refresh Raydium's portfolio page and confirm pending amounts reset or decreased. ## Troubleshooting ### Slippage on deposit ratio The deposit ratio is computed at quote time. If the pool price moves before you sign, the transaction may fail. Re-quote and retry. Raise slippage only as much as needed for volatile pairs. ### Token-2022 transfer fees on deposit If a Token-2022 mint charges transfer fees, the pool receives less than you send. Raydium shows the transfer-fee warning before you sign. ### Dust at withdrawal Rounding may leave tiny amounts in the pool or in your wallet. This is normal and does not indicate a failed withdrawal. ### CLMM position is not an LP token CLMM positions are NFTs. Check Raydium's portfolio page or your wallet's NFT view instead of looking for fungible LP tokens. ### Harvesting just before a reward ends If a reward stream ends while you have pending rewards, the pending amount stops growing. You can still claim it after the end time unless the farm has been wound down in an unusual way. ### Priority fee too low If the transaction sits pending or expires, retry with a higher priority-fee setting. ### Claiming across many farms Large batch claims can fail if too many positions or reward streams are included. Claim a few positions at a time. ## Pointers * [CPMM accounts](/products/cpmm/accounts) and [CLMM accounts](/products/clmm/accounts) — on-chain state. * [CLMM fees](/products/clmm/fees) — CLMM's built-in fees and rewards. * [Wallet integration](/integration-guides/wallet-integration) — showing positions in a wallet UI. * [CLMM math](/algorithms/clmm-math) — range ratio formulas. # Burn & Earn (permanent liquidity lock) Source: https://docs.raydium.io/user-flows/burn-and-earn Permanently lock a CPMM or CLMM LP position while retaining the right to claim trading fees via a transferable Raydium Fee Key NFT. **What "burn" means here.** Despite the name, the LP tokens / position NFT are **locked** in a program-owned escrow, not destroyed. The liquidity can never be withdrawn, but fees continue to accrue and can be claimed by whoever holds the Fee Key NFT. The NFT is transferable; whoever owns it owns the fee stream. ## What Burn & Earn is Burn & Earn is a Raydium feature that lets a pool creator (or any LP) make a deposit **permanent** on-chain while keeping the right to harvest trading fees. A position becomes non-redeemable, but a newly minted **Raydium Fee Key** NFT represents the ongoing right to `collectFees` from that locked position. Common use cases: * **Launch trust.** Projects commit to not rug-pulling by locking the deployer's LP. Holders can verify on-chain that the liquidity is permanent. * **Fee-bearing collectible.** A tradable NFT that generates yield. Sell the NFT; transfer the fee stream. * **Treasury-grade LP.** Protocols that want to keep liquidity forever for brand / discovery purposes, while still monetizing the fee side. ## Supported pool types | Program | Burn & Earn support | | --------------- | --------------------------------- | | CPMM | Yes | | CLMM | **Full-range only** (recommended) | | AMM v4 (Hybrid) | Not supported | For CLMM, only positions spanning the full price range should be locked. Once locked, you cannot rebalance — if price moves out of your chosen range you permanently stop earning fees on that position. A tight-range Burn & Earn position is almost always a mistake. ## How the lock works at the program level 1. The original LP token (CPMM) or position NFT (CLMM) is transferred into a program-owned PDA owned by the Burn & Earn program. 2. A **Fee Key NFT** is minted to the caller. Its metadata points back to the locked position's PDA. 3. Burn & Earn exposes `collectFees` (or the equivalent per-pool-type call) that: * Verifies the signer holds the Fee Key NFT for the given locked position. * Calls the underlying pool program's fee-claim instruction via CPI. * Routes the collected fees to the Fee Key holder's ATAs. 4. There is no corresponding `withdraw` — the underlying LP / position never exits the escrow. The Fee Key is a standard SPL NFT. It can be sold on any Solana NFT marketplace, moved between wallets, used as collateral on lending protocols that accept NFTs, or wrapped into another contract. Whoever holds it at the time of a `collectFees` call receives the fees. ## UI walk-through 1. Create a CPMM pool, or a CLMM pool with a full-range position, as usual. 2. Go to **Liquidity → Create → Burn & Earn**. 3. Select the position to lock. 4. Verify the position details (NFT address, size, pool) and type the confirmation sentence the UI shows. This is intentionally friction-heavy because the operation is irreversible. 5. Click **Confirm, lock the liquidity permanently**. 6. A Fee Key NFT arrives in your wallet. It appears on the Portfolio page under locked positions. Claim fees the same way you claim from any position — through the Portfolio page or via `collectFees`. ## API surface The pool info endpoint returns a `burnPercent` field indicating the share of that pool's liquidity that has been locked via Burn & Earn: ```http theme={null} GET https://api-v3.raydium.io/pools/info/list ?poolType=standard &poolSortField=default &sortType=desc &pageSize=1 &page=1 ``` ```json theme={null} { "data": [ { "id": "...", "burnPercent": 99.17, ... } ] } ``` `burnPercent` is a handy surface for trust-signalling: a token whose LP is 99%+ burned is dramatically less rug-able than one whose LP is 0% burned. ## Pitfalls * **Irreversible.** The LP side can never be retrieved. Confirm the amount is something you're willing to never touch again. * **CLMM tight ranges are a trap.** Lock only full-range CLMM positions, unless you truly understand that a range-bound locked position stops earning once price exits the band. * **Losing the Fee Key NFT is losing the fees forever.** Treat it like a deed. Do not accidentally burn it; do not stake it in a hostile contract. * **Token-2022 mints with transfer fees**: Burn & Earn supports them but the transfer-fee haircut applies on every fee claim — budget for it. * **There is no partial lock.** If you want 50% locked and 50% movable, split the position into two positions first, then lock only one. ## LaunchLab uses Burn & Earn under the hood Tokens graduated from [LaunchLab](/products/launchlab) use Burn & Earn for post-migration LP. The default split on Raydium's own LaunchLab: **90% of LP tokens are burned** (locked in Burn & Earn) and **10% go to the creator as a Fee Key NFT**. Other platforms building on LaunchLab can configure different burn / creator / platform splits. See [`products/launchlab/platforms`](/products/launchlab/platforms) and [`products/launchlab/creator-fees`](/products/launchlab/creator-fees). ## Where to go next * [`products/cpmm/instructions`](/products/cpmm/instructions) — CPMM fee-claim mechanics. * [`products/clmm/instructions`](/products/clmm/instructions) — CLMM fee-claim mechanics. * [`products/launchlab/creator-fees`](/products/launchlab/creator-fees) — creator-side use of Fee Key NFTs. Sources: * Raydium web UI Burn & Earn flow at raydium.io/liquidity. * `api-v3.raydium.io/pools/info/list` response schema. # Buying a LaunchLab token Source: https://docs.raydium.io/user-flows/buying-a-launchlab-token How to find, research, and buy a token on Raydium LaunchLab — including risk factors, slippage settings, and what happens when the bonding curve graduates. LaunchLab is a **permissionless** platform. Anyone can create a token with any name or ticker at any time. There is no vetting process. Always research a token independently before purchasing. This page explains mechanics only and does not constitute financial advice. ## What LaunchLab tokens are LaunchLab tokens are issued on a **bonding curve**: a smart-contract-managed price curve where price rises as more tokens are bought and falls when tokens are sold. The curve operates until a graduation threshold (typically 85 SOL, though creators may set a different amount) is reached, at which point the token automatically migrates to a Raydium CPMM or AMM v4 pool and becomes freely tradable through normal DEX routing. Before graduation, you can only buy and sell against the curve itself. After graduation, the token trades on Raydium like any other pool. ## Before you buy ### Do your own research * Verify the token's mint address matches the one announced by the project in question — scammers routinely create identically-named tokens. * Check whether the **mint authority has been revoked**. If it hasn't, the creator could mint new supply and dilute your position. Wallets such as Solflare and explorers such as Solscan display this status. * Review the bonding curve progress: how many tokens have been sold, how many are left, and the current price. This is visible on the token's LaunchLab page. * Inspect the creator address's history if possible. ### Understand the curve mechanics Prices on the bonding curve are **not fixed**. Every buy moves the price up; every sell moves it down. A large buy immediately before yours can significantly raise the price you pay (front-running / sandwich risk). Use slippage limits (see below). ## Step-by-step: buying via the UI 1. Navigate to [raydium.io/launchlab](https://raydium.io/launchlab) and find the token you want to buy. 2. Click the token page to open its detail view. 3. Connect your wallet if you haven't already. 4. Enter the amount of SOL (or the quote token) you want to spend. 5. Review the **estimated tokens received** and the **price impact** shown in the UI. 6. Adjust your **slippage tolerance** if needed (see below). 7. Click **Buy** and approve the transaction in your wallet. 8. After the transaction confirms, the tokens will appear in your wallet. You can sell back to the curve at any time before graduation using the **Sell** button on the same token page. After graduation the token is in a Raydium pool; use the standard Swap interface instead. ## Slippage **Set an appropriate slippage tolerance.** Bonding-curve tokens can be volatile. If you set slippage too low, your transaction may fail when price moves between your quote and execution. If you set it too high, you are exposed to sandwich bots. A reasonable starting point for most launches is 1–3%; raise it only if needed on very low-liquidity launches nearing graduation. Slippage tolerance is the maximum price difference you are willing to accept between the quoted price and the executed price. The LaunchLab UI allows you to set this before confirming your transaction. ## After graduation When the curve's graduation threshold is reached (by any trader), the token migrates to a Raydium AMM pool automatically. At that point: * The bonding curve is closed; no further buys or sells go through it. * Your tokens remain in your wallet untouched. * You can now trade the token on Raydium's Swap page or any aggregator that indexes Raydium pools. * Pool migration can take a few minutes to appear in aggregator UIs. ## Risks | Risk | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Liquidity-control risk | LP handling depends on platform and launch settings. Check whether LP is burned, locked, assigned to the creator, or assigned to the platform. | | No graduation | If the curve never reaches its threshold, trading remains limited to the curve only. You can still sell back to the curve, but at a potentially lower price than you paid. | | Price volatility | Bonding curves are inherently speculative. Price can drop to near zero. | | Fake tokens | Scammers create look-alike tokens. Verify the mint address independently. | | Smart-contract risk | No on-chain program is risk-free. Only use funds you are prepared to put at risk. | ## Pointers * [LaunchLab overview](/products/launchlab/overview) — how the bonding curve and graduation mechanics work in detail. * [LaunchLab overview for users](/user-flows/launchlab-overview) — product-level flow and safety basics. * [Creating a LaunchLab token](/user-flows/creating-a-launchlab-token) — the creator's perspective. * [How creator fees work](/user-flows/how-creator-fees-work) — understand what fees you pay and where they go. * [LaunchLab platforms](/user-flows/launchlab-platforms) — how platform settings can affect launch behavior. * [Trust and safety](/getting-started/trust-and-safety) — general guidelines for safe DeFi usage. # Pool Creation Source: https://docs.raydium.io/user-flows/choosing-a-pool-type Choose the right Raydium pool type before creating a CPMM pool, CLMM pool, or farm. **TL;DR.** If you just want to LP and walk away, pick **CPMM**. If you are willing to actively manage a price range in exchange for higher fee capture per dollar, pick **CLMM**. Do not open new **AMM v4** positions unless you specifically need OpenBook orderbook connectivity for an existing pair. ## The three pools in one paragraph Raydium runs three AMM programs in parallel. **CPMM** is a standard `xy=k` constant-product pool — simple, robust, supports Token-2022, accepts 0.01% / 0.25% / 1% fee tiers. **CLMM** is a Uniswap V3–style concentrated-liquidity pool — you pick a price range, and your capital only earns fees while price is inside that range. **AMM v4** is the original Raydium hybrid that couples a constant-product curve with an OpenBook orderbook; it is still accepted by the program but no longer the default for new pools. Every new launch of a novel pair today goes to CPMM or CLMM. For a side-by-side product-level comparison see [`products/index`](/products/index) and [`reference/fee-comparison`](/reference/fee-comparison). This page helps you choose the right creation flow before opening the CPMM, CLMM, or farm walkthrough. Use CPMM for passive, full-range liquidity and most new standard token pairs. Use CLMM when you want concentrated liquidity and can manage a price range. Add incentive rewards for eligible LP tokens after the pool exists. ## Decision matrix | Your situation | Recommended pool | Why | | ------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------- | | First time LP, want low ongoing effort | **CPMM 0.25%** | Single deposit, no range management, fees auto-compound in reserves. | | Stable-to-stable pair (USDC/USDT, wSOL/stSOL) | **CLMM 0.01% tight range** | Tight range captures the bulk of fees at minimal IL. Huge capital efficiency vs CPMM. | | Correlated but drifting (SOL/JitoSOL, ETH/WETH bridged) | **CLMM 0.05% wide range** | Captures fees, tolerates drift, still far more efficient than CPMM. | | Volatile majors (SOL/USDC, BTC/USDC) | **CLMM 0.25% medium range** OR **CPMM 0.25%** | CLMM if you will manage; CPMM if you will set-and-forget. | | Long-tail memecoin or low-liquidity pair | **CPMM 1%** | Wide CLMM ranges barely beat CPMM on such pairs; CPMM's simpler accounting wins. | | Need orderbook liquidity on top of AMM | **AMM v4** | The only program that integrates with OpenBook's orderbook. | | Launching a new token bonding curve | **LaunchLab** (graduates to CPMM) | Not strictly a "pool type" decision — the LaunchLab product chooses CPMM for you. | ## The three questions that actually matter ### 1. Am I willing to rebalance? CLMM rewards active management; CPMM rewards inaction. If you can check your position once a week and move the range when price drifts to an edge, CLMM's fee capture per dollar is 4–40× higher than CPMM on the same pair at the same nominal fee tier. If you cannot, your CLMM position will silently sit out of range while CPMM LPs keep earning. Choose CPMM. ### 2. How correlated are the two tokens? Impermanent loss grows with **divergence** between the two prices. For tightly correlated assets (stable/stable, LST/SOL), IL is tiny and a narrow CLMM range is very effective. For weakly correlated assets (e.g., memecoin/SOL) IL can easily exceed fee income for a full year; widen the range or use CPMM. See [`algorithms/impermanent-loss`](/algorithms/impermanent-loss) for the math and worked examples. ### 3. What fee tier matches my pair's volatility? Higher volatility → higher realised volume per TVL → a higher fee tier is sustainable. Rough guideline: * Stable/stable → 0.01% * Major/major correlated → 0.05% * Major/major volatile → 0.25% * Long-tail / memecoin → 1% A pair with lots of arbitrage flow at 0.25% may earn more LP revenue than the same pair at 1% because the higher fee can reduce volume. Check live 24-hour volume, TVL, and fee APR in the Raydium pool list before committing size. ## CPMM vs CLMM in practice ### CPMM * **Deposit:** two tokens at the current price ratio. Receive fungible LP tokens. * **Earn:** fees compound into pool reserves; your LP share appreciates. * **Withdraw:** burn LP tokens, receive pro-rata share of reserves plus earned fees. * **IL profile:** standard `xy=k` IL — see [`algorithms/impermanent-loss`](/algorithms/impermanent-loss). * **Best suited for:** passive LP, long-tail pairs, Token-2022 mints with transfer fees (CPMM handles them natively via `SwapV2`). ### CLMM * **Deposit:** two tokens (or one if the price is at a range boundary) for a specific `[tick_lower, tick_upper]` range. Receive a **position NFT**, not fungible LP tokens. * **Earn:** fees accrue per-position and must be **claimed** explicitly (`collectFee`). They do not auto-compound. * **Withdraw:** burn or partially decrease the position; receive remaining tokens and any unclaimed fees. * **IL profile:** IL is amplified within the range (you are effectively levered), but you also stop being exposed if price leaves the range. See [`algorithms/impermanent-loss`](/algorithms/impermanent-loss#clmm-specific-il). * **Best suited for:** active LP, correlated assets, pairs where you have a view on the price distribution. ### AMM v4 * **Deposit:** two tokens at the current price ratio. Pool is also registered against an OpenBook market, and pool assets sit partly on the orderbook. * **Earn:** fees from on-curve swaps plus any maker/taker interaction via OpenBook. * **Withdraw:** as CPMM, but the program must also unwind orderbook orders before settling. * **When to choose:** only when you need the OpenBook integration for an existing long-lived pair. New pairs should go to CPMM. See [`products/amm-v4/overview`](/products/amm-v4/overview) for the full hybrid model. ## Worked example: SOL/USDC as an LP Assume \$10,000 capital, SOL at \$160, projected 30-day volume \$150M on each pool. | Pool | Your TVL | Estimated fee APR\* | Rebalance effort | | --------------------- | ------------------- | ---------------------- | ------------------------------------ | | CPMM 0.25% | \$10k in \$50M pool | \~27% | None | | CLMM 0.25%, ±5% range | \$10k concentrated | \~110% (when in-range) | Check weekly, rebalance on ±4% drift | | CLMM 0.05%, ±2% range | \$10k concentrated | \~180% (when in-range) | Check daily | | AMM v4 0.25% | \$10k in \$30M pool | \~18% | None | \*APR numbers are illustrative; real numbers depend on volume, realised volatility, your time-in-range, and fee compounding. Use [`algorithms/clmm-apr`](/algorithms/clmm-apr) to estimate for your specific parameters. ## Common mistakes * **"CLMM always earns more."** Only while in range. Out-of-range CLMM earns zero; CPMM keeps earning at any price. * **"Tighter range = more fees."** Yes while in range — and more frequent out-of-range events if you don't rebalance. Tight ranges on volatile pairs are for active managers. * **"Higher fee tier = more fee income."** Not if the tier is too high for the pair and kills volume. Always quote volume-to-TVL at each tier before choosing. * **"LP token and position NFT are interchangeable."** CPMM LP tokens are fungible SPL tokens (farmable). CLMM position NFTs are non-fungible and carry your specific range. Farm v6 only stakes CPMM / AMM v4 LP tokens, not CLMM position NFTs. ## Where to go next * [`user-flows/add-remove-liquidity`](/user-flows/add-remove-liquidity) — mechanics of depositing and withdrawing. * [`algorithms/impermanent-loss`](/algorithms/impermanent-loss) — IL math and scenarios. * [`algorithms/clmm-apr`](/algorithms/clmm-apr) — estimating CLMM fee APR before opening a position. * [`reference/fee-comparison`](/reference/fee-comparison) — fee-tier table across products. Sources: * Raydium pool list and pool detail pages. * Empirical CLMM fee distribution across the top-100 Raydium CLMM pools, April 2026. # Claim rewards Source: https://docs.raydium.io/user-flows/claim-rewards Harvest farm rewards and collect CLMM fees and rewards through the Raydium UI. Rewards accrue automatically, but they remain pending until you claim them. This page covers two claim paths: **farm harvest** for staked farm positions and **CLMM collect** for concentrated liquidity positions. ## Farm harvest Farm rewards accrue while your LP tokens are staked in an active farm. Harvesting transfers pending rewards to your wallet. It does not withdraw your stake. ### UI walkthrough 1. Open [raydium.io/portfolio](https://raydium.io/portfolio). 2. Find your farm position. 3. Click **Harvest**. 4. Review pending amounts per reward stream. 5. Confirm and approve in your wallet. ## Combined deposit or withdraw with harvest Farm deposits and withdrawals often settle pending rewards as part of the same action. If you add more stake or withdraw stake, review the confirmation carefully because it may also harvest pending rewards. ## CLMM collect fees CLMM positions earn swap fees in both pool mints. Fees accrue on the position and do not auto-compound. 1. Open your CLMM position from [raydium.io/portfolio](https://raydium.io/portfolio). 2. Click **Collect fees**. 3. Review the token amounts. 4. Confirm and approve. To compound manually, collect fees, rebalance the two tokens if needed, then increase liquidity on the position. ## CLMM collect rewards Some CLMM pools have reward streams in addition to swap fees. 1. Open the CLMM position. 2. Click **Collect rewards**. 3. Review reward mints and amounts. 4. Confirm and approve. If the UI offers **Collect all**, use it to collect fees and rewards together. ## Timing and fee economics Every claim costs a Solana transaction fee and may include a priority fee during congestion. * For small pending amounts, wait until rewards are meaningful relative to the transaction fee. * For larger positions, weekly or monthly claims are often enough. * There is usually no need to claim every day unless you are actively compounding. ## Troubleshooting ### Harvesting just before a reward ends If a reward stream ends while you have pending rewards, the pending amount stops growing. You can still claim it after the end time unless the farm has been wound down in an unusual way. ### Transfer-fee reward mints If a reward mint charges transfer fees, your net received amount can be lower than the pending display. Review wallet balance changes after confirmation. ### First harvest fails Refresh the page and retry. If your position was created or migrated recently, the UI may need to initialize or refresh account data before harvest works. ### Priority fee too low If the claim sits pending or expires, retry with a higher priority-fee setting. ### Claiming across many farms Large batch claims can fail if too many positions or reward streams are included. Claim a few positions at a time. ## Verification After harvest or collect: * Check that the transaction status is **Success**. * Confirm the reward or fee token balance increased in your wallet. * Refresh Raydium's portfolio page and confirm pending amounts reset or decreased. ## Pointers * [`products/farm-staking/instructions`](/products/farm-staking/instructions) — harvest instruction details. * [`products/clmm/fees`](/products/clmm/fees) — CLMM's built-in rewards. * [`integration-guides/wallet-integration`](/integration-guides/wallet-integration) — showing pending rewards in a wallet UI. # Create a CLMM pool Source: https://docs.raydium.io/user-flows/create-clmm-pool Create a concentrated-liquidity pool through the Raydium UI: choose fee tier, set initial price, open the first range, and verify. A CLMM pool requires two permanent choices: a **fee tier** and an **initial price**. You also need to open an initial position so the pool has liquidity. Take extra care before signing. ## What you need * A Solana wallet holding: * The two token mints you want to pair. * About 0.3 SOL for pool and position creation rent plus priority fees. * A fee-tier decision. * A clear initial market price and first range. CLMM supports both SPL Token and Token-2022 mints. ## Fee tiers | Fee tier | Best for | | -------- | --------------------------- | | 0.01% | Stablecoin-stablecoin pairs | | 0.05% | Correlated pairs | | 0.25% | Most volatile major pairs | | 1% | Long-tail or exotic pairs | Lower fee tiers need tighter prices and higher volume. Higher fee tiers tolerate more volatility but can reduce volume. ## UI walkthrough On [raydium.io/clmm/create-pool](https://raydium.io/clmm/create-pool): 1. **Choose token pair.** Paste both mint addresses. 2. **Pick fee tier.** Use the UI recommendation as a starting point. 3. **Set initial price.** Use an existing market price. If a pool already exists for the pair, compare against it. 4. **Choose the first range.** Enter the lower and upper price for the initial position. 5. **Enter deposit amount.** The UI computes the required token ratio from your range. 6. **Review and sign.** Confirm the pair, fee tier, initial price, range, deposit amounts, and rent. ## Setting the initial price correctly If other CLMM or CPMM pools exist for the same pair, use their current price as the starting point. If no Raydium pool exists, use a reliable market source. If you set the initial price wrong, the first trade will likely arbitrage the pool and drain the underpriced side. There is no simple reset button. You usually need to withdraw and recreate the pool. ## First position: range selection * **Tight range:** best for correlated pairs, but needs active management. * **Medium range:** common for volatile majors such as SOL/USDC. * **Wide range:** lower capital efficiency, but easier to leave alone. * **Full range:** behaves more like a CPMM, but gives up much of the benefit of concentrated liquidity. If the current price sits outside your range, the initial position is out of range and earns no fees until price moves into range. ## Post-creation checklist 1. **Pool page loads.** Confirm the pool address, pair, and fee tier. 2. **Position appears.** Check your Raydium portfolio for the position NFT. 3. **Range is correct.** Confirm lower and upper price. 4. **Liquidity is non-zero.** The pool should show active liquidity for your selected range. 5. **Test a tiny swap.** Verify the route works before sharing the pool publicly. ## Troubleshooting ### Creation cost is higher than expected Wide ranges and small fee tiers can require more tick-array rent. Narrow the starting range or add more SOL before retrying. ### Wrong token order The UI canonicalizes token order, so the displayed token A/token B may differ from the order you entered. Review the price quote carefully before signing. ### Wrong fee tier You cannot change the fee tier after pool creation. If it is wrong, close your liquidity and create a new pool at the correct tier. ### Zero-liquidity pool Creating pool state without a position leaves the pool unusable for swaps. Open an initial position before sharing the pool. ### Out-of-range initial position If your first range does not include the initial price, the position earns no fees. Reopen a position around the current price. ## Pointers * [`products/clmm/overview`](/products/clmm/overview) — conceptual model. * [`products/clmm/accounts`](/products/clmm/accounts) — PoolState and PersonalPositionState layouts. * [`algorithms/clmm-math`](/algorithms/clmm-math) — tick and sqrt-price math. * [`user-flows/add-remove-liquidity`](/user-flows/add-remove-liquidity) — managing a position post-creation. # Create a CPMM pool Source: https://docs.raydium.io/user-flows/create-cpmm-pool Create a Raydium CPMM pool through the UI: choose tokens, set the fee tier and initial price, seed liquidity, and verify the pool. CPMM is the right product for most new pools. It is cheaper to create than AMM v4, supports Token-2022, and is the recommended default for permissionless listings. ## What you need * A Solana wallet holding: * Enough of each mint to seed initial liquidity. * About 0.2 SOL for pool creation rent, token-account creation, and priority fees. * The two token mint addresses. * A fee tier. The default 0.25% tier suits most volatile pairs. ## UI walkthrough On [raydium.io/liquidity/create-pool](https://raydium.io/liquidity/create-pool): 1. **Select pool type.** Choose **Standard AMM (CPMM)**. Use CLMM only if you understand range management. 2. **Enter both tokens.** Paste mint addresses or search by symbol. For Token-2022 mints, review any warnings. 3. **Set fee tier.** Common options are 0.01%, 0.25%, and 1%. Higher fees suit more volatile or long-tail pairs. 4. **Set initial price and deposit.** Enter one side and the initial price. The UI computes the other side. 5. **Review.** Check mint addresses, fee tier, initial price, LP tokens, pool share, and one-time rent. 6. **Confirm and sign.** Your wallet signs the pool-creation transaction. 7. **Wait for indexing.** The pool is live on-chain immediately, but the Raydium UI can take a few minutes to show it in search. ## Picking the initial price The pool's first price comes from your initial deposit ratio. If you deposit 10 SOL and 1000 USDC, the initial SOL price is 100 USDC. Choose a price close to the market. If the price is too high or too low, arbitrage traders will rebalance the pool and you will pay for that difference through your starting inventory. ## Post-creation checklist 1. **Open the pool page.** Confirm the pair, fee tier, and pool address. 2. **Check LP tokens.** Your wallet or Raydium portfolio should show the new LP token. 3. **Do a tiny test swap.** Use a small amount to confirm the pool routes as expected. 4. **Wait for discovery.** If the pool does not appear in search immediately, refresh after a few minutes and search by mint or pool ID. ## Token-2022 considerations If either mint is Token-2022 with transfer fees, the effective user cost is the pool fee plus the mint's transfer fee. The UI surfaces Token-2022 warnings before you sign. If either mint has a transfer hook, swaps depend on that hook program. If the hook program changes or blocks transfers, the pool may become hard to use. Treat unknown transfer-hook mints as higher risk. Non-transferable mints cannot be used in CPMM pools. ## Troubleshooting ### Wrong initial price If you catch the mistake before signing, go back and correct the deposit ratio. If you already created the pool, you usually need to add liquidity at the corrected price, let arbitrage settle it, or create a new pool. ### Priority fee too low Pool creation can expire during congestion. Retry with a higher priority-fee setting in the wallet or Raydium UI. ### Initial liquidity is too small Very small pools produce bad price impact and poor user experience. Add more liquidity before sharing the pool publicly. ### Duplicate pools Multiple CPMM pools can exist for the same token pair at different fee tiers. Before creating one, search Raydium by both token mints and check whether the project already endorses a pool. ### Permissioned mints A mint with a freeze authority can freeze token accounts. For unknown mints, verify the freeze authority and project controls before providing meaningful liquidity. ## Pointers * [`products/cpmm/accounts`](/products/cpmm/accounts) — PoolState layout. * [`products/cpmm/instructions`](/products/cpmm/instructions) — the initialize instruction. * [`user-flows/add-remove-liquidity`](/user-flows/add-remove-liquidity) — next step after pool creation. # Create a farm Source: https://docs.raydium.io/user-flows/create-farm Create a Raydium farm through the UI to incentivize LP holders: choose staking mint, reward mints, schedule, budget, and monitoring. A farm distributes reward tokens to users who stake a specific staking mint, typically a CPMM or AMM v4 LP token. Farm v6 is the current generation and the only one accepting new farm creation. ## When to create a farm Create a farm when: * You need to attract liquidity to a specific pool. * You want to reward LPs beyond native swap fees. * You are distributing treasury tokens to protocol-aligned LPs. A farm is unnecessary if the pool's swap fees already provide enough incentive. ## What you need * **Staking mint:** usually the LP token of an existing CPMM or AMM v4 pool. * **Reward mint(s):** 1-5 mints to distribute. * **Reward budget:** amount per reward mint for the full schedule. * **Wallet SOL:** about 0.1 SOL for creation rent, token accounts, and priority fees. ## Farm version choice | Version | Status | Use | | ------- | ------- | ------------------- | | v3 | Legacy | Existing farms only | | v5 | Legacy | Existing farms only | | v6 | Current | All new farms | Use Farm v6 for every new farm. ## Designing the reward schedule For each reward mint, decide: * **Total reward amount:** the budget you will fund up front. * **Start time:** now or a scheduled future timestamp. * **End time:** usually 30-90 days after start. * **Target APR:** your intended annualized reward relative to expected TVL. Most farm launches overestimate TVL. If less liquidity arrives than expected, the actual APR will be higher and the farm will spend the same budget faster per dollar of TVL. ## UI walkthrough On [raydium.io/farms/create](https://raydium.io/farms/create/): 1. **Pick staking mint.** Paste the LP mint address. Confirm it matches the pool you want to incentivize. 2. **Add reward mints.** Add 1-5 rewards with total amount, start time, and end time. 3. **Review APR and budget.** Check the projected APR, total reward spend, and per-reward breakdown. 4. **Create the farm.** Sign the farm creation transaction. 5. **Fund rewards.** Sign the funding transaction that moves reward tokens into the vault. 6. **Wait for indexing.** The farm should appear on Raydium within a few minutes. ## Monitoring the farm ### Stake progress Check total staked LP, number of stakers, and TVL after launch. If TVL is far below expectations, the displayed APR may be higher than planned. ### Realized APR Compare the displayed APR against your target. If token price or TVL changes materially, decide whether to top up, extend, or let the farm finish. ### Remaining reward balance Monitor remaining rewards before the end date. If rewards run dry early, users may see pending rewards that cannot be harvested until the vault is refilled. ## Top-up and extension Use the farm management UI to add more rewards or extend a running schedule when available. Keep the same public communication standard as launch: announce changes before users rely on the APR. If a farm has ended, create a new schedule or restart incentives according to the UI options available for that farm. ## Reclaiming unused rewards Unused rewards can be reclaimed only by the farm admin and only after the reward stream ends. Do not reclaim rewards early unless you have clearly announced the wind-down. ## Troubleshooting ### Under-funded vault If the reward vault balance is lower than the schedule requires, harvests can fail near the end. Fund the full budget up front. ### Wrong staking mint Creating a farm against the wrong LP mint is a serious mistake. Double-check the pool and LP mint before signing. ### Farm is not a pool A farm does not create liquidity. It only rewards users who stake an existing LP token. ### Priced per second Farm v6 emits rewards over time. Think in tokens per day or total budget over duration, not slots or blocks. ### Tokens with transfer fees If your reward mint has transfer fees, users receive less than the displayed gross reward. Disclose this in launch communications. ### CLMM positions do not use Farm v6 CLMM has built-in position rewards. Do not create a Farm v6 farm for CLMM position NFTs unless the UI explicitly supports that flow. ## Pointers * [`products/farm-staking/overview`](/products/farm-staking/overview) — farm concept. * [`products/farm-staking/instructions`](/products/farm-staking/instructions) — instruction-level reference. * [`user-flows/add-remove-liquidity`](/user-flows/add-remove-liquidity) — user-side harvest and collect flow. * [`algorithms/constant-product`](/algorithms/constant-product) — LP share economics. # Creating a LaunchLab token Source: https://docs.raydium.io/user-flows/creating-a-launchlab-token Two creation modes — JustSendit for a fast no-config launch, LaunchLab for full control over supply, curve, vesting, and post-migration fees. Covers UI walkthrough, key decisions, and what to do after launch. Raydium LaunchLab is infrastructure for token creation. Launching a token carries financial and reputational risk. Nothing on this page constitutes financial, legal, or investment advice. Consult appropriate professionals before launching a token that involves public fundraising. ## Two creation modes LaunchLab offers two creation paths accessible from [raydium.io/launchlab/create](https://raydium.io/launchlab/create): | | **JustSendit** | **LaunchLab** | | ------------------- | ------------------------------ | ------------------------- | | Setup time | \~1 minute | \~5 minutes | | Token supply | Fixed default | Fully configurable | | SOL target | 85 SOL (fixed) | 24 SOL minimum, custom | | Curve % | Default | 51–80% on curve | | Vesting | None | Optional | | Post-migration fees | Default (10% creator LP share) | Configurable | | Best for | Quick community launches | Structured token launches | *** ## JustSendit mode The fastest way to launch. Minimal input required; the platform fills in sensible defaults. ### What you need * A connected wallet with enough SOL for transaction fees (\~0.05–0.1 SOL for creation). * A token name and ticker symbol. * An image or GIF that meets the UI requirements. ### Steps 1. Go to [raydium.io/launchlab/create](https://raydium.io/launchlab/create) and select **JustSendit**. 2. Enter the token **name** and **ticker**. 3. Optionally add a **description** and **social links** (website, Twitter/X, Telegram). 4. Upload a **logo image or GIF**. 5. Optionally enter an **initial buy amount** in SOL to purchase tokens at creation. This changes the first on-curve price state and should be reviewed before signing. 6. Review the summary and click **Create**. 7. Approve the transaction in your wallet. Your token launches immediately on the bonding curve. It graduates to a Raydium AMM pool once 85 SOL has been collected by the curve. *** ## LaunchLab mode Full-control creation with configurable supply, fundraising target, curve allocation, vesting, and fee parameters. ### What you need * A connected wallet with \~0.1–0.5 SOL for creation and vault rent. * All the inputs below ready before you start (settings cannot be changed after launch). ### Configuration options #### Token details * **Name, ticker, logo** — same as JustSendit. * **Description and social links** — recommended for discoverability. #### Supply and curve allocation * **Total supply** — total number of tokens to be minted (e.g. 1,000,000,000). * **Curve allocation (%)** — the percentage of total supply placed on the bonding curve for public purchase (51–80%). The remainder seeds the post-graduation liquidity pool. The curve allocation and total supply are fixed at launch and **cannot be changed**. Choose carefully before signing. #### SOL fundraising target * The minimum graduation threshold is **24 SOL**. You can set it higher. * This is the amount of SOL the curve must collect before the token graduates to a CPMM pool. * Setting it too high risks the curve never graduating if demand is insufficient. #### Vesting (optional) Vesting locks a portion of the token supply and releases it linearly to designated wallets over a schedule. | Field | Description | | ------------------- | ---------------------------------------------------------------------------------------- | | Total locked amount | Tokens to lock across all beneficiaries (as % of total supply, subject to program caps). | | Cliff period | Wait time after graduation before any tokens unlock (e.g. 180 days). | | Unlock period | Duration of linear unlock after the cliff (e.g. 365 days). | Locked tokens do not enter the curve and are not part of the graduation liquidity pool. They are released to each beneficiary's wallet over the schedule above. **Important vesting rules:** * Cliff and unlock periods are fixed at launch — they cannot be changed retroactively. * If a beneficiary loses their wallet key, locked tokens are permanently unreachable. Use a multisig if recovery matters. * Any portion of `total_locked_amount` not allocated to a beneficiary before graduation is permanently stuck. Allocate the full amount. For the full on-chain mechanics see [LaunchLab vesting](/products/launchlab/vesting). #### Post-migration fee sharing When the token graduates: * LP tokens are minted to the CPMM pool. * By default on Raydium: **90% of LP tokens are burned** (permanently locked via Burn & Earn) and **10% are given to the creator** as a Fee Key NFT. * The Fee Key NFT entitles you to collect 10% of LP trading fees from the post-migration pool — indefinitely. Enable this if you want ongoing revenue from your token's liquidity and accept the Fee Key NFT custody responsibilities. See [How creator fees work](/user-flows/how-creator-fees-work) for details. ### Steps 1. Select **LaunchLab** mode on the creation page. 2. Fill in token details (name, ticker, logo, description, socials). 3. Set **total supply** and **curve allocation percentage**. 4. Set your **SOL fundraising target** (≥ 24 SOL). 5. Configure **vesting** if needed (set beneficiary wallets and schedule). 6. Enable **post-migration fee sharing** if you want a creator LP share. 7. Optionally enter an initial buy amount. 8. Review the full summary — curve price range, graduation condition, LP split. 9. Click **Create** and approve the transaction. *** ## After your token launches ### Monitor curve progress Your token's LaunchLab page shows real-time progress: * % of curve sold. * SOL raised vs. graduation target. * Current price and price chart. LaunchLab does not intervene in launches that fail to reach graduation. Monitor curve progress and communicate clearly with your community. ### Graduation Graduation is triggered automatically by the first transaction that pushes the curve past the threshold — this can be called by any user (the caller earns a small bounty). No action is required from you as creator. After graduation: * A CPMM pool is created with the collected SOL and remaining curve tokens as initial liquidity. * If you enabled post-migration fees, a **Fee Key NFT** is minted to your wallet. * Claim pre-migration creator fees from your Portfolio page. ### Claim pre-migration creator fees If your platform enables creator fees during the bonding curve phase, they accumulate in a vault. Claim them from the **Portfolio** page on raydium.io at any time. *** ## Pitfalls to avoid | Pitfall | What to do | | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | Setting SOL target too high | Pick a target the market can realistically hit. | | Over-allocating vesting before beneficiaries are confirmed | Unallocated vested supply is permanently inaccessible after graduation. | | Transferring or burning the Fee Key NFT unintentionally | Fee claim rights are permanently lost. Keep the NFT in the creating wallet or a wallet you control. | | Not making an initial buy | Without an early buy, sniping bots may capture all early tokens at near-zero cost, which can undermine community trust. | ## Pointers * [LaunchLab overview](/products/launchlab/overview) — the curve lifecycle and graduation mechanics. * [LaunchLab overview for users](/user-flows/launchlab-overview) — product-level lifecycle and launch modes. * [LaunchLab bonding curve](/products/launchlab/bonding-curve) — curve math and graduation threshold derivation. * [LaunchLab vesting](/products/launchlab/vesting) — full vesting account layout and instruction reference. * [How creator fees work](/user-flows/how-creator-fees-work) — pre- and post-migration fee details. * [LaunchLab platforms](/user-flows/launchlab-platforms) — how platform configuration affects launch settings and fees. * [Launch a token (developer guide)](/user-flows/launch-token-launchlab) — programmatic SDK walkthrough for developers. # Earn referral fees (LaunchLab) Source: https://docs.raydium.io/user-flows/earn-referral-fees-launchlab Share a LaunchLab token page link with your wallet as the referrer and earn 0.1% of every trade that comes through it — paid automatically in SOL. The LaunchLab referral is separate from the 1% Raydium swap referral described in [Referrals & Blinks](/user-flows/referrals-and-blinks). The 1% referral applies to Raydium's general Swap interface; the LaunchLab referral (0.1%) applies to trades on LaunchLab token pages specifically. Referral links do not verify token quality or safety. Only share tokens you are willing to stand behind, and make sure your audience can verify the mint address and launch settings themselves. ## How it works When you share a token page link that includes your wallet address as the referrer, anyone who trades via your link generates a **0.1% commission** paid directly to your wallet. The commission is: * Denominated in SOL (or the pool's quote token). * Sent automatically to your wallet on each trade — no separate claim step. * Applicable to buys and sells on the bonding curve while the token is pre-graduation. ### Session scope A referral link points to a specific token, but if the user who clicked your link then trades *other* LaunchLab tokens within the same browser session, you may also earn commissions on those trades. This extends your earning potential beyond the single token you initially shared. ## Generating your referral link 1. Connect your wallet on [raydium.io/launchlab](https://raydium.io/launchlab). 2. Navigate to any LaunchLab token's detail page. 3. Click the **Share** button on the token page. 4. Copy the generated link — it includes your wallet address as the referrer parameter. 5. Share the link wherever your audience is: social media, communities, newsletters. Your wallet must be connected when you click Share. The link will not contain a valid referrer parameter if generated without a connected wallet. ## What you earn | Scenario | Commission | | ----------------------------------------------------------- | ----------------------- | | Recipient buys the linked token | 0.1% of the trade value | | Recipient sells the linked token | 0.1% of the trade value | | Recipient trades other LaunchLab tokens in the same session | 0.1% of those trades | Earnings are sent in real time — there is no vault or pending balance to claim. ## Comparison: LaunchLab referral vs. Swap referral | | LaunchLab referral | Swap referral (Blinks) | | ------- | -------------------------- | --------------------------------------------------------------------- | | Rate | 0.1% | 1% | | Scope | LaunchLab token pages | Raydium Swap interface | | Payment | Automatic, per trade | Manual claim or auto depending on integration | | Setup | Share button on token page | Generate via [referrals-and-blinks](/user-flows/referrals-and-blinks) | ## Pointers * [Referrals & Blinks](/user-flows/referrals-and-blinks) — the higher-rate 1% swap referral program. * [LaunchLab overview](/user-flows/launchlab-overview) — how LaunchLab trading and graduation work. * [Buying a LaunchLab token](/user-flows/buying-a-launchlab-token) — how the trades you are referring work. * [How creator fees work](/user-flows/how-creator-fees-work) — the creator's own fee streams (distinct from the referral). # How creator fees work Source: https://docs.raydium.io/user-flows/how-creator-fees-work LaunchLab token creators can earn fees in two distinct phases: pre-migration (while the token is on the bonding curve) and post-migration (after graduation to a CPMM pool via a Fee Key NFT). Creator fees have two entirely separate mechanisms — one for the bonding-curve phase and one for the post-graduation AMM phase. They are funded differently, held differently, and claimed differently. Both are described here. Creator fees depend on platform configuration and launch settings. Verify the exact settings before signing. This page explains mechanics only and is not financial, legal, tax, or investment advice. ## Phase 1 — Pre-migration (bonding curve) While your token is trading on the bonding curve, a creator-fee component is taken from each buy and sell and accrued in a per-token vault. This is configured by the **platform** your token is launched on. ### Details * **Denominated in** the quote token (usually SOL). * **Rate** — set by the platform at launch time. Check the platform's fee schedule or the token's on-chain state. * **Claimable anytime** — go to your **Portfolio** page on raydium.io and claim pending fees at any time. No waiting for graduation. * **Stops at graduation** — no new pre-migration fees accrue after the curve graduates. Any residual balance in the vault remains claimable. ### How to claim 1. Connect the creator wallet to [raydium.io](https://raydium.io). 2. Navigate to **Portfolio**. 3. Find the token under your created tokens. 4. Click **Claim** next to the pre-migration fee balance. Programmatic claim: see [`products/launchlab/creator-fees`](/products/launchlab/creator-fees) for the SDK instruction. *** ## Phase 2 — Post-migration (Fee Key NFT) When your token's bonding curve reaches its graduation threshold, liquidity migrates to a Raydium CPMM pool. LP tokens are minted and distributed according to the **platform configuration**: | Recipient | Raydium default | Description | | --------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------ | | Burned (Burn & Earn) | 90% | Permanently locked; trading fees from this share flow to the Burn & Earn trust mechanism. | | Creator (Fee Key NFT) | 10% | A Fee Key NFT is minted to your wallet. This NFT is your claim on 10% of the pool's ongoing LP trading fees. | | Platform | 0% (Raydium default) | Some third-party platforms take a share here. | These defaults apply to tokens launched through Raydium's own LaunchLab UI. Third-party platforms may configure different splits — always check the platform page before launching. ### The Fee Key NFT The Fee Key NFT is a non-fungible token minted to your wallet at graduation that represents your right to collect LP trading fees from your share of the post-graduation pool. **Do not burn or transfer the Fee Key NFT unless you intentionally want to relinquish fee-claiming rights.** If the NFT is lost or burned, fee claim rights are **permanently forfeited** — there is no recovery path. * The Fee Key can be traded or transferred; whoever holds it can claim fees. * Fees accumulate continuously from LP trading activity in the CPMM pool. * Claim from the Portfolio page, or via the CPMM pool's fee-claim instruction programmatically. ### How to claim post-migration fees 1. Connect the wallet that holds the Fee Key NFT. 2. Navigate to **Portfolio** on raydium.io. 3. Find the graduated token's pool under your positions. 4. Click **Claim** to collect accrued trading fees. *** ## If post-migration fees are not enabled If a platform sets creator fees as disabled, your token graduates to an **AMM v4 pool** (the legacy migration path) instead of CPMM. In this mode: * No Fee Key NFT is minted. * There is no program-level post-migration creator fee. * Some alternative fee-claim options may exist via CLI tools depending on how the launch was configured. *** ## Fee flow summary ``` Bonding curve phase: Each buy/sell → small % → creator vault (quote token) Claim any time from Portfolio Graduation: LP tokens minted → split per platform config ├── X% burned (Burn & Earn) ├── Y% → creator as Fee Key NFT ← you hold this └── Z% → platform (if applicable) Post-graduation: Every CPMM swap → LP fees → split by LP share Your Fee Key NFT share → claimable from Portfolio ``` *** ## Pointers * [LaunchLab creator fees (technical reference)](/products/launchlab/creator-fees) — on-chain mechanism, SDK calls, and integrator checklist. * [LaunchLab platforms](/products/launchlab/platforms) — how platform configuration drives the burn/creator/platform LP split. * [LaunchLab platforms for creators](/user-flows/launchlab-platforms) — user-facing platform checklist. * [Burn & Earn](/user-flows/burn-and-earn) — the lock mechanism that the burned LP share uses. * [Creating a LaunchLab token](/user-flows/creating-a-launchlab-token) — enable post-migration fees during launch configuration. # User Flows Source: https://docs.raydium.io/user-flows/index Task-oriented guides: swap, add/remove liquidity, create a pool or farm, and launch or buy a token. ## Who this chapter is for End users and developers who want a step-by-step recipe rather than a reference. Pages here favor *completeness of one path* over *coverage of all options*; the Products and SDK chapters are the exhaustive reference. Each flow has two views side-by-side where applicable: **UI walkthrough** (for end users) and **Programmatic walkthrough** (TS SDK, for developers). ## For Traders Basic swap, slippage tolerance, priority fees, how routing picks AMM vs. CLMM. Generate a swap link that pays you 1% on every swap through it; render as a one-click Blink on X. ## For Liquidity Providers Deposit, withdraw, harvest farm rewards, and collect CLMM fees and rewards. LP decision guide: CPMM vs CLMM vs AMM v4, matched to capital profile, pair correlation, and willingness to rebalance. From "I have two tokens" to a live CPMM pool, including LP token receipt. Choose a fee tier, set initial price, open the first position. Fund reward vaults, set emission schedule, deploy a farm against existing pool LP tokens. Why migrate, the checklist, the scripted path, verification. Permanently lock a CPMM / CLMM position while retaining a transferable Fee Key NFT that keeps earning trading fees. ## For Token Creators What LaunchLab is, who uses it, and how launches move from bonding curves to Raydium pools. How to find, research, and buy a token on the bonding curve — including risk factors and slippage settings. JustSendit (instant) vs. LaunchLab (full config) modes — supply, curve, vesting, and post-migration fees. Share a token page link and earn 0.1% of every trade that comes through it. Pre-migration vault fees and the post-migration Fee Key NFT explained side by side. What third-party platforms can configure and what creators should check before launching. # LaunchLab overview Source: https://docs.raydium.io/user-flows/launchlab-overview What LaunchLab is, who it is for, and how token launches move from bonding curve trading to Raydium pool liquidity. LaunchLab is permissionless token-launch infrastructure. Anyone can create a token, choose a name or ticker, and configure launch parameters. Always verify mint addresses and launch settings before buying or promoting a token. This page explains product mechanics only and is not financial, legal, or investment advice. LaunchLab lets creators issue tokens and bootstrap initial liquidity through a bonding curve. Buyers trade against the curve before graduation. When the launch reaches its graduation threshold, liquidity migrates to a Raydium AMM pool and trading continues through normal Raydium routing. ## Who uses LaunchLab | Audience | Common tasks | | --------- | ------------------------------------------------------------------------------------------------------- | | Traders | Discover early-stage tokens, buy or sell on the bonding curve, and track graduation progress. | | Creators | Launch a token without code, configure curve parameters, and optionally earn creator fees. | | Platforms | Build branded launch environments with their own fee settings, branding, and launch discovery surfaces. | ## How a launch works 1. A creator configures token metadata, supply, curve settings, graduation target, and optional fee settings. 2. The token opens on a bonding curve. Buyers and sellers trade directly against that curve. 3. The curve price changes as buys and sells happen. 4. When the curve reaches its graduation target, liquidity migrates to a Raydium pool. 5. After graduation, users trade the token through Raydium Swap and aggregators that route through Raydium pools. ## Creation modes | Mode | Use it when | Key settings | | ---------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | JustSendit | You want the fastest no-code launch with default settings. | Name, ticker, image, optional social links, optional first buy. | | LaunchLab | You need more control over supply, graduation target, curve allocation, vesting, or creator fees. | Supply, raise target, curve allocation, vesting, post-migration fee share. | JustSendit uses a default graduation target. LaunchLab mode lets creators set the graduation target, subject to the UI and program limits. ## Graduation and liquidity Graduation closes the bonding curve and migrates assets into a Raydium pool. The post-migration pool type and LP treatment depend on the platform configuration and creator-fee settings. * If post-migration creator fees are enabled, the launch migrates to CPMM and a Fee Key NFT can represent the creator's fee rights. * If the platform uses a legacy path without post-migration creator fees, the launch may migrate to AMM v4. * Platform settings can change how LP tokens are burned, locked, or assigned after migration. ## Safety basics * Verify the token mint, not only the ticker or logo. * Review the graduation target and curve progress before trading. * Check creator and platform fee settings before launching or buying. * Treat Fee Key NFTs as fee-rights assets. If transferred or burned, the claim rights move or may be lost. * Read [Trust and safety](/getting-started/trust-and-safety) before connecting a wallet or signing transactions. ## Pointers * [Buying a LaunchLab token](/user-flows/buying-a-launchlab-token) — how the trading flow works before and after graduation. * [Creating a LaunchLab token](/user-flows/creating-a-launchlab-token) — JustSendit and LaunchLab creation walkthroughs. * [How creator fees work](/user-flows/how-creator-fees-work) — pre-migration fees and post-migration Fee Key NFTs. * [LaunchLab platforms](/user-flows/launchlab-platforms) — what platform configurations can control. * [LaunchLab protocol overview](/products/launchlab/overview) — deeper protocol mechanics. # LaunchLab platforms Source: https://docs.raydium.io/user-flows/launchlab-platforms How third-party platform configurations shape LaunchLab launches, fees, branding, and post-migration settings. A LaunchLab platform is an on-chain configuration used by a launch surface or community. Platform settings can affect fees, branding, creator-fee support, and post-migration pool behavior. LaunchLab supports third-party launch environments through Platform PDAs. A platform can offer its own branded token launch experience while using LaunchLab program infrastructure and Raydium pool migration. ## What a platform can control | Setting | Why it matters | | ---------------------------- | ------------------------------------------------------------------------------------------------------ | | Creator-fee support | Determines whether token creators can earn pre-migration and post-migration fees. | | Platform fees | Sets where platform-level fee revenue is sent. | | Branding metadata | Gives the platform a name, description, and logo that can be displayed in app surfaces. | | Launch grouping | Lets the UI show tokens launched under the same platform. | | Post-migration pool settings | Can define CPMM fee configuration when the platform migrates launches to CPMM. | | LP distribution | Can affect how post-migration LP tokens are burned, assigned to creators, or assigned to the platform. | ## What creators should check Before launching under any platform, confirm: * Which wallet receives platform fees. * Whether creator fees are enabled. * Whether post-migration liquidity goes to CPMM or AMM v4. * How LP tokens are split after graduation. * Whether a Fee Key NFT will be minted and who receives it. * Whether the platform has its own terms, policies, or eligibility rules. Platform settings can materially change creator economics and user expectations. Review them before signing the launch transaction. This page explains mechanics only and is not legal, financial, or investment advice. ## What traders should check When buying a LaunchLab token, the platform can affect the fee path and post-migration liquidity handling. Before trading: * Check the token's platform and launch settings. * Confirm the post-migration pool type if the UI displays it. * Check whether LP is burned, locked, or assigned. * Verify the token mint from the token page, not from social media text alone. ## Pointers * [LaunchLab overview](/user-flows/launchlab-overview) — user-facing launch lifecycle. * [Creating a LaunchLab token](/user-flows/creating-a-launchlab-token) — creator setup flow. * [How creator fees work](/user-flows/how-creator-fees-work) — creator-fee paths and Fee Key NFTs. * [LaunchLab platform config](/products/launchlab/platform-config) — technical configuration reference. * [LaunchLab platforms reference](/products/launchlab/platforms) — protocol-level platform details. # Migrate AMM v4 → CPMM Source: https://docs.raydium.io/user-flows/migrate-amm-v4-to-cpmm Move liquidity from an AMM v4 pool to a CPMM pool through Raydium: checklist, UI steps, verification, and common concerns. AMM v4 is fully operational and has no announced sunset. Migration is a choice, not a forced move. CPMM offers a better LP fee split by default, lower creation costs, Token-2022 support, and no OpenBook dependency. ## Why migrate | Dimension | AMM v4 | CPMM | | ---------------------- | ------------------- | -------------------------------------- | | Fee destination | Protocol + LP split | LPs receive the default swap-fee share | | OpenBook dependency | Yes | None | | Creation cost | Higher | Lower | | Token-2022 support | No | Yes | | New listings routed to | Rare | Default | If your pair trades heavily, the LP-fee uplift can justify migration. If the old pool still has strong liquidity and the project has not endorsed a destination CPMM pool, migration may not be worth the coordination cost. ## Pre-migration checklist For pool operators and project treasuries: * [ ] Announce the migration timeline to LPs. * [ ] Give LPs at least 48 hours where possible. * [ ] Confirm the destination CPMM pool and fee tier. * [ ] Prepare post-migration announcements with the new pool ID and LP mint. * [ ] Decide whether a new farm is needed. For individual LPs: * [ ] Confirm you are following the project's endorsed migration. * [ ] Keep enough SOL for multiple transactions. * [ ] Note your AMM v4 LP balance and expected underlying amounts. * [ ] Check whether your AMM v4 LP tokens are staked in a farm. ## UI migration: individual LP The migration is two actions: remove liquidity from AMM v4, then add liquidity to CPMM. ### Step 1: withdraw from AMM v4 1. Open the AMM v4 pool or your portfolio position. 2. If your LP tokens are staked in a farm, unstake them first. 3. Click **Remove liquidity**. 4. Choose the percentage to withdraw. 5. Review the token amounts you will receive. 6. Confirm and approve. ### Step 2: deposit to CPMM 1. Open the destination CPMM pool. 2. Click **Add liquidity**. 3. Enter the amount of one token. 4. Review the paired token amount and any leftover balance. 5. Confirm and approve. Between withdrawal and deposit, pool prices can move. If the two pools have different ratios, some tokens may remain as dust in your wallet. ## UI migration: project treasury For a project treasury, the process is the same but requires coordination: 1. **Announce** the migration ahead of time. 2. **Seed the CPMM pool** before asking users to migrate. 3. **Migrate treasury LP** first and publish transaction links. 4. **Create a new farm** if incentives should continue on the CPMM LP mint. 5. **Monitor individual LP migrations** over the next days or weeks. 6. **Deprecate the old pool** only after users had enough time to move. ## Verification ### Post-withdraw * AMM v4 LP token balance should decrease or reach zero. * The two underlying tokens should appear in your wallet. * The transaction should show **Success** on a Solana explorer. ### Post-deposit * CPMM LP token balance should appear in your wallet. * Raydium's portfolio should show the new position. * The CPMM pool page should show your liquidity share. ### Proof of migration For transparency, publish both transaction links: * Withdraw from AMM v4. * Deposit to CPMM. ## Rollback considerations Rolling back from CPMM to AMM v4 means withdrawing from CPMM and depositing to AMM v4. It is usually not worth doing unless there is a specific product reason. AMM v4 pool creation is more expensive and requires OpenBook market setup. ## Common concerns ### My AMM v4 LP is staked in a farm. Can I migrate? Yes. Unstake first, withdraw LP from AMM v4, deposit into CPMM, then stake the new CPMM LP tokens if a new farm exists. ### The CPMM pool does not exist yet Most LPs should wait for the project to create and endorse a destination pool. Creating one yourself means you take the initial price and liquidity risk. ### I have a large position. What about slippage? Withdraw and deposit in smaller slices if your position is large relative to pool TVL. Review each quote and avoid signing if the received amounts are materially worse than expected. ### What happens to LP tokens I forget to migrate? They stay in the old pool and keep earning according to AMM v4 rules. Nothing forces migration. ## Troubleshooting ### Destination LP token account is new The first CPMM deposit may need to create a new LP token account. Keep extra SOL in your wallet for rent and fees. ### Second transaction expires If withdrawal lands but deposit expires, your tokens remain in your wallet. Reopen the CPMM pool and retry with a higher priority-fee setting. ### Wrong CPMM pool Multiple CPMM pools can exist for the same pair. Use the pool endorsed by the project or the pool with the intended fee tier and liquidity. ### Tax implications In some jurisdictions, LP withdrawal can be a taxable event. Consult local rules before migrating large positions. ## Pointers * [`products/cpmm/overview`](/products/cpmm/overview) — why CPMM. * [`user-flows/create-cpmm-pool`](/user-flows/create-cpmm-pool) — creating the destination pool. * [`user-flows/add-remove-liquidity`](/user-flows/add-remove-liquidity) — deposit and withdraw mechanics. * [`products/amm-v4/overview`](/products/amm-v4/overview) — AMM v4 product context. # Perpetuals Source: https://docs.raydium.io/user-flows/perpetuals Start trading Raydium Perps: open the perps app, create an account, deposit collateral, and place your first order. Raydium Perps is powered by Orderly Network. It is separate from Raydium spot swaps and liquidity pools. Perps trading uses an order book, not AMM pool liquidity. ## What Raydium Perps is Raydium Perps is a gasless perpetuals trading surface at [`perps.raydium.io`](https://perps.raydium.io/perp/PERP_SOL_USDC). You can trade perpetual futures from a Solana wallet without paying gas for every order placement, update, or cancellation. The product uses Orderly Network's infrastructure for the underlying venue. Raydium provides the product surface, while Orderly powers the central limit order book, matching, and perps venue mechanics. Key points: * **Perpetual futures:** trade long or short exposure without holding the underlying asset. * **Gasless trading:** placing and canceling orders does not require a Solana transaction. * **Leverage:** markets support leverage, with limits depending on the asset and venue settings. * **Multi-collateral deposits:** deposit supported collateral assets and use them for margin. * **Self-custody:** you connect with your wallet and approve account actions. Raydium Perps may be unavailable in restricted jurisdictions. Do not use the product if your location or local law prohibits access to Raydium Perps or the underlying venue. ## How to start trading You can fund an account and start trading from the perps app. ### 1. Navigate to Perpetuals Open [`perps.raydium.io`](https://perps.raydium.io/perp/PERP_SOL_USDC). Check the URL before connecting your wallet. ### 2. Create an account First-time users create a perps account. This is a one-time setup step. Review the wallet prompt before signing. ### 3. Deposit funds Deposit supported collateral. Raydium Perps supports deposits from major chains and supports collateral such as native SOL and USDC where available. After deposit, confirm your account equity and available margin before placing an order. ### 4. Start trading Choose a market, review the order book, select order type, set size and leverage, then submit the order. Start small until you understand how margin, liquidation price, and funding affect the position. ## What to check before opening a position * **Market:** confirm you selected the intended perp market. * **Direction:** long benefits from price rising; short benefits from price falling. * **Leverage:** higher leverage increases liquidation risk. * **Liquidation price:** check where the position can be liquidated. * **Order type:** market orders prioritize execution; limit orders control price. * **Margin:** make sure your account has enough available margin after fees. ## Where to go next * [`user-flows/perpetuals-trading-basics`](/user-flows/perpetuals-trading-basics) — perps concepts for new traders. * [`products/perps/order-types`](/products/perps/order-types) — supported order types. * [`products/perps/collateral`](/products/perps/collateral) — supported collateral and account funding. * [`products/perps/fees`](/products/perps/fees) — maker, taker, and withdrawal fees. # Multi-collateral deposit Source: https://docs.raydium.io/user-flows/perpetuals-multi-collateral-deposit Deposit supported collateral for Raydium Perps and understand global and per-user collateral limits. Raydium Perps supports multiple collateral assets, but USDC remains the settlement currency for profit and loss. Collateral limits can change. Check the live Raydium Perps and Orderly documentation before relying on a limit for a large deposit. ## Supported collateral Raydium Perps supports deposits from major chains and collateral assets such as SOL, USDC, USDT, ETH, BNB, WBTC, YUSD, and USD1 where available. The exact list, chains, and limits are controlled by the underlying venue. Treat the app as the source of truth before depositing. ## Global limits Global limits apply across the full venue, shared by all users. The referenced Raydium page lists examples such as: | Collateral | Chain examples | | ---------- | ------------------------ | | USDT | Arbitrum, BSC, Ethereum | | ETH | Arbitrum, Base, Ethereum | | BNB | BSC | | USD1 | BSC, Ethereum | | YUSD | BSC, Ethereum | | WBTC | Ethereum | | SOL | Solana | If a global limit is full, new deposits for that asset may be unavailable even if your wallet is below its own limit. ## Per-user limits Per-user limits apply to each account or wallet. The referenced Raydium page lists per-user limits for assets including USDT, ETH, SOL, BNB, YUSD, WBTC, and USD1. Check the deposit screen before sending funds. If the app shows a lower available deposit amount than your wallet balance, you may be hitting a per-user or global collateral cap. ## Deposit flow 1. Open [`perps.raydium.io`](https://perps.raydium.io/perp/PERP_SOL_USDC). 2. Connect your wallet and open the deposit flow. 3. Select collateral asset and source chain. 4. Review the amount, destination account, and any venue warnings. 5. Confirm the deposit. 6. Wait for the account balance and available margin to update. ## Where to go next * [`user-flows/perpetuals`](/user-flows/perpetuals) — start trading Raydium Perps. * [`user-flows/perpetuals-trading-basics`](/user-flows/perpetuals-trading-basics) — margin and liquidation basics. * [`user-flows/perpetuals-trading-fees`](/user-flows/perpetuals-trading-fees) — fees. # Perpetuals order types Source: https://docs.raydium.io/user-flows/perpetuals-order-types Order types and flags available on Raydium Perps: market, limit, stop, scale, IOC, FOK, post-only, and reduce-only. Raydium Perps uses an order book. Pick the order type based on whether you care more about immediate execution, exact price, or position management. ## Order types Raydium Perps supports these order types: | Order type | What it does | When to use it | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | **Market** | Buys or sells immediately against the best available order-book prices. Any unfilled remainder is canceled if the book cannot fill the full size. | You want immediate execution and accept possible slippage. | | **Limit** | Buys or sells at your limit price or better. The order fills only if the market reaches that price. | You want price control and can wait for a fill. | | **Stop market** | Stays inactive until the trigger price is reached, then becomes a market order. | You want an execution trigger and prioritize getting out or in once triggered. | | **Stop limit** | Stays inactive until the trigger price is reached, then becomes a limit order. | You want a trigger but still want a limit price. | | **Scale** | Splits one larger order into multiple smaller limit orders across a price range. | You want to enter or exit gradually. | ## Order flags Order flags change how limit-style orders behave. | Flag | Behavior | | --------------- | ------------------------------------------------------------------------------------------------------------------------------ | | **IOC** | Immediate-or-cancel. Fills as much as possible immediately at your limit price or better, then cancels the unfilled remainder. | | **FOK** | Fill-or-kill. Fills the entire order immediately at your limit price or better, or cancels with no partial fill. | | **Post-only** | Adds liquidity as a maker. If it would match immediately, it cancels instead. | | **Reduce-only** | Prevents the order from increasing your position. It can only reduce or close an existing position in that market. | ## User checks Before submitting an order, confirm: * Market and direction are correct. * Size and leverage match your intent. * Stop trigger and limit price are not inverted. * Reduce-only is enabled when you only intend to close or reduce exposure. * Post-only is enabled when you want maker behavior. ## Where to go next * [`user-flows/perpetuals`](/user-flows/perpetuals) — start trading Raydium Perps. * [`user-flows/perpetuals-trading-basics`](/user-flows/perpetuals-trading-basics) — perps risk basics. * [`user-flows/perpetuals-trading-fees`](/user-flows/perpetuals-trading-fees) — maker and taker fees. # Perpetuals trading basics Source: https://docs.raydium.io/user-flows/perpetuals-trading-basics A trader-focused primer for Raydium Perps: long and short positions, leverage, margin, liquidation, funding, and order execution. Perpetual futures are higher-risk products than spot swaps. Read this page before opening your first Raydium Perps position. This page explains product mechanics only. It is not financial, legal, tax, or investment advice. Perps can liquidate your collateral quickly when markets move against your position. ## What a perpetual future is A perpetual future is a derivative contract that tracks an asset price without an expiry date. You do not hold the underlying asset. You hold a position whose profit and loss changes with the market price. You can: * **Go long:** profit if price rises. * **Go short:** profit if price falls. ## Leverage Leverage lets you open more notional exposure than your collateral alone would support. It multiplies both gains and losses. Example: * With 1x exposure, a 1% adverse move is a 1% loss on the position. * With 10x exposure, a 1% adverse move is roughly a 10% loss on the collateral backing that position. * With 50x exposure, small price moves can quickly approach liquidation. Use lower leverage while learning the product. ## Margin and liquidation Your collateral backs open positions. If losses reduce your margin below the venue's maintenance requirement, the position can be liquidated. Before opening a position, check: * Your available margin. * Your estimated liquidation price. * Whether other open positions share the same collateral. * Whether funding or fees will reduce your margin over time. ## Funding Perpetuals use funding payments to keep perp prices aligned with spot markets. Depending on market conditions, longs may pay shorts or shorts may pay longs. Funding can help or hurt your position even if price does not move. Check the current funding rate before entering a trade. ## Order execution Market orders prioritize immediate execution. They can fill at a worse price in thin or volatile markets. Limit orders let you choose a maximum buy price or minimum sell price. They may not fill if the market does not reach your price. For full order behavior, see [`products/perps/order-types`](/products/perps/order-types). ## Risk checklist * Is the market correct? * Is the order direction correct? * Is the leverage appropriate? * Is the liquidation price far enough from current price? * Is the order type appropriate for current liquidity? * Do you understand the funding rate? ## Where to go next * [`user-flows/perpetuals`](/user-flows/perpetuals) — start trading Raydium Perps. * [`products/perps/collateral`](/products/perps/collateral) — collateral rules. * [`products/perps/fees`](/products/perps/fees) — trading and withdrawal fees. # Perpetuals trading fees Source: https://docs.raydium.io/user-flows/perpetuals-trading-fees Trading and withdrawal fees on Raydium Perps, including maker fees, taker tiers, and Orderly infrastructure fees. Raydium Perps fees are separate from Raydium spot AMM fees. Perps trading fees are tied to the Orderly-powered venue. ## Trading fees * **Maker orders:** 0 bps. * **Taker orders:** volume-tiered. * **Orderly infrastructure:** included in the taker fee. Orderly takes a 1 bps cut from taker volume. ## Withdrawal fee Withdrawals have a flat fee charged by Orderly Network. The referenced Raydium page lists this as **1 USDC per withdrawal**. ## Taker fee tiers Taker fees are based on trailing 30-day trading volume. | Tier | 30-day volume | Taker fee | | ---- | -----------------: | --------: | | 1 | Below 500k USDC | 4.5 bps | | 2 | At least 500k USDC | 4.0 bps | | 3 | More than 2M USDC | 3.5 bps | | 4 | More than 5M USDC | 3.0 bps | | 5 | More than 25M USDC | 2.5 bps | | 6 | More than 50M USDC | 2.25 bps | | 7 | More than 80M USDC | 2.0 bps | Check the live app before trading because venue parameters can change. ## User checks Before placing a trade: * Check whether your order is likely to be maker or taker. * Confirm the fee estimate in the order preview. * Account for withdrawal fees when moving funds out. * Check whether higher volume changes your taker tier. ## Where to go next * [`user-flows/perpetuals`](/user-flows/perpetuals) — start trading Raydium Perps. * [`user-flows/perpetuals-order-types`](/user-flows/perpetuals-order-types) — order behavior. * [`user-flows/perpetuals-multi-collateral-deposit`](/user-flows/perpetuals-multi-collateral-deposit) — deposits. # Referrals & Blinks Source: https://docs.raydium.io/user-flows/referrals-and-blinks Generate a Raydium swap link that pays you a 1% referral fee on every swap made through it. When shared on X, compatible wallets render the link as an interactive Blink. **Spot-swap referrals.** This 1% referral link is a feature of Raydium's spot **swap UI**, not Perps or LaunchLab (those have their own referral programs). LaunchLab creators can earn a separate 0.1% via token share links — see [`products/launchlab/creator-fees`](/products/launchlab/creator-fees) and the earn-referral-fees flow. ## What you get * A shareable URL that pre-populates the Raydium swap UI with your chosen destination token **and your wallet as the referral recipient**. * When anyone executes a swap through the link while it's active in their session, **1% of the input volume is routed to the referrer's wallet** as part of the swap transaction. * Links shared on X.com render as **Blinks** (Solana Actions) if the viewer's wallet has Actions / Blinks enabled — a one-click swap widget inside the post. ## How to create a link 1. Open [raydium.io/swap](https://raydium.io/swap) and connect your wallet. The referral address is derived from the connected wallet; without a connection the link still works but earns no fee. 2. Select the **destination token** you want others to swap into. The source side is left to the recipient to choose. 3. Click the share/link icon in the swap panel (top-right). 4. Copy the generated URL. Share it anywhere. The URL format is stable. If you are building an app that generates referral links directly. ## How Blinks work **Blinks (Blockchain Links)** are a Solana Foundation standard for rendering on-chain action URLs as interactive UI inside social platforms. A Blinks-supporting Solana wallet reads the link metadata, constructs a pre-built transaction, and shows the user a signing prompt inline. Requirements: * The viewer's **wallet must have Actions / Blinks enabled**. This is typically a toggle in wallet settings. * The **platform must honor the standard**. X.com does via its unfurler; some others don't, in which case the link falls back to a plain hyperlink. From the referrer's perspective, Blink rendering is automatic — the same URL works as a plain link in wallets that don't support Blinks. ## Fee accounting The 1% referral fee is **charged on top of the AMM trade fee**, not carved out of it. For a swap on a pool with a 0.25% total fee: ``` User sends: 1,000 USDC AMM trade fee: 2.50 USDC (to LP + protocol split) Referral fee: 10.00 USDC (1% → referrer's wallet) Net to swap: 987.50 USDC (then priced through the curve) ``` Referral fees apply to the Raydium swap route created by the referral link. Direct pool interactions outside the referral route do not carry referral fees. ## Disabling / exiting a referral There is no "remove referral" instruction. To do a swap with no referral fee, simply load [raydium.io/swap](https://raydium.io/swap) directly instead of clicking a referral link. The UI uses no referrer when the URL has no referrer parameter. ## FAQ **Does the referrer need to sign anything?** No. The referrer's wallet address is the only data that travels with the link; the payment to the referrer is a token transfer inside the swapper's transaction. **Can I refer myself?** Yes, but pointless — you pay yourself 1%, minus the tx fee you just paid on a larger transaction. **What happens if the destination token is an SPL with a transfer hook?** The referral transfer happens at the input side (in USDC or the source token), not the destination, so transfer hooks on the destination mint do not affect the referral payment. Transfer hooks on the input token can block the swap entirely; Raydium rejects these upfront. **Does the referral fee still apply if the swap routes through multiple pools?** Yes — the 1% is computed on the original input amount and is independent of the route. ## Where to go next * [`user-flows/swap`](/user-flows/swap) — the underlying swap flow. * [`products/launchlab/creator-fees`](/products/launchlab/creator-fees) — LaunchLab's own 0.1% creator referral system. Sources: * Raydium swap UI referral share feature at raydium.io/swap. * Solana Foundation Blinks / Actions specification. # Swap Source: https://docs.raydium.io/user-flows/swap Executing a token swap through the Raydium UI: choose tokens, review the route, set slippage and priority fees, sign, and troubleshoot common failures. Swap is the most common user operation. This page walks through the Raydium UI flow, explains the settings you can control, and catalogs the failure modes you're likely to hit. ## Inputs Every swap needs: * **Token in** (mint address + amount) * **Token out** (mint address) * **Slippage tolerance** (default 0.5% in the UI; 1% on volatile pairs) * **Priority fee** (defaults auto-sized) The amount is specified either as "exact-in" (you set what you pay; output computed) or "exact-out" (you set what you get; input computed). The UI defaults to exact-in. ## UI walkthrough On [raydium.io/swap](https://raydium.io/swap): 1. **Enter amounts.** Pick input and output tokens; type an amount in either box. The other auto-populates using the current quote. 2. **Review the route.** The route panel shows which pool(s) Raydium picked: * Single-pool (most common): "SOL → USDC via CLMM 0.05%" * Multi-hop: "SOL → USDC via CPMM, then USDC → RAY via CLMM" * Split: "SOL → USDC via 60% CLMM 0.05%, 40% CPMM 0.25%" For each hop: the TVL, fee, and expected impact. Bigger trades show a prominent warning when impact exceeds 1%. 3. **Check slippage.** Default is 0.5%. For stable pairs it's 0.1%; for meme tokens 2–5%. If the quote moves while you're looking at it (prices fluctuate), the UI re-quotes every 10 seconds and flashes. 4. **Set priority fee.** Auto by default; three tiers: * **Normal** (50th percentile) — default * **Fast** (75th percentile) * **Turbo** (95th percentile) Advanced users can enter a specific micro-lamports/CU number. 5. **Confirm and sign.** Transaction goes out; UI tracks confirmation. Expected time 5–15 seconds on normal congestion. 6. **Post-swap.** UI shows the actual received amount (may differ from quote by up to slippage); click through to the tx explorer for full detail. ## How routing picks a pool When multiple pools exist for the same pair, Raydium's router picks by: 1. **Highest `amountOut` after fees.** Compute the quote for each eligible pool; pick the one that gives most output. 2. **Tie-break by TVL.** Higher TVL means smaller impact on the next trade — resilient to price fluctuation between quote and submission. 3. **Prefer single-pool over multi-hop.** Multi-hop compounds fees and slippage; the router only picks multi-hop if it beats single-pool by >5 bps. 4. **Split if large.** For trades that are large relative to pool TVL, the route may split across pools to reduce price impact. Products compared on typical pairs: * **CPMM pools** dominate at low TVL (`<$1M`) because they're cheap to create and the swap-fee share funds LPs directly. * **CLMM pools** dominate at medium/high TVL for volatile pairs — concentrated liquidity means better quotes inside the common price range. * **AMM v4** pools dominate on long-established pairs with deep OpenBook integration (SOL-USDC, RAY-USDC). Most new pairs never touch AMM v4. ## Token-2022 transfer fees If the input or output mint is Token-2022 with a transfer fee: * The quote's `amountOut` is the user's net received amount after fee. * Displayed "fee" in the UI splits into "Pool fee" (LP fee) and "Transfer fee" (paid to mint issuer). See [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees). ## Slippage Slippage protects against adverse price movement between quote and execution. Raydium converts your slippage percentage into a minimum amount you must receive: ``` minimumAmountOut = expectedAmountOut × (1 - slippage) ``` If actual output would be less, the tx reverts with `ExceededSlippage`. Common defaults: | Pair type | Slippage | | ---------------------------- | ---------------------------- | | Stable-stable | 0.01–0.1% | | Major-major (SOL/USDC) | 0.1–0.5% | | Mid-cap volatiles | 0.5–1% | | Meme tokens | 2–5% | | Token-2022 with transfer fee | add transfer fee to slippage | Too-tight slippage causes frequent reverts under normal price movement. Too-loose slippage invites sandwich attacks (see [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev)). ## Priority fees Priority fees pay validators more to process your transaction during congestion. The UI defaults to an automatic setting. Use **Fast** or **Turbo** when the network is busy, when you are swapping a volatile pair, or when a previous transaction expired before confirmation. Higher priority fees do not improve the quoted price. They only improve the chance that your transaction lands before the quote goes stale. ## Common failure modes ### Tx reverts with `ExceededSlippage` Price moved more than your tolerance between quote and execution. Fixes: 1. Increase slippage only as much as needed. 2. Reduce trade size (less impact). 3. Re-quote and retry; price may have been transient. 4. Avoid very high slippage on major pairs, because it can expose you to worse execution. ### Tx times out Priority fee too low for current congestion. Fix: raise priority fee; see `integration-guides/priority-fee-tuning`. ### `InvalidAccountData` on CLMM swap The route may depend on a CLMM account that failed to load or changed while the quote was open. Refresh the page, re-quote, and retry. If it persists, choose another route if the UI offers one. ### "Pool not found" / stale pool data Pool was created recently and hasn't indexed yet. Re-fetch by ID directly: Wait a few minutes, refresh the app, and search by the pool or token mint again. Newly created pools can take a short time to appear in indexed UI search. ### Output is zero / very small Swap size exceeds pool liquidity. The pool is too shallow; reduce trade size, try another route, or wait for more liquidity. ### Tx lands but balance didn't change Refresh your wallet and Raydium balances. If the transaction succeeded on the explorer, check whether the output token account is hidden in your wallet UI. ## Verification Use the transaction link shown by Raydium after confirmation. On Solscan or SolanaFM, check: * **Status:** Success. * **Signer:** your wallet address. * **Token balance changes:** input token decreased, output token increased. * **Programs invoked:** a Raydium swap program plus token/system programs. ## Pointers * [`products/cpmm/instructions`](/products/cpmm/instructions) / [`products/clmm/instructions`](/products/clmm/instructions) — per-product swap instruction details. * [`algorithms/slippage-and-price-impact`](/algorithms/slippage-and-price-impact) — formal definitions. * [`integration-guides/aggregator`](/integration-guides/aggregator) — routing logic in depth for builders. Sources: * [raydium.io/swap](https://raydium.io/swap) — production swap UI. # API v1 Source: https://docs.raydium.io/api-reference/api-v1/overview Legacy Raydium read API at api.raydium.io. Pool / pair / farm / SDK lists. Kept live for clients that have not migrated to API v3. This is Raydium's original REST API for pools, farms, SDKs, and token data. It continues to serve production traffic and SDK clients but is now considered legacy. ## Host | Environment | Host | | ----------- | ---------------- | | Mainnet | `api.raydium.io` | | Devnet | Not available | ## Migration notice **For new integrations, use [API v3](/api-reference/api-v3/overview) instead.** The v3 endpoints offer: * Better performance and caching * Simplified response schemas * Clearer endpoint organization * Ongoing feature development ## Related docs See [API v3](/api-reference/api-v3/overview) for the recommended replacement endpoints. See [CLMM](/products/clmm) and [CPMM](/products/cpmm) for pool structures and terminology. # Get farm info by IDs Source: https://docs.raydium.io/api-reference/api-v3-endpoints/farms/get-farm-info-by-ids /api-reference/openapi/api-v3.yaml get /farms/info/ids Retrieve farm pool details by farm IDs. Cached 60 seconds. # Get farm on-chain keys Source: https://docs.raydium.io/api-reference/api-v3-endpoints/farms/get-farm-on-chain-keys /api-reference/openapi/api-v3.yaml get /farms/key/ids Retrieve on-chain account keys for farms. Cached 60 seconds. # Get farms by LP mint Source: https://docs.raydium.io/api-reference/api-v3-endpoints/farms/get-farms-by-lp-mint /api-reference/openapi/api-v3.yaml get /farms/info/lp Search for farms by LP token stake address. Paginated. Cached 60 seconds. # Get IDO pool keys Source: https://docs.raydium.io/api-reference/api-v3-endpoints/ido/get-ido-pool-keys /api-reference/openapi/api-v3.yaml get /ido/key/ids Retrieve on-chain structure for IDO pools. Cached 20 seconds. # Get chain time offset Source: https://docs.raydium.io/api-reference/api-v3-endpoints/main/get-chain-time-offset /api-reference/openapi/api-v3.yaml get /main/chain-time Get offset between local UTC time and Solana chain time. Cached 60 seconds. # Get CLMM dynamic-fee configurations Source: https://docs.raydium.io/api-reference/api-v3-endpoints/main/get-clmm-dynamic-fee-configurations /api-reference/openapi/api-v3.yaml get /main/clmm-dynamic-config Retrieve the calibrated `DynamicFeeConfig` tiers a new CLMM pool can opt into when `enableDynamicFee=true` is passed to `CreateCustomizablePool`. Each entry exposes the per-tier parameters (filter period, decay period, reduction factor, dynamic-fee control, max volatility accumulator) that govern the volatility-tracking surcharge. Cached 60 seconds. # Get CLMM limit-order configurations Source: https://docs.raydium.io/api-reference/api-v3-endpoints/main/get-clmm-limit-order-configurations /api-reference/openapi/api-v3.yaml get /main/clmm-limit-order-config Retrieve the per-pool limit-order configuration (whether limit orders are enabled, the minimum order size, and the keeper public key) for pools that support limit orders. Used by SDKs and UIs before calling `OpenLimitOrder`. Cached 60 seconds. # Get CLMM pool configurations Source: https://docs.raydium.io/api-reference/api-v3-endpoints/main/get-clmm-pool-configurations /api-reference/openapi/api-v3.yaml get /main/clmm-config Retrieve allowed configurations for concentrated liquidity pools. Cached 60 seconds. # Get CPMM pool configurations Source: https://docs.raydium.io/api-reference/api-v3-endpoints/main/get-cpmm-pool-configurations /api-reference/openapi/api-v3.yaml get /main/cpmm-config Retrieve allowed configurations for constant-product pools. Cached 60 seconds. # Get estimated transaction fees Source: https://docs.raydium.io/api-reference/api-v3-endpoints/main/get-estimated-transaction-fees /api-reference/openapi/api-v3.yaml get /main/auto-fee Retrieve estimated on-chain costs (lamports). Cached 60 seconds. # Get main statistics Source: https://docs.raydium.io/api-reference/api-v3-endpoints/main/get-main-statistics /api-reference/openapi/api-v3.yaml get /main/info Retrieve TVL and 24-hour volume for all Raydium pools. Cached 60 seconds. # Get RAY stake pools Source: https://docs.raydium.io/api-reference/api-v3-endpoints/main/get-ray-stake-pools /api-reference/openapi/api-v3.yaml get /main/stake-pools Retrieve RAY token staking pools. Cached 60 seconds. # Get default mint list Source: https://docs.raydium.io/api-reference/api-v3-endpoints/mint/get-default-mint-list /api-reference/openapi/api-v3.yaml get /mint/list Retrieve UI's default mint list with metadata and logos. Cached 60 seconds. # Get mint info by IDs Source: https://docs.raydium.io/api-reference/api-v3-endpoints/mint/get-mint-info-by-ids /api-reference/openapi/api-v3.yaml get /mint/ids Retrieve metadata for specific mint addresses. Cached 20 seconds. # Get mint prices Source: https://docs.raydium.io/api-reference/api-v3-endpoints/mint/get-mint-prices /api-reference/openapi/api-v3.yaml get /mint/price Retrieve current USD prices for mint tokens. Cached 5 seconds. # Get CLMM position history Source: https://docs.raydium.io/api-reference/api-v3-endpoints/pools/get-clmm-position-history /api-reference/openapi/api-v3.yaml get /pools/line/position Retrieve historical price and liquidity data for CLMM pools. Cached 30 seconds. # Get pool info by IDs Source: https://docs.raydium.io/api-reference/api-v3-endpoints/pools/get-pool-info-by-ids /api-reference/openapi/api-v3.yaml get /pools/info/ids Retrieve detailed pool information by pool IDs. Cached 60 seconds. # Get pool liquidity history Source: https://docs.raydium.io/api-reference/api-v3-endpoints/pools/get-pool-liquidity-history /api-reference/openapi/api-v3.yaml get /pools/line/liquidity Retrieve historical TVL data for a pool (up to 30 days). Cached 300 seconds. # Get pool on-chain keys Source: https://docs.raydium.io/api-reference/api-v3-endpoints/pools/get-pool-on-chain-keys /api-reference/openapi/api-v3.yaml get /pools/key/ids Retrieve on-chain account keys for pools. Cached 60 seconds. # Get pools by LP mint Source: https://docs.raydium.io/api-reference/api-v3-endpoints/pools/get-pools-by-lp-mint /api-reference/openapi/api-v3.yaml get /pools/info/lps Retrieve pool info by LP token addresses. Cached 60 seconds. # Get pools by token mint Source: https://docs.raydium.io/api-reference/api-v3-endpoints/pools/get-pools-by-token-mint /api-reference/openapi/api-v3.yaml get /pools/info/mint Filter pools by one or two token mints. Mint ordering normalized internally. Cached 60 seconds. # List pools with advanced filters (v2) Source: https://docs.raydium.io/api-reference/api-v3-endpoints/pools/list-pools-with-advanced-filters-v2 /api-reference/openapi/api-v3.yaml get /pools/info/list-v2 Advanced pool listing with cursor-based pagination. Cached 60–90 seconds. # List pools with pagination Source: https://docs.raydium.io/api-reference/api-v3-endpoints/pools/list-pools-with-pagination /api-reference/openapi/api-v3.yaml get /pools/info/list **Legacy endpoint.** Retrieve paginated pool list with filtering and sorting. Cached 60 seconds. pageSize limited to 1000. Kept live for existing integrations; new integrations should query pools by ID, mint pair, or LP mint via the other `/pools/info/*` endpoints, or use the indexer-backed routing surface in the Transaction API. # Raydium API v3 Source: https://docs.raydium.io/api-reference/api-v3/overview REST API for querying Raydium pools, mints, farms, and chain data on Solana ## What is Raydium API v3? Raydium API v3 provides a REST interface to query liquidity pools, token metadata, farms, and chain statistics across Raydium's trading venues on Solana. It's used by wallets, aggregators, DEX routers, and traders to source canonical pool data, price feeds, and historical analytics. The API is designed for high throughput — responses are typically 1–5 minutes stale depending on endpoint sensitivity. All responses are wrapped in a consistent envelope with metadata. ## Base hosts | Environment | URL | | ----------- | ---------------------------------- | | **Mainnet** | `https://api-v3.raydium.io` | | **Devnet** | `https://api-v3-devnet.raydium.io` | ## Response envelope All endpoints return a JSON envelope with request metadata and the response payload: ### Success response ```json theme={null} { "id": "abc123def456", "success": true, "data": { // Endpoint-specific data } } ``` ### Error response ```json theme={null} { "id": "abc123def456", "success": false, "msg": "query ids type error" } ``` The `id` field is a unique request identifier (UUID). Always inspect `success` before processing `data`. ## Common gotchas ### Mint pair ordering on `/pools/info/mint` When you query `/pools/info/mint?mint1=ABC&mint2=XYZ`, the endpoint internally normalizes the pair by sorting mints alphabetically. If you need to search for a single mint, omit `mint2` entirely — the API will find all pools containing that mint. ### Pool type filter values The `poolType` query parameter accepts specific enum values: * `all` — all pool types * `concentrated` — CLMM pools only * `standard` — AMM (constant-product) pools only * `allFarm` — pools with at least one active farm * `concentratedFarm` — concentrated pools with farms * `standardFarm` — standard pools with farms Typos or case sensitivity mismatches will be rejected. ### Pool sort field values Valid `poolSortField` values are: * `default` (sorts by 24h volume) * `liquidity` (TVL) * `volume24h`, `volume7d`, `volume30d` * `fee24h`, `fee7d`, `fee30d` * `apr24h`, `apr7d`, `apr30d` ### Pagination with `/pools/info/list-v2` The v2 endpoint uses cursor-based pagination via `nextPageId`. The opaque token is returned in the response and must be passed back verbatim. Do not attempt to construct your own cursor — it encodes Elasticsearch state. ### Page size defaults and limits * `/pools/info/list` and `/pools/info/mint` cap `pageSize` at **1000**. * `/farms/info/lp` caps `pageSize` at **100**. * `/pools/info/list-v2` caps `size` at **1000**. Requests exceeding limits are rejected with an error. ### CLMM dynamic-fee and limit-order configs Two new endpoints expose calibration data for the CLMM `CreateCustomizablePool` flow: * `GET /main/clmm-dynamic-config` — list of `DynamicFeeConfig` tiers (filter period, decay period, reduction factor, max numerator, dynamic-fee control). Pass the `id` of one of these into `createCustomizablePool` when `enableDynamicFee=true`. * `GET /main/clmm-limit-order-config` — per-pool limit-order configuration: whether limit orders are enabled, the minimum input amount, and the off-chain `limit_order_admin` keeper authorized to settle filled orders. Per-wallet limit-order data (open / filled / closed) lives on Temp API; see [`api-reference/temp-api-v1/overview`](/api-reference/temp-api-v1/overview). ## Related documentation * [REST API overview](/sdk-api/rest-api) * [API reference](/api-reference) * [Integration guide for aggregators](/integration-guides/aggregator) ## Example usage **Get main statistics:** ```bash theme={null} curl -s https://api-v3.raydium.io/main/info | jq . ``` **List top 10 pools by TVL:** ```bash theme={null} curl -s 'https://api-v3.raydium.io/pools/info/list?poolType=all&poolSortField=liquidity&sortType=desc&pageSize=10&page=1' | jq . ``` **Get SOL-USDC pools:** ```bash theme={null} curl -s 'https://api-v3.raydium.io/pools/info/mint?mint1=So11111111111111111111111111111111111111112&mint2=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&poolType=all&poolSortField=liquidity&sortType=desc&pageSize=5&page=1' | jq . ``` **Get mint prices:** ```bash theme={null} curl -s 'https://api-v3.raydium.io/mint/price?mints=So11111111111111111111111111111111111111112,EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R' | jq . ``` **Get farm pools for an LP token:** ```bash theme={null} curl -s 'https://api-v3.raydium.io/farms/info/lp?lp=&pageSize=10&page=1' | jq . ``` ## Status and availability The Raydium API v3 is live on mainnet. For availability and incidents, follow [@Raydium](https://x.com/Raydium). # Dynamic IPFS API Source: https://docs.raydium.io/api-reference/dynamic-ipfs-v1/overview Regenerate dynamic NFT metadata and position visuals for Raydium liquidity positions The Dynamic IPFS API serves regenerated metadata and images for CLMM and CPMM liquidity positions on Solana. This service is designed for dApps that render dynamic NFTs—such as concentrated liquidity position cards—by providing real-time pool information, position valuation, and unclaimed fees without requiring on-chain lookups. ## Hosts | Environment | Host | | ----------- | -------------------------------- | | Mainnet | `dynamic-ipfs.raydium.io` | | Devnet | `dynamic-ipfs-devnet.raydium.io` | ## Overview The API exposes three endpoints, each returning a complete position interface object: * **`/clmm/position`** — Fetch metadata for an active CLMM position * **`/lock/clmm/position`** — Fetch metadata for a locked CLMM position * **`/lock/cpmm/position`** — Fetch metadata for a locked CPMM position Each response includes the full pool details (tokens, decimals, TVL), position amounts, USD valuation, TVL percentage within the pool, and accumulated (but unclaimed) fees and rewards. ## Use cases Generate dynamic position visualization NFTs by requesting metadata for a specific position. The API computes real-time position state from on-chain data and caches results, making it ideal for: * Rendering position cards with live USD value and fee accrual * Displaying TVL percentage and token amounts in the pool * Showing unclaimed trading fees and reward tokens * Building portfolio dashboards that track position performance ## Authentication No authentication is required. All endpoints are public. ## Rate limiting None enforced. Best effort service. ## Common patterns ### Fetch a CLMM position ```bash theme={null} curl -s "https://dynamic-ipfs.raydium.io/clmm/position?id=4Ygp92zRvl4kCr97V3Zs5xg4k3qX1tN6m8pR2vL9jA1" ``` The `id` parameter should be the on-chain public key of the position account (a Solana address in base58 format). ### Fetch a locked position ```bash theme={null} curl -s "https://dynamic-ipfs.raydium.io/lock/clmm/position?id=" ``` ## Related docs See [CLMM](/products/clmm) for position account structure and how to derive position IDs. # API reference Source: https://docs.raydium.io/api-reference/index Catalogue of every Raydium-operated HTTP API: pool data, transaction construction, perps, the LaunchLab forum / mint / history / auth services, IPFS gateway, owner-positions, and the legacy Python API. Endpoints listed below open into an interactive playground. Every endpoint on this site has a **Try it** panel powered by Mintlify's OpenAPI playground. The playground runs in your browser and hits the live production (or devnet) hosts directly. Read [Authentication](#authentication) before sending requests that require a wallet signature. ## Service catalogue Raydium runs eleven public HTTP services. Each one is documented as its own group in the navigation tree to the left. | Service | Mainnet host | Devnet host | What it does | | ------------------------------------------------------------------ | ------------------------------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | [API v3](/api-reference/api-v3/overview) | `api-v3.raydium.io` | `api-v3-devnet.raydium.io` | Pool / mint / config / chain-info — the canonical read API the UI and SDK rely on. | | [Transaction API](/api-reference/route-api-v2/overview) | `transaction-v1.raydium.io` | `transaction-v1-devnet.raydium.io` | Server-side swap / liquidity transaction construction. The aggregator entry point. | | [Perps API](/api-reference/perp-api-v1/overview) | `api-perp-v1.raydium.io` | — | Settings, asset metadata, RPC selection for the Raydium Perps front end. | | [LaunchLab Mint API](/api-reference/launch-mint-v1/overview) | `launch-mint-v1.raydium.io` | `launch-mint-v1-devnet.raydium.io` | Token search, indexes, leaderboards, per-mint metadata for LaunchLab launches. | | [LaunchLab History API](/api-reference/launch-history-v1/overview) | `launch-history-v1.raydium.io` | `launch-history-v1-devnet.raydium.io` | Trade history, K-line aggregates for LaunchLab pools. | | [LaunchLab Forum API](/api-reference/launch-forum-v1/overview) | `launch-forum-v1.raydium.io` | `launch-forum-v1-devnet.raydium.io` | Comments, threads, IPFS uploads tied to a LaunchLab launch. Wallet-signed. | | [LaunchLab Auth API](/api-reference/launch-auth-v1/overview) | `launch-auth-v1.raydium.io` | `launch-auth-v1-devnet.raydium.io` | Issues short-lived `ray-token` JWTs from a wallet-signed message. Required to call the forum API. | | [Dynamic IPFS API](/api-reference/dynamic-ipfs-v1/overview) | `dynamic-ipfs.raydium.io` | `dynamic-ipfs-devnet.raydium.io` | NFT image / metadata regenerator. Drives CLMM position art and other on-chain NFTs whose look depends on dynamic state. | | [Owner API](/api-reference/owner-api-v1/overview) | `owner-v1.raydium.io` | `owner-v1-devnet.raydium.io` | Per-wallet positions, balances, claimable rewards. | | [API v1](/api-reference/api-v1/overview) | `api.raydium.io` | — | Legacy v1/v2 endpoints, kept live for clients that have not migrated to API v3. | | [Temp API](/api-reference/temp-api-v1/overview) | `temp-api-v1.raydium.io` | `temp-api-v1-devnet.raydium.io` | Holding pen for short-lived bespoke endpoints. Currently exposes CPMM creator-fee summaries. Subject to change without notice. | ## Authentication Most services are read-only and accept anonymous requests. Two patterns appear: * **Wallet-signed handshake** — required by `launch-auth-v1` to mint a `ray-token`, then carried as the `ray-token` header by `launch-forum-v1`. Sign a Solana ed25519 message of the form `time:` with your wallet, send the signature + wallet address to `launch-auth-v1` `/request-token`, receive a JWT back, and pass it as the `ray-token` request header on subsequent forum calls. * **No auth** — every other service. Trusted callers (the Raydium UI, integrators) hit the endpoints directly over HTTPS. The Mintlify playground lets you paste a `ray-token` in the auth panel before sending forum requests; the value is held in your browser only. ## Rate limits All hosts are behind Cloudflare with progressive rate limiting per source IP. Integrators that need higher limits should contact the Raydium team. Bursts above the published limits are returned as `HTTP 429` with a `Retry-After` header. ## Response envelope Most services wrap their JSON payloads in a uniform envelope: ```json theme={null} { "id": "uuid-v4-per-request", "success": true, "data": { ... } } ``` On failure: ```json theme={null} { "id": "uuid-v4-per-request", "success": false, "msg": "human-readable error string", "data": null } ``` `api-v3` returns the envelope as `{ "id", "success", "data" }`. The pump-\* services use `addBorderSuccess` / `addBorderErr` helpers that produce the same shape with `msg` populated only on errors. Check each service's intro page for the exact envelope it uses. ## How to use this section Click any endpoint in the left navigation. You will see: * The HTTP method, full URL, and base host. * Request parameters with types pulled from the OpenAPI spec. * A **Try it** panel that lets you set parameters and send a real request to mainnet (or pick a devnet server from the dropdown when available). * The response shape, sourced from a representative live response and the source code's `addBorderSuccess` callsite. For SDK-level access — building transactions client-side, batching swaps, the TypeScript types — see [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk). ## Pointers * [`sdk-api/rest-api`](/sdk-api/rest-api) — narrative overview of the REST surface, written for first-time integrators. * [`integration-guides/aggregator`](/integration-guides/aggregator) — how to use the Transaction API alongside on-chain pools. * [`reference/program-addresses`](/reference/program-addresses) — every program ID returned by `api-v3` resolves to one of these. # LaunchLab Auth API Source: https://docs.raydium.io/api-reference/launch-auth-v1/overview Wallet-signature handshake that mints short-lived ray-token JWTs for the LaunchLab Forum and Mint APIs. The Auth API handles wallet-based authentication for LaunchLab services. Exchange a signed message for a JWT token, then use that token to authenticate requests to the Forum and Mint creation APIs. ## API Endpoints **Mainnet:** [https://launch-auth-v1.raydium.io](https://launch-auth-v1.raydium.io)\ **Devnet:** [https://launch-auth-v1-devnet.raydium.io](https://launch-auth-v1-devnet.raydium.io) ## Authentication Flow No pre-existing credentials are required. Authentication is wallet-based: 1. **Get current timestamp** (Unix seconds) 2. **Sign message** with your wallet: `"Sign in to raydium.io: " + ` 3. **Submit signature** to `/request-token` endpoint 4. **Receive JWT token** valid for your specified duration 5. **Use token** in `ray-token` header for protected endpoints ## Message Format The message you sign must follow this exact format: ``` Sign in to raydium.io: ``` **Example:** ``` Sign in to raydium.io: 1704067200 ``` * Use your current Unix timestamp in seconds (not milliseconds) * Ed25519 signature (native Solana signing) * Encode signature as base58 string * Pass timestamp and signature to `/request-token` ## Token Validation Use `/check-token` to verify your token is still valid. The endpoint also automatically extends token lifetime if near expiry. Token lifetime can be configured server-side; default is typically 24 hours. ## Token Revocation Revoke a token with `/del-token` endpoint. ## Supported Signing Methods * **Standard wallet:** Ed25519 signature with `/request-token` * **Ledger wallet:** Signed transaction with `/request-token-ledger` ## Use Cases * **Forum posting** — authenticate before posting comments * **Mint creation** — sign wallet ownership before submitting new tokens * **User sessions** — maintain authenticated state across API calls * **Multi-wallet support** — issue separate tokens per wallet address ## Related Resources [Forum API](/api-reference/launch-forum-v1/overview)\ [Mint API](/api-reference/launch-mint-v1/overview)\ [LaunchLab Platform Overview](/products/launchlab) # LaunchLab Forum API Source: https://docs.raydium.io/api-reference/launch-forum-v1/overview Pool comment threads, IPFS image uploads, and forum browsing tied to a LaunchLab launch. Wallet-signed via ray-token. The Forum API enables discussion and community engagement around LaunchLab pools. Post comments with optional image attachments and retrieve pool discussion threads. All write operations require authentication via the Auth API. ## API Endpoints **Mainnet:** [https://launch-forum-v1.raydium.io](https://launch-forum-v1.raydium.io)\ **Devnet:** [https://launch-forum-v1-devnet.raydium.io](https://launch-forum-v1-devnet.raydium.io) ## Authentication **Read operations (GET):** No authentication required. **Write operations (POST):** Requires `ray-token` header — a JWT issued by the Auth API after wallet signature verification. [Get token from Auth API](/api-reference/launch-auth-v1/overview) ## Request Headers When posting comments, include the authentication header: ``` ray-token: ``` ## Features * **Post Comments:** Submit text comments with optional image attachments (max 5 MB) * **Browse Discussions:** View all comments on a pool with pagination * **Content Moderation:** NSFW detection and hidden comments filtering * **Pagination:** Query with `lastId` and `fetchType` (old/new) for efficient browsing * **Image Upload:** Comments support Pinata IPFS image hosting ## Use Cases * **Community discussion** around token projects * **Project announcements** and updates via comments * **User feedback** and sentiment tracking * **Social proof** and community engagement metrics * **Content moderation** with NSFW and spam detection ## Related Resources [Auth API](/api-reference/launch-auth-v1/overview)\ [Mint API](/api-reference/launch-mint-v1/overview)\ [LaunchLab Platform Overview](/products/launchlab) # LaunchLab History API Source: https://docs.raydium.io/api-reference/launch-history-v1/overview Trade history and OHLC kline data for LaunchLab pools at 1m, 5m, and 15m intervals. The History API provides historical trading data for LaunchLab pools. Retrieve candlestick (OHLC) data at multiple intervals (1-minute, 5-minute, 15-minute) and access individual trade records with pagination. ## API Endpoints **Mainnet:** [https://launch-history-v1.raydium.io](https://launch-history-v1.raydium.io)\ **Devnet:** [https://launch-history-v1-devnet.raydium.io](https://launch-history-v1-devnet.raydium.io) ## Authentication No authentication required. All endpoints are publicly accessible. ## Data Features * **Kline Data:** OHLC candlestick charts at 1m, 5m, and 15m intervals * **Trade History:** Individual trades with trader, amount, and timestamp * **Pagination:** Use `nextPageKey` for efficient large-dataset queries * **Limit Constraints:** Kline requests accept 1-500 candles per query (default 300); trades accept 1-100 (default 50) * **Filtering:** Filter trade history by trader wallet and amount ranges ## Use Cases * **Chart display** with candlestick visualization * **Price analysis** and technical analysis indicators * **Trading statistics** and volume tracking * **User trade audit** trails filtered by wallet and date range * **Historical price discovery** for mints ## Related Resources [Mint API](/api-reference/launch-mint-v1/overview)\ [Forum API](/api-reference/launch-forum-v1/overview)\ [LaunchLab Platform Overview](/products/launchlab) # Get platform config Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/configuration/get-platform-config /api-reference/openapi/launch-mint-v1.yaml get /campaign/configs # Get mint details Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/details/get-mint-details /api-reference/openapi/launch-mint-v1.yaml get /get/by/mints # Get user mints Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/details/get-user-mints /api-reference/openapi/launch-mint-v1.yaml get /get/by/user # Get featured recent mints Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/discovery/get-featured-recent-mints /api-reference/openapi/launch-mint-v1.yaml get /get/random/index-left-mint # Get featured top mint Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/discovery/get-featured-top-mint /api-reference/openapi/launch-mint-v1.yaml get /get/random/index-top-mint # List Bonk mints Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/discovery/list-bonk-mints /api-reference/openapi/launch-mint-v1.yaml get /get/list-bonk-custom # List mints with sorting Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/discovery/list-mints-with-sorting /api-reference/openapi/launch-mint-v1.yaml get /get/list # Search mints Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/discovery/search-mints /api-reference/openapi/launch-mint-v1.yaml get /get/search # Get referrer rewards Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/user-activity/get-referrer-rewards /api-reference/openapi/launch-mint-v1.yaml get /campaign/referrer # Get user creation stats Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/user-activity/get-user-creation-stats /api-reference/openapi/launch-mint-v1.yaml get /get-by-user/stats/create # Get user volume stats Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/user-activity/get-user-volume-stats /api-reference/openapi/launch-mint-v1.yaml get /get-by-user/stats/volume # LaunchLab Mint API Source: https://docs.raydium.io/api-reference/launch-mint-v1/overview Search, leaderboards, per-mint metadata, vesting queries, and mint-creation flows for LaunchLab tokens. The Mint API provides comprehensive access to LaunchLab token creation, discovery, and management. Query mints by name, symbol, or creator, retrieve leaderboards, and submit new mint creation requests. ## API Endpoints **Mainnet:** [https://launch-mint-v1.raydium.io](https://launch-mint-v1.raydium.io)\ **Devnet:** [https://launch-mint-v1-devnet.raydium.io](https://launch-mint-v1-devnet.raydium.io) ## Authentication Read operations require no authentication. Write operations (mint creation) require a `ray-token` header issued by the Auth API after wallet signature verification. [Auth API Documentation](/api-reference/launch-auth-v1/overview) ## Use Cases * **Search and discover** tokens by name or symbol * **View leaderboards** (top mints by market cap, latest, hottest) * **Retrieve user statistics** (creation and trading volume over time) * **Query vesting schedules** for token unlock information * **Submit mint creation** requests with metadata and images * **Track referrer rewards** from platform referral programs ## Related Resources [LaunchLab Platform Overview](/products/launchlab)\ [Forum API](/api-reference/launch-forum-v1/overview)\ [History API](/api-reference/launch-history-v1/overview) # Owner API Source: https://docs.raydium.io/api-reference/owner-api-v1/overview Retrieve per-wallet positions, balances, and claimable rewards The Owner API provides per-wallet views of Raydium positions and balances. It fetches cached data for positions, locked liquidity, created pools, and IDO participation, making it ideal for wallet UIs and portfolio dashboards. ## Hosts | Environment | Host | | ----------- | ---------------------------- | | Mainnet | `owner-v1.raydium.io` | | Devnet | `owner-v1-devnet.raydium.io` | ## Overview The API exposes five main endpoints organized by resource type: * **`/position/stake/{owner}`** — Stake positions (standard AMM / CPMM LP) * **`/position/clmm-lock/{owner}`** — Locked CLMM positions * **`/create-pool/{owner}`** — Pools created by this wallet * **`/main/ido/{owner}`** — IDO participation data ## Data freshness This service caches position data and updates it periodically. Very recent position changes (within seconds) may not appear immediately in responses. ## Common patterns ### Fetch all stake positions for a wallet ```bash theme={null} curl -s "https://owner-v1.raydium.io/position/stake/{owner}" ``` Replace `{owner}` with the wallet's public key in base58 format. ### Fetch locked positions ```bash theme={null} curl -s "https://owner-v1.raydium.io/position/clmm-lock/{owner}" ``` ### Check IDO participation ```bash theme={null} curl -s "https://owner-v1.raydium.io/main/ido/{owner}" ``` ## Empty results If a wallet has no data for a particular endpoint, the service returns a 404 error with request metadata: ```json theme={null} { "id": "...", "success": false } ``` ## Related docs See [CLMM](/products/clmm) and [CPMM](/products/cpmm) for position account structures and terminology. # Get perp market statistics Source: https://docs.raydium.io/api-reference/perp-api-v1-endpoints/main/get-perp-market-statistics /api-reference/openapi/perp-api-v1.yaml get /main/info Returns 24h/7d/30d trading volume and open interest (long/short/total). # Get default perp pool list Source: https://docs.raydium.io/api-reference/perp-api-v1-endpoints/pool/get-default-perp-pool-list /api-reference/openapi/perp-api-v1.yaml get /pool/default-list Returns list of default perpetual trading pools with volume and stats. # Perps API Overview Source: https://docs.raydium.io/api-reference/perp-api-v1/overview Configuration and metadata service for Raydium Perpetual Futures. ## What is the Perps API? The Raydium Perps API (V1) is a configuration and metadata service for the Raydium Perpetual Futures frontend and integrations. It provides: * **UI configuration** – current version, minimum version support * **RPC endpoints** – whitelisted Solana RPC endpoints for the UI * **Market statistics** – 24h/7d/30d trading volume and open interest * **Regional restrictions** – per-country availability checks * **Pool metadata** – active perp markets and volume data * **Campaign data** – leaderboard, user stats, and rewards * **P\&L sharing** – generate shareable position screenshots **Important**: Order placement itself is handled by Orderly Network's API. This service focuses on frontend support and metadata. ## Architecture Overview The Perps system consists of two independent components: 1. **Raydium Perp API (this service)** – Reads configuration, provides UI data, generates images 2. **Orderly Network** – Executes orders, manages positions, and settlement When a user places a perp order through the Raydium UI: 1. The UI fetches market configuration from this API (pools, RPCs, availability) 2. The UI sends the order to Orderly Network's API 3. Orderly executes the order and maintains position state 4. The UI retrieves position data and stats from Orderly's API or our campaign endpoint This separation allows Raydium to manage metadata and branding while Orderly handles the heavy lifting of order matching and settlement. ## API Endpoints by Category ### Main Endpoints Core service information and availability checks. #### `GET /main/version` Returns current stable UI version and minimum supported version. **Use**: Check if the client's UI version is still supported. **Response**: ```json theme={null} { "id": "...", "success": true, "data": { "latest": "1.2.0", "least": "1.0.0" } } ``` #### `GET /main/rpcs` Returns whitelisted Solana RPC endpoints for the UI to use. **Use**: Populate RPC selector in the UI; ensures clients connect to stable, Raydium-approved endpoints. #### `GET /main/info` Returns market-wide statistics. **Use**: Display 24h volume, 7d volume, 30d volume, and total/long/short open interest on the dashboard. **Response**: ```json theme={null} { "id": "...", "success": true, "data": { "volume": { "24h": 1234567, "7d": 9876543, "30d": 50000000 }, "openInterest": { "long": 5000000, "short": 3000000, "all": 8000000 } } } ``` #### `GET /main/availability-check` Checks if perp trading is available in the user's region. **Use**: Warn or restrict access in restricted regions (e.g., USA). **How it works**: * Reads the `cf-ipcountry` header from Cloudflare (if behind Cloudflare) * Falls back to a default config if the header is absent * Returns availability status per region **Response**: ```json theme={null} { "id": "...", "success": true, "data": { "available": true, "country": "US" } } ``` #### `GET /main/temp-key?wallet=...` Generates a temporary Ed25519 keypair for initial authentication or temporary signing. **Use**: Non-custodial temporary key generation for certain auth flows. **Response**: ```json theme={null} { "id": "...", "success": true, "data": { "key": "ed25519:AAAA..." } } ``` ### Pool Endpoints Perpetual market configuration. #### `GET /pool/default-list` Returns list of default perp markets with 24h/7d/30d volume. **Use**: Populate market selector or dashboard widget with available perp pairs. **Response**: ```json theme={null} { "id": "...", "success": true, "data": [ { "symbol": "BTC/USDC", "volume24h": "1000000", "volume7d": "7000000", "volume30d": "30000000" } ] } ``` ### Campaign Endpoints Leaderboard, user stats, and reward data. #### `GET /campaign/configs` Returns active campaign parameters and rules. **Use**: Display campaign terms and participation requirements in the UI. #### `GET /campaign/user?wallet=...&index=0` Returns a user's campaign stats (volume, P\&L, score, earned rewards). **Use**: Display in user's profile or account dashboard. **Response** (user with no history defaults to zeroed data): ```json theme={null} { "id": "...", "success": true, "data": { "userInfo": { "index": "42", "walletAddress": "11111...1111", "volume": 500000, "pnl": 25000, "pnlW": 22500, "score": 850, "rewards": [...] } } } ``` #### `GET /campaign/list?index=0` Returns leaderboard for a given campaign index (paginated). **Use**: Display top traders and rankings. **Response**: ```json theme={null} { "id": "...", "success": true, "data": { "updateTime": 1699999999000, "rows": [ { "rank": 1, "wallet": "11111...1111", "volume": 5000000, "pnl": 250000, "score": 9500 } ] } } ``` ### Share Endpoints Generate shareable position screenshots. #### `POST /share/position` Generates a shareable image of the user's current perp position. **Use**: Social sharing (Twitter, Discord) of live positions. **Request**: ```json theme={null} { "symbol": "BTC/USDC", "side": "long", "size": "0.5", "entryPrice": "45000", "markPrice": "46000", "pnl": "500", "leverage": "5x" } ``` **Response**: ```json theme={null} { "id": "...", "success": true, "data": { "imgFileName": "abc123def456", "msg": "Position image generated" } } ``` #### `POST /share/history-position` Generates a shareable image of a closed position with realized P\&L. **Use**: Share closed trades with profit/loss details. **Request**: ```json theme={null} { "symbol": "ETH/USDC", "side": "short", "size": "10", "entryPrice": "2500", "exitPrice": "2450", "realizedPnl": "500" } ``` **Response**: Same as `/share/position`. ## Response Envelope All endpoints return a standard envelope: ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "success": true, "data": { ... } } ``` On error: ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "success": false, "msg": "Error message or code" } ``` ## Caching Most endpoints return a `cache-control: max-age=60` header, meaning: * Results are cached server-side and updated every 60 seconds * Clients may also cache for 60 seconds to reduce load * Real-time data is not guaranteed; expect 0–60 second staleness ## Regional Availability Regional restrictions are handled via the `cf-ipcountry` header (Cloudflare). Supported regions and restrictions are configured server-side and updated periodically. ## Network Endpoints | Environment | Host | | ----------- | ------------------------ | | Production | `api-perp-v1.raydium.io` | No devnet version is available; perp trading is mainnet-only. ## Integration with Orderly Network To place an order: 1. Call `/campaign/user` or `/main/info` to fetch metadata and display to the user 2. Send the order to **Orderly Network's API** (not this API) 3. Orderly returns a trade confirmation and position state 4. Call `/campaign/user` again later to see updated stats Raydium's perp API does not handle order placement; it is purely read-only metadata and configuration. ## See Also * [OpenAPI Specification](/api-reference/openapi/perp-api-v1.yaml) * [Raydium Perpetual Futures](/products/perps) * [Orderly Network](https://orderly.network) # Compute swap quote (base input) Source: https://docs.raydium.io/api-reference/route-api-v2-endpoints/compute/compute-swap-quote-base-input /api-reference/openapi/route-api-v2.yaml get /compute/swap-base-in Calculate the expected output amount when swapping a fixed input amount. Useful for displaying a preview before transaction building. # Compute swap quote (base output) Source: https://docs.raydium.io/api-reference/route-api-v2-endpoints/compute/compute-swap-quote-base-output /api-reference/openapi/route-api-v2.yaml get /compute/swap-base-out Calculate the required input amount to achieve a desired output amount. Useful for swaps where you know the target output. # Build swap transaction (base input) Source: https://docs.raydium.io/api-reference/route-api-v2-endpoints/transaction/build-swap-transaction-base-input /api-reference/openapi/route-api-v2.yaml post /transaction/swap-base-in Generate a serialized swap transaction for a fixed input amount. Requires a successful compute response from `/compute/swap-base-in`. # Build swap transaction (base output) Source: https://docs.raydium.io/api-reference/route-api-v2-endpoints/transaction/build-swap-transaction-base-output /api-reference/openapi/route-api-v2.yaml post /transaction/swap-base-out Generate a serialized swap transaction for a desired output amount. Requires a successful compute response from `/compute/swap-base-out`. # Transaction API Overview Source: https://docs.raydium.io/api-reference/route-api-v2/overview Server-side swap transaction builder for Solana. ## What is the Transaction API? The Raydium Transaction API (Route V2) is a server-side service that builds serialized Solana swap transactions without requiring clients to maintain an RPC connection or bundle the entire Raydium SDK. This dramatically simplifies integration for: * Web frontends that cannot run a local RPC client * Mobile applications with limited resources * Headless trading bots * Aggregators and wallet providers Instead of performing complex pool routing and transaction construction on the client, you request swap quotes and transaction building from our API, then sign and broadcast the result using any Solana RPC. ## Workflow Overview The Transaction API separates concerns into two phases: ### 1. Compute Phase: Get a Quote Call `/compute/swap-base-in` or `/compute/swap-base-out` to receive the expected swap output (or required input) based on current pool states. This endpoint is **read-only** and does not require any signing: ```bash theme={null} GET /compute/swap-base-in?inputMint=EPjF...&outputMint=So111...&amount=1000000&slippageBps=50&txVersion=V0 ``` Response includes: * Expected output amount * Route breakdown (which pools and liquidity sources are used) * Price impact ### 2. Transaction Phase: Build and Sign Once you have the compute response, pass it (along with wallet and configuration) to `/transaction/swap-base-in` or `/transaction/swap-base-out`: ```bash theme={null} POST /transaction/swap-base-in Content-Type: application/json { "wallet": "YourWalletAddress", "swapResponse": { ...response from /compute/swap-base-in }, "txVersion": "V0", "computeUnitPriceMicroLamports": "1000", "wrapSol": false, "unwrapSol": false, "inputAccount": "TokenAccount1...", "outputAccount": "TokenAccount2..." } ``` Response contains: * A base64-encoded versioned transaction ready to sign * Address lookup table addresses (if txVersion=V0) Your client then: 1. Decodes the transaction 2. Signs it with the user's keypair 3. Broadcasts it via any Solana RPC 4. Awaits confirmation ## Compute Endpoints ### `GET /compute/swap-base-in` **Use case**: User specifies input amount, we compute the output. **Required query parameters**: * `inputMint` – Mint address of the token you're sending * `outputMint` – Mint address of the token you want * `amount` – Input amount in lamports (smallest unit) * `slippageBps` – Maximum acceptable slippage in basis points (0–10000) * `txVersion` – `V0` or `LEGACY` **Optional**: * `referrerBps` – If you have a referrer authority, basis points of the output to collect as referrer fee ### `GET /compute/swap-base-out` **Use case**: User specifies desired output, we compute the required input. **Required query parameters**: * `inputMint`, `outputMint`, `amount` (desired output), `slippageBps`, `txVersion` **Note**: No referrer basis points for base-out (not yet implemented). ## Transaction Endpoints ### `POST /transaction/swap-base-in` Builds a transaction for a fixed input amount. **Required body**: * `wallet` – Your signing wallet address * `swapResponse` – The entire compute response object * `txVersion` – Transaction version * `computeUnitPriceMicroLamports` – Priority fee in micro-lamports **Optional**: * `wrapSol` – If true, wrap native SOL for input * `unwrapSol` – If true, unwrap WSOL to SOL in output * `inputAccount` – Token account for input (required if not wrapping SOL) * `outputAccount` – Token account for output * `nonceInfo` – Durable nonce for offline signing * `jitoInfo` – Jito MEV protection bundle params * `referrerWallet` – Referrer wallet for fee collection ### `POST /transaction/swap-base-out` Builds a transaction for a fixed output amount. **Same parameters as base-in**, except: * `referrerInfo` field is currently commented out (not yet implemented) ## Response Envelope All endpoints return a standard envelope: ```json theme={null} { "id": "550e8400-e29b-41d4-a716-446655440000", "success": true, "version": "V1", "data": { ... } } ``` On error, `success` is `false` and `msg` contains the error code (e.g., `REQ_WALLET_ERROR`, `REQ_SLIPPAGE_BPS_ERROR`). ## Transaction Response Shape A successful transaction response looks like: ```json theme={null} { "id": "...", "success": true, "version": "V1", "data": { "transaction": "AgABB...", // Base64-encoded transaction "addressLookupTableAddresses": ["Address1...", "Address2..."] // For V0 only } } ``` ## Integration Example Here's a typical flow in pseudocode: ```typescript theme={null} // 1. Get quote const quote = await fetch( 'https://transaction-v1.raydium.io/compute/swap-base-in?' + 'inputMint=EPjF...&outputMint=So111...&amount=1000000&slippageBps=50&txVersion=V0' ).then(r => r.json()); // 2. Validate quote if (!quote.success) { throw new Error(quote.msg); } // 3. Build transaction const tx = await fetch( 'https://transaction-v1.raydium.io/transaction/swap-base-in', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ wallet: userWalletAddress, swapResponse: quote, txVersion: 'V0', computeUnitPriceMicroLamports: '1000', inputAccount: userInputTokenAccount, outputAccount: userOutputTokenAccount, }), } ).then(r => r.json()); // 4. Decode and sign const transaction = VersionedTransaction.deserialize( Buffer.from(tx.data.transaction, 'base64') ); transaction.sign([userKeypair]); // 5. Send via RPC const rpc = new Connection('https://api.mainnet-beta.solana.com'); const sig = await rpc.sendTransaction(transaction); await rpc.confirmTransaction(sig); ``` ## Key Parameters Explained ### `txVersion` * **V0**: Modern Solana transaction with Address Lookup Tables (ALTs). Smaller serialization size, lower fees. * **LEGACY**: Pre-ALT transaction format. Larger, but works with all RPC endpoints. Choose **V0** when possible; fall back to **LEGACY** if your RPC or wallet does not support ALTs. ### `computeUnitPriceMicroLamports` Priority fee for faster block inclusion. Set to `0` for no priority fee, or higher values (e.g., `1000`) to compete in congested networks. Units are micro-lamports per compute unit. ### `slippageBps` Maximum slippage tolerance in basis points. `100` = 1%, `50` = 0.5%. * Use lower values (e.g., 25–50 bps) for most stable swaps * Increase for volatile or low-liquidity pairs ### `wrapSol` and `unwrapSol` * **wrapSol**: If `true`, the API wraps your native SOL into WSOL. No `inputAccount` needed. * **unwrapSol**: If `true`, the API unwraps the output WSOL back to native SOL. No `outputAccount` needed. If both are `false`, you must provide explicit token accounts. ## Network Endpoints | Network | Mainnet | Devnet | | -------- | --------------------------- | ---------------------------------- | | Host | `transaction-v1.raydium.io` | `transaction-v1-devnet.raydium.io` | | Protocol | HTTPS | HTTPS | ## Error Codes Common error messages: | Code | Meaning | | ------------------------- | ------------------------------------------------ | | `REQ_SLIPPAGE_BPS_ERROR` | Slippage is invalid or out of range | | `REQ_INPUT_MINT_ERROR` | Input mint address is invalid | | `REQ_OUTPUT_MINT_ERROR` | Output mint address is invalid | | `REQ_AMOUNT_ERROR` | Amount is not a valid number | | `REQ_TX_VERSION_ERROR` | txVersion must be V0 or LEGACY | | `REQ_WALLET_ERROR` | Wallet address is invalid | | `REQ_INPUT_ACCOUT_ERROR` | Input token account missing or invalid | | `REQ_OUTPUT_ACCOUT_ERROR` | Output token account missing or invalid | | `UNKNOWN_ERROR` | Server-side error; check your request parameters | ## See Also * [OpenAPI Specification](/api-reference/openapi/route-api-v2.yaml) * [Aggregator Integration Guide](/integration-guides/aggregator) # Temp API Source: https://docs.raydium.io/api-reference/temp-api-v1/overview Temporary holding area for short-lived endpoints The Temp API is a temporary holding area for endpoints that do not yet have a permanent home in the main Raydium APIs. This service is intended for rapid prototyping, internal tools, and features under active development. ## Hosts | Environment | Host | | ----------- | ------------------------------- | | Mainnet | `temp-api-v1.raydium.io` | | Devnet | `temp-api-v1-devnet.raydium.io` | ## Stability warning **This service has no API stability guarantees.** Endpoints may change, be deprecated, moved to another service, or removed entirely without notice. Use this API only for: * Internal tools and scripts * Features in active development * Non-critical paths that can tolerate breakage * Testing and prototyping Do not depend on this API for production dApps or critical integrations. ## Overview This service currently exposes: * **`/cp-creator-fee`** — Retrieve accumulated creator fees for a wallet across CPMM pools. * **`/limit-order/order/list`** — A wallet's currently-parked CLMM limit orders (open and partially-filled) served from the indexer cache. * **`/limit-order/history/order/list-by-user`** — A wallet's historical (closed) CLMM limit orders, with optional `poolId` / `mint1` / `mint2` / `hideCancel` filters; cursor-paginated via `nextPageId`. * **`/limit-order/history/event/list-by-pda`** — Per-PDA event log (`open` / `increase` / `decrease` / `settle` / `close`) for one or more limit-order PDAs; cursor-paginated via `nextPageId`. ## Common patterns ### Check creator fees for a wallet ```bash theme={null} curl -s "https://temp-api-v1.raydium.io/cp-creator-fee?wallet=" ``` Returns a list of pools where the wallet is the creator, along with accumulated but unclaimed creator fees in Token A and Token B. ### List a wallet's currently-parked CLMM limit orders ```bash theme={null} curl -s "https://temp-api-v1.raydium.io/limit-order/order/list?wallet=" ``` Returns both fully-unfilled and partially-filled orders. Each row is a decoded `LimitOrderState` (PDA, pool, owner, tick, side, FIFO cohort phase, total/filled amounts) plus the pool context needed to call `SettleLimitOrder`, including a precomputed `pendingSettle` amount. The active-orders endpoint is unpaginated — it returns all of the wallet's currently-cached orders in one payload. ### List a wallet's closed limit orders ```bash theme={null} curl -s "https://temp-api-v1.raydium.io/limit-order/history/order/list-by-user?wallet=&size=50" ``` Returns a page of historical orders, each summarized with `fillStatus` (`NONE` / `PARTIAL` / `FULL`) and the input/output amounts at the time the order left the active set. Append `&nextPageId=` to fetch the next page. ### Pull the event timeline for a specific order ```bash theme={null} curl -s "https://temp-api-v1.raydium.io/limit-order/history/event/list-by-pda?pda=" ``` `pda` may be a single key or a comma-separated list. Each row is one on-chain mutation (`open` / `increase` / `decrease` / `settle` / `close`) with txid, slot, block time, signed-by-owner-or-keeper flag (`autoRunner`), and the input/output deltas in human units. ### Empty result If the wallet has no records cached, the active-orders endpoint returns: ```json theme={null} { "id": "...", "success": true, "data": { "rows": [] } } ``` The history endpoints return the same `{ rows: [], nextPageId?: ... }` shape with an empty `rows` array. The legacy `/cp-creator-fee` endpoint instead returns `"data": []` for backward compatibility. ## Related docs See [CPMM](/products/cpmm) for pool structure and creator fee mechanics, and [CLMM → Instructions](/products/clmm/instructions) for the on-chain limit-order flow these endpoints surface. # Aggregator integration Source: https://docs.raydium.io/integration-guides/aggregator How to discover Raydium pools, quote across CPMM / CLMM / AMM v4, and route atomic swaps through the right set of programs. Written for DEX aggregators and wallet swap UIs. An aggregator's job is to give a user the best possible price across many pools, possibly splitting a single input across multiple pool routes, and execute it atomically. This page documents the Raydium-specific pieces of that job: discovery, quoting, and transaction assembly. ## Discovery ### Pool inventory You need the full list of live Raydium pools for each product. Three options: 1. **REST API** (simplest): `GET https://api-v3.raydium.io/pools/info/list?poolType=all&pageSize=1000&page=1` returns pools in batches of 1000. Paginate until you have them all. Cache for 1–5 minutes. 2. **On-chain scan**: `getProgramAccounts` on CPMM, CLMM, and AMM v4 program IDs, filtered by the state account discriminator. Yields \~every live pool with \~10s of RPC time. Useful when the API is down or rate-limited. 3. **Hybrid**: use the API as the primary source; run a daily on-chain scan as a sanity check. The team commits to keeping the API comprehensive, but pools created through direct CPI (no frontend) can occasionally lag. ### Mint-pair lookup For a specific `(mintA, mintB)` pair, use `GET /pools/info/mint?mint1=...&mint2=...&poolType=all&sort=liquidity`. Returns every pool at any fee tier and product type. Up to \~10 results per pair is common on well-trafficked mints; sort by TVL and take the top few for routing. ## Quoting Quote math differs per product. Use the SDK's pure math functions so you don't re-implement: ```ts theme={null} // CPMM const cpmmQuote = raydium.cpmm.computeAmountOut({ poolInfo: cpmmPool, amountIn, mintIn, mintOut, slippage: 0, // compute exact expected; layer slippage at route level }); // CLMM — crosses ticks; deterministic but more expensive. // `computeAmountOutFormat` is the canonical helper exposed via `PoolUtils` in // raydium-sdk-v2: the `*Format` suffix signals that it returns the output // pre-shaped for transaction building (including `remainingAccounts` for tick arrays). const { output: clmmOut, remainingAccounts } = PoolUtils.computeAmountOutFormat({ poolInfo: clmmPool, poolState: clmmPoolState, tickArrayCache, amountIn, tokenIn: mintIn, slippage: 0, }); // AMM v4 const ammV4Quote = raydium.liquidity.computeAmountOut({ poolInfo: ammV4Pool, amountIn, mintIn: mintIn, mintOut: mintOut, slippage: 0, }); ``` Returns for all three: `{ amountOut, fee, priceImpact, minAmountOut }`. For aggregator comparison, use `amountOut` (pre-slippage). ### Cache freshness Pool state gets stale fast. Recommended freshness targets: | Pool type | Re-fetch frequency | Why | | ---------------------- | ------------------ | ------------------------------------------------------------ | | CPMM with `<$100k` TVL | `<10s` | Reserves move on every trade. | | CPMM with `>$10M` TVL | 30–60s | Reserves dominant; small trades are noise. | | CLMM | `<30s` | Tick boundaries; a single big trade can re-config liquidity. | | AMM v4 | `<30s` | OpenBook-side movements not captured in vaults. | For an aggregator taking quotes at interactive latency, subscribe to WebSocket account updates (`accountSubscribe`) on each relevant pool state. That flips the model from polling to push. ### Token-2022 adjustments If any mint in the route has a Token-2022 transfer fee, the quote math must adjust inputs and outputs per [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees). The SDK handles this if the `poolInfo.mintA.extensions.transferFeeConfig` is populated. Confirm by looking at the `.extensions` field before trusting the quote. ## Routing ### Single-pool routes Most routes are single-pool. Pick the pool whose `amountOut` is highest. If multiple are close, tie-break by fee tier (lower is better), then by TVL (more is safer). ### Split routing For large trades where a single pool has >5% price impact, split across pools. A simple greedy algorithm: ``` remaining = amountIn routes = [] while remaining > 0: best_pool, best_size = argmax over pools of: marginal_out_per_in(pool, current_size_toward_pool + epsilon) size = min(remaining, best_pool.max_size_at_target_impact) routes.append((best_pool, size)) remaining -= size ``` This produces a routing vector `[(pool_A, 0.6), (pool_B, 0.3), (pool_C, 0.1)]` that minimizes aggregate impact. A proper convex-optimization solution (e.g. equalize marginal prices across pools) is within \~1% of the greedy result in practice. ### Multi-hop routes `USDC → RAY → SOL` via two separate pools is common when no direct `USDC-SOL` pool gives a good quote (rare). Apply per-hop slippage bounds; each hop enforces its own `minAmountOut`. See [`algorithms/slippage-and-price-impact`](/algorithms/slippage-and-price-impact). Multi-hop across the same pool (e.g. two CLMM hops on `SOL-USDC`) is always suboptimal vs a single hop — do not generate such routes. ## Transaction assembly ### Single-hop, single-pool Use the SDK's `raydium.trade.swap` directly: ```ts theme={null} const { execute } = await raydium.trade.swap({ poolKeys: poolInfo, amountIn, amountOut: quote.minAmountOut, fixedSide: "in", inputMint: mintIn, txVersion: TxVersion.V0, computeBudgetConfig: { units: 250_000, microLamports: priorityFee, }, }); ``` ### Split and multi-hop Compose ATAs + instructions manually. Pattern: ``` [1] ComputeBudget set_compute_unit_limit [2] ComputeBudget set_compute_unit_price [3] createATA (if needed, once per mint the user doesn't hold) [4..N] SwapInstruction for each (pool, size) in routes [N+1] CloseAccount (if you wrap/unwrap SOL) ``` All inside one transaction for atomicity. For a 3-pool split on V0 with address lookup tables, this typically fits in \~1100 bytes. For 4+ pools, the transaction size cap forces either multi-tx or consolidation at a hub mint. ### Atomicity Aggregators must guarantee atomicity: either the full route lands or none of it does. Raydium's swap instructions revert on `ExceededSlippage`, so a multi-pool route where one hop fails causes the whole transaction to revert. Free. The one exception: if your route goes through Raydium + a third-party DEX, make sure that DEX also has a revert-on-slippage model. Some programs ignore slippage bounds (rare). ## Pitfalls ### 1. Stale quotes Between the user seeing "You receive 125.43 RAY" and the transaction landing, reserves can shift. Re-fetch pool state immediately before submission; re-quote; if the new quote is >1% worse, pause and re-confirm with the user. ### 2. Pool blacklists Some Raydium pools are scam tokens with transfer fees set to 99% or with non-transferable extensions. The REST API tags these (see the `tags` field); skip any pool tagged `scam` or `honeypot`. Running your own safety checks on top of Raydium's tags is prudent. ### 3. Observation-state requirement on CLMM CLMM `SwapV2` takes an `observation_state` account. The SDK populates it for you; hand-built instructions often forget, which causes the program to revert with `AccountNotFound`. Always include it. ### 4. Address lookup tables Raydium maintains public lookup tables for its most-used accounts (main mints, program IDs, AmmConfigs). Aggregators should consume these — it saves \~100 bytes per transaction and enables larger routes to fit in V0. Pulling the LUT addresses: ```ts theme={null} const raydiumLUTs = await raydium.getRaydiumLutAddresses(); ``` ### 5. Handling congestion During high-volume windows, transactions can sit in the mempool for multiple blocks. Aggressive retry on TX expiry (not on revert — reverts are deterministic) is recommended. The SDK's `sendAndConfirm` option does basic retries; production aggregators layer their own logic (Jito bundles, multi-RPC broadcast) on top. ## Checklist Before going live, verify: * [ ] Pool discovery covers CPMM + CLMM + AMM v4 comprehensively. * [ ] Quotes match Raydium's own UI quote within 1 basis point on a handful of test trades. * [ ] Split routing kicks in for trades >5% impact on any single pool. * [ ] Priority fees are sized against recent pool-program fees (see [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning)). * [ ] Token-2022 transfer fees are computed and displayed to the user. * [ ] Transactions revert cleanly when slippage is exceeded. * [ ] Retry logic distinguishes tx expiry (retry) from revert (don't retry). ## Pointers * [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev) — sandwich resistance, bundles. * [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) — sizing compute-budget instructions. * [`sdk-api/rest-api`](/sdk-api/rest-api) — pool-list endpoints. Sources: * [Raydium SDK v2 `trade` module](https://github.com/raydium-io/raydium-sdk-V2) # CPI from a custom program Source: https://docs.raydium.io/integration-guides/cpi-integration Composition patterns for on-chain programs that call Raydium — escrows, aggregator proxies, auto-compounding vaults — with full worked examples that include account construction, PDA signers, remaining accounts, and compute-budget budgeting. [`sdk-api/rust-cpi`](/sdk-api/rust-cpi) covers the low-level mechanics of invoking each Raydium program. This page is the higher-level companion: *why* you would compose Raydium into your own program, *which* pattern fits your use case, and the full glue you need end-to-end. ## When CPI is the right tool A custom program makes sense when the trade needs to happen atomically with other on-chain state changes that only your program can make. Common cases: * **Escrow / limit-order programs** — user deposits a mint into your escrow, your program watches for a price condition, and when it triggers, your program atomically swaps through Raydium and credits the user's account. * **Aggregator proxies** — a single instruction that routes a swap through Raydium + one or more other DEXes, with all hops under a single slippage check owned by your program. * **Auto-compounding vaults** — deposit LP or farm stake into your vault, vault harvests rewards on a schedule, re-supplies liquidity, issues share tokens. * **Strategy vaults** — leveraged LP positions that rebalance by swapping through CLMM; liquidators that close positions and swap collateral in one transaction. * **Token-launch platforms with custom vesting** — your program holds vesting tokens and releases into a Raydium pool on a schedule. If you just want to send a swap from off-chain code, CPI is overkill — use the SDK. CPI earns its complexity only when atomicity with your own state is the requirement. ## Composition patterns ### Pattern 1: Thin proxy Your program exposes a single instruction that validates some policy (e.g. whitelisted mint pairs, fee discount for verified users) and then forwards to Raydium. ``` ┌──────────────┐ user tx ┌────────────────┐ CPI ┌──────────┐ │ user │─────────────▶│ your program │──────▶│ Raydium │ └──────────────┘ │ (validate) │ │ (CPMM) │ └────────────────┘ └──────────┘ ``` State lives in the user's ATAs. Your program owns no tokens. Minimal trust footprint. ### Pattern 2: Escrow Your program owns a PDA that holds the user's input mint. On trigger, the PDA signs a CPI to Raydium to swap its own balance. ``` deposit trigger user ───────────▶ PDA vault ───────────────▶ Raydium swap (your prog) (signed by PDA) │ ▼ PDA vault (output mint) │ withdraw ▼ user ``` Critical detail: the PDA signs via `CpiContext::new_with_signer`. See [Signer seeds](#pda-signer-seeds). ### Pattern 3: Composed multi-hop Your program issues multiple CPIs in one instruction, enforcing a single slippage bound across all of them. The Raydium swap instructions each have their own `minimum_amount_out`, but you set those to 0 (or a very loose floor) and enforce a strict final minimum yourself after the last hop. ``` instruction: CPI swap: tokenA → tokenB (raydium, loose min) CPI swap: tokenB → tokenC (raydium / third-party, loose min) CPI swap: tokenC → tokenD (raydium, loose min) require(user.tokenD_ata.amount - pre_balance >= user_min_out) ``` This gives you a single reverting gate for the whole route. Only use this pattern when you trust every hop to be slippage-safe; otherwise, let each hop enforce its own min. ### Pattern 4: Vault / strategy Your program holds LP tokens or farm stake in a PDA. A keeper (or the user) calls `compound()`, which: 1. Harvests rewards from the farm. 2. Swaps rewards for pool tokens (CPI into CPMM or CLMM). 3. Deposits the proceeds back into the LP (another CPI). 4. Stakes the new LP (another CPI). All in one transaction so the vault's NAV moves atomically. Compute budget is typically 600k–1M CU; address lookup tables are mandatory. ## Account list construction The calling program's `Accounts` struct mirrors the Raydium program's account order, but most Raydium-side accounts are `UncheckedAccount` because Raydium validates them itself. You only add constraints on accounts *you* own: ```rust theme={null} use anchor_lang::prelude::*; use anchor_spl::token::{Token, TokenAccount}; #[derive(Accounts)] pub struct EscrowSwap<'info> { /// The escrow PDA; holds input mint and signs the CPI. #[account( mut, seeds = [b"escrow", user.key().as_ref()], bump = escrow.bump, )] pub escrow: Account<'info, Escrow>, #[account(mut)] pub user: Signer<'info>, // ----- Raydium-side accounts, mostly unchecked ----- /// CHECK: validated by CPMM #[account(mut)] pub pool_state: UncheckedAccount<'info>, /// CHECK: validated by CPMM pub amm_config: UncheckedAccount<'info>, /// CHECK: validated by CPMM pub pool_authority: UncheckedAccount<'info>, #[account(mut)] pub input_vault: Account<'info, TokenAccount>, #[account(mut)] pub output_vault: Account<'info, TokenAccount>, /// CHECK: validated by CPMM #[account(mut)] pub observation_state: UncheckedAccount<'info>, /// Escrow's input ATA — owned by the escrow PDA. #[account( mut, associated_token::mint = input_mint, associated_token::authority = escrow, )] pub escrow_input_ata: Account<'info, TokenAccount>, /// Escrow's output ATA. #[account( mut, associated_token::mint = output_mint, associated_token::authority = escrow, )] pub escrow_output_ata: Account<'info, TokenAccount>, pub input_mint: Account<'info, anchor_spl::token::Mint>, pub output_mint: Account<'info, anchor_spl::token::Mint>, pub cpmm_program: Program<'info, raydium_cp_swap::program::RaydiumCpSwap>, pub token_program: Program<'info, Token>, pub token_program_2022: Program<'info, anchor_spl::token_2022::Token2022>, } ``` The asymmetry — strict validation on your accounts, `UncheckedAccount` on Raydium's — is not laziness. The receiver validates its own; double-validating at the caller just burns CU and risks going out of sync when Raydium ships a new struct layout field. ## The CPI call itself ```rust theme={null} use raydium_cp_swap::cpi::{self, accounts::Swap as CpmmSwap}; pub fn escrow_swap( ctx: Context, amount_in: u64, minimum_amount_out: u64, ) -> Result<()> { let user_key = ctx.accounts.user.key(); let bump = ctx.accounts.escrow.bump; let seeds: &[&[u8]] = &[b"escrow", user_key.as_ref(), &[bump]]; let signer: &[&[&[u8]]] = &[seeds]; let cpi_accounts = CpmmSwap { payer: ctx.accounts.user.to_account_info(), authority: ctx.accounts.escrow.to_account_info(), amm_config: ctx.accounts.amm_config.to_account_info(), pool_state: ctx.accounts.pool_state.to_account_info(), input_token_account: ctx.accounts.escrow_input_ata.to_account_info(), output_token_account: ctx.accounts.escrow_output_ata.to_account_info(), input_vault: ctx.accounts.input_vault.to_account_info(), output_vault: ctx.accounts.output_vault.to_account_info(), input_token_program: ctx.accounts.token_program.to_account_info(), output_token_program: ctx.accounts.token_program.to_account_info(), input_token_mint: ctx.accounts.input_mint.to_account_info(), output_token_mint: ctx.accounts.output_mint.to_account_info(), observation_state: ctx.accounts.observation_state.to_account_info(), }; let cpi_ctx = CpiContext::new_with_signer( ctx.accounts.cpmm_program.to_account_info(), cpi_accounts, signer, ); cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out)?; Ok(()) } ``` ### PDA signer seeds The CPI succeeds only if the PDA passed as `authority` matches the derivation the caller claims. The two must agree on: 1. The seed byte sequence (here `[b"escrow", user.key().as_ref()]`). 2. The bump. 3. The calling program ID (your program, not Raydium's). Raydium doesn't care who the authority is — it only cares that the `authority` signature covers the transaction and that the input ATA is owned by that authority. The validation happens in `anchor_spl::token::transfer`: the ATA's `authority` field must equal the signer. Common bug: passing `user` as the authority (and transferring from `escrow_input_ata` that is owned by the escrow PDA). The SPL Token program rejects with `owner mismatch`. Always make the `authority` field match the ATA owner. ## Remaining accounts Several Raydium instructions take a variable-length list of accounts appended after the fixed ones — **remaining accounts**. * **CLMM `SwapV2`**: 1–8 `TickArrayState` accounts for the tick arrays the swap may traverse, in swap direction. * **Farm v6 `Deposit` / `Harvest` / `Withdraw`**: `(reward_vault, user_reward_ata)` pairs, one pair per live reward slot. * **Token-2022 transfer-hook mints**: the transfer-hook program plus any accounts the hook needs. The Anchor CPI helpers don't type-check remaining accounts. Pass them through: ```rust theme={null} let cpi_ctx = CpiContext::new_with_signer(program, accounts, signer) .with_remaining_accounts(ctx.remaining_accounts.to_vec()); ``` **Ordering matters.** For CLMM: ``` remaining = [ tick_array_in_direction_0, // first one crossed tick_array_in_direction_1, ..., ] ``` For farm v6 harvest: ``` remaining = [ reward_vault_0, user_reward_ata_0, reward_vault_1, user_reward_ata_1, // omit any slot whose reward_state is Uninitialized ] ``` Your calling program must pass the remaining accounts it receives from the client through unchanged. Don't try to filter or reorder them. ## Compute budget for composed calls A CPI costs \~1,500 CU for the call frame itself; the callee's own CU use stacks on top. Rough budget per Raydium CPI: | Call | CU (SPL Token) | CU (Token-2022) | | --------------------------------- | -------------- | --------------- | | CPMM swap\_base\_input | \~150,000 | \~200,000 | | CLMM swap\_v2 (single tick array) | \~180,000 | \~230,000 | | CLMM swap\_v2 (crosses 2 ticks) | \~220,000 | \~270,000 | | Farm v6 deposit | \~120,000 | \~150,000 | | Farm v6 harvest (per reward slot) | +30,000 | +40,000 | | AMM v4 swap\_base\_in | \~140,000 | n/a | Add \~1,500 for each CPI frame and \~20,000 for your own program's overhead. An auto-compounder doing `harvest → swap A → swap B → deposit LP → stake LP` easily hits 700k CU. Always set an explicit `ComputeBudgetProgram::set_compute_unit_limit`: ```ts theme={null} import { ComputeBudgetProgram } from "@solana/web3.js"; const tx = new Transaction().add( ComputeBudgetProgram.setComputeUnitLimit({ units: 900_000 }), ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFeeMicroLamports }), yourInstruction, ); ``` The default 200k CU ceiling will silently exhaust long before a composed call completes. ## Error propagation Raydium's programs return Anchor errors with stable error codes. Your calling program sees them as `Err(ProgramError::Custom(code))`. Bubble through by default: ```rust theme={null} cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out)?; ``` Or intercept for specific codes: ```rust theme={null} use raydium_cp_swap::error::ErrorCode as CpmmErr; match cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out) { Ok(_) => {}, Err(err) if is_err(err, CpmmErr::ExceededSlippage) => { // Your program might want to retry at a larger slippage, or unwind state. return err!(YourErr::PoolTooVolatile); } Err(err) => return Err(err), } ``` The error code-to-meaning mapping is stable per the IDL policy ([`sdk-api/anchor-idl`](/sdk-api/anchor-idl)); new codes append at the end, existing codes never change meaning. ## Full worked example: limit-order escrow Flow: 1. **`open_order`** — user deposits `amount_in` of `input_mint` into escrow PDA; record target `min_amount_out` and expiry. 2. **`execute_order`** — anyone (keeper) calls with the current pool accounts. Program checks the current quote ≥ `min_amount_out`, then CPIs Raydium swap and keeps the output in escrow. 3. **`claim`** — user withdraws the output mint from escrow. ```rust theme={null} #[account] pub struct LimitOrder { pub user: Pubkey, pub input_mint: Pubkey, pub output_mint: Pubkey, pub amount_in: u64, pub min_out: u64, pub expiry_unix: i64, pub state: u8, // 0 open, 1 filled, 2 cancelled, 3 expired pub bump: u8, } #[program] pub mod limit_orders { use super::*; pub fn execute_order( ctx: Context, ) -> Result<()> { let order = &ctx.accounts.order; require!(order.state == 0, OrderErr::NotOpen); require!(Clock::get()?.unix_timestamp < order.expiry_unix, OrderErr::Expired); let user_key = order.user; let seeds: &[&[u8]] = &[b"order", user_key.as_ref(), &[order.bump]]; let signer: &[&[&[u8]]] = &[seeds]; let pre_out_balance = ctx.accounts.escrow_output_ata.amount; let cpi_accounts = CpmmSwap { payer: ctx.accounts.keeper.to_account_info(), authority: ctx.accounts.order.to_account_info(), amm_config: ctx.accounts.amm_config.to_account_info(), pool_state: ctx.accounts.pool_state.to_account_info(), input_token_account: ctx.accounts.escrow_input_ata.to_account_info(), output_token_account: ctx.accounts.escrow_output_ata.to_account_info(), input_vault: ctx.accounts.input_vault.to_account_info(), output_vault: ctx.accounts.output_vault.to_account_info(), input_token_program: ctx.accounts.token_program.to_account_info(), output_token_program: ctx.accounts.token_program.to_account_info(), input_token_mint: ctx.accounts.input_mint.to_account_info(), output_token_mint: ctx.accounts.output_mint.to_account_info(), observation_state: ctx.accounts.observation_state.to_account_info(), }; let cpi_ctx = CpiContext::new_with_signer( ctx.accounts.cpmm_program.to_account_info(), cpi_accounts, signer, ); // Let the escrow enforce the minimum — we trust Raydium's slippage, but we // also re-check our own post-swap delta in case a future change ever relaxes it. cpi::swap_base_input(cpi_ctx, order.amount_in, order.min_out)?; ctx.accounts.escrow_output_ata.reload()?; let delta = ctx.accounts.escrow_output_ata.amount .checked_sub(pre_out_balance) .ok_or(error!(OrderErr::AccountingError))?; require!(delta >= order.min_out, OrderErr::InsufficientOutput); let order = &mut ctx.accounts.order; order.state = 1; Ok(()) } } ``` The keeper pays the transaction fee (they get a keeper fee elsewhere — not shown). The escrow PDA signs the CPI. Both the Raydium-side slippage check *and* the escrow's own delta check enforce the floor — belt and braces. ## Testing Pulling Raydium programs into a local validator for integration tests (from `Anchor.toml`): ```toml theme={null} [test.validator] clone = [ { address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" }, # CPMM { address = "CLMM...." }, # CLMM { address = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" }, # AMM v4 { address = "FarmqiPv5eAj3j1GMdMCMUGXqPUvmquZtMy86QH6rzhG" }, # Farm v6 ] ``` Clone the pool state accounts too so your tests can actually execute swaps; `anchor test` fetches them from mainnet at startup. See [`sdk-api/rust-cpi`](/sdk-api/rust-cpi#testing-a-cpi-flow). ## Pitfalls specific to composition ### Reentrancy Solana has no true reentrancy — a CPI can't call back into the originating program in the same invocation. But you can still build yourself into a logical reentrancy: a CPI that reads your state, then your code reads it again assuming the CPI didn't change it. For Raydium, the CPIs don't touch your state, so this is less a concern than e.g. flash-loan contexts. But if you compose Raydium with a lending protocol, be aware. ### Account mutability drift If your program passes an account as `mut` but Raydium expects it read-only (or vice versa), the runtime rejects the invocation with `InvalidAccountData`. Always check Raydium's instruction's expected mutability in the IDL; `anchor_cp_swap::cpi::accounts::Swap` enforces it via its field types. ### Token-2022 program field Input and output mints may be under different token programs — one SPL Token, one Token-2022. The CPI has separate `input_token_program` and `output_token_program` fields for this reason. Always check each mint's `owner` field and route the correct program into each slot. ### Versioned transactions A composed tx that does 2+ Raydium CPIs plus an ATA creation rarely fits in a legacy (v0-without-LUT) transaction. Use V0 with address lookup tables; pull Raydium's public LUTs via `raydium.getRaydiumLutAddresses()`. ## Pointers * [`sdk-api/rust-cpi`](/sdk-api/rust-cpi) — low-level CPI mechanics. * [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) — sizing compute budget. * [`products/cpmm/code-demos`](/products/cpmm/code-demos), [`products/clmm/code-demos`](/products/clmm/code-demos), [`products/farm-staking/code-demos`](/products/farm-staking/code-demos) — per-product CPI snippets. Sources: * [raydium-cp-swap CPI crate](https://github.com/raydium-io/raydium-cp-swap) * [raydium-clmm CPI crate](https://github.com/raydium-io/raydium-clmm) * [Anchor CPI docs](https://www.anchor-lang.com/docs/cross-program-invocations) # Integration Guides Source: https://docs.raydium.io/integration-guides/index Recipes for common integration scenarios: aggregators, wallets, program-to-program CPI, routing, and performance tuning. ## Who this chapter is for Teams building *on top of* Raydium — aggregators, wallets, custom trading programs, bots. If you are a Raydium end user, go to [User Flows](/user-flows) instead. ## Chapter contents How aggregators fetch Raydium pool state, quote swaps, and construct cross-DEX routes. What a wallet needs to display Raydium positions, farms, and transaction simulations correctly. Invoking Raydium programs from your own Solana program — account lists, signer seeds, error propagation, common mistakes. Split routing across AMM/CLMM, Jito bundles, sandwich-resistance patterns, MEV-share considerations. How to size compute-unit requests and priority fees for each Raydium instruction; tuning under mainnet congestion. ## Writing brief * Every guide starts with "this is the problem" (e.g., "my swap transactions fail under load") and ends with a validated recipe. * Include benchmarks where meaningful (e.g., typical CU usage for a CPMM swap vs. a CLMM swap). * Link into SDK & API for the underlying primitives; don't redefine them. # Priority fees and compute budget Source: https://docs.raydium.io/integration-guides/priority-fee-tuning Right-sizing compute-unit limits and priority fees for Raydium transactions — benchmarks per instruction, estimation strategies, and congestion-handling patterns. Every Solana transaction sets (implicitly or explicitly) two parameters: a **compute unit limit** (max CUs the tx may consume; default 200,000 × number of instructions up to a per-tx cap) and a **priority fee** in micro-lamports per CU. Under-sizing either kills transactions — too-low CU limits cause `ProgramFailedToComplete`; too-low priority fees cause the tx to sit unconfirmed until expiry. ## The two settings ```ts theme={null} import { ComputeBudgetProgram } from "@solana/web3.js"; const tx = new Transaction() .add(ComputeBudgetProgram.setComputeUnitLimit({ units: 250_000 })) .add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50_000 })) .add(yourRaydiumSwapIx); ``` * `setComputeUnitLimit(units)` — caps compute; the transaction pays for at most `units` CUs. * `setComputeUnitPrice(microLamports)` — priority fee bid, in micro-lamports per CU. Total priority fee = `units × microLamports × 1e-6` lamports. Cost math: a 250k CU limit at 50k micro-lamports/CU bids `250_000 × 50_000 / 1e6 = 12,500` lamports ≈ 0.0000125 SOL ≈ \$0.003 at \$200 SOL. Priority fees at this scale are noise for most user swaps but material for bots doing 1000 txs/day. ## CU benchmarks per instruction Benchmarks from mainnet execution logs, averaged across recent runs. Numbers are approximate (±15%); re-measure for your specific flows. | Instruction | SPL Token | Token-2022 (simple) | Token-2022 (transfer fee) | | -------------------------------- | --------------- | ------------------- | ------------------------- | | CPMM initialize\_pool | 180,000 | 200,000 | — | | CPMM swap\_base\_input | 140,000 | 180,000 | 200,000 | | CPMM swap\_base\_output | 150,000 | 185,000 | 205,000 | | CPMM deposit | 130,000 | 160,000 | 180,000 | | CPMM withdraw | 120,000 | 150,000 | 170,000 | | CLMM create\_pool | 70,000 | 85,000 | — | | CLMM open\_position\_v2 | 120,000 | 140,000 | 160,000 | | CLMM increase\_liquidity\_v2 | 150,000 | 175,000 | 195,000 | | CLMM decrease\_liquidity\_v2 | 140,000 | 165,000 | 185,000 | | CLMM swap\_v2 (0 tick crossings) | 170,000 | 205,000 | 225,000 | | CLMM swap\_v2 (1 tick crossing) | 220,000 | 255,000 | 275,000 | | CLMM swap\_v2 (3 tick crossings) | 320,000 | 355,000 | 375,000 | | CLMM collect\_fee | 80,000 | 95,000 | 105,000 | | AMM v4 swap\_base\_in | 140,000 | — | — | | AMM v4 deposit | 120,000 | — | — | | AMM v4 withdraw | 110,000 | — | — | | Farm v6 create\_farm | 70,000 | 85,000 | — | | Farm v6 deposit (1 reward slot) | 130,000 | 155,000 | 175,000 | | Farm v6 deposit (3 reward slots) | 220,000 | 255,000 | 275,000 | | Farm v6 withdraw | matches deposit | | | | Farm v6 harvest | matches deposit | | | | Farm v3/v5 deposit | 100,000 | — | — | | LaunchLab initialize | 100,000 | — | — | | LaunchLab buy\_exact\_in | 140,000 | — | — | | LaunchLab graduate | 250,000 | — | — | The "tick crossings" row for CLMM is the biggest CU variable. If you don't know how many ticks the swap will cross, budget for the worst case — 8 crossings is the hard cap (the program loads at most 8 tick arrays). ### Composed transactions Sum the individual budgets and add: * **+1,500 CU per CPI frame** — the runtime's fixed overhead for each cross-program call. * **+20,000 CU per ATA creation** — `create_associated_token_account` is not free. * **+5,000 CU for `setComputeUnitLimit` / `setComputeUnitPrice`** each. Example: a user swap that creates the output ATA and wraps native SOL: ``` wrap_sol (create_ata + system transfer + sync_native) ≈ 30,000 CPMM swap_base_input (SPL) ≈ 140,000 close_account (unwrap) ≈ 5,000 ComputeBudget instructions ≈ 10,000 ──────────────────────────────────────────────────────── Total ≈ 185,000 → budget 250,000 ``` Padding: set the CU limit \~25% above the expected usage. Under-estimating costs the whole tx; over-estimating just raises priority-fee cost proportionally (priority fee is `units × microLamports`, so \~25% over-budget costs 25% extra in priority fee). ## Priority-fee estimation Solana's local fee market means priority fees are **per-writable-account**. A tx that writes to a hot account (popular pool state) pays more than a tx that writes to a cold account. The global fee level is not the right metric for Raydium swaps; you want fees on the specific pools you're touching. ### Strategy 1: RPC provider estimator Each major RPC provider publishes a priority-fee estimator that queries recent fees on specific accounts: ```ts theme={null} // Helius const response = await fetch(`https://mainnet.helius-rpc.com/?api-key=${apiKey}`, { method: "POST", body: JSON.stringify({ jsonrpc: "2.0", id: "fee-estimate", method: "getPriorityFeeEstimate", params: [{ accountKeys: [poolStatePubkey.toBase58()], options: { priorityLevel: "High" }, }], }), }); const { result } = await response.json(); const microLamports = result.priorityFeeEstimate; ``` Priority levels across most providers: `Min` / `Low` / `Medium` / `High` / `VeryHigh` / `UnsafeMax`. Map them to percentiles: | Level | Percentile | Use case | | -------- | ---------- | ---------------------------------- | | Min | 25th | Background, non-urgent bot traffic | | Low | 50th | Normal user swaps | | Medium | 60th | Default for wallet UIs | | High | 75th | Time-sensitive arbitrage | | VeryHigh | 95th | Liquidations, last-chance exits | Providers: Helius (`getPriorityFeeEstimate`), Triton (`getRecentPrioritizationFees` with account list), QuickNode (similar). ### Strategy 2: Direct RPC query Use the standard `getRecentPrioritizationFees` RPC: ```ts theme={null} const fees = await connection.getRecentPrioritizationFees({ lockedWritableAccounts: [poolStatePubkey], }); // fees: Array<{ slot, prioritizationFee }> // Recent N slots; default ~150 slots. const median = percentile(fees.map(f => f.prioritizationFee), 0.5); ``` This is the vanilla Solana RPC method; works with any provider. Downside: the sample is small (150 slots ≈ 60 seconds) and noisy. For smoother estimates, use a provider's aggregation. ### Strategy 3: Historical self-tuning For bots running constant flow, track your own landed vs. expired rates: ``` per-pool target: 80% land rate at <30s if current_land_rate < 80%: priorityFee += 10% if current_land_rate > 95%: priorityFee -= 5% ``` This self-corrects faster than public estimators and captures per-pool structure the public estimators don't always see. ## Handling CU-exhaustion failures Symptom: tx fails with `exceeded maximum number of instructions allowed (200000)` or `ProgramFailedToComplete`. Diagnosis: ```bash theme={null} solana confirm -v # Look for "consumed N of M compute units" and which instruction exhausted. ``` Fixes: 1. **Raise the CU limit.** If your tx was using 195k of a 200k budget, bump to 300k. 2. **Split the transaction.** If you're hitting the 1.4M per-tx cap, break into two txs. Farm `harvest then stake` is a classic one to split when rewards are many. 3. **Trim accounts.** Each additional writable account adds \~2,000 CU. Pruning unused accounts helps on marginal cases. 4. **Use lookup tables.** LUT lookups are \~50 CU per resolved address, saving the 5,000 CU of a full account reference per entry. ## Handling stuck transactions Symptom: tx submitted, never confirms, eventually expires with `BlockhashNotFound`. Diagnosis: * `getSignatureStatuses([sig])` returns `null` → leader never saw it. * Returns `{ confirmationStatus: null }` → leader saw it but didn't include. Fixes: 1. **Raise priority fee.** Re-submit with 2× the current fee. 2. **Rebuild with fresh blockhash.** Blockhash lifetime is \~60 seconds; beyond that the tx is invalid regardless of fees. 3. **Multi-RPC broadcast.** Some RPCs have better leader connectivity than others. Submit to 3–5 in parallel. 4. **Switch to Jito bundles.** See [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev). Bundles bypass public packet queues. Retry logic skeleton: ```ts theme={null} async function submitWithRetry(buildTx, maxAttempts = 5) { for (let attempt = 0; attempt < maxAttempts; attempt++) { const tx = await buildTx({ priorityFee: basePriorityFee * Math.pow(1.5, attempt), blockhash: (await connection.getLatestBlockhash()).blockhash, }); try { const sig = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: attempt > 0, // skip after first try to save latency }); const result = await connection.confirmTransaction(sig, "confirmed"); if (result.value.err) { // Logic error; don't retry. throw result.value.err; } return sig; } catch (e) { if (isExpiredError(e)) continue; // retry if (isRevertError(e)) throw e; // don't retry; deterministic failure throw e; } } throw new Error("submit: exhausted retries"); } ``` ## Under congestion When the network is congested (Jupiter / Jito bundle dashboards show backlog, RPC latency spikes, tx expiry rates climb), adjust: | Parameter | Normal conditions | Congested conditions | | -------------------------- | ------------------- | ------------------------------- | | CU limit | +25% above estimate | +25% above estimate (unchanged) | | Priority fee percentile | 50th | 75th–95th | | Retry count | 3 | 5–7 | | Retry backoff | 500ms | 1000ms | | Use Jito bundles | Optional | Strongly recommended | | Blockhash refresh on retry | Yes | Yes, mandatory | Watching congestion signals: * Priority-fee 75th percentile > 500k micro-lamports: congestion. * Jito 50th percentile tip > 0.001 SOL: congestion. * RPC response p99 > 2s: RPC-specific issue or congestion. ## Fee budgeting for bots A trading bot running \~1000 txs/day needs a priority-fee budget. Back-of-envelope: ``` Average CU per tx: ~250,000 50th percentile fee: ~20,000 micro-lamports/CU Cost per tx: 250_000 × 20_000 × 1e-6 = 5_000 lamports = 5e-6 SOL Daily cost (1000 tx): 5e-3 SOL ≈ $1 @ $200 SOL Monthly cost: ~$30 ``` That's the minimum. During congestion, multiply by 5–10×. Plan for \~\$150–300/month in priority fees for a steady-flow bot. Bots that must land in specific slots (liquidations, arb) pay 95th percentile continuously and spend \~10× more. Jito bundle tips dominate at that scale — often \$1000+/month — but the alternative (being front-run or expiring) is worse. ## Pitfalls ### 1. Forgetting the CU limit Default is 200k CUs × (instructions in tx). A single-instruction swap defaults to 200k; that's enough for CPMM on SPL Token but not CLMM with tick crossings or anything Token-2022. Always set it explicitly. ### 2. Priority fee on the wrong account If you estimate priority fee against the token mint but the hot account is the pool state, your estimate is too low. The pool state is the right writable-account to target for Raydium. ### 3. Fees scale with CU limit `total_priority_fee = units × microLamports`. Raising `units` from 200k to 1M at 50k micro-lamports/CU multiplies priority fee 5×. Don't over-budget CU just in case; measure. ### 4. Default tx version Legacy transactions have lower account limits; V0 transactions with address lookup tables unlock larger routes. The SDK uses V0 by default in `txVersion: TxVersion.V0`. Don't drop to legacy unless you need wallet compatibility. ### 5. `skipPreflight` hides CU errors `skipPreflight: true` sends the tx without local simulation. You save \~100ms but lose the early feedback on CU exhaustion. Use it only on retries, not on the first attempt. ## Pointers * [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev) — Jito bundle strategies. * [`integration-guides/aggregator`](/integration-guides/aggregator) — transaction assembly. * [`integration-guides/cpi-integration`](/integration-guides/cpi-integration) — CU stacking across composed CPIs. * [Solana compute budget program docs](https://docs.solana.com/developing/programming-model/runtime#compute-budget) Sources: * [Solana `getRecentPrioritizationFees` RPC](https://docs.solana.com/api/http#getrecentprioritizationfees) * [Helius priority fee API](https://docs.helius.dev/solana-apis/priority-fee-api) * Benchmarks: mainnet execution logs (Raydium SDK integration tests, April 2026). # Routing and MEV Source: https://docs.raydium.io/integration-guides/routing-and-mev Split routing across Raydium products, sandwich-resistance patterns, Jito bundle integration, and the trade-offs between private- and public-mempool submission paths. "MEV" on Solana is not identical to Ethereum's mempool-driven MEV. Block leaders see tx packets as they arrive, not as an ordered mempool; front-running happens via leader-side reordering or co-located searchers, and sandwich attacks are executed by bots that watch pool state and race your transaction with higher fees. The mitigations differ accordingly. ## Split routing primer "Split routing" means breaking one logical swap across multiple pools so that marginal prices equalize — same output as trading each slice at its own pool's price. It reduces effective price impact when any single pool is shallow relative to the trade size. The problem statement: given pools `P_1, ..., P_n` with functions `f_i(x)` mapping input `x` to output, find the split `x_1 + ... + x_n = X` that maximizes `Σ f_i(x_i)`. Because each `f_i` is concave, the optimum satisfies `f'_1(x_1) = f'_2(x_2) = ... = f'_n(x_n)` (equal marginal prices). ### Greedy implementation A simple approach that gets within \~1% of optimal in practice: ``` remaining = X routes = [] step = X / 1000 // slice size while remaining > 0: best_pool = argmax over i of f'_i(current_x_i + step) x_i += step routes.append((best_pool, step)) remaining -= step ``` Finer `step` → closer to optimal, more iterations. In practice 100–500 slices is a reasonable sweet spot. ### Convex-optimization implementation For production-grade aggregators, solve the optimization directly. Each pool has a closed-form `f'_i(x)`: * **Constant-product (CPMM / AMM v4)**: `f'(x) = y * R_y / (R_x + x)^2` where `R_x, R_y` are reserves and `y = R_x * R_y / (R_x + x) - R_y` ... (simpler derivation: marginal price is `R_y / (R_x + x)`, so splitting to equalize marginal prices is a 1D search). * **CLMM**: piecewise smooth — within one tick, `f'(x)` is a rational function of `sqrt_price`; across a tick, it steps discretely. Split with a small-step solver or treat each contiguous tick as its own "pool". The output of split routing is a vector `[(pool_1, x_1), (pool_2, x_2), ...]` that your transaction assembly step turns into a sequence of swap instructions. ### When split routing helps | Trade size vs TVL | Split helps? | | ----------------- | -------------------------- | | `<0.1%` | No — single-pool dominates | | 0.1–1% | Marginally | | 1–5% | Yes, 10–50 bps improvement | | `>5%` | Yes, large improvement | If you're running a wallet's in-UI swap for a retail user doing `<$10k` on a deep pool, don't bother splitting — gas overhead exceeds the improvement. For an aggregator quoting institutional flow, always split. ## Multi-hop routes When no direct pool exists, or the direct pool's impact is huge, hop through an intermediate: ``` tokenA → tokenHub → tokenB ``` Common hubs: USDC, SOL, RAY. Each hop has: * Its own slippage bound (lower on direct hops; per-hop on multi-hop). * Its own fee paid. * Its own price impact. The total impact compounds: `(1 - impact_1) * (1 - impact_2)`. A 1% impact hop twice is 1.99% total, not 2%. **Never hop through the same pool twice.** Going `A → B → A → B` via the same CLMM just burns fees and slippage. Aggregators should filter such routes at generation. (Note: this is *cycling the same pair*, not multi-hop in general — routing `A → USDC → B` through different pools is the standard, useful pattern endorsed above.) **Per-hop vs end-to-end minimum.** With CPI composition ([`integration-guides/cpi-integration`](/integration-guides/cpi-integration)), you can set each hop's `minimum_amount_out` to 0 and enforce a single end-to-end minimum in your proxy. Without CPI, each hop enforces its own minimum, which requires computing reasonable intermediate bounds — commonly `quote_i * (1 - slippage_bps/10000)` per hop. ## Sandwich attacks ### Mechanism A bot watches the transaction gossip stream. When it sees your swap: 1. Front-run: bot buys the same token *before* you, pushing the pool price up. 2. Victim tx: you swap at the worse price. 3. Back-run: bot sells into the elevated price, capturing the spread. The bot pays priority fees to both its transactions; the profit is the sandwich delta minus twice the priority fee. Profitable only on pools where your trade moves the price meaningfully. ### Mitigations **Tight slippage.** If your minimum-out is 0.5% below quote, a sandwich that moves the price more than 0.5% reverts you but the bot's pre-trade still executed at your old price. They lose money. Sandwich bots target wide slippage (≥1–2%); sub-0.3% slippage is largely immune. **Private-mempool submission (Jito).** Submit your transaction as part of a Jito bundle. Bundles don't appear on the public gossip stream; bots can't see the trade in-flight and front-run it. Trade-off: bundles require a validator-side tip, and not every leader is Jito-enabled (though most are). **Smaller trade sizes.** Split the trade across multiple transactions so no single tx moves price enough to be a profitable sandwich target. Increases total gas cost. **Time randomization.** Submit during lower-volume times if possible. Not available for interactive user swaps but viable for scheduled bot flow. Raydium's CLMM pools typically see less sandwich activity than CPMM because the single-tick liquidity structure means small trades don't move price at all (they stay within a tick). Deep CLMM pools are the best sandwich-resistance venue organically. ## Jito bundles Jito is a modified Solana validator client that accepts **bundles** — ordered groups of transactions landed atomically. Bots use Jito for MEV extraction; regular users use Jito for protection from the same bots. ### How bundles work * Connect to a Jito block engine endpoint (e.g. `https://mainnet.block-engine.jito.wtf`). * Submit a bundle of 1–5 transactions plus a tip to one of Jito's tip accounts. * If the current leader is running Jito, your bundle is considered. The auction winner for this slot (bundle with the highest tip-per-CU) lands; others drop. ### Sizing the tip Tip sizes follow the recent-bundle distribution. Jito publishes real-time percentiles: ```ts theme={null} const tipRes = await fetch("https://worker.jito.wtf/api/v1/bundles/tip_floor"); const tips = await tipRes.json(); // { ema_landed_tips_25th_percentile, 50th, 75th, 95th, 99th } // A user-facing swap on a normal day — 50th percentile is fine. const tipSol = tips.ema_landed_tips_50th_percentile_lamports / 1e9; // A time-sensitive bot trade during congestion — 75–95th percentile. ``` Typical ranges: 0.0001–0.001 SOL for non-urgent user swaps; 0.01–0.1 SOL during congestion for high-priority bots. ### Constructing a bundle ```ts theme={null} import { SearcherClient } from "jito-ts"; const client = new SearcherClient("https://mainnet.block-engine.jito.wtf"); const tipIx = SystemProgram.transfer({ fromPubkey: user.publicKey, toPubkey: JITO_TIP_ACCOUNTS[Math.floor(Math.random() * 8)], // 8 tip accts lamports: tipLamports, }); const tx1 = new VersionedTransaction(...); // the swap tx1.sign([user]); const bundleUuid = await client.sendBundle([tx1], tipLamports); // Optionally: await confirmation via client.getBundleStatuses([bundleUuid]) ``` Pitfalls: * **Tip must be in-bundle.** Include the `SystemProgram.transfer` to a Jito tip account as an instruction inside one of the bundle's transactions (typically the last one). A separate tip tx that's not part of the bundle is ignored. * **Leader isn't Jito-enabled.** \~75% of leaders run Jito; \~25% don't. Bundles sent when a non-Jito leader holds the slot are dropped. The client will retry automatically. * **Expiry.** Bundles use the same blockhash-expiry model as regular txs. Assemble and send quickly; \~60s window. ### Bundles vs priority fees Priority fees bribe the leader to include your tx sooner. Jito bundles additionally hide the tx from the public mempool. Use priority fees for urgency; use bundles for sandwich protection. Belt and braces: use both on high-value user swaps. See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) for sizing priority fees. ## MEV-share / revert-protected RPC Some RPC providers offer "MEV-share" or "revert-protected" endpoints that internally route your transaction through Jito bundles or equivalent private paths: * **Helius** — staked connections with bundle support. * **QuickNode** — "Revert Protect" endpoint; automatically forms bundles around submitted txs. * **Triton** — private-flow tier. Using one of these is the simplest path for projects that don't want to manage bundle logic themselves. Trade-off: opaque internals; you trust the provider's bundle construction. ## Congestion handling During high-volume windows (mainnet launches, major listings, sustained rally), leader packet queues fill up. Symptoms: * Txs sit unconfirmed for 60+ seconds, then expire with "blockhash not found". * Priority fees that worked yesterday are insufficient today. * Simulation succeeds but execution never lands. Strategies: 1. **Aggressive retry on expiry.** On `TransactionExpiredBlockheightExceeded`, re-build with a fresh blockhash and re-submit. Do *not* retry on revert — reverts are deterministic. 2. **Multi-RPC broadcast.** Submit the same tx to multiple RPCs in parallel; whichever reaches a leader first wins. 3. **Priority fee ramping.** Start with the 50th percentile; if first attempt expires, retry at 75th, then 95th. 4. **Jito bundles as fallback.** Jito leaders tend to be less congested because the block engine sorts bundles by tip-per-CU; high-tip bundles get precedence. 5. **Simulate less.** Under congestion, simulate once up front; don't re-simulate on retries since the pool state will have shifted anyway. Re-simulation during congestion often fails spuriously. ## Per-product MEV considerations **CPMM.** Highly sandwichable on low-TVL pools. The constant-product curve amplifies even small bot pre-trades. Recommend Jito bundles for any CPMM trade >0.5% of pool TVL. **CLMM.** Less sandwichable on deep pools because within-tick trades don't move price. But cross-tick trades absolutely do; sandwiches targeting tick crossings are a known pattern. Tight slippage (`<0.3%`) is the best defense. **AMM v4 + OpenBook.** OpenBook orderbook fills run through the same tx, so sandwich bots that don't know the orderbook state under-estimate price impact and often fail. Organic low-MEV venue for this reason. **LaunchLab.** During early-bonding-curve phase, front-running is rampant on hyped launches. Curves move fast and slippage is wide. Jito bundles are strongly recommended. After graduation, the resulting CPMM follows normal CPMM dynamics. **Farms.** Harvest and stake operations aren't swaps and aren't sandwichable. No special handling needed. ## Checklist For a production aggregator / wallet swap UI: * [ ] Slippage defaults to ≤0.5% on normal pairs; user can override. * [ ] Jito bundle submission enabled by default for swaps >\$1k USD value. * [ ] Priority fee sourced from a live estimate (not hard-coded). * [ ] Retry logic distinguishes revert (don't retry) from expiry (retry with new blockhash). * [ ] Multi-hop routes set per-hop minimums, not end-to-end. * [ ] Split routing active for trades >1% of any single pool's TVL. * [ ] Pool freshness: re-fetch state immediately before submission; re-quote if stale. * [ ] Sandwich-resistant on shallow pools: either Jito-only, or reject if slippage >1%. ## Pointers * [`integration-guides/aggregator`](/integration-guides/aggregator) — pool discovery, quoting, transaction assembly. * [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) — CU and priority fee sizing. * [`integration-guides/cpi-integration`](/integration-guides/cpi-integration) — single-slippage-gate multi-hop composition. * [`algorithms/slippage-and-price-impact`](/algorithms/slippage-and-price-impact) — formal definitions. Sources: * [Jito docs](https://docs.jito.wtf) * [Solana validator docs on priority fees](https://docs.solana.com/transaction_fees) * [jito-ts](https://github.com/jito-labs/jito-ts) — TypeScript bundle client. # Wallet integration Source: https://docs.raydium.io/integration-guides/wallet-integration Displaying Raydium positions, pool shares, farm stakes, and pending rewards in a wallet UI — detection patterns, simulation for transaction preview, and Token-2022 display specifics. Wallets integrating Raydium typically need to answer four questions per user: what pools does this user have LP in? what positions (CLMM NFTs) do they hold? what farms are they staked in? how much is it all worth? This page documents each. ## Detecting Raydium positions ### Classic LP tokens (CPMM, AMM v4) These look like any other SPL Token: the user's ATA holds a balance. A wallet shows this as just another token by default. To reveal it as a Raydium LP position: 1. Enumerate user's token accounts: `connection.getParsedTokenAccountsByOwner(user, { programId: TOKEN_PROGRAM_ID })`. 2. For each mint, check Raydium's mint list: `GET https://api-v3.raydium.io/pools/info/lps?lps=,...` (batch up to \~50 LP mints per call). 3. For mints that match, the API returns the pool reference. Use it to compute the position's token-denominated value: ``` token_a_owned = user_lp_balance * poolReserves.A / lpMint.supply token_b_owned = user_lp_balance * poolReserves.B / lpMint.supply usd_value = token_a_owned * priceA_usd + token_b_owned * priceB_usd ``` Show both the LP balance and the unwrapped amounts — users think in underlying tokens, not LP units. ### CLMM position NFTs CLMM positions are NFTs. Each position's `PersonalPositionState` PDA is derived from the NFT mint. To detect: 1. Enumerate user's NFTs. For legacy Metaplex NFTs: filter token accounts to those with supply 1 and decimals 0. 2. For each NFT mint, try to derive the PersonalPositionState PDA: ```ts theme={null} import { CLMM_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2"; const [positionPda] = PublicKey.findProgramAddressSync( [Buffer.from("position"), nftMint.toBuffer()], CLMM_PROGRAM_ID, ); const accountInfo = await connection.getAccountInfo(positionPda); if (!accountInfo || !accountInfo.owner.equals(CLMM_PROGRAM_ID)) return null; // It's a Raydium CLMM position — decode. ``` 3. Decode via `raydium.clmm.getPositionInfo({ positionPda })` to get: * `poolId` → fetch pool to resolve mints * `tickLower`, `tickUpper` → display range * `liquidity`, `tokensOwedA/B` → compute position value + pending fees * `rewardInfos` → pending per-stream rewards 4. For position-NFTs issued under Token-2022 (`OpenPositionWithToken22Nft`), the NFT mint's program is Token-2022 rather than SPL Token. Enumerate both when scanning. ### Farm stakes Farm v3 / v5 / v6 each have a per-user ledger PDA. Derivations: ```ts theme={null} // Try all three farm versions for each of the user's potential farm interactions. // Cheapest approach: ask the API first, which has indexed all user positions. const r = await fetch( `https://api-v3.raydium.io/positions/staking?wallet=${user.toBase58()}` ).then(r => r.json()); for (const s of r.data.stakings) { // s.farmId, s.stakedAmount, s.pendingRewards[], s.poolApr, ... } ``` For wallets that prefer fully on-chain detection: iterate possible `UserLedger` PDAs by hashing the user with a curated list of "likely" farm IDs. Enumerating all farm IDs exhaustively is impractical (thousands exist); use the API. ## Computing position value ### CPMM / AMM v4 LP ```ts theme={null} const poolInfo = await raydium..getPoolInfoFromRpc({ poolId }); const myShare = userLpBalance / poolInfo.lpMint.supply; const tokensA = BigInt(poolInfo.mintAmountA) * BigInt(userLpBalance) / BigInt(poolInfo.lpMint.supply); const tokensB = BigInt(poolInfo.mintAmountB) * BigInt(userLpBalance) / BigInt(poolInfo.lpMint.supply); ``` Then multiply each by the mint's USD price (from `raydium.token` or a price oracle). ### CLMM position ```ts theme={null} const position = await raydium.clmm.getPositionInfo({ positionPda }); const pool = await raydium.clmm.getPoolInfoFromRpc({ poolId: position.poolId }); const { amountA, amountB } = PoolUtils.getAmountsFromLiquidity({ sqrtPriceX64: pool.sqrtPriceX64, tickLower: position.tickLower, tickUpper: position.tickUpper, liquidity: position.liquidity, slippage: 0, // for display, no slippage }); // Pending fees — display separately as "uncollected" const fees = { A: position.tokenFeesOwedA, B: position.tokenFeesOwedB, }; // Pending rewards — a CLMM pool has 0–3 reward streams. const rewards = position.rewardInfos.map(r => ({ mint: r.rewardMint, rewardAmount: r.rewardAmountOwed, })); ``` Render as: * Liquidity value (current price) * Uncollected fees * Pending rewards per stream * Range: `[tickLower_price, tickUpper_price]` with a visual bar showing whether current price is in-range ### Farm stake ```ts theme={null} // The API response already includes pending rewards; use it directly. const apr = stakingFarm.apr; // %, annualized const staked = stakingFarm.stakedAmount; // smallest units of staking mint const rewards = stakingFarm.pendingRewards; // array of { mint, amount } ``` For on-chain computation, mirror the farm's accounting: ``` pending_reward_i = user.deposited * farm.reward_per_share_x64[i] / 2^64 - user.reward_debts[i] ``` Make sure to refresh `reward_per_share_x64` with the lazy-update formula before computing (elapsed time × emission rate ÷ total\_staked). ## Transaction simulation for preview Before a user signs, wallets usually preview the balance changes. Use `simulateTransaction`: ```ts theme={null} const sim = await connection.simulateTransaction(tx, { sigVerify: false, commitment: "confirmed", accounts: { encoding: "base64", addresses: [userTokenAtaA, userTokenAtaB, userLpAta].map(a => a.toBase58()), }, }); // Decode each returned account's balance and diff against pre-tx balances. for (const [i, acctData] of sim.value.accounts!.entries()) { const newBalance = decodeTokenAccount(acctData!.data[0]).amount; // compare to pre-tx balance, show Δ } ``` The `accounts` parameter asks the validator to return post-simulation account state for listed addresses. Much more accurate than trying to predict the balance change from the instruction shape alone. ### Simulation pitfalls * **CLMM swaps need valid tick arrays.** If the user's input size would cross into an uninitialized tick array, simulation reverts (same as execution). Surface this clearly in the UI. * **Priority fee.** Simulation runs without the compute-budget instructions applied. For a large transaction that would exceed the default 200k CU, simulation fails but actual execution with an explicit CU limit succeeds. Always set the CU limit on the simulated tx too. * **Fresh blockhash.** Simulation uses the current blockhash; if signing takes >60s the tx becomes invalid. Re-simulate if the user hesitates. ## Token-2022 display Tokens under the Token-2022 program should be labeled as such in the wallet's token list, since they have different risk surfaces: * **Transfer-fee mints**: display the current `transferFeeBasisPoints` as "Transfer fee: X%" next to the balance. Warn when receiving — users may not realize they will receive less than the sender sent. * **Transfer-hook mints**: surface the hook program ID. A malicious hook can block outbound transfers; users should verify the hook is the one they expect. * **Non-transferable mints**: display "Non-transferable" and disable swap/send. These are typically soulbound tokens or credentials. * **Interest-bearing mints**: the UI balance derived from `TokenAccount.amount` does **not** reflect accrued interest. Use `amountToUiAmount` from `@solana/spl-token` (which applies the scaling factor) for the displayed value. ## Farm APR display APR displayed to users should combine all live reward streams, converted to USD, and annualized: ```ts theme={null} let apr = 0; for (const r of farm.rewardInfos) { if (r.rewardState !== 1) continue; // skip not-running const annualRewardTokens = Number(r.emissionPerSecond) * 86400 * 365 / 1e; const annualRewardValue = annualRewardTokens * priceUsd(r.rewardMint); const tvlValue = Number(farm.totalStaked) * priceUsd(farm.stakingMint) / 1e; apr += annualRewardValue / tvlValue; } ``` Display as `APR: X.Y%`. If the staking mint is an LP token, also compute the underlying LP's base fee APR and label the sum as "Total APR" or "APR + fees". ## Pointers * [`products/clmm/ticks-and-positions`](/products/clmm/ticks-and-positions) — position-value derivation. * [`products/farm-staking/accounts`](/products/farm-staking/accounts) — farm state fields. * [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees) — display semantics for transfer-fee tokens. Sources: * Raydium SDK v2 — position/farm helpers. * User-position endpoints on `api-v3.raydium.io`. # LaunchLab code demos Source: https://docs.raydium.io/products/launchlab/code-demos End-to-end TypeScript: create a LaunchLab launch, buy and sell on the bonding curve, graduate into a CPMM pool, and collect creator fees. **Version banner.** * SDK: `@raydium-io/raydium-sdk-v2@0.2.42-alpha` * Cluster: Solana `mainnet-beta` * Program ID: see [`reference/program-addresses`](/reference/program-addresses) * Last verified: 2026-04 Pin the SDK version in your `package.json`. The bonding-curve interface has evolved between minor releases. ## Setup Demos here mirror files in [`raydium-sdk-V2-demo/src/launchpad`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/launchpad). Bootstrap follows the demo repo's [`config.ts.template`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template): ```ts theme={null} import { Connection, Keypair, clusterApiUrl, PublicKey } from "@solana/web3.js"; import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; import BN from "bn.js"; import fs from "node:fs"; const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta")); const owner = Keypair.fromSecretKey( new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))), ); const raydium = await Raydium.load({ owner, connection, cluster: "mainnet", disableFeatureCheck: true, blockhashCommitment: "finalized", }); export const txVersion = TxVersion.V0; ``` ## Create a launch Source: [`src/launchpad/createMint.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/createMint.ts) (and [`createBonkMintApi.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/createBonkMintApi.ts) for the API-driven Bonk variant) ```ts theme={null} import { NATIVE_MINT } from "@solana/spl-token"; const { execute, extInfo } = await raydium.launchpad.createLaunchpad({ programId: /* LaunchLab program ID from reference/program-addresses */, // Token metadata for the new base mint: name: "Example Token", symbol: "EXMPL", uri: "https://example.com/metadata.json", decimals: 6, // Curve params: curveType: 0, // 0 = quadratic supply: new BN(1_000_000_000).mul(new BN(10).pow(new BN(6))), // 1B base (6 dec) graduationFractionBps: 8000, // 80% → graduation initialK: new BN("40"), // curve shape parameter // Quote side: quoteMint: NATIVE_MINT, // WSOL openTime: new BN(Math.floor(Date.now() / 1000) + 60), // opens in 1min // Fee policy: fees: { buyNumerator: new BN(100), // 1.00% buyDenominator: new BN(10_000), sellNumerator: new BN(100), sellDenominator: new BN(10_000), lpShare: new BN(60), creatorShare: new BN(20), protocolShare: new BN(20), totalShare: new BN(100), }, postGraduationLpPolicy: "burn", // "burn" | "lock" | "toCreator" txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Launch:", extInfo.launchState.toBase58()); console.log("Base mint:", extInfo.baseMint.toBase58()); console.log("Create tx:", txId); ``` Notes: * `initialK` is the scale factor for the quadratic curve. Tune it to target a specific opening CPMM price at graduation. See [`products/launchlab/bonding-curve`](/products/launchlab/bonding-curve) for the derivation. * The SDK handles creating the base mint, the metadata PDA, and both vaults in a single transaction. It may exceed 1232 bytes if the metadata URI is long; in that case the SDK splits into two transactions. * After `Initialize`, the launch is not tradable until `openTime`. Set `openTime` a minute or two ahead to give front-runners less chance to grab the first buy. ## Fetch launch state ```ts theme={null} const launchId = new PublicKey(""); const launch = await raydium.launchpad.getLaunchById({ launchId }); console.log("Status:", ["Active","Graduated","Cancelled"][launch.status]); console.log("Base sold:", launch.baseSold.toString(), "/", launch.baseSupplyMax.toString()); console.log("Quote collected:", launch.quoteReserveReal.toString(), "target:", launch.quoteReserveTarget.toString()); if (launch.status === 1) { console.log("Post-graduation CPMM pool:", launch.cpmmPoolState.toBase58()); } ``` `getLaunchById` returns the decoded `LaunchState` plus the computed "progress toward graduation" fraction as a `Decimal`. ## Buy — exact quote in Source: [`src/launchpad/buy.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/buy.ts) ```ts theme={null} const quoteIn = new BN(1).mul(new BN(10).pow(new BN(9))); // 1 SOL const minimumBaseOut = new BN(0); // accept any; tighten for production // Preview the quote off-chain so your UI can show expected base_out: const preview = raydium.launchpad.computeBuyBase({ launchState: launch, quoteIn, }); console.log("Expected base_out:", preview.baseOut.toString(), "price impact:", preview.priceImpact.toString()); const { execute } = await raydium.launchpad.buyExactIn({ launchInfo: launch, quoteIn, minimumBaseOut, txVersion: TxVersion.V0, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Buy tx:", txId); ``` `computeBuyBase` mirrors the on-chain Newton solver (quadratic curve) or the closed-form CPMM-inverse (curve\_type 1). Use it to populate the "You receive" UI field. ## Buy — exact base out ```ts theme={null} const baseOut = new BN(1_000_000).mul(new BN(10).pow(new BN(6))); // 1M base const maximumQuoteIn = new BN(2).mul(new BN(10).pow(new BN(9))); // cap at 2 SOL const { execute } = await raydium.launchpad.buyExactOut({ launchInfo: launch, baseOut, maximumQuoteIn, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` Useful for "buy exactly X tokens" UIs. Rejects with `ExceededSlippage` if the curve has moved enough that the quote requirement now exceeds `maximumQuoteIn`. ## Sell Source: [`src/launchpad/sell.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/sell.ts) ```ts theme={null} const baseIn = new BN(500_000).mul(new BN(10).pow(new BN(6))); // 0.5M base const minimumQuoteOut = new BN(0); const { execute } = await raydium.launchpad.sellExactIn({ launchInfo: launch, baseIn, minimumQuoteOut, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` The curve's sell path is symmetric to the buy path: reducing `base_sold` by `baseIn` returns `quote_out` equal to the integrated area under the curve between `base_sold − baseIn` and `base_sold`, minus the sell fee. ## Auto-graduate on the threshold-crossing buy The SDK chains a `Graduate` instruction inside the `buy*` transaction when it detects the post-buy state will cross the threshold: ```ts theme={null} const { execute, willGraduate } = await raydium.launchpad.buyExactIn({ launchInfo: launch, quoteIn: new BN(100).mul(new BN(10).pow(new BN(9))), // large buy minimumBaseOut: new BN(0), txVersion: TxVersion.V0, autoGraduate: true, // default }); if (willGraduate) { console.log("This buy will trigger graduation."); } const { txId } = await execute({ sendAndConfirm: true }); ``` Because `Graduate` is permissionless, anyone (including an MEV bot) can race to land the first `Graduate` after the threshold is crossed — typically seconds later, not minutes. The first-lander just pays the rent for the CPMM pool accounts; they get no other benefit. ## Manual `Graduate` If `autoGraduate` was off or the threshold-crossing transaction failed, you can fire the graduation separately: ```ts theme={null} const { execute } = await raydium.launchpad.graduate({ launchInfo: launch, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` Reverts with `NotAtThreshold` if `quote_reserve_real < quote_reserve_target` at submission time. Retry-safe — a second `Graduate` attempt after success reverts with `NotActive`. ## Collect creator fees Source: [`src/launchpad/claimCreatorFee.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/claimCreatorFee.ts) (single mint) and [`collectAllCreatorFees.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/launchpad/collectAllCreatorFees.ts) (batched) ```ts theme={null} const { execute } = await raydium.launchpad.collectCreatorFees({ launchInfo: launch, txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` Transfers the accrued creator-fee-counter quote amount to the creator's ATA on the quote mint. Callable pre- or post-graduation; use it periodically rather than waiting for a huge balance to accumulate. ## Track a launch through its lifecycle Putting it together, a monitoring script might look like: ```ts theme={null} async function watch(launchId: PublicKey) { while (true) { const launch = await raydium.launchpad.getLaunchById({ launchId }); const progress = Number(launch.quoteReserveReal) / Number(launch.quoteReserveTarget); console.log( `status=${["Active","Graduated","Cancelled"][launch.status]}`, `progress=${(progress * 100).toFixed(2)}%`, `num_buys=${launch.stateData.numBuys}`, ); if (launch.status === 1) { console.log("Graduated to CPMM pool:", launch.cpmmPoolState.toBase58()); break; } await new Promise(r => setTimeout(r, 10_000)); } } ``` ## Rust CPI Calling LaunchLab from your own Anchor program is rare (most launch integrations are TS-side only). If you do, the program ships an Anchor crate `raydium_launchlab` with `cpi::accounts::Buy`, `cpi::accounts::Sell`, etc. — pattern mirrors the CPMM / CLMM CPI examples. See [`sdk-api/rust-cpi`](/sdk-api/rust-cpi) for a generalized template once this site is populated. ## Pitfalls * **Fee-split arithmetic off-by-one.** If `total_share` is not exactly `lp_share + creator_share + protocol_share`, `Initialize` reverts with `InvalidFeeShares`. Set `totalShare` equal to the sum. * **Using a non-allowed quote mint.** `launch_config.allowed_quote_mints` is a fixed list; passing any other mint reverts. Check with `raydium.launchpad.getConfig()` first. * **Metadata size.** Long `uri` strings push the Metaplex CPI over the budget. Keep `uri` under \~200 chars — most CDN-hosted JSON metadata fits easily. * **Graduation race.** Automated bots monitor `quote_reserve_real` and front-run `Graduate` within a slot or two of the threshold crossing. This is benign — it only costs them rent — but it means your UI should treat `status` transitions as fast events. ## Where to go next * [`products/launchlab/bonding-curve`](/products/launchlab/bonding-curve) — curve math. * [`products/cpmm/code-demos`](/products/cpmm/code-demos) — what to do with the resulting pool. * [`user-flows/launch-token-launchlab`](/user-flows/launch-token-launchlab) — the end-to-end launch-a-token guide, including off-chain steps. Sources: * [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2) * Raydium LaunchLab program source # LaunchLab creator fees Source: https://docs.raydium.io/products/launchlab/creator-fees Pre-migration fee accrual, the post-migration Fee Key NFT, Raydium's default 90/10 burn/creator split, 0.1% token-share referral, and how platforms configure custom splits. **Two separate mechanisms.** Creator fees have a **pre-migration** phase (fees accrue while the token is still on the bonding curve) and a **post-migration** phase (fees accrue after graduation to the CPMM/AMM v4 pool). They're funded differently and claimed differently. This page covers both. ## Pre-migration: direct fee accrual While a token is still on the bonding curve, every buy and every sell pays a creator-fee component that accumulates in a per-token vault. The **creator** (the wallet that launched the token) can claim from the vault at any time. Characteristics: * Denominated in the **quote token** (usually SOL). * Configured by the **platform** (platform config controls whether creator fees are on and at what bps). * Claimable anytime from the Portfolio page on the LaunchLab UI; programmatically via the LaunchLab `claim_creator_fees` instruction (exact name in the IDL). * Continues until graduation; at graduation, residual pre-migration fees are claimable but no new pre-migration fees will accrue. ## Post-migration: Fee Key NFT via Burn & Earn When a token's bonding curve reaches its target and liquidity migrates to a CPMM (or AMM v4) pool, LP tokens are minted and **split according to the platform's configuration**. The split always sums to 100% and always involves some combination of: * **Burned** — transferred into [Burn & Earn](/user-flows/burn-and-earn) for permanent locking. The Fee Key for this portion is held by a platform-controlled or burn-designated account. * **Creator** — a Fee Key NFT minted directly to the token creator's wallet. This Fee Key represents the right to collect LP trading fees from the creator's share of LP tokens. * **Platform** — optional; a share that flows to the platform treasury as another Fee Key NFT. ### Raydium's own LaunchLab defaults | Share | Default on raydium.io/launchlab | | ---------------------------------------- | --------------------------------------------------------- | | Burned (Burn & Earn, perpetually locked) | 90% | | Creator (Fee Key NFT) | 10% | | Platform | 0% (Raydium's platform does not take a platform LP share) | At these defaults, **only the creator claims trading fees from their share**; the other 90% is locked and its fees go to Burn & Earn (flowing back to the trust-signalling mechanic rather than any individual pocket). ### Other platforms Third-party platforms building on LaunchLab can configure their own split. A platform that wants to fund itself via post-migration LP fees can set (say) 70 / 10 / 20 (burn / creator / platform). See [`products/launchlab/platforms`](/products/launchlab/platforms) for the Platform PDA fields that drive this. Regardless of split, the **mechanism is the same**: each non-burned share is represented as a Fee Key NFT, which can be traded, transferred, or collected against. ### What happens if post-migration creator fees are disabled If a platform sets `creator_fee_enabled = false`, the graduated token does **not** go through Burn & Earn. Instead, it migrates to an **AMM v4** pool using the legacy LaunchLab migration path. In that mode, the creator does not receive a Fee Key NFT, and there is no program-level post-migration creator fee. Some creators in this mode use an off-chain CLI tool to claim optional post-migration fees configured via `creatorFeeOn` — see [`products/launchlab/instructions`](/products/launchlab/instructions) for the on-chain surface. ## The 0.1% token-share referral Separately from LP-fee sharing, LaunchLab creators (and any user, really) can share a **token page link** with their wallet as the referrer. When anyone trades that token (or other LaunchLab tokens in the same session) via the shared link: * **0.1% of every trade** is airdropped to the referrer's wallet. * Paid in SOL or the pool's quote token. * Paid automatically on every trade; no claim step. This is a distinct product from the 1% swap referral covered in [`user-flows/referrals-and-blinks`](/user-flows/referrals-and-blinks). The 1% referral applies to Raydium's spot swap UI; the 0.1% token share referral applies to LaunchLab token pages specifically. ## Claiming fees ### Creator vault (pre-migration) ```ts theme={null} import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; const raydium = await Raydium.load({ owner, connection }); const { execute } = await raydium.launchlab.claimCreatorFees({ mint, // launched token mint txVersion: TxVersion.V0, }); await execute({ sendAndConfirm: true }); ``` ### Fee Key NFT (post-migration) Holding the Fee Key in your wallet is enough — claim from the Portfolio page, or programmatically via the relevant pool program's fee-claim instruction. See [`products/cpmm/instructions`](/products/cpmm/instructions) or the AMM v4 equivalent for the on-chain surface. ## Integrator checklist * **Show both fee streams.** A creator-dashboard that only shows one of "pre-migration vault" / "post-migration Fee Key" leaves money on the table for users who don't know the other exists. * **Respect Fee Key ownership transfers.** If a creator sells or transfers their Fee Key NFT, the previous owner loses access to future fee claims. Your UI should read the current NFT holder each time, not cache the original creator address. * **Platform-config drift.** Raydium's defaults may change. For any platform other than Raydium's own, always read the Platform PDA live to see the actual burn / creator / platform split for that specific launch. * **Token-2022 collateral and fee tokens.** Creator fees can be denominated in Token-2022 tokens with transfer fees; factor the transfer-fee haircut into any displayed "claimable fee" number. ## Where to go next * [`products/launchlab/platforms`](/products/launchlab/platforms) — platform-level configuration that drives creator-fee behavior. * [`user-flows/burn-and-earn`](/user-flows/burn-and-earn) — the lock mechanism that backs post-migration LP sharing. * [`user-flows/launch-token-launchlab`](/user-flows/launch-token-launchlab) — the end-to-end creator flow. Sources: * LaunchLab program IDL and `claim_creator_fees` instruction. * Raydium LaunchLab UI defaults as observed on raydium.io/launchlab (2026-04). # LaunchLab instructions Source: https://docs.raydium.io/products/launchlab/instructions Initialize, Buy, Sell, Graduate, CollectFees, SetParams — argument shapes, account lists, and pre/postconditions for every LaunchLab instruction. LaunchLab exposes a tight instruction set: six user-facing calls plus a handful of admin primitives. The SDK wraps all of them; this page documents the raw surface for aggregators, monitoring tools, and programs that need CPI. ## Instruction inventory | Group | Instruction | Callable by | | ---------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Global config | `CreateConfig` / `UpdateConfig` | Admin | | Launch lifecycle | `Initialize` / `InitializeV2` | Anyone (creator) — SPL Token launches; V2 records `amm_creator_fee_on` for the eventual CPMM graduation | | Launch lifecycle | `InitializeWithToken2022` | Anyone (creator) — Token-2022 launch, optional `TransferFeeConfig` | | Trade | `BuyExactIn` / `BuyExactOut` | Anyone — exact-input / exact-output buy on the bonding curve | | Trade | `SellExactIn` / `SellExactOut` | Anyone — exact-input / exact-output sell on the bonding curve | | Graduation | `MigrateToAmm` | Migration wallet (set on `GlobalConfig`) — graduate to AMM v4. Used when `amm_creator_fee_on = BothToken` so the creator fee can be collected on either side. | | Graduation | `MigrateToCpswap` | Migration wallet — graduate to CPMM. Used when `amm_creator_fee_on = QuoteToken` and required for Token-2022 launches. Wraps `InitializeWithPermission` on CPMM. | | Fees | `CollectFee` | Admin — sweep protocol fees from a launch | | Fees | `CollectMigrateFee` | Admin — sweep accrued migration fees | | Fees | `ClaimCreatorFee` | Creator — claim accrued creator fees during the curve phase | | Vesting | `CreateVestingAccount` | Creator — allocate locked tokens to a beneficiary, unlocked after graduation | | Vesting | `CreatePlatformVestingAccount` | Platform admin — allocate locked tokens to platform-side beneficiaries | | Vesting | `ClaimVestedToken` | Beneficiary — claim unlocked tokens after the cliff | | Platform config | `CreatePlatformConfig` / `UpdatePlatformConfig` | Platform admin | | Platform config | `UpdatePlatformCurveParam` / `RemovePlatformCurveParam` | Platform admin — manage the per-platform list of permitted curve shapes | | Platform fees | `ClaimPlatformFee` / `ClaimPlatformFeeFromVault` | Platform admin | | Platform access | `CreatePlatformGlobalAccess` / `ClosePlatformGlobalAccess` | Admin — gate which platforms may use a given `GlobalConfig` | The "ExactIn/ExactOut" split mirrors CPMM's `SwapBaseInput` / `SwapBaseOutput` — on-chain they are separate instruction discriminators with slightly different rounding. **Graduation path selection.** `migrate_type` is recorded on `PoolState` at `Initialize{V2,WithToken2022}` time and determines which of the two graduation instructions can run. Token-2022 launches always migrate to CPMM. SPL Token launches migrate to either AMM v4 or CPMM depending on the `amm_creator_fee_on` setting: * `BothToken` → `MigrateToAmm` → AMM v4 pool (creator fee can be collected from either side; AMM v4 has no native creator-fee field, so creator fees are taken via the LP-lock NFT mechanism instead). * `QuoteToken` → `MigrateToCpswap` → CPMM pool with `creator_fee_on = OnlyQuoteToken` (creator continues to earn fees from the CPMM pool through the LaunchLab Fee Key NFT — see [`products/launchlab/creator-fees`](/products/launchlab/creator-fees)). **Note on the `AmmCreatorFeeOn` enum name.** The Rust source calls this enum `AmmCreatorFeeOn` with variants `QuoteToken` and `BothToken`. The name is misleading: in current operational practice the variant doesn't only control which side the creator fee is collected from on the post-graduation CPMM pool — it also picks the graduation **target program** (AMM v4 vs CPMM) and pairs with `migrate_type` on the launch's `PoolState`. Treat the field as "migration target + post-graduation creator-fee side" rolled into one. The on-chain enum name has not been refactored, but reasoning about it as `MigrationTarget` matches reality more closely. ## `Initialize` Create a new launch. **Arguments** ``` launch_params: { curve_type: u8, base_supply_max: u64, base_supply_graduation: u64, k: u128, // or initial_virtual_quote_reserve for curve_type=1 open_time: u64, quote_mint: Pubkey, base_token_metadata: { // inline name/symbol/uri; program CPIs to Metaplex name: String, symbol: String, uri: String, }, fees: { buy_numerator: u64, buy_denominator: u64, sell_numerator: u64, sell_denominator: u64, lp_share: u64, creator_share: u64, protocol_share: u64, total_share: u64, }, post_graduation_lp_policy: u8, // 0 = Burn, 1 = Lock, 2 = ToCreator } ``` **Accounts** (abridged) | # | Name | W | S | Notes | | -- | ------------------ | - | - | --------------------------------------------------------- | | 1 | `creator` | W | S | Pays rent + base mint creation. | | 2 | `launch_config` | | | Protocol config binding. | | 3 | `launch_state` | W | | New account. | | 4 | `launch_authority` | | | PDA. | | 5 | `base_mint` | W | S | Fresh Keypair (or PDA) — this instruction initializes it. | | 6 | `base_vault` | W | | ATA of `launch_authority` on `base_mint`. | | 7 | `quote_mint` | | | | | 8 | `quote_vault` | W | | ATA of `launch_authority` on `quote_mint`. | | 9 | `metadata` | W | | Metaplex metadata PDA. | | 10 | `metaplex_program` | | | | | 11 | `token_program` | | | SPL Token only. | | 12 | `system_program` | | | | | 13 | `rent` | | | | **Preconditions** * `quote_mint ∈ launch_config.allowed_quote_mints`. * `base_supply_graduation ≤ base_supply_max`. * Fee parameters pass `launch_config.max_*_fee_rate` checks. * `open_time ≥ now − slop` (SDK enforces `≥ now`; program tolerates slight backdating). * `curve_type` is recognized. **Postconditions** * `base_mint` has `supply = base_supply_max`, all in `base_vault`. * `base_mint.mint_authority = launch_authority`, `freeze_authority = None`. * `LaunchState` initialized with `status = Active`, `base_sold = 0`, `quote_reserve_real = 0`. * `quote_reserve_target` computed from curve params + `base_supply_graduation` + `buy_numerator` (approximately). **Common errors** — `InvalidQuoteMint`, `FeeRateTooHigh`, `InvalidCurveParams`, `MathOverflow`. ## `Buy` (canonical variant: `BuyExactIn`) User provides a fixed `quote_in`; the curve computes `base_out`. **Arguments** ``` quote_in: u64 minimum_base_out: u64 ``` **Accounts** | # | Name | W | S | | -- | -------------------------- | - | - | | 1 | `user` | W | S | | 2 | `launch_state` | W | | | 3 | `launch_authority` | | | | 4 | `base_vault` | W | | | 5 | `quote_vault` | W | | | 6 | `user_base_ata` | W | | | 7 | `user_quote_ata` | W | | | 8 | `base_mint` | | | | 9 | `quote_mint` | | | | 10 | `token_program` | | | | 11 | `associated_token_program` | | | | 12 | `system_program` | | | **Preconditions** * `launch_state.status == Active`. * `now ≥ open_time`. * `user_quote_ata.balance ≥ quote_in`. * `quote_in > 0`. **Effect** 1. Split `quote_in` into `quote_in_after_fee` and the fee parts. 2. Newton-solve the curve for `base_out` given the post-fee quote. 3. `require(base_out ≥ minimum_base_out)` else revert `ExceededSlippage`. 4. Move `quote_in` user → vault. Move `base_out` vault → user. 5. Update `base_sold += base_out`, `quote_reserve_real += quote_in_after_fee × (lp_share / total_share)`. 6. Update fee counters (`protocol_fees_quote`, `creator_fees_quote`). 7. `state_data.num_buys += 1`. 8. If `quote_reserve_real ≥ quote_reserve_target` after the update, the SDK typically chains a `Graduate` ix in the same transaction. The program does not auto-graduate inside `Buy` — a subsequent `Graduate` is required. ## `BuyExactOut` User specifies the exact `base_out`; program computes `quote_in`. **Arguments** ``` base_out: u64 maximum_quote_in: u64 ``` Same accounts as `BuyExactIn`. Uses the closed-form quadratic integral (or CPMM inverse, for curve\_type 1) rather than Newton iteration. ## `Sell` / `SellExactIn` / `SellExactOut` Mirror of `Buy`. User returns `base_in` to the curve and receives `quote_out`. The fee is deducted from `quote_out`, so the user receives less than the raw integrated proceeds. **Preconditions** — * `user_base_ata.balance ≥ base_in`. * Selling cannot push `base_sold` below 0 (redundant with the above given accounting is consistent). * Launch is `Active`. **Effect** — symmetrical to `Buy`. `base_sold` decreases, `quote_reserve_real` decreases. Fees still accrue. ## `MigrateToAmm` / `MigrateToCpswap` Graduate a launch into a tradeable AMM pool once the curve has hit `total_quote_fund_raising`. The two instructions correspond to the two graduation targets — AMM v4 and CPMM — and **only one of them is valid for any given launch**, determined by `pool_state.migrate_type` (set at `Initialize` time). **Who signs** * `MigrateToAmm` — the `migrate_to_amm_wallet` recorded on the binding `GlobalConfig`. * `MigrateToCpswap` — the `migrate_to_cpswap_wallet` recorded on the binding `GlobalConfig`. These wallets are typically held by the Raydium-operated graduation crank; in practice graduation lands seconds after the threshold is crossed, regardless of who triggered the final buy. **Arguments** `MigrateToAmm` takes three (mainly OpenBook market parameters that the program forwards to AMM v4): ``` base_lot_size: u64 quote_lot_size: u64 market_vault_signer_nonce: u8 ``` `MigrateToCpswap` takes none. **Effect (common to both)** 1. Verify `pool_state.status == Migrate` (i.e., `quote_reserve_target` has been reached). Otherwise revert with `PoolMigrated` (status was already `Migrated`) or `PoolFunding` (still in funding). 2. Verify `pool_state.migrate_type` matches the instruction (`0` for AMM, `1` for CPMM). Otherwise revert with `MigrateTypeNotMatch`. 3. Compute the post-graduation reserves: * `base_amount_out = base_vault.amount − vesting_schedule.total_locked_amount` * `quote_amount_out = quote_vault.amount − quote_protocol_fee − migrate_fee − platform_fee` 4. CPI into the target program (`AMM v4 Initialize2` or `CPMM InitializeWithPermission`) with those reserves to create the post-graduation pool. 5. Split the resulting LP per the binding `PlatformConfig.{platform_scale, creator_scale, burn_scale}` (CPMM only) — one piece minted to `platform_nft_wallet`, one to a creator NFT wrapped by the LP-Lock program, one burned via Burn & Earn. For AMM v4 graduation, the LP disposition is governed by AMM v4's own initialization parameters. 6. Revoke `base_mint.mint_authority` (set to `None`). 7. Flip `pool_state.status = Migrated`, set `vesting_schedule.start_time = block_time + cliff_period`. **Postconditions** — `BuyExactIn`, `BuyExactOut`, `SellExactIn`, `SellExactOut` will reject from this point on with `PoolMigrated`. The resulting AMM pool is canonical and trades like any other AMM v4 / CPMM pool. **Common errors** — `PoolFunding`, `PoolMigrated`, `MigrateTypeNotMatch`, `InvalidCpSwapConfig`, `MathOverflow`. ## `CollectFee` Admin sweep of the protocol's accrued trade fees on a single launch. **Arguments** — none. **Accounts** | # | Name | W | S | Notes | | - | ------------------------- | - | - | ----------------------------------------------------- | | 1 | `protocol_fee_owner` | | S | Must equal `global_config.protocol_fee_owner`. | | 2 | `authority` | | | PDA `[b"vault_auth_seed"]`; signs the vault transfer. | | 3 | `pool_state` | W | | Mutated to zero `quote_protocol_fee`. | | 4 | `global_config` | | | Source of truth for the signer. | | 5 | `quote_vault` | W | | Drained by `quote_protocol_fee`. | | 6 | `recipient_token_account` | W | | ATA of `protocol_fee_owner` on `quote_mint`. | | 7 | `quote_mint` | | | | | 8 | `token_program` | | | SPL Token (the quote mint is always SPL Token). | **Effect** — transfer `pool_state.quote_protocol_fee` from `quote_vault` to `recipient_token_account`, then zero the counter. Callable any time after the first buy. ## `CollectMigrateFee` Admin sweep of the migration fee accumulated at graduation. Same account shape as `CollectFee` with `migrate_fee_owner` as the signer (instead of `protocol_fee_owner`) and `pool_state.migrate_fee` as the drained counter. ## `ClaimCreatorFee` Per-creator sweep of accrued creator fees across **every launch the creator owns** that uses the same quote mint. Drains the per-creator fee vault, not the per-pool one. **Arguments** — none. **Accounts** | # | Name | W | S | Notes | | - | -------------------------- | - | - | ------------------------------------------------------------------- | | 1 | `creator` | W | S | The pool creator. | | 2 | `fee_vault_authority` | | | PDA `[b"creator_fee_vault_auth_seed"]`. | | 3 | `creator_fee_vault` | W | | PDA at seeds `[creator, quote_mint]`; the aggregated creator vault. | | 4 | `recipient_token_account` | W | | `init_if_needed`; ATA of `creator` on `quote_mint`. | | 5 | `quote_mint` | | | | | 6 | `token_program` | | | | | 7 | `system_program` | | | For ATA creation if needed. | | 8 | `associated_token_program` | | | | **Effect** — transfer the entire balance of `creator_fee_vault` to `recipient_token_account`. Reverts with a require-greater-than-zero check if the vault is empty. ## `ClaimPlatformFee` Per-platform sweep that drains a launch's quote vault directly. Use this when a platform wants to claim its slice for one specific launch without going through the aggregated platform vault. **Arguments** — none. **Accounts** | # | Name | W | S | Notes | | -- | -------------------------- | - | - | ------------------------------------------------- | | 1 | `platform_fee_wallet` | W | S | Must equal `platform_config.platform_fee_wallet`. | | 2 | `authority` | | | PDA `[b"vault_auth_seed"]`. | | 3 | `pool_state` | W | | Drained by `pool_state.platform_fee`. | | 4 | `platform_config` | | | Source of truth for the signer. | | 5 | `quote_vault` | W | | Drained. | | 6 | `recipient_token_account` | W | | `init_if_needed`; ATA of `platform_fee_wallet`. | | 7 | `quote_mint` | | | | | 8 | `token_program` | | | | | 9 | `system_program` | | | | | 10 | `associated_token_program` | | | | **Effect** — transfer `pool_state.platform_fee` from `quote_vault` to `recipient_token_account`, zero the counter. ## `ClaimPlatformFeeFromVault` Per-platform aggregated sweep. Drains the platform's per-quote-mint fee vault that accumulates fees from every launch routed through the platform. **Arguments** — none. **Accounts** | # | Name | W | S | Notes | | - | -------------------------- | - | - | ------------------------------------------------- | | 1 | `platform_fee_wallet` | W | S | Must equal `platform_config.platform_fee_wallet`. | | 2 | `fee_vault_authority` | | | PDA `[b"platform_fee_vault_auth_seed"]`. | | 3 | `platform_config` | | | | | 4 | `platform_fee_vault` | W | | PDA at seeds `[platform_config, quote_mint]`. | | 5 | `recipient_token_account` | W | | `init_if_needed`; ATA of `platform_fee_wallet`. | | 6 | `quote_mint` | | | | | 7 | `token_program` | | | | | 8 | `system_program` | | | | | 9 | `associated_token_program` | | | | **Effect** — transfer the full balance of `platform_fee_vault` to `recipient_token_account`. Reverts if the vault is empty. ## Vesting and platform-config instructions These are documented on dedicated pages because each has its own state model: * [`CreateVestingAccount`, `CreatePlatformVestingAccount`, `ClaimVestedToken`](/products/launchlab/vesting) * [`CreatePlatformConfig`, `UpdatePlatformConfig`, `UpdatePlatformCurveParam`, `RemovePlatformCurveParam`, `CreatePlatformGlobalAccess`, `ClosePlatformGlobalAccess`](/products/launchlab/platform-config) * [`CreateConfig`, `UpdateConfig`](/products/launchlab/global-config) ## State-change matrix | Instruction | `status` | `real_base` | `real_quote` | Fee counters | Post-state pool | | --------------------------------------- | ---------- | ----------- | ---------------- | ---------------------------------------------- | -------------------------------------- | | `Initialize{V2,WithToken2022}` | Funding | 0 | 0 | 0 | — | | `BuyExactIn(q_in)` | Funding | +∆ | +∆q\_after\_fee | `quote_protocol_fee += ∆`, `platform_fee += ∆` | — | | `SellExactIn(b_in)` | Funding | −∆ | −∆q\_before\_fee | (same) | — | | Threshold reached | → Migrate | — | — | — | — | | `MigrateToAmm` / `MigrateToCpswap` | → Migrated | (frozen) | (frozen) | `migrate_fee` set | created, LP split per `PlatformConfig` | | `CollectFee` / `CollectMigrateFee` | any | — | — | counter zeroed | — | | `ClaimCreatorFee` / `ClaimPlatformFee*` | any | — | — | drains vault | — | | `CreateVestingAccount` | Funding | — | — | — | bumps `allocated_share_amount` | | `ClaimVestedToken` | Migrated | — | — | — | drains `base_vault` | ## Where to go next * [`products/launchlab/code-demos`](/products/launchlab/code-demos) — TypeScript examples for each instruction. * [`products/launchlab/accounts`](/products/launchlab/accounts) — full state shape. * [`reference/error-codes`](/reference/error-codes) — LaunchLab error enum. Sources: * [Raydium SDK v2 `LaunchLab` module](https://github.com/raydium-io/raydium-sdk-V2) * Raydium LaunchLab program source # LaunchLab platforms Source: https://docs.raydium.io/products/launchlab/platforms How third parties register a Platform PDA on LaunchLab to offer their own branded token-launch experience, set platform fees, and configure post-migration parameters. A **Platform** in LaunchLab is a third-party launch environment built on top of Raydium's LaunchLab program. Platforms can brand themselves, charge their own fees, and configure the post-bonding-curve migration parameters for every token launched under them — without running a separate on-chain program. Raydium's own launch UI is just one platform among many. ## Why platforms exist Raydium wanted LaunchLab to be a **shared primitive**, not a single branded product. The Platform PDA system lets ecosystems, tooling providers, communities, and meme teams offer curated or branded launch experiences that all settle into the same underlying LaunchLab bonding-curve program and the same CPMM (or AMM v4) post-migration pool. Examples of platforms you might see in the wild: * **Raydium's own LaunchLab UI** at raydium.io/launchlab — the reference implementation. * **Partner launchpads** — third parties who've registered their own Platform PDA to brand the launch experience. * **Niche platforms** — e.g. a community-specific platform that only launches memecoins tied to a particular theme, with a custom fee share funding its treasury. All of these tokens still live on the same LaunchLab program, graduate to the same CPMM program, and can be routed by the same aggregators. ## The Platform PDA Each platform is represented by a Platform PDA stored in the LaunchLab program. Fields include: | Field | Description | | ---------------------------------- | --------------------------------------------------------------------------------- | | `platform_key` | Seed / identifier for the platform, used to derive the Platform PDA. | | `platform_fee_destination` | Account that receives the platform's cut of trading fees pre- and post-migration. | | `platform_fee_bps` | Platform fee share on pre-migration swaps (varies by platform). | | `creator_fee_enabled` | Whether this platform enables post-migration creator-fee sharing via Burn & Earn. | | `creator_fee_share_bps` | If enabled, share of LP fees that flow to the creator's Fee Key NFT. | | `cp_config_id` | If post-migration target is CPMM, which `AmmConfig` to use — chooses fee tier. | | `name`, `description`, `image_url` | Display metadata used by wallets and UIs that support platform branding. | | `tokens_launched` | Counter or on-chain index of tokens created under this platform. | Exact field names are in the LaunchLab IDL ([`sdk-api/anchor-idl`](/sdk-api/anchor-idl)). The table above is a descriptive overview. ## What a platform can control **Fee capture:** * **Platform fee share** on every pre-migration buy/sell. * **Post-migration share** — when the token graduates, the LP tokens are minted to a multi-party split defined by the platform: a portion to burn (Burn & Earn — a liquidity-locking pattern that lets the locked LP keep claiming its share of pool fees, see [`user-flows/burn-and-earn`](/user-flows/burn-and-earn)), a portion to creator (Fee Key NFT), and a portion to the platform itself. **Post-migration destination:** * **CPMM (default if creator fees enabled)** with a `AmmConfig` chosen via `cp_config_id`. Different `AmmConfig`s have different fee tiers; a platform can pick a higher-fee tier if its launches are high-volume speculative assets. * **AMM v4 (if creator fees disabled)** — for backward compatibility with the original LaunchLab flow. **User experience:** * **Branding metadata** used by the Raydium app and any wallet / explorer that surfaces LaunchLab tokens. * **Launch-page customization** — platforms that host their own frontend can filter tokens to only show their own platform's launches. ## What a platform cannot control * **Bonding-curve math.** The curve shape (logistic, exponential, etc.) is part of the LaunchLab program itself. Platforms choose from the supported curves; they cannot add arbitrary custom curves. * **Migration threshold logic.** The condition that triggers graduation to CPMM / AMM v4 (target market cap reached) is program-enforced. * **Core security / audit posture.** Every platform inherits LaunchLab's audit history; a malicious platform cannot bypass it by altering the program. ## Registering a platform The LaunchLab program exposes a `create_platform` instruction (exact name from the IDL). Callers pass: * The seed that will derive the Platform PDA. * The fee destination account. * Desired fee bps (subject to program-level caps). * Post-migration config (CPMM `AmmConfig` ID if applicable). * Branding metadata. Rent for the Platform PDA is paid by the creator. Updates to branding and some fee parameters are permitted post-creation by the platform's authority; structural parameters (migration destination program) typically are not. See [`products/launchlab/instructions`](/products/launchlab/instructions) for the exact instruction signature in the current LaunchLab IDL. ## Economic interaction A single swap on a LaunchLab bonding curve distributes value roughly like this (exact ratios set by the platform): ``` Buyer pays: 100 SOL Protocol fee (LaunchLab): – small % Platform fee: – platform_fee_bps% Creator fee (pre-migration): – creator_fee_bps% (if enabled) Remainder (to curve): ~ rest ``` Post-migration, LP tokens are split per the platform config: ``` LP tokens minted at graduation: 100% → Burn & Earn (burned): X% (default on Raydium: 90) → Creator Fee Key NFT: Y% (default on Raydium: 10) → Platform: Z% (platform-configured; may be 0) ``` The **Burn & Earn share** converts to shared fee income distributed to the Fee Key holder — which, under Raydium's own defaults, is the burn "vault" representing broad-market fee backing for token trust. ## Integrator notes * **Listing tokens by platform.** Use the API pool-info / mint endpoints with a `platform` filter (when supported) to list only tokens launched under a specific Platform PDA. * **Branding display.** If you build a wallet or explorer that surfaces LaunchLab tokens, reading the platform's name / image URL from the Platform PDA is far better UX than showing "LaunchLab" for every token. * **Fee math.** Every bonding-curve quote must account for the platform fee in addition to the protocol and creator fees. The SDK's quote helpers do this automatically when the platform PDA is supplied. ## Where to go next * [`products/launchlab/overview`](/products/launchlab/overview) — LaunchLab program model. * [`products/launchlab/creator-fees`](/products/launchlab/creator-fees) — how creator and platform fees interact post-migration. * [`user-flows/burn-and-earn`](/user-flows/burn-and-earn) — the lock mechanism that post-migration LP uses. Sources: * LaunchLab program IDL bundled in the Raydium SDK v2: [`raydium-io/raydium-sdk-V2`](https://github.com/raydium-io/raydium-sdk-V2) under `src/raydium/launchpad/`. * `api-v3.raydium.io/launchlab/platforms` endpoints (when available). # Tips & Gotchas Source: https://docs.raydium.io/products/launchlab/tips-and-gotchas/index Operational notes for LaunchLab platforms: fee lifecycle, LP distribution, CPMM fee configs, creator fees, and fee redistribution. Use these pages when you are configuring a LaunchLab platform or debugging post-migration economics. They focus on settings that materially affect token creators, platform operators, and Fee Key NFT holders. These pages describe protocol and SDK mechanics only. They are not financial, legal, tax, or investment advice. Review platform settings before signing because fee routing, LP distribution, and creator-fee custody can materially affect user expectations. End-to-end fee lifecycle from bonding curve trading through CPMM migration. How `migrateCpLockNftScale` splits migrated LP tokens across platform, creator, and burn shares. How CPMM `AmmConfig` accounts define trade, protocol, fund, creator, and creation fees. How platforms can redirect CPMM creator fees to a platform wallet or PDA. ## Related pages * [Platform config](/products/launchlab/platform-config) — field-level platform configuration reference. * [Creator fees](/products/launchlab/creator-fees) — technical creator-fee reference. * [Burn & Earn](/user-flows/burn-and-earn) — user-facing permanent liquidity lock explanation. * [How creator fees work](/user-flows/how-creator-fees-work) — creator-facing explanation of fee rights and Fee Key NFTs. # LaunchLab & CPMM fee reference Source: https://docs.raydium.io/products/launchlab/tips-and-gotchas/launchlab-cpmm-fee-reference End-to-end fee flow for LaunchLab tokens from bonding curve trading through CPMM migration. LaunchLab tokens have two trading phases, and each phase has its own fee model. Do not collapse these into one "creator fee" in UI copy or accounting. ## Fee lifecycle ### Phase 1: Bonding curve Every bonding-curve trade can include four additive fees: | Fee | Set by | Recipient | | ------------ | --------------------------------------- | --------------------- | | Protocol fee | LaunchLab global config, `tradeFeeRate` | Raydium | | Platform fee | Platform config, `feeRate` | Platform claim wallet | | Creator fee | Platform config, `creatorFeeRate` | Token creator vault | | Referral fee | Per-transaction, `shareFeeRate` | Referrer wallet | Fees that accrue to platform and creator vaults must be claimed. Referral fees are transferred directly during the trade. ## Migration When the bonding curve reaches the graduation target: 1. Liquidity migrates to the configured Raydium pool. 2. For CPMM migration, LP tokens are minted. 3. `migrateCpLockNftScale` controls whether LP tokens are burned or locked through Burn & Earn for the platform and creator. 4. `cpConfigId` determines which CPMM `AmmConfig` the migrated pool uses. ## Phase 2: CPMM pool After migration, CPMM swaps can include: | Fee | How it works | | ---------------- | --------------------------------------------------------------------------- | | Trade fee | Deducted from swap input and split among LPs, protocol, and treasury. | | CPMM creator fee | Additional fee deducted from swap input and attributed to the pool creator. | Fee Key NFT holders earn from the LP-share side of the trade fee. The CPMM creator fee is separate and is claimed through creator-fee collection. ## Creator revenue streams A creator can have up to three separate revenue streams: | Source | Mechanism | Typical claim path | | ------------------------- | ------------------------------------- | ------------------------ | | Bonding-curve creator fee | Pre-migration vault | `claimCreatorFee()` | | CPMM creator fee | `creator_fee_rate` on the CPMM config | `collectCreatorFee()` | | LP fee share | Fee Key NFT from locked LP tokens | Burn & Earn harvest flow | These do not replace each other. If all are enabled, they must be tracked and claimed separately. ## Common gotchas * CPMM creator fees are not carved out of the trade fee. They are additional. * Fee Key NFT rights are LP-fee rights, not CPMM creator-fee rights. * Standard CPMM pools created outside the LaunchLab migration path do not automatically have CPMM creator fees. * Burned LP tokens leave liquidity in the pool permanently, but no wallet can claim the fees represented by the burned share. ## Pointers * [LP fee distribution](/products/launchlab/tips-and-gotchas/lp-fee-distribution) * [How creator fees work](/user-flows/how-creator-fees-work) # LP fee distribution Source: https://docs.raydium.io/products/launchlab/tips-and-gotchas/lp-fee-distribution How LaunchLab platform settings split migrated CPMM LP tokens between platform Fee Keys, creator Fee Keys, and burned liquidity. When a LaunchLab token migrates to CPMM, the migrated pool mints LP tokens. Platform config controls what happens to those LP tokens. ## The LP split `migrateCpLockNftScale` has three fields. The values must sum to `1_000_000`, which represents 100%. | Field | Meaning | | --------------- | ------------------------------------------------------------------------------------- | | `platformScale` | LP tokens are locked through Burn & Earn. The platform receives the Fee Key NFT. | | `creatorScale` | LP tokens are locked through Burn & Earn. The token creator receives the Fee Key NFT. | | `burnScale` | LP tokens are burned permanently. No Fee Key NFT is created for this share. | This is a platform-level setting. Tokens launched through the same platform inherit the same LP distribution until the platform updates the setting. ## Examples ```ts theme={null} // 40% platform, 50% creator, 10% burned. migrateCpLockNftScale: { platformScale: new BN(400_000), creatorScale: new BN(500_000), burnScale: new BN(100_000), } // 100% creator Fee Key. migrateCpLockNftScale: { platformScale: new BN(0), creatorScale: new BN(1_000_000), burnScale: new BN(0), } // 100% burned. migrateCpLockNftScale: { platformScale: new BN(0), creatorScale: new BN(0), burnScale: new BN(1_000_000), } ``` ## Relationship to CPMM fees `migrateCpLockNftScale` controls **who can claim LP fee rights**. It does not set the swap fee rate. The migrated pool's swap fee behavior comes from the CPMM `AmmConfig` selected by `cpConfigId`. For example, if a migrated CPMM pool uses a 0.25% trade-fee config and `creatorScale` is 500,000, the creator's Fee Key represents 50% of the locked LP fee rights. If `platformScale` is 400,000, the platform's Fee Key represents 40%. ## Updating the split Platform config updates are rate-limited by epoch. Treat LP distribution as an important platform policy, not a casual UI setting. Before updating: * Confirm the three scale values sum to `1_000_000`. * Confirm the platform understands who will receive Fee Key NFTs after future migrations. * Document the change for creators before new launches use it. ## Pointers * [Platform config](/products/launchlab/platform-config) * [Burn & Earn](/user-flows/burn-and-earn) * [LaunchLab & CPMM fee reference](/products/launchlab/tips-and-gotchas/launchlab-cpmm-fee-reference) * [LaunchLab platforms](/user-flows/launchlab-platforms) # Redistribute creator fees Source: https://docs.raydium.io/products/launchlab/tips-and-gotchas/redistribute-creator-fees How LaunchLab platforms can redirect CPMM creator fees to a platform wallet or PDA and redistribute them off-chain or through custom logic. By default, CPMM creator fees from a LaunchLab-migrated pool are attributed to the original token creator. Platforms can override this for future migrations by setting `platformCpCreator`. ## Why redirect creator fees Platforms use creator-fee redirection when they need: * Custom distribution logic between creators, communities, and platform revenue. * A PDA-controlled fee collector for on-chain redistribution. * Unified accounting across all migrated pools under one platform. Redirecting creator fees changes who can collect them. Token creators lose direct collection access for pools that migrate after the setting is active unless the platform redistributes fees to them. ## How it works 1. The platform admin calls `updatePlatformConfig`. 2. `updateInfo.type` is set to `updatePlatformCpCreator`. 3. The value is a wallet or PDA that will become the CPMM pool creator for future migrated pools. 4. Future LaunchLab migrations under that platform assign CPMM creator fees to the configured address. 5. The platform collects creator fees and redistributes them according to its own rules. ```ts theme={null} await raydium.launchpad.updatePlatformConfig({ programId, platformAdmin: owner, updateInfo: { type: "updatePlatformCpCreator", value: new PublicKey(""), }, txVersion: TxVersion.V0, }); ``` ## Gotchas * **All-or-nothing per platform.** The setting applies to every future migration under that platform. * **Forward-looking only.** Already-migrated pools keep their existing pool creator. * **Epoch-limited updates.** Platform config updates are rate-limited by Solana epoch. * **Creator fees must exist.** If the selected CPMM `AmmConfig` has `creator_fee_rate = 0`, there are no CPMM creator fees to collect. * **WSOL handling matters.** If collected fees are WSOL and you need native SOL, close the WSOL token account after collection. ## Redistribution checklist If you redistribute to original creators: 1. Resolve each original creator from the LaunchLab pool account tied to the CPMM pool mints. 2. Collect pending creator fees for the platform wallet or PDA. 3. Calculate each creator's agreed share. 4. Transfer SPL tokens or SOL to creators, creating ATAs as needed. 5. Publish enough accounting detail for creators to verify distributions. ## Pointers * [LaunchLab platforms](/user-flows/launchlab-platforms) * [Platform config](/products/launchlab/platform-config) * [Creator fees](/products/launchlab/creator-fees) # LaunchLab vesting Source: https://docs.raydium.io/products/launchlab/vesting Locked tokens, cliff and linear-unlock schedule, the VestingRecord per-beneficiary account, and the create / claim instructions creators and platforms use to distribute reserved supply after graduation. Vesting is **optional** on a LaunchLab launch. Set `vesting_param.total_locked_amount = 0` at `Initialize` and the section below does not apply. Once enabled, the schedule is fixed for the launch's lifetime; the cliff and unlock periods cannot be changed retroactively. ## Why vesting The bonding curve sells `base_supply_graduation` tokens during fundraising and seeds the post-graduation pool with the remainder. Vesting carves an additional slice out of the supply, locks it for a configurable cliff, then releases it linearly to one or more beneficiaries — typically the creator's team, advisors, or platform partners. Practical use cases: * **Team allocation.** A creator reserves, say, 5% of the supply for the founding team, locked for 6 months and unlocking linearly over the following 12 months. * **Platform allocation.** A launch platform receives a slice of every token it lists, on the same schedule, via `CreatePlatformVestingAccount`. * **Advisor / contributor grants.** Multiple beneficiaries with their own `VestingRecord` accounts, each tracking their own claimed amount independently. Locked tokens never enter the curve and are not part of the graduation LP. They sit dormant in the pool's `base_vault` until each beneficiary calls `ClaimVestedToken`. ## Schedule shape Vesting for a launch is described by three numbers, recorded once at `Initialize` time: | Field | Type | Meaning | | --------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `total_locked_amount` | `u64` | Sum of all base tokens locked across all beneficiaries (creator + platform). Must satisfy `total_locked_amount <= supply * max_lock_rate / 1_000_000` from the binding `GlobalConfig`. | | `cliff_period` | `u64` (seconds) | Wait time after fundraising ends before any tokens unlock. | | `unlock_period` | `u64` (seconds) | Duration of the linear unlock window after the cliff. `0` means everything unlocks instantly at the end of the cliff. | These three values live on `PoolState.vesting_schedule` (a `VestingSchedule` struct) plus the on-chain `start_time`, which the program records as `block_time + cliff_period` at the moment fundraising successfully ends (when graduation conditions are first met). ```rust theme={null} // states/pool.rs pub struct VestingSchedule { pub total_locked_amount: u64, pub cliff_period: u64, pub unlock_period: u64, pub start_time: u64, // set by the program at fundraising end pub allocated_share_amount: u64, // running sum of allocations to vesting records } ``` `allocated_share_amount` is the total amount already assigned to `VestingRecord` accounts via `CreateVestingAccount` / `CreatePlatformVestingAccount`. It must never exceed `total_locked_amount`. If a creator over-allocates, the next `CreateVestingAccount` call reverts with `InvalidTotalLockedAmount`. ## Linear unlock formula After fundraising ends, the program computes the cumulative unlocked amount for each `VestingRecord` as: ``` elapsed = min(now, start_time + unlock_period) − start_time unlocked_amount = token_share_amount × elapsed / unlock_period ``` If `unlock_period == 0`, the entire `token_share_amount` becomes claimable in one step at `start_time`. Otherwise the curve is a straight line from 0 at `start_time` to `token_share_amount` at `start_time + unlock_period`, capped at `token_share_amount` thereafter. The amount transferred on each `ClaimVestedToken` call is the delta between the freshly recomputed cumulative unlocked amount and the running `claimed_amount` field on the record. ``` delta_amount = unlocked_amount − vesting_record.claimed_amount vesting_record.claimed_amount = unlocked_amount ``` A claim before `start_time` reverts with `VestingNotStarted`. A claim after `start_time + unlock_period` settles the full remainder. ## Account layouts ### `VestingSchedule` Lives inline on `PoolState`. See [`accounts`](/products/launchlab/accounts). ### `VestingRecord` Per-beneficiary record. PDA derived as: ``` seeds = [ b"pool_vesting", pool_state.key(), beneficiary.key(), ] program = LaunchLab program ``` ```rust theme={null} // states/vesting.rs #[account] pub struct VestingRecord { pub epoch: u64, // recent_epoch tracker pub pool: Pubkey, // back-pointer to PoolState pub beneficiary: Pubkey, // who can call ClaimVestedToken pub claimed_amount: u64, // cumulative claimed pub token_share_amount: u64, // total allocated to this beneficiary pub padding: [u64; 8], } ``` A beneficiary can only have **one** `VestingRecord` per launch. Allocating again to the same beneficiary on the same launch reverts because the PDA already exists. ## Instructions ### `CreateVestingAccount` Creator-only. Allocates a slice of the pool's `total_locked_amount` to a new beneficiary by initializing a fresh `VestingRecord` PDA. **Arguments** ``` share_amount: u64 // tokens to assign to this beneficiary ``` **Accounts** | # | Name | W | S | Notes | | - | ---------------- | - | - | ---------------------------------------------------------------------------------------- | | 1 | `creator` | W | S | Must equal `pool_state.creator`; pays rent for the new account. | | 2 | `beneficiary` | W | | Receives the unlocked tokens later. The pubkey is locked in here — it cannot be changed. | | 3 | `pool_state` | W | | Mutated to bump `vesting_schedule.allocated_share_amount`. | | 4 | `vesting_record` | W | | `init`; PDA `[b"pool_vesting", pool_state, beneficiary]`. | | 5 | `system_program` | | | Required for account creation. | **Preconditions** * `share_amount > 0`. * `pool_state.vesting_schedule.allocated_share_amount + share_amount <= total_locked_amount`. * The `beneficiary` pubkey has no existing `VestingRecord` for this pool. **Postconditions** * `vesting_record` initialized with `token_share_amount = share_amount`, `claimed_amount = 0`. * `pool_state.vesting_schedule.allocated_share_amount += share_amount`. **Common errors** — `InvalidTotalLockedAmount`, `InvalidInput`. ### `CreatePlatformVestingAccount` Platform-admin variant of `CreateVestingAccount`. The platform's vesting wallet (stored on `PlatformConfig.platform_vesting_wallet`) is the beneficiary, and the share is bounded by `PlatformConfig.platform_vesting_scale`. The signer must equal `platform_config.platform_vesting_wallet`. Other accounts mirror `CreateVestingAccount`. Use this when a platform contracts to receive a fixed vesting share on every launch it lists. ### `ClaimVestedToken` Beneficiary-only. Transfers any newly-unlocked tokens from the pool's `base_vault` to the beneficiary's ATA. **Arguments** None (the program computes the claim amount from the schedule). **Accounts** | # | Name | W | S | Notes | | -- | -------------------------- | - | - | ------------------------------------------------------ | | 1 | `beneficiary` | W | S | Must equal `vesting_record.beneficiary`. | | 2 | `authority` | | | PDA `[b"vault_auth_seed"]`; signs the vault transfer. | | 3 | `pool_state` | W | | Mutated only if the schedule needs to be re-validated. | | 4 | `vesting_record` | W | | `claimed_amount` is updated. | | 5 | `base_vault` | W | | Pool's base-token vault; debited. | | 6 | `beneficiary_ata` | W | | Receives the unlocked tokens; `init_if_needed`. | | 7 | `base_mint` | | | Pool's base mint. | | 8 | `token_program` | | | SPL Token or Token-2022 program. | | 9 | `associated_token_program` | | | For ATA creation if needed. | | 10 | `system_program` | | | Required for account creation. | **Preconditions** * `block_time >= pool_state.vesting_schedule.start_time` (otherwise `VestingNotStarted`). * `pool_state.status == PoolStatus::Migrated` — graduation must have already happened. Calling before graduation reverts. * The unlocked-amount delta is greater than zero. A no-op call (computed delta is 0) reverts. **Postconditions** * `vesting_record.claimed_amount` advances to the new cumulative unlocked amount. * `delta_amount` of base token is transferred to `beneficiary_ata`. **Common errors** — `VestingNotStarted`, `NoAssetsToCollect`, `MathOverflow`. ## Worked example A launch sets: * `supply = 1_000_000_000` * `total_locked_amount = 100_000_000` (10% of supply) * `cliff_period = 180 * 86400` (180 days) * `unlock_period = 365 * 86400` (1 year linear after the cliff) The creator allocates two `VestingRecord` accounts immediately after `Initialize`: * Beneficiary A (team): `share_amount = 70_000_000` * Beneficiary B (advisor): `share_amount = 30_000_000` `allocated_share_amount = 100_000_000`, equal to `total_locked_amount` — no further allocations possible. Fundraising completes on `2027-01-01T00:00Z`. The program sets `start_time = 2027-01-01 + 180 days = 2027-06-30`. On `2027-09-30` (90 days after `start_time`), Beneficiary A calls `ClaimVestedToken`: ``` elapsed = min(now, start_time + 365·86400) − start_time = 90 · 86400 unlocked_amount = 70_000_000 × (90 / 365) ≈ 17_260_274 delta_amount = 17_260_274 − 0 = 17_260_274 ``` A's wallet receives 17.26M base tokens. `vesting_record.claimed_amount` advances to 17\_260\_274. Six months later (`2028-03-31`, 270 days after `start_time`), A claims again: ``` unlocked_amount = 70_000_000 × (270 / 365) ≈ 51_780_822 delta_amount = 51_780_822 − 17_260_274 = 34_520_548 ``` A receives another 34.52M tokens. After `2028-06-30` (the end of `unlock_period`), the next claim transfers the remaining \~18.22M and leaves `claimed_amount == token_share_amount`. ## Edge cases * **Beneficiary loses their key.** The pubkey on `VestingRecord.beneficiary` is the only signer that can call `ClaimVestedToken`. There is no recovery path. Set the beneficiary to a multisig if recovery matters. * **Token-2022 transfer fees.** If the base mint is a Token-2022 mint with a transfer-fee extension, the beneficiary receives `delta_amount − transfer_fee`, not the full delta. The pool's vault still records the gross amount as transferred — the difference accrues to the mint's withheld-fee account. * **Pool not graduated.** Calling `ClaimVestedToken` before graduation reverts. The vesting clock starts only when fundraising actually completes; an aborted launch (which never sets `start_time`) leaves locked tokens unreachable in the vault. * **Over-allocation attempts.** The program enforces `allocated_share_amount <= total_locked_amount` on every `CreateVestingAccount`. The remainder (if any) of `total_locked_amount` left unallocated is **lost** — those tokens stay in the vault forever once the launch graduates. Allocate the full amount unless that's the intent. ## Pointers * [`products/launchlab/accounts`](/products/launchlab/accounts) — full `PoolState` layout including `VestingSchedule`. * [`products/launchlab/instructions`](/products/launchlab/instructions) — graduation lifecycle. * [`products/launchlab/platform-config`](/products/launchlab/platform-config) — `platform_vesting_scale` semantics for platform allocations. * [`products/launchlab/global-config`](/products/launchlab/global-config) — `max_lock_rate` ceiling that bounds `total_locked_amount`. Sources: * `raydium-launch/programs/launchpad/src/states/vesting.rs` — `VestingRecord`. * `raydium-launch/programs/launchpad/src/states/pool.rs` — `VestingSchedule`, `VestingParams`, `is_vesting_started`, `vesting_end_time`. * `raydium-launch/programs/launchpad/src/instructions/create_vesting_account.rs`. * `raydium-launch/programs/launchpad/src/instructions/claim_vested_token.rs`. # Deploy a CLMM pool Source: https://docs.raydium.io/quick-start/deploy-clmm-pool Paste-and-go Node script that creates a new CLMM pool, then opens an initial concentrated position. Mirrors the official raydium-sdk-V2-demo flow. **What this does.** Creates a new CLMM pool at the fee tier of your choice, then opens an initial concentrated position. Two transactions, one script. Code is lifted from the official demos in [`raydium-sdk-V2-demo/src/clmm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/clmm) and adapted to a single Node-runnable file. ## Setup Make sure you've read the [Quick start prerequisites](/quick-start) and have `RPC_URL`, `KEYPAIR`, and the deps installed. CLMM pool creation has a one-time fee plus per-tick-array rent for the initial position. You'll also need both seed mints in your wallet — opening a position when the price sits inside the chosen range requires liquidity on both sides. ## Step 1 — `config.ts` Save as `config.ts`. This is the same shape as the demo repo's [`src/config.ts.template`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template) — `disableFeatureCheck` is forced to `true` (recommended for any non-trivial integration so the SDK does not block on its startup feature-detect call): ```ts theme={null} // config.ts import { Raydium, TxVersion, parseTokenAccountResp } from "@raydium-io/raydium-sdk-v2"; import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js"; import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"; import bs58 from "bs58"; import fs from "node:fs"; export const owner: Keypair = Keypair.fromSecretKey( // accept either a JSON-array keypair file (same shape Solana CLI writes) or a bs58 secret in env process.env.KEYPAIR_BS58 ? bs58.decode(process.env.KEYPAIR_BS58) : new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))), ); export const connection = new Connection( process.env.RPC_URL ?? clusterApiUrl("mainnet-beta"), "confirmed", ); export const txVersion = TxVersion.V0; const cluster = "mainnet" as "mainnet" | "devnet"; let raydium: Raydium | undefined; export const initSdk = async (params?: { loadToken?: boolean }) => { if (raydium) return raydium; raydium = await Raydium.load({ owner, connection, cluster, disableFeatureCheck: true, disableLoadToken: !params?.loadToken, blockhashCommitment: "finalized", }); return raydium; }; export const fetchTokenAccountData = async () => { const solAccountResp = await connection.getAccountInfo(owner.publicKey); const tokenAccountResp = await connection.getTokenAccountsByOwner(owner.publicKey, { programId: TOKEN_PROGRAM_ID, }); const token2022Req = await connection.getTokenAccountsByOwner(owner.publicKey, { programId: TOKEN_2022_PROGRAM_ID, }); return parseTokenAccountResp({ owner: owner.publicKey, solAccountResp, tokenAccountResp: { context: tokenAccountResp.context, value: [...tokenAccountResp.value, ...token2022Req.value], }, }); }; ``` ## Step 2 — `createPool.ts` Save alongside `config.ts`. Source: [`src/clmm/createPool.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPool.ts). ```ts theme={null} // createPool.ts import { CLMM_PROGRAM_ID, DEVNET_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2"; import { PublicKey } from "@solana/web3.js"; import Decimal from "decimal.js"; import { initSdk, txVersion } from "./config"; export const createPool = async () => { const raydium = await initSdk({ loadToken: true }); // RAY const mint1 = await raydium.token.getTokenInfo("4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R"); // USDT const mint2 = await raydium.token.getTokenInfo("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"); // Fee tiers come from the live API. On devnet the published `id` field is wrong; // re-derive the PDA before passing it to the SDK. const clmmConfigs = await raydium.api.getClmmConfigs(); const { execute } = await raydium.clmm.createPool({ programId: CLMM_PROGRAM_ID, // programId: DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID, mint1, mint2, ammConfig: { ...clmmConfigs[0], id: new PublicKey(clmmConfigs[0].id), fundOwner: "", description: "", }, initialPrice: new Decimal(1), txVersion, // optional: set up priority fee here // computeBudgetConfig: { units: 600000, microLamports: 46591500 }, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("clmm pool created:", { txId: `https://explorer.solana.com/tx/${txId}` }); process.exit(); }; createPool(); ``` ## Step 3 — `createPosition.ts` Source: [`src/clmm/createPosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPosition.ts). ```ts theme={null} // createPosition.ts import { ApiV3PoolInfoConcentratedItem, TickUtils, PoolUtils, ClmmKeys, } from "@raydium-io/raydium-sdk-v2"; import BN from "bn.js"; import Decimal from "decimal.js"; import { initSdk, txVersion } from "./config"; import { isValidClmm } from "./utils"; export const createPosition = async () => { const raydium = await initSdk(); let poolInfo: ApiV3PoolInfoConcentratedItem; // RAY-USDC pool const poolId = "61R1ndXxvsWXXkWSyNkCxnzwd3zUNB8Q2ibmkiLPC8ht"; let poolKeys: ClmmKeys | undefined; if (raydium.cluster === "mainnet") { const data = await raydium.api.fetchPoolById({ ids: poolId }); poolInfo = data[0] as ApiV3PoolInfoConcentratedItem; if (!isValidClmm(poolInfo.programId)) throw new Error("target pool is not CLMM pool"); } else { const data = await raydium.clmm.getPoolInfoFromRpc(poolId); poolInfo = data.poolInfo; poolKeys = data.poolKeys; } // Optional: pull on-chain real-time price to avoid slippage errors from a stale API quote. // const rpcData = await raydium.clmm.getRpcClmmPoolInfo({ poolId: poolInfo.id }); // poolInfo.price = rpcData.currentPrice; const inputAmount = 0.000001; // RAY amount const [startPrice, endPrice] = [0.000001, 100000]; const { tick: lowerTick } = TickUtils.getPriceAndTick({ poolInfo, price: new Decimal(startPrice), baseIn: true, }); const { tick: upperTick } = TickUtils.getPriceAndTick({ poolInfo, price: new Decimal(endPrice), baseIn: true, }); const epochInfo = await raydium.fetchEpochInfo(); const res = await PoolUtils.getLiquidityAmountOutFromAmountIn({ poolInfo, slippage: 0, inputA: true, tickUpper: Math.max(lowerTick, upperTick), tickLower: Math.min(lowerTick, upperTick), amount: new BN(new Decimal(inputAmount || "0").mul(10 ** poolInfo.mintA.decimals).toFixed(0)), add: true, amountHasFee: true, epochInfo, }); const { execute, extInfo } = await raydium.clmm.openPositionFromBase({ poolInfo, poolKeys, tickUpper: Math.max(lowerTick, upperTick), tickLower: Math.min(lowerTick, upperTick), base: "MintA", ownerInfo: { useSOLBalance: true }, baseAmount: new BN(new Decimal(inputAmount || "0").mul(10 ** poolInfo.mintA.decimals).toFixed(0)), otherAmountMax: res.amountSlippageB.amount, txVersion, computeBudgetConfig: { units: 600000, microLamports: 100000 }, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("clmm position opened:", { txId, nft: extInfo.nftMint.toBase58() }); process.exit(); }; createPosition(); ``` ## Step 4 — `utils.ts` Source: [`src/clmm/utils.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/utils.ts). ```ts theme={null} // utils.ts import { CLMM_PROGRAM_ID, DEVNET_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2"; const VALID_PROGRAM_IDS = new Set([ CLMM_PROGRAM_ID.toBase58(), DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID.toBase58(), ]); export const isValidClmm = (programId: string) => VALID_PROGRAM_IDS.has(programId); ``` ## Run it ```bash theme={null} # create the pool first RPC_URL="https://api.mainnet-beta.solana.com" \ KEYPAIR="$HOME/.config/solana/id.json" \ npx tsx createPool.ts # then open an initial position against the new pool id # (edit poolId at the top of createPosition.ts to point at your new pool) RPC_URL="https://api.mainnet-beta.solana.com" \ KEYPAIR="$HOME/.config/solana/id.json" \ npx tsx createPosition.ts ``` ## What just happened **Transaction 1 — `raydium.clmm.createPool`** initialized: * the pool state at the canonical PDA for `(mint1, mint2, ammConfig)`, * `token_0_vault` and `token_1_vault` (sorted by mint byte order), * the `observation` ring buffer, * the inline tick-array bitmap, and set the initial `sqrt_price_x64` from your `initialPrice`. **Transaction 2 — `raydium.clmm.openPositionFromBase`** opened a concentrated position: * minted a position NFT to your wallet (the NFT *is* the position; transferring it transfers the position), * allocated tick arrays at the lower and upper bounds (one-time rent if first position in those ranges; tick arrays are never closed by the program, so subsequent positions in the same arrays pay no extra rent), * deposited `inputAmount` of `mint1` and the matching pair amount of `mint2` (computed by `PoolUtils.getLiquidityAmountOutFromAmountIn`), * credited the position with liquidity proportional to the range width. The narrower the range, the higher the capital efficiency per dollar of TVL — and the more painful the impermanent loss when price drifts out of range. The range used above (`[0.000001, 100000]`) is effectively full-range; tighten it to concentrate fees near current spot. ## Picking a fee tier `clmmConfigs[0]` is the lowest-fee tier. The full set is published at `GET https://api-v3.raydium.io/main/clmm-config`: | Index | `tradeFeeRate` | Tick spacing | Use when | | ----- | ---------------- | ------------ | ----------------------------------------------------------- | | 0 | `100` (1bp) | 1 | Stable / stable, very low impermanent loss expected | | 1 | `500` (5bp) | 10 | Highly correlated assets (e.g. liquid-staked vs underlying) | | 2 | `2_500` (25bp) | 60 | Standard token pair, blue-chip + stable | | 3 | `10_000` (1.00%) | 120 | Volatile or thin pair where IL risk is high | See [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type) for a full decision matrix. ## Common errors * **`Pool already exists for this config`** — A CLMM pool already exists for this `(mint1, mint2, ammConfig)` triple. Look up the existing pool ID and skip Step 2. * **`Insufficient funds for amount B`** — Your wallet has the requested amount of `mintA` but not the matching `mintB`. Opening a position when the price sits inside the range requires liquidity on both sides. * **`Tick out of range`** — Your `lowerPrice` or `upperPrice` falls outside the representable price range. Use a more reasonable range relative to current price. * **Stale price** — A quote from the API can be 5–60 seconds stale. If `executePosition` fails on slippage, uncomment the `getRpcClmmPoolInfo` block in `createPosition.ts` to re-fetch the live price right before signing. ## Caveats * **Position NFT is your only handle.** Lose the NFT or transfer it, lose access to the position. Treat it like a key. * **Out-of-range positions earn no fees.** If price moves outside `[lowerPrice, upperPrice]`, your position is parked entirely in one asset and earns nothing until you rebalance. * **Tick array rent is one-way.** The first position to touch a never-initialised tick array pays its rent; the program does not expose a path to close tick arrays, so that rent is permanent. Subsequent positions in the same array are free. ## Next * [`products/clmm/overview`](/products/clmm/overview) — full CLMM mechanics. * [`products/clmm/ticks-and-positions`](/products/clmm/ticks-and-positions) — the math behind ticks. * [`algorithms/impermanent-loss`](/algorithms/impermanent-loss) — quantifying CLMM IL amplification. * [`user-flows/create-clmm-pool`](/user-flows/create-clmm-pool) — the same flow via the Raydium UI. Sources: * [`raydium-sdk-V2-demo/src/clmm/createPool.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPool.ts) * [`raydium-sdk-V2-demo/src/clmm/createPosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPosition.ts) * [`raydium-sdk-V2-demo/src/clmm/utils.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/utils.ts) * [`raydium-sdk-V2-demo/src/config.ts.template`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template) # Deploy a CPMM pool Source: https://docs.raydium.io/quick-start/deploy-cpmm-pool Paste-and-go Node script that creates a new CPMM pool. Configure mints and seed amounts via env vars. **What this does.** Creates a brand-new CPMM pool for two mints you specify, picks the 0.25% fee tier, seeds initial liquidity at the price implied by the seed amounts, and prints the new pool ID and tx signature. ## Setup Make sure you've read the [Quick start prerequisites](/quick-start) and have `RPC_URL`, `KEYPAIR`, and the deps installed. You'll also need to **fund the wallet with the seed amounts** of both mints, plus enough SOL to cover the one-time pool-creation fee (\~0.15 SOL on mainnet, see [`reference/program-addresses`](/reference/program-addresses) for the current value). ## The script Save as `create-cpmm.mjs`: ```ts theme={null} // create-cpmm.mjs import { Connection, Keypair, PublicKey } from "@solana/web3.js"; import { Raydium, TxVersion, CREATE_CPMM_POOL_PROGRAM, CREATE_CPMM_POOL_FEE_ACC, } from "@raydium-io/raydium-sdk-v2"; import BN from "bn.js"; import fs from "node:fs"; // ── Config from env ────────────────────────────────────────────── const RPC_URL = process.env.RPC_URL ?? "https://api.mainnet-beta.solana.com"; const KEYPAIR = process.env.KEYPAIR ?? `${process.env.HOME}/.config/solana/id.json`; const MINT_A = process.env.MINT_A; // required, base58 const MINT_B = process.env.MINT_B; // required, base58 const AMOUNT_A = process.env.AMOUNT_A; // required, integer raw units const AMOUNT_B = process.env.AMOUNT_B; // required, integer raw units if (!MINT_A || !MINT_B || !AMOUNT_A || !AMOUNT_B) { console.error("Set MINT_A, MINT_B, AMOUNT_A, AMOUNT_B env vars."); process.exit(1); } // ── Setup ──────────────────────────────────────────────────────── const connection = new Connection(RPC_URL, "confirmed"); const owner = Keypair.fromSecretKey( new Uint8Array(JSON.parse(fs.readFileSync(KEYPAIR, "utf8"))), ); const raydium = await Raydium.load({ owner, connection, cluster: "mainnet", disableFeatureCheck: true, blockhashCommitment: "finalized", }); // ── Pick the 0.25% fee tier ───────────────────────────────────── const feeConfigs = await raydium.api.getCpmmConfigs(); const feeConfig = feeConfigs.find((c) => c.index === 0); if (!feeConfig) throw new Error("0.25% fee tier not found in CPMM configs."); // ── Resolve mint metadata (Token-2022-aware) ──────────────────── const mintA = await raydium.token.getTokenInfo(new PublicKey(MINT_A)); const mintB = await raydium.token.getTokenInfo(new PublicKey(MINT_B)); // ── Build and execute ─────────────────────────────────────────── const { execute, extInfo } = await raydium.cpmm.createPool({ programId: CREATE_CPMM_POOL_PROGRAM, poolFeeAccount: CREATE_CPMM_POOL_FEE_ACC, mintA, mintB, mintAAmount: new BN(AMOUNT_A), mintBAmount: new BN(AMOUNT_B), startTime: new BN(0), // open immediately feeConfig, associatedOnly: false, ownerInfo: { useSOLBalance: true }, txVersion: TxVersion.V0, computeBudgetConfig: { units: 600_000, microLamports: 100_000, }, }); const { txId } = await execute({ sendAndConfirm: true }); console.log(`Pool ID: ${extInfo.address.poolId.toBase58()}`); console.log(`LP mint: ${extInfo.address.lpMint.toBase58()}`); console.log(`Vault A: ${extInfo.address.vaultA.toBase58()}`); console.log(`Vault B: ${extInfo.address.vaultB.toBase58()}`); console.log(`Tx: https://solscan.io/tx/${txId}`); ``` ## Run it Example: create a SOL/USDC pool with 1 SOL and 160 USDC seed: ```bash theme={null} export MINT_A="So11111111111111111111111111111111111111112" # wSOL export MINT_B="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" # USDC export AMOUNT_A="1000000000" # 1 SOL (9 decimals) export AMOUNT_B="160000000" # 160 USDC (6 decimals) node create-cpmm.mjs ``` Expected output: ``` Pool ID: HgC5...3kXb LP mint: 4ZAS...9rkV Vault A: J9Mu...mP2k Vault B: AaJq...8wxx Tx: https://solscan.io/tx/5dQ... ``` ## What just happened 1. **`getCpmmConfigs`** pulled the live list of fee tiers from `api-v3.raydium.io` and picked index 0 (the 0.25% tier — see [`reference/fee-comparison`](/reference/fee-comparison) for the full set). 2. **`getTokenInfo`** resolved each mint's metadata, including which token program owns it. CPMM accepts both SPL Token and Token-2022 mints; the SDK routes automatically. 3. **`createPool`** built one transaction that: * sorts the mints into canonical order, * derives the pool PDA, vaults, LP mint, and authority, * pays the one-time `create_pool_fee` to `CREATE_CPMM_POOL_FEE_ACC`, * creates the caller's ATAs if missing, * seeds the vaults with `AMOUNT_A` and `AMOUNT_B`. 4. The **initial price** is set by the seed ratio: `price = AMOUNT_B / AMOUNT_A` after decimal adjustment. Pick this carefully — bots will arbitrage any mispricing within seconds of the pool opening. 5. **`startTime: new BN(0)`** opens trading immediately. To stage liquidity before opening to the public, set a future Unix timestamp. ## Common errors * **`pool already exists`** — A pool already exists for this mint pair at this fee tier. Look it up before creating. * **`insufficient funds`** — Your wallet doesn't have enough of `MINT_A`, `MINT_B`, or SOL (for the create-pool fee + rent). * **`Token-2022 extension not supported`** — One of your mints uses an extension CPMM doesn't accept. See [`reference/token-2022-support`](/reference/token-2022-support). ## After deploy You can immediately swap against the new pool — the [Swap from CLI](/quick-start/swap-from-cli) script accepts your new `POOL_ID` directly. Aggregators (Jupiter, etc.) will index the new pool within minutes. ## Next * [`products/cpmm/overview`](/products/cpmm/overview) — what CPMM is and when to choose it. * [`user-flows/create-cpmm-pool`](/user-flows/create-cpmm-pool) — the same flow with screenshots, via the Raydium UI. * [`user-flows/choosing-a-pool-type`](/user-flows/choosing-a-pool-type) — should you have used CLMM instead? # Quick start Source: https://docs.raydium.io/quick-start/index Three paste-and-go Node scripts that swap, create a CPMM pool, and create a CLMM pool — minimum config, real on-chain output in under five minutes. **What this chapter is.** Three end-to-end Node scripts you can paste into a file, set a few env vars, and run. Each one performs a real on-chain operation against Raydium. Use these to validate your setup before building anything bigger. **What this chapter is not.** Reference documentation. For the full builder API, parameter reference, and corner cases, see [SDK & API](/sdk-api) and the per-product `code-demos` pages. ## Prerequisites Run this once: ```bash theme={null} mkdir raydium-quickstart && cd raydium-quickstart npm init -y npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token bn.js ``` You need: * **Node 18+** with ESM support (`"type": "module"` in `package.json` or `.mjs` extension). * **A Solana keypair** at `~/.config/solana/id.json` (or anywhere — set `KEYPAIR`). * **A funded wallet.** \~0.05 SOL covers any single script in this chapter. * **An RPC URL.** Free public RPC works for testing; for anything more than a smoke test, use a paid provider. Set these once per shell: ```bash theme={null} export RPC_URL="https://api.mainnet-beta.solana.com" export KEYPAIR="$HOME/.config/solana/id.json" ``` **Cluster.** All three scripts default to mainnet. To run on devnet, set `RPC_URL=https://api.devnet.solana.com` and change `cluster: "mainnet"` to `cluster: "devnet"` inside the script. Be aware that pool seed liquidity on devnet is sparse; expect to mint your own test tokens. ## The three scripts Swap one token for another through any CPMM pool. Fastest "I can hit Raydium from a script" check. Spin up a new CPMM pool for a token pair you choose. Picks the 0.25% fee tier and seeds initial liquidity. Spin up a new CLMM pool with a fee tier of your choice and a single concentrated position. ## After this chapter Each script ends with a minimal output (pool ID, tx signature) and a single line pointing at the next page in the [SDK & API](/sdk-api) chapter for the operation it performed. When you're ready to write your own integration: * **Backends and bots** → [Trade API](/sdk-api/trade-api). * **Frontends and full-control apps** → [TypeScript SDK](/sdk-api/typescript-sdk). * **On-chain composition** → [Rust CPI](/sdk-api/rust-cpi). * **Picking among them** → the comparison table at the top of [SDK & API](/sdk-api). ## Version banner ``` SDK: @raydium-io/raydium-sdk-v2@0.2.42-alpha Programs: see /reference/program-addresses Cluster: mainnet-beta (also runs on devnet with the change noted above) Verified: 2026-04 ``` If a script breaks on a newer SDK version, check the SDK's release notes — the builder return shapes have evolved between minor releases. # Swap from CLI Source: https://docs.raydium.io/quick-start/swap-from-cli Paste-and-go Node script that quotes and executes a CPMM swap. Configure four env vars and run it. **What this does.** Loads a CPMM pool from RPC, quotes a swap with 0.5% slippage, builds the transaction, signs with your keypair, and submits it. End-to-end in \~30 lines. ## Setup Make sure you've read the [Quick start prerequisites](/quick-start) and have `RPC_URL`, `KEYPAIR`, and the deps installed. ## The script Save as `swap.mjs`: ```ts theme={null} // swap.mjs import { Connection, Keypair, PublicKey } from "@solana/web3.js"; import { Raydium, TxVersion, CurveCalculator } from "@raydium-io/raydium-sdk-v2"; import BN from "bn.js"; import fs from "node:fs"; // ── Config from env ────────────────────────────────────────────── const RPC_URL = process.env.RPC_URL ?? "https://api.mainnet-beta.solana.com"; const KEYPAIR = process.env.KEYPAIR ?? `${process.env.HOME}/.config/solana/id.json`; const POOL_ID = process.env.POOL_ID; // required, base58 const INPUT_MINT = process.env.INPUT_MINT; // required, base58 const AMOUNT_RAW = process.env.AMOUNT_RAW; // required, integer in raw units (e.g. 1_000_000_000 for 1 SOL) if (!POOL_ID || !INPUT_MINT || !AMOUNT_RAW) { console.error("Set POOL_ID, INPUT_MINT, AMOUNT_RAW env vars."); process.exit(1); } // ── Setup ──────────────────────────────────────────────────────── const connection = new Connection(RPC_URL, "confirmed"); const owner = Keypair.fromSecretKey( new Uint8Array(JSON.parse(fs.readFileSync(KEYPAIR, "utf8"))), ); const raydium = await Raydium.load({ owner, connection, cluster: "mainnet", disableFeatureCheck: true, blockhashCommitment: "finalized", }); // ── Load pool ──────────────────────────────────────────────────── const { poolInfo, poolKeys, rpcData } = await raydium.cpmm.getPoolInfoFromRpc(new PublicKey(POOL_ID)); const baseIn = poolInfo.mintA.address === INPUT_MINT; // ── Quote ──────────────────────────────────────────────────────── const inputAmount = new BN(AMOUNT_RAW); const swapResult = CurveCalculator.swap( inputAmount, baseIn ? rpcData.baseReserve : rpcData.quoteReserve, baseIn ? rpcData.quoteReserve : rpcData.baseReserve, rpcData.configInfo.tradeFeeRate, ); console.log(`Quote: ${inputAmount.toString()} -> ${swapResult.destinationAmountSwapped.toString()}`); console.log(`Fee: ${swapResult.tradeFee.toString()}`); // ── Build and execute ──────────────────────────────────────────── const { execute } = await raydium.cpmm.swap({ poolInfo, poolKeys, inputAmount, swapResult, slippage: 0.005, // 0.5% baseIn, txVersion: TxVersion.V0, computeBudgetConfig: { units: 250_000, microLamports: 50_000, }, }); const { txId } = await execute({ sendAndConfirm: true }); console.log(`Swap landed: https://solscan.io/tx/${txId}`); ``` ## Run it Pick any CPMM pool you have liquidity for. Example with the canonical SOL/USDC CPMM pool: ```bash theme={null} export POOL_ID="" export INPUT_MINT="So11111111111111111111111111111111111111112" export AMOUNT_RAW="10000000" # 0.01 SOL node swap.mjs ``` Expected output: ``` Quote: 10000000 -> 1640000 Fee: 25000 Swap landed: https://solscan.io/tx/4Z9... ``` ## What just happened 1. **`Raydium.load`** initialised the SDK — fetched the global config, set up your wallet context. 2. **`getPoolInfoFromRpc`** pulled the live pool state directly from RPC (not from the API cache). For high-value swaps you always want fresh state. 3. **`CurveCalculator.swap`** computed the constant-product output net of the pool's fee. This is the same math the program runs on-chain, so you can compare quotes off- and on-chain. 4. **`raydium.cpmm.swap`** built the transaction with V0 format (address lookup tables enabled) and added an explicit compute-budget config. The compute-budget tip helps the tx land in busy windows. 5. **`execute({ sendAndConfirm: true })`** signed, sent, and waited for confirmation. ## Common errors * **`Pool not found`** — Wrong `POOL_ID`, or you're pointed at the wrong cluster (mainnet pool ID against a devnet RPC, etc.). * **`Insufficient funds for transaction`** — Your wallet doesn't have enough SOL for the swap input + fees + ATA rent. * **`Slippage tolerance exceeded`** — The pool's price moved between quote and execution. Re-run; or raise the `slippage` parameter; or use the SDK's `computeAmountOut` which always re-fetches reserves. * **`Token account not initialized`** — Output token's ATA didn't exist and the implicit-create instruction landed but failed for some reason; check your wallet's SOL balance and try again. ## Next * [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — full SDK reference. * [`products/cpmm/instructions`](/products/cpmm/instructions) — what the swap instruction looks like on-chain. * [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) — sizing `computeBudgetConfig` for production. # RAY Source: https://docs.raydium.io/ray/index RAY supply, mint address, emissions, allocation, vesting, utility, and safety checks. RAY is Raydium's native SPL token. Use this page for the canonical mint, fixed supply, allocation, emissions, vesting status, and how RAY connects to buybacks and staking. ## Token facts | Field | Value | | --------------------- | -------------------------------------------------------------- | | Token | RAY | | Chain | Solana | | Mint address | `4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R` | | Decimals | 6 | | Maximum supply | 555,000,000 RAY | | Mint authority | Disabled | | Mining reserve | 188,700,000 RAY, or 34% of supply | | Current emissions | Approximately 1.9 million RAY per year from the mining reserve | | Team and seed vesting | Completed on February 21, 2024 | Always verify the mint address before interacting with RAY. Tickers, token names, and logos can be copied by anyone. ## What RAY is used for RAY connects to Raydium in three user-facing ways: * **Protocol alignment:** a portion of Raydium trading fees is used to buy back RAY. * **Staking:** users can stake RAY in the Raydium app and claim RAY rewards. * **Liquidity:** RAY is a common base or quote asset across Raydium pools. You do not need RAY to swap, provide liquidity, create pools, or use Raydium Perps. ## Allocation | Allocation | Percentage | Amount | Address | | ----------------------- | ---------: | ------------------: | ---------------------------------------------- | | Mining reserve | 34% | 188,700,000 RAY | `fArUAncZwVbMimiWv5cPUfFsapdLd8QMXZE4VXqFagR` | | Partnership & ecosystem | 30% | 166,500,000 RAY | `DmKR61BQk5zJTNCK9rrt8fM8HrDH6DSdE3Rt7sXKoAKb` | | Team | 20% | 111,000,000 RAY | `HoVhs7NAzcAaavr2tc2aaTynJ6kwhdfC2B2Z7EthKpeo` | | Liquidity | 8% | 44,400,000 RAY | `85WdjCsxksAoCo6NNZSU4rocjUHsJwXrzpHJScg8oVNZ` | | Community & seed | 6% | 33,300,000 RAY | `HuBBhoS81jyHTKMbhz8B3iYa8HSNShnRwXRzPzmFFuFr` | | Advisors | 2% | 11,100,000 RAY | `5unqG9sYX995czHCtHkkJvd2EaTE58jdvmjfz1nVvo5x` | | **Total** | **100%** | **555,000,000 RAY** | — | ## Vesting Team and seed allocations, representing 25.9% of total supply, were fully locked for the first 12 months after the token generation event. They then unlocked linearly each day from months 13 through 36. Vesting concluded on February 21, 2024. ## Emissions RAY emissions come from the mining reserve. Current emissions are approximately 1.9 million RAY per year. Emissions are distributed through Raydium reward programs. The exact reward rate depends on the specific farm or staking program. ## How to verify RAY Use the mint address as the source of truth: ```text theme={null} 4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R ``` You can verify the mint on Solscan, Solana Explorer, or with SPL Token tooling: ```bash theme={null} spl-token supply 4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R ``` ## Related pages * [`ray/ray-buybacks`](/ray/ray-buybacks) * [`ray/ray-staking`](/ray/ray-staking) * [`ray/protocol-fees`](/ray/protocol-fees) * [`ray/treasury`](/ray/treasury) * [`ray/ray-token-white-paper`](/ray/ray-token-white-paper) # Protocol fees Source: https://docs.raydium.io/ray/protocol-fees Raydium trading-fee splits, pool-creation fees, fee conventions, and verification notes. Raydium fees are split between liquidity providers, RAY buybacks, and treasury destinations. Fee split percentages are percentages of the trading fee, not percentages of the full trade amount. ## Fee types Raydium has four user-facing fee categories. | Fee type | When it applies | What it pays for | | ----------------- | -------------------------------------------------------- | --------------------------------------------- | | Swap fee | Every swap | LP earnings, RAY buybacks, and treasury share | | Pool creation fee | When creating CPMM or Standard AMM v4 pools | Spam prevention and protocol infrastructure | | Rent | When creating pools, positions, farms, or token accounts | Solana account storage | | LaunchLab fees | During token launches and graduation | LaunchLab creator and protocol fee flow | Rent is paid to create Solana accounts. It is separate from Raydium protocol fees. ## Trading-fee split | Pool type | LP share | RAY buyback | Treasury | | --------------- | -------: | ----------: | -------: | | CLMM | 84% | 12% | 4% | | CPMM | 84% | 12% | 4% | | Standard AMM v4 | 88% | 12% | — | These percentages are applied to the pool's trading fee. Example for a CLMM or CPMM pool with a `0.25%` swap fee: ```text theme={null} Swap fee: 0.25% of trade LP share: 0.25% * 84% = 0.2100% of trade RAY buyback: 0.25% * 12% = 0.0300% of trade Treasury share: 0.25% * 4% = 0.0100% of trade ``` Example for a Standard AMM v4 pool with a `0.25%` swap fee: ```text theme={null} Swap fee: 0.25% of trade LP share: 0.25% * 88% = 0.2200% of trade RAY buyback: 0.25% * 12% = 0.0300% of trade ``` ## Swap-fee tiers | Product | Common fee tiers | Notes | | ------------------------ | ----------------------- | ---------------------------------------------------- | | CLMM | 0.01%, 0.05%, 0.25%, 1% | Fee tier is tied to the pool's AmmConfig. | | CPMM | 0.01%, 0.25%, 1% | Default user-facing tier is commonly 0.25%. | | Standard AMM v4 | 0.25% | Legacy constant-product pools. | | LaunchLab pre-graduation | 1% buy and sell fee | Applies while the token trades on the bonding curve. | ## Pool creation fees Creating a Standard AMM v4 or CPMM pool costs `0.15 SOL` in pool creation fees. | Pool type | Pool creation fee | | --------------- | --------------------------------------------------------: | | CPMM | 0.15 SOL | | Standard AMM v4 | 0.15 SOL | | CLMM | No separate Raydium pool creation fee; rent still applies | Pool creation fees are separate from account rent and transaction fees. ## Fee denominator conventions Different Raydium programs encode fee rates with different denominators. | Product | Denominator | Example | | --------- | ------------------------------------------: | -------------------------------------------- | | AMM v4 | 10,000 | `25` means `0.25%` | | CPMM | 1,000,000 | `2,500` means `0.25%` | | CLMM | 1,000,000 | `2,500` means `0.25%` | | LaunchLab | 1,000,000 or program-specific config fields | Check LaunchConfig fields before integrating | This matters when reading raw on-chain accounts or SDK responses. Always normalize rates before comparing products. ## How fees accrue 1. A trader pays the swap fee as part of the swap. 2. The LP share remains in or accrues to the pool's accounting. 3. Buyback and treasury shares accumulate in program fee accounting. 4. Collection transactions move those balances to the relevant addresses listed in [`ray/treasury`](/ray/treasury). For LPs, fees are realized differently by product: * **AMM v4 and CPMM:** LP fees accumulate in pool reserves and are realized when LP tokens are burned on withdrawal. * **CLMM:** LP fees accrue per position and can be claimed with the fee-collection flow. * **LaunchLab:** launch-specific creator and protocol fees follow LaunchLab configuration. ## Token-2022 transfer fees Token-2022 transfer fees are not Raydium fees. If a Token-2022 mint has a transfer fee, that fee is paid to the mint's configured authority. A swap involving Token-2022 tokens may therefore include: ```text theme={null} Raydium pool fee + Token-2022 transfer fee ``` The two fees have different destinations and should be displayed separately in integrations. ## Practical pitfalls * **Do not multiply by the wrong base.** A 12% buyback share is 12% of the swap fee, not 12% of the trade. * **Normalize denominators.** AMM v4 uses a different denominator than CPMM and CLMM. * **Separate protocol fees from network fees.** Solana base fees and priority fees are paid to the network, not Raydium. * **Account for Token-2022 fees.** Transfer fees can materially change the user's effective received amount. * **Fetch fresh config.** If your integration caches AmmConfig data, refresh before building high-value transactions. ## Related pages * [`ray/ray-buybacks`](/ray/ray-buybacks) * [`ray/treasury`](/ray/treasury) * [`reference/fee-comparison`](/reference/fee-comparison) * [`products/cpmm/fees`](/products/cpmm/fees) * [`products/clmm/fees`](/products/clmm/fees) * [`products/amm-v4/fees`](/products/amm-v4/fees) # Protocol metrics & analytics Source: https://docs.raydium.io/ray/protocol-metrics-and-analytics Dashboards, reports, and APIs for tracking Raydium activity. Independent analytics providers parse Raydium on-chain data and publish dashboards. Raydium also exposes public APIs for self-reported app and protocol data. ## Dashboards | Dashboard | Provider | | ------------------------------------------------------------- | -------------- | | [Raydium overview](https://blockworks.com) | Blockworks | | [Solana DeFi overview](https://solana.blockworksresearch.com) | Blockworks | | [Raydium overview](https://analytics.topledger.xyz) | TopLedger | | [Raydium economics](https://app.artemisanalytics.com) | Artemis | | [Raydium Financials](https://tokenterminal.com) | Token Terminal | ## Deep dives | Dashboard | Provider | | --------------------------------------------------- | --------- | | [CLMM analytics](https://analytics.topledger.xyz) | TopLedger | | [AMM v4 analytics](https://analytics.topledger.xyz) | TopLedger | ## Reports Messari has published quarterly reports covering Raydium performance, revenue, and market position. Blockworks also publishes Raydium quarterly coverage. ## Raydium APIs | API | Documentation | | ------------------- | -------------------------------------------------------------------- | | Raydium API v3 | [api-v3.raydium.io/docs](https://api-v3.raydium.io/docs) | | Perps API (Orderly) | [api-perps-v1.raydium.io/docs](https://api-perps-v1.raydium.io/docs) | # Buybacks Source: https://docs.raydium.io/ray/ray-buybacks How Raydium trading fees route into buybacks and how to verify the flow on-chain. 12% of Raydium trading fees are used to buy back RAY. Bought-back RAY is held by the protocol at a public on-chain address. ## Flow at a glance ```text theme={null} Swap fee -> LP share -> RAY buyback share -> Treasury share, where applicable ``` For CLMM and CPMM pools, the trading-fee split is: ```text theme={null} 84% to LPs 12% to RAY buybacks 4% to treasury ``` For Standard AMM v4 pools, the trading-fee split is: ```text theme={null} 88% to LPs 12% to RAY buybacks ``` ## What buyback share means The buyback percentage applies to the **trading fee**, not to the full trade amount. Example: a pool charges a `0.25%` swap fee. The buyback share is `12%` of that fee. ```text theme={null} Trade amount: 1,000 USDC Swap fee: 1,000 * 0.25% = 2.50 USDC RAY buyback share: 2.50 * 12% = 0.30 USDC ``` The effective buyback amount is `0.03%` of the trade amount in this example. ## Collection addresses Protocol-side fees are collected by program-specific addresses before being converted or routed. | Source | Collection address | | --------------- | --------------------------------------------- | | CLMM | `projjosVCPQH49d5em7VYS7fJZzaqKixqKtus7yk416` | | CPMM | `ProCXqRcXJjoUd1RNoo28bSizAA6EEqt9wURZYPDc5u` | | Standard AMM v4 | `PNLCQcVCD26aC7ZWgRyr5ptfaR7bBrWdTFgRWwu2tvF` | Collection addresses can hold many different tokens. Each pool pays fees in the tokens it trades, so a collection address may hold USDC, wSOL, RAY, or other SPL tokens before routing. ## Buyback holding address Bought-back RAY is held at: ```text theme={null} DdHDoz94o2WJmD9myRobHCwtx1bESpHTd4SSPe6VEZaz ``` You can inspect this address directly on Solscan or with SPL Token tooling: ```bash theme={null} spl-token accounts --owner DdHDoz94o2WJmD9myRobHCwtx1bESpHTd4SSPe6VEZaz ``` ## Why buybacks are on-chain Raydium's buyback flow is auditable from public addresses and transaction history. * **Balances are visible:** collection and holding addresses can be inspected by anyone. * **Transfers are traceable:** incoming fee transfers and outgoing routing transactions are public. * **No private accounting is required:** the chain is the ledger for the flow. ## How to verify a buyback flow 1. Open a Raydium swap transaction in Solscan or Solana Explorer. 2. Identify the pool program and fee transfers. 3. Match protocol-side fee movement to the relevant collection address. 4. Inspect outgoing transactions from the collection address. 5. Confirm RAY accumulation at `DdHDoz...VEZaz`. For the complete address map, see [`ray/treasury`](/ray/treasury). # Staking Source: https://docs.raydium.io/ray/ray-staking How to stake and unstake RAY in the Raydium app. You can stake RAY in the Raydium app to earn additional RAY rewards. ## How to stake 1. Open [raydium.io](https://raydium.io) and connect your wallet. 2. Go to the **Staking** page. 3. Enter the amount of RAY to stake. 4. Click **Stake**. 5. Review the transaction in your wallet and approve it. After the transaction confirms, the staking page shows your staked balance and pending rewards. ## How to unstake 1. Open the **Staking** page or **Portfolio** page. 2. Click the minus button for your staked RAY position. 3. Enter the amount to unstake. 4. Confirm and sign the transaction in your wallet. ## Before staking * Keep SOL in your wallet for transaction fees. * Verify the RAY mint: `4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R`. * Check pending rewards and transaction details before signing. # White paper Source: https://docs.raydium.io/ray/ray-token-white-paper Official Raydium Protocol token white paper reference. The Raydium Protocol token white paper describes RAY, including tokenomics, disclosures, and supporting information. The white paper content will be maintained here as part of the Raydium documentation set. # Treasury Source: https://docs.raydium.io/ray/treasury Protocol multisig, fee collection, buyback, treasury, and pool-creation fee addresses. This page lists the public addresses used for Raydium protocol fee collection, RAY buybacks, USDC treasury balances, and SOL pool-creation fees. Inspect balances and transfers on-chain before relying on them for accounting. ## Flow at a glance ```text theme={null} Pool fees -> Program-specific collection address -> RAY buyback route -> USDC treasury route, where applicable Pool creation fees -> Program-specific SOL fee address ``` Every address in this flow is public and inspectable. ## Protocol multisig | Purpose | Address | | ----------------- | ---------------------------------------------- | | Protocol multisig | `GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ` | Quick links: * [Squads](https://v3.squads.so) * [Solscan](https://solscan.io/account/GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ) ## Fee collection addresses When a swap pays a protocol-side fee, the relevant program accumulates that fee at a collection address. | Source | Collection address | | --------------- | --------------------------------------------- | | CLMM | `projjosVCPQH49d5em7VYS7fJZzaqKixqKtus7yk416` | | CPMM | `ProCXqRcXJjoUd1RNoo28bSizAA6EEqt9wURZYPDc5u` | | Standard AMM v4 | `PNLCQcVCD26aC7ZWgRyr5ptfaR7bBrWdTFgRWwu2tvF` | Collection addresses can hold a mixed set of SPL tokens. For example, fees from SOL/USDC pools may produce wSOL and USDC balances, while SOL/RAY pools may produce wSOL and RAY balances. Inspect a collection address with: ```bash theme={null} spl-token accounts --owner projjosVCPQH49d5em7VYS7fJZzaqKixqKtus7yk416 ``` ## RAY buyback holding Bought-back RAY is held at: | Purpose | Address | | ---------------------------- | ---------------------------------------------- | | Bought-back RAY accumulation | `DdHDoz94o2WJmD9myRobHCwtx1bESpHTd4SSPe6VEZaz` | Inspect it with: ```bash theme={null} spl-token accounts --owner DdHDoz94o2WJmD9myRobHCwtx1bESpHTd4SSPe6VEZaz ``` Historical buyback activity can be reconstructed from incoming transfers and swaps that route into this address. ## Treasury addresses Fees not routed to RAY buybacks can consolidate to USDC treasury addresses. | Source | Treasury address | | ------ | ---------------------------------------------- | | CLMM | `CHynyGLd4fDo35VP4yftAZ9724Gt49uXYXuqmdWtt68F` | | CPMM | `FS3HipLdf13nhaeN9NhRfGr1cnH84GdNEam3WHhUXVYJ` | Inspect a treasury address with: ```bash theme={null} spl-token accounts --owner CHynyGLd4fDo35VP4yftAZ9724Gt49uXYXuqmdWtt68F ``` ## Pool creation fees Creating a CPMM or Standard AMM v4 pool charges a SOL-denominated pool creation fee. These fees accumulate at program-specific addresses. | Source | Address | | --------------- | ---------------------------------------------- | | CPMM | `DNXgeM9EiiaAbaWvwjHj9fQQLAX5ZsfHyvmYUNRAdNC8` | | Standard AMM v4 | `7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5` | CLMM pool creation still requires account rent, but it does not use the same separate SOL pool creation fee path. ## How to audit a fee flow Anyone can verify Raydium's fee flow from public transaction data: 1. Pull a swap transaction from a Raydium pool. 2. Identify the pool program: CLMM, CPMM, or Standard AMM v4. 3. Inspect the fee accounting and token transfers in the transaction. 4. Match protocol-side movement to the relevant collection address. 5. Trace outgoing transactions from the collection address. 6. Match RAY accumulation to `DdHDoz...VEZaz` or USDC balances to the treasury addresses above. ## Reporting discrepancies If observed routing does not match the addresses listed here, report it through [`security/disclosure`](/security/disclosure). # AI integration Source: https://docs.raydium.io/sdk-api/ai-integration Plug Raydium docs into your coding agent or LLM workflow — MCP server, llms.txt index, per-page copy, and pre-built context files. These docs are designed to be consumed by AI tools as well as humans. If you're building with a coding agent (Claude Code, Cursor, Windsurf, Continue, etc.) or running RAG over docs, the surfaces below let you wire Raydium documentation in without any custom scraping. ## What's available | Surface | URL pattern | Use when | | ------------------------ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | **MCP server** | `https://docs.raydium.io/mcp` | You want your AI editor (Claude Code, Cursor, Windsurf, etc.) to query and cite Raydium docs natively. | | **`llms.txt` index** | `https://docs.raydium.io/llms.txt` | You're building a RAG pipeline and need a flat index of every page. | | **`llms-full.txt` body** | `https://docs.raydium.io/llms-full.txt` | You want the full corpus as a single concatenated file for offline indexing. | | **Per-page copy menu** | The `Copy page` button at the top of every body page | You're pasting one page into a chat with an LLM. | | **Per-page deep-links** | `View as Markdown`, `Open in ChatGPT`, `Open in Claude`, `Open in Cursor`, `Open in VS Code` | One-click hand-off from a doc page to your tool of choice. | The `Copy page` button and the deep-link menu sit in the top-right of every page (next to the page title). Both are powered by the documentation platform's contextual menu. ## MCP server The MCP (Model Context Protocol) server lets AI clients query the Raydium docs as a tool. Once configured, your agent can ask "search Raydium docs for `addLiquidity` parameters" and get authoritative answers cited back. ### Claude Code ```bash theme={null} claude mcp add --transport http raydium-docs https://docs.raydium.io/mcp ``` After adding, ask Claude Code to "search the Raydium docs for X" and it will use the tool. ### Cursor Add to your Cursor settings (`Cmd/Ctrl + ,` → MCP): ```json theme={null} { "mcpServers": { "raydium-docs": { "url": "https://docs.raydium.io/mcp" } } } ``` ### Windsurf, Continue, generic clients Any MCP-compatible client can point at `https://docs.raydium.io/mcp`. If your client requires a manifest, it's at `https://docs.raydium.io/mcp/.well-known/mcp.json`. ### What the server exposes The Raydium docs MCP server exposes one primary tool, `search_docs(query: string)`, which returns the highest-ranked passages for the query along with their canonical URLs. The agent is responsible for citing the URL it used; we don't track or rate-limit per-agent. ## `llms.txt` for RAG `llms.txt` is an emerging standard for "machine-readable docs index". Raydium publishes: * `https://docs.raydium.io/llms.txt` — a flat list of every page with title and one-line summary, organized by chapter. * `https://docs.raydium.io/llms-full.txt` — the full Markdown body of every page, concatenated, with page boundaries preserved as headings. The `full` variant is regenerated on every docs deploy. Pull it on a schedule (daily is plenty) or fetch on-demand. ```bash theme={null} # Drop the full corpus into your RAG ingestion pipeline. curl -sSL https://docs.raydium.io/llms-full.txt -o raydium-docs.md ``` ## Per-page hand-off menu Every body page has a contextual menu (top-right, next to the title) with these one-click actions: * **Copy** — copy the page as plain Markdown. * **View as Markdown** — open the source `.md` in a new tab so you can save it. * **Open in ChatGPT / Claude / Perplexity** — pre-load the page content into a chat prompt for the named tool. * **Open in Cursor / VS Code** — open a buffer in the named editor with the page content. This is the right surface when you want to ask a model a question about a single page without setting up MCP. ## Pre-built context files for coding agents If you're integrating Raydium and want your agent to have the right baseline knowledge from the start, drop these files into your project: ### `.cursorrules` / `.windsurfrules` / agent system prompt ```text theme={null} You are integrating with Raydium, a Solana DeFi protocol with five product surfaces: AMM v4, CPMM, CLMM, Farm, and LaunchLab. Authoritative docs live at docs.raydium.io. The official SDK is `@raydium-io/raydium-sdk-v2` (pin the version you've verified against). For server-built swaps, prefer the Trade API at transaction-v1.raydium.io. Always: - Pass a `Connection` and `cluster` that match. - Keep all amounts as `BN` instances; never call `.toNumber()` on amounts. - Pre-fund the user's wallet for ATA creation rent. - Pass an explicit `computeBudgetConfig` for any tx that may compete in high-volume windows. - Re-fetch `poolInfo` immediately before high-value transactions; cached state goes stale. When uncertain about an instruction's accounts list, defer to `docs.raydium.io/products//accounts` and the on-chain IDL. ``` Save as `.cursorrules` (Cursor), `.windsurfrules` (Windsurf), or `CLAUDE.md` (Claude Code) — or paste into your agent's system prompt. The exact filename and location vary by tool version, so check your tool's settings docs if it does not pick the file up automatically. ### Pinned context list For coding agents that accept a list of "always include" pages, this list is the minimum useful context for most Raydium integrations: ```text theme={null} https://docs.raydium.io/sdk-api/typescript-sdk https://docs.raydium.io/sdk-api/trade-api https://docs.raydium.io/products/cpmm/instructions https://docs.raydium.io/products/clmm/instructions https://docs.raydium.io/integration-guides/priority-fee-tuning https://docs.raydium.io/reference/program-addresses https://docs.raydium.io/reference/error-codes ``` Adjust based on which products you're integrating. ## Indexing recipe If you're building your own RAG and don't want to use `llms-full.txt`, here's the canonical recipe: ```ts theme={null} import { fetch } from "undici"; const INDEX_URL = "https://docs.raydium.io/llms.txt"; const BASE = "https://docs.raydium.io"; const index = await (await fetch(INDEX_URL)).text(); const pageUrls = [...index.matchAll(/^- \[(.+?)\]\((.+?)\)/gm)].map(m => m[2]); for (const path of pageUrls) { const md = await (await fetch(`${BASE}${path}.md`)).text(); // ingest md into your vector store, keyed by path } ``` Every page is served at its canonical path with a `.md` suffix appended (e.g. `/sdk-api/typescript-sdk` → `/sdk-api/typescript-sdk.md`). ## Caveats * **Don't paste private state into prompts.** The MCP server only knows what's in the public docs. Wallet keys, RPC credentials, and similar secrets should never enter agent context. * **AI output isn't authoritative.** Models hallucinate program addresses, instruction names, and account lists with worrying ease. Always verify against the docs and the IDL before trusting agent-generated transactions. * **Version drift.** SDK v2 is pre-1.0; agents trained on older releases may emit code that doesn't compile against your pinned version. Include the pinned SDK version in your agent's system prompt. ## Pointers * [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — the primary SDK reference. * [`sdk-api/trade-api`](/sdk-api/trade-api) — server-built swap transactions, ideal for agent-friendly integrations. * [`reference/program-addresses`](/reference/program-addresses) — verified program IDs. * [Model Context Protocol](https://modelcontextprotocol.io) — the upstream MCP spec. * [`llms.txt` proposal](https://llmstxt.org) — context on the index standard. Sources: * [Mintlify MCP and llms.txt support](https://mintlify.com/docs/ai/llms). * Raydium docs deployment. # Anchor IDL Source: https://docs.raydium.io/sdk-api/anchor-idl Where to find each Raydium program's IDL, how to regenerate TS / Rust / Python clients from it, and the protocol's IDL-change policy. ## What an IDL is Anchor programs on Solana publish an **IDL** (Interface Definition Language) file describing their instructions, account layouts, error enum, and struct schemas. The IDL is the source of truth for client code generation — the TS SDK, Rust CPI crate, and third-party clients are all generated from (or hand-written against) it. Raydium publishes IDLs for CPMM, CLMM, and LaunchLab. AMM v4, Stable AMM, and Farm (v3 / v5 / v6) predate Anchor or are otherwise not Anchor-distributed — their account structures are hand-maintained in the SDK. ## Where to find them IDLs live in a dedicated repository: ``` https://github.com/raydium-io/raydium-idl ``` The exact files: | Program | IDL file | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | CPMM | [`raydium_cpmm/raydium_cp_swap.json`](https://github.com/raydium-io/raydium-idl/blob/master/raydium_cpmm/raydium_cp_swap.json) | | CLMM | [`raydium_clmm/raydium_clmm.json`](https://github.com/raydium-io/raydium-idl/blob/master/raydium_clmm/raydium_clmm.json) | | LaunchLab | [`raydium_launchpad/raydium_launchpad.json`](https://github.com/raydium-io/raydium-idl/blob/master/raydium_launchpad/raydium_launchpad.json) | | AMM v4 | no official IDL — see [`raydium-sdk-V2/src/raydium/liquidity/layout.ts`](https://github.com/raydium-io/raydium-sdk-V2/blob/master/src/raydium/liquidity/layout.ts) for hand-written layouts | | Stable AMM | no official IDL — layouts in the SDK | | Farm | no official IDL — layouts in the SDK | The IDL files are versioned in the repo's git history; pin to a specific commit if you need byte-for-byte reproducibility. IDLs can also be pulled directly from the deployed programs via Anchor's **on-chain IDL** feature (if the program publisher opted in): ```bash theme={null} anchor idl fetch --provider.cluster mainnet ``` CPMM, CLMM, and LaunchLab all have on-chain IDLs. AMM v4, Stable AMM, and Farm do not (pre-Anchor programs). ## Regenerating a TypeScript client Anchor's codegen produces a typed client from the IDL: ```bash theme={null} # Using the anchor CLI anchor build anchor idl parse \ --file target/idl/cpmm.json \ --out target/types/cpmm.ts # Or with the `@coral-xyz/anchor` TS helpers at runtime: ``` ```ts theme={null} import { Program, AnchorProvider } from "@coral-xyz/anchor"; // Pull the IDL directly from the raydium-idl repo (or vendor it into your project). import cpmmIdl from "./idls/raydium_cp_swap.json"; const provider = new AnchorProvider(connection, wallet, {}); const program = new Program(cpmmIdl as any, CPMM_PROGRAM_ID, provider); // Typed method builders auto-generated: await program.methods .swapBaseInput(new BN(amountIn), new BN(minAmountOut)) .accounts({ ... }) .rpc(); ``` Most integrators do **not** do this — they use the higher-level `raydium.cpmm.swap(...)` helper which wraps the Anchor methods plus all the bookkeeping (ATA creation, transfer-fee adjustment, compute budget, Token-2022 program routing). Regenerate only when you need a layer below the SDK. ## Regenerating a Rust client (CPI crate) Raydium publishes Anchor crates for the programs that have IDLs: ```toml theme={null} # Cargo.toml [dependencies] raydium_cp_swap = { git = "https://github.com/raydium-io/raydium-cp-swap", branch = "master", features = ["cpi"] } raydium_amm_v3 = { git = "https://github.com/raydium-io/raydium-clmm", branch = "master", features = ["cpi"] } ``` The `cpi` feature exposes `cpi::accounts::` account structs and `cpi::()` invokers — ready-to-use CPI wrappers. See [`sdk-api/rust-cpi`](/sdk-api/rust-cpi) for usage patterns. If you prefer to generate fresh bindings: ```bash theme={null} # From the IDL, using anchor-client anchor idl parse \ --file raydium_cpmm/raydium_cp_swap.json \ --out src/generated/cpmm_bindings.rs ``` ## Regenerating a Python client There is no official Raydium Python SDK. Third-party generators include: * **`anchorpy`** — Python port of `@coral-xyz/anchor`. Generates typed method builders from IDLs. * **`solders`** — low-level Solana primitives (transactions, keypairs, pubkeys) in Rust bindings; used underneath `anchorpy`. ```bash theme={null} pip install anchorpy solders solana ``` ```python theme={null} from anchorpy import Program, Provider from solana.rpc.async_api import AsyncClient from pathlib import Path import json idl = json.loads(Path("cpmm.json").read_text()) provider = Provider(AsyncClient("https://api.mainnet-beta.solana.com"), wallet) program = Program(idl, CPMM_PROGRAM_ID, provider) await program.rpc["swap_base_input"](amount_in, min_amount_out, ctx=Context(accounts={...})) ``` See [`sdk-api/python-integration`](/sdk-api/python-integration) for a fuller walk-through. ## IDL change policy Raydium follows these rules for IDL stability: 1. **Instruction discriminators never change.** Adding new instructions extends the enum at the end; existing discriminators remain stable. 2. **Account struct layouts evolve only additively.** New fields go at the end, preceded by a size bump in the on-chain schema. Existing fields keep their offsets. 3. **Error enum codes are append-only.** An existing error code always means the same thing. 4. **Breaking changes ship in new programs.** When a redesign is needed, the team deploys a new program ID (e.g. CPMM as a fresh program rather than upgrading AMM v4). Old pools continue to run on the old program; new pools go to the new one. This policy makes regenerated clients backward-compatible: a client generated against a two-version-old IDL will still decode current state correctly (it sees extra trailing bytes as padding). ## What to do when the IDL changes 1. **Update the SDK.** `npm update @raydium-io/raydium-sdk-v2`. 2. **Regenerate your client code** if you use Anchor codegen directly. 3. **Diff the account layout.** The new layout's trailing fields are the only thing your code hasn't seen; confirm whether you need them. 4. **Don't assume old instruction discriminators are invalid.** Per rule 1, they still work. 5. **Re-run integration tests** against devnet before rolling to mainnet. ## IDL troubleshooting ### "Invalid discriminator" errors Usually means a client built against version N of the IDL is trying to invoke an instruction that existed only in a pre-deploy version of the program. Re-pull the IDL from the live program: ```bash theme={null} anchor idl fetch ``` ### Account decode failures If `program.account..fetch(pubkey)` throws with "Invalid account discriminator", the account was created by a previous program version and Anchor is rejecting its 8-byte discriminator. The fix is to use the raw layout parser from the SDK (`PoolInfoLayout.decode(accountData)`) which does not enforce Anchor discriminators. ### Missing instructions in the generated client Anchor's TS codegen only generates methods for instructions whose IDL entry has a `name` that parses as a valid identifier. Raydium's instructions all satisfy this, but if you see a mismatch, check whether the IDL file is from the current SDK release. ## Pointers * [`sdk-api/rust-cpi`](/sdk-api/rust-cpi) — using the Rust CPI crates. * [`sdk-api/python-integration`](/sdk-api/python-integration) — Python via `anchorpy`. * [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — the higher-level TS client. Sources: * [Raydium IDL repository](https://github.com/raydium-io/raydium-idl) * [Anchor IDL spec](https://www.anchor-lang.com/docs/idl) # SDK & API Source: https://docs.raydium.io/sdk-api/index Reference for Raydium's TypeScript SDK, REST API, Anchor IDLs, and integration patterns for Rust, Python, and AI tools. ## Who this chapter is for Developers integrating against Raydium from off-chain (TS/JS, Python, backends) or from another Solana program (Rust CPI). Also AI agents and coding-tool integrators — see [AI integration](/sdk-api/ai-integration). **Want to run something first?** Three paste-and-go scripts — swap, deploy CPMM pool, deploy CLMM pool — live in [Quick start](/quick-start). Run a real transaction in five minutes, then come back here for the full reference. ## Pick your integration path Five paths to choose from. The right one depends on what's calling Raydium and how much control you need. | Path | Best for | Latency | Custom logic | Languages | | ------------------------------------------------- | -------------------------------------------------------------------------- | -------------- | ------------------------- | ------------------------------- | | [Trade API](/sdk-api/trade-api) | Backends, bots, Blinks — anywhere you want a server-built tx | Lowest | Limited (preset routes) | Any | | [TypeScript SDK](/sdk-api/typescript-sdk) | Node/TS apps and frontends needing full builder access | Medium | Full | TS / JS | | [Python integration](/sdk-api/python-integration) | Python backends and ML pipelines | Medium | Full (manual instr build) | Python | | [Rust CPI](/sdk-api/rust-cpi) | Composing programs that call Raydium from on-chain | N/A (on-chain) | Full + composable | Rust | | [Anchor IDL](/sdk-api/anchor-idl) | Codegen for languages without first-party support (Go, Kotlin, Swift, ...) | Variable | Full | Any with an Anchor codegen tool | If you're not sure: backends and bots → **Trade API**; frontends and full-control apps → **TypeScript SDK**; on-chain composition → **Rust CPI**. ## Chapter contents `@raydium-io/raydium-sdk-v2`: installation, initialization, core modules (pool, farm, launch), transaction builders, common pitfalls. Public endpoints for pool info, mint info, price, and server-built transactions. Rate limits, caching guidance, versioning. Server-built swap transactions: quote, build, sign, send. The SDK-free integration pattern for backends, bots, and Blinks. Where to find each program's IDL, how to regenerate clients from it, what to watch for when the IDL is updated. Patterns for invoking Raydium programs from another Solana program — account list construction, signer seeds, error handling. `solders` + `solana-py` recipes for building Raydium swaps/pool ops from Python. Useful for backends and bots. MCP server, `llms.txt` index, per-page copy menu, pre-built context files for Claude Code / Cursor / Windsurf and other coding agents. ## Version discipline Every code sample on every page in this chapter has a banner at the top: ``` SDK: @raydium-io/raydium-sdk-v2 vX.Y.Z Program: see /reference/program-addresses Cluster: mainnet-beta Verified: YYYY-MM-DD ``` If you touch a code sample, update the banner. # Python integration Source: https://docs.raydium.io/sdk-api/python-integration Building Raydium-facing bots and backends in Python via solders, solana-py, and anchorpy — environment setup, reading pool state, sending swaps, and the typical bot architecture. Raydium does not publish an official Python SDK. The patterns here compose three well-maintained community libraries: **`solders`** (Rust-bound Solana primitives), **`solana-py`** (RPC client), and **`anchorpy`** (Anchor-style instruction builders from IDLs). The combination covers everything the TS SDK does; it is just less polished. ## Environment ```bash theme={null} python -m venv .venv source .venv/bin/activate pip install solders solana anchorpy construct base58 ``` Versions that work together as of this writing: ``` solders == 0.21.* solana == 0.34.* anchorpy == 0.20.* ``` `anchorpy` periodically lags `anchor-lang`'s version; for a recently deployed Raydium program, verify the IDL compiles under your pinned `anchorpy` before committing. ## Connection and keypair ```python theme={null} from solana.rpc.async_api import AsyncClient from solders.keypair import Keypair client = AsyncClient("https://api.mainnet-beta.solana.com", commitment="confirmed") owner = Keypair.from_bytes(bytes(open("keypair.json", "rb").read())) ``` `AsyncClient` is the `async` variant; the sync `Client` is available for quick scripts but async is preferred for anything that sends multiple requests. ## Reading pool state Most production usage reads decoded pool state from Raydium's REST API (see [`sdk-api/rest-api`](/sdk-api/rest-api)) rather than decoding on-chain data manually — it is simpler and the latency is acceptable for most use cases. ```python theme={null} import httpx async def get_pool(pool_id: str) -> dict: async with httpx.AsyncClient() as http: r = await http.get( "https://api-v3.raydium.io/pools/info/ids", params={"ids": pool_id}, ) r.raise_for_status() data = r.json() if not data["success"]: raise RuntimeError(data["error"]["message"]) return data["data"][0] pool = await get_pool("58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2") print(pool["price"], pool["day"]["volume"]) ``` For bots that need lowest-possible latency, decode on-chain bytes directly: ```python theme={null} from construct import Struct, Int64ul, Int128ul, Bytes, this # Partial CPMM PoolState layout (first few fields) POOL_STATE_LAYOUT = Struct( "discriminator" / Bytes(8), "amm_config" / Bytes(32), "pool_creator" / Bytes(32), "token_0_vault" / Bytes(32), "token_1_vault" / Bytes(32), "lp_mint" / Bytes(32), "token_0_mint" / Bytes(32), "token_1_mint" / Bytes(32), # ... ) from solders.pubkey import Pubkey async def decode_pool(pool_id: Pubkey) -> dict: resp = await client.get_account_info(pool_id) data = resp.value.data return POOL_STATE_LAYOUT.parse(data) ``` The full layout is in `src/raydium/cpmm/layout.ts` (TS source); port it to `construct` as needed. `anchorpy` can do this automatically given the IDL — see below. ## Building and sending a swap For simplicity, use Raydium's **server-built-transaction** endpoint. The server returns a signed-ready transaction; you only need to add your signature: ```python theme={null} import httpx import base64 from solders.transaction import VersionedTransaction from solana.rpc.types import TxOpts async def swap(pool_id: str, amount_in: int, slippage_bps: int): async with httpx.AsyncClient() as http: r = await http.get( "https://api-v3.raydium.io/transaction/swap-base-in", params={ "poolId": pool_id, "amount": amount_in, "inputMint": "So11111111111111111111111111111111111111112", # WSOL "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", # USDC "slippageBps": slippage_bps, "wallet": str(owner.pubkey()), "txVersion": "V0", "computeUnitPriceMicroLamports": 50_000, }, ) r.raise_for_status() data = r.json()["data"] # Decode the pre-built tx, sign with our keypair, send. raw = base64.b64decode(data["tx"]["transaction"]) tx = VersionedTransaction.from_bytes(raw) tx.sign([owner]) sig = await client.send_transaction(tx, opts=TxOpts(skip_preflight=False)) await client.confirm_transaction(sig.value, commitment="confirmed") return sig.value, data["swapResponse"] ``` This is the fastest path to a working bot. The server quote expires quickly (≈30s); do not cache. ## Building a swap client-side (via `anchorpy`) For lower latency or when you cannot reach Raydium's API (sanctioned regions, air-gapped setups): ```python theme={null} from anchorpy import Program, Provider, Wallet, Context from solana.rpc.async_api import AsyncClient from solders.pubkey import Pubkey import json idl = json.load(open("cpmm.json")) # from raydium-sdk-v2 provider = Provider(client, Wallet(owner)) program = Program(idl, Pubkey.from_string(CPMM_PROGRAM_ID), provider) # Invoke swap_base_input: tx_sig = await program.rpc["swap_base_input"]( amount_in, minimum_amount_out, ctx=Context( accounts={ "payer": owner.pubkey(), "authority": owner.pubkey(), "amm_config": amm_config_pk, "pool_state": pool_state_pk, "input_token_account": user_input_ata, "output_token_account": user_output_ata, "input_vault": input_vault_pk, "output_vault": output_vault_pk, "input_token_program": TOKEN_PROGRAM_ID, "output_token_program": TOKEN_PROGRAM_ID, "input_token_mint": input_mint, "output_token_mint": output_mint, "observation_state": observation_state_pk, }, ), ) ``` PDA derivations (observation state, pool authority) follow the same formulas as in the CPMM chapter. `anchorpy` does not auto-derive them. ## Typical bot architecture A common Python Raydium bot structure: ``` ┌──────────────────┐ │ Scheduler │ cron / asyncio / redis queue └──────────┬───────┘ │ ▼ ┌──────────────────┐ │ Price poller │ httpx + Raydium REST API │ (per pool) │ or WebSocket RPC sub └──────────┬───────┘ │ event ▼ ┌──────────────────┐ │ Strategy engine │ compute signal, decide trade params └──────────┬───────┘ │ trade params ▼ ┌──────────────────┐ │ TX builder │ Raydium REST server-built-tx or anchorpy │ + signer │ solders.Keypair └──────────┬───────┘ │ VersionedTransaction ▼ ┌──────────────────┐ │ RPC sender │ solana-py AsyncClient + Jito RPC │ (retry + monitor)│ priority-fee logic └──────────┬───────┘ │ sig ▼ ┌──────────────────┐ │ Ledger store │ Postgres for positions, pending txs, PnL └──────────────────┘ ``` Key decisions for production: * **RPC provider.** Public mainnet RPCs rate-limit aggressively. Use a dedicated provider (Helius, QuickNode, Triton) for sustained traffic. * **WebSocket for pool state.** `client.account_subscribe(pool_id)` pushes updates on every state change. Much tighter than polling. * **Priority fee provider.** Helius has a `getPriorityFeeEstimate` endpoint; Triton has their own. Size your fee based on the 75th percentile of recent fees on the target program. * **Bundles for MEV-sensitive trades.** Route through Jito's block engine if you cannot tolerate sandwich risk. Python libs: `jito-sdk-python` (third-party, quality varies). ## Reading farm state ```python theme={null} FARM_V6_ID = Pubkey.from_string("...") async def get_farm_v6(farm_id: Pubkey): resp = await client.get_account_info(farm_id) return farm_v6_idl_program.account["FarmState"].decode(resp.value.data) farm = await get_farm_v6(farm_id) print(farm.total_staked, farm.reward_info_count) for r in farm.reward_infos[:farm.reward_info_count]: print(r.reward_mint, r.emission_per_second_x64) ``` `anchorpy`'s `.account["X"].decode(bytes)` gives a native Python object matching the IDL struct. ## Pitfalls ### 1. Decimal handling Python's native `float` is IEEE-754 double; amounts in 9-decimal mints (1 SOL = 1e9 units) stay accurate but ratios and products lose precision. Use `int` (`solders` returns `int` for all amount fields) and route through `decimal.Decimal` for any price arithmetic. ### 2. Slot-based vs timestamp-based reasoning Some farm versions use slot counters; LaunchLab uses timestamps. `solana-py` returns `slot` in RPC responses, but converting slot → timestamp is lossy (varies by leader schedule). If you need wall-clock time, call `get_block_time(slot)` explicitly. ### 3. Connection pool exhaustion `AsyncClient` opens one HTTP connection per request by default. Under high load, reuse `httpx.AsyncClient` sessions and set an appropriate `limits=httpx.Limits(max_connections=100)`. ### 4. Transaction size limits Python-built transactions are not smaller than TS-built ones — the 1232-byte limit applies equally. Use V0 transactions (address lookup tables) for anything that routes through more than \~2 pools. ## Pointers * [`sdk-api/rest-api`](/sdk-api/rest-api) — the HTTP endpoints used above. * [`sdk-api/anchor-idl`](/sdk-api/anchor-idl) — where to get the IDL for `anchorpy`. * [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev) — Jito bundle patterns. Sources: * [solders](https://github.com/kevinheavey/solders) * [solana-py](https://github.com/michaelhly/solana-py) * [anchorpy](https://github.com/kevinheavey/anchorpy) # REST API surface Source: https://docs.raydium.io/sdk-api/rest-api High-level overview of Raydium's eleven public HTTP services — what each one does, the conventions they share (response envelope, auth, rate limits, caching), and how to pick the right service for an integration. **Endpoint-level docs live in the [API Reference](/api-reference) tab.** Every endpoint there has an interactive **Try it** panel powered by Mintlify's OpenAPI playground — fill in parameters in the browser and hit live mainnet (or devnet, where available) directly. This page is the **narrative companion**: what services exist, when to use which, and the conventions that span all of them. If you are looking for "what does `GET /pools/info/ids` accept", click through to API Reference; if you are looking for "which service should I integrate", read on. ## The eleven services at a glance Raydium runs eleven public HTTP services. Each is documented as its own group in the [API Reference](/api-reference) tab and has an OpenAPI spec backing the interactive playground. | Service | Mainnet host | Devnet host | What it serves | | ------------------------------------------------------------------ | ------------------------------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | [API v3](/api-reference/api-v3/overview) | `api-v3.raydium.io` | `api-v3-devnet.raydium.io` | Canonical pool / mint / config / chain-info read API. The default front door for the UI and most integrators. | | [Transaction API](/api-reference/route-api-v2/overview) | `transaction-v1.raydium.io` | `transaction-v1-devnet.raydium.io` | Server-side swap transaction construction. | | [Perps API](/api-reference/perp-api-v1/overview) | `api-perp-v1.raydium.io` | — | Settings, asset metadata, RPC selection for the Raydium Perps front end. | | [LaunchLab Mint API](/api-reference/launch-mint-v1/overview) | `launch-mint-v1.raydium.io` | `launch-mint-v1-devnet.raydium.io` | Token search, indexes, leaderboards, per-mint metadata. | | [LaunchLab History API](/api-reference/launch-history-v1/overview) | `launch-history-v1.raydium.io` | `launch-history-v1-devnet.raydium.io` | Trade history and OHLC k-line aggregates for LaunchLab pools. | | [LaunchLab Forum API](/api-reference/launch-forum-v1/overview) | `launch-forum-v1.raydium.io` | `launch-forum-v1-devnet.raydium.io` | Comment threads and IPFS uploads on LaunchLab launches. **Wallet-signed.** | | [LaunchLab Auth API](/api-reference/launch-auth-v1/overview) | `launch-auth-v1.raydium.io` | `launch-auth-v1-devnet.raydium.io` | Mints short-lived `ray-token` JWTs from a wallet-signed message. Required by Forum. | | [Dynamic IPFS API](/api-reference/dynamic-ipfs-v1/overview) | `dynamic-ipfs.raydium.io` | `dynamic-ipfs-devnet.raydium.io` | Image / metadata regeneration for dynamic NFTs (CLMM positions, etc.). | | [Owner API](/api-reference/owner-api-v1/overview) | `owner-v1.raydium.io` | `owner-v1-devnet.raydium.io` | Per-wallet positions, balances, claimable rewards. | | [API v1 (legacy)](/api-reference/api-v1/overview) | `api.raydium.io` | — | Legacy `/v1` and `/v2` paths kept live for clients that have not migrated to API v3. | | [Temp API](/api-reference/temp-api-v1/overview) | `temp-api-v1.raydium.io` | `temp-api-v1-devnet.raydium.io` | Holding pen for short-lived bespoke endpoints. **Surface can change without notice.** | Versioning lives in the **hostname** for the v3 / v1 services — there is no further path-level versioning. Breaking changes ship as a new host with overlap; the team has publicly committed to at least 6 months of overlap on any v3 → v4 migration. ## Pick a service | If you want to… | Use | | ----------------------------------------------------------------------- | -------------------------------------------------------------- | | Read pool metadata, prices, APRs, fee configs | **API v3** | | Read mint metadata (name, symbol, logo, decimals, risk tags) | **API v3** `/mint/list`, `/mint/price` | | Build a swap / add-liquidity / remove-liquidity transaction server-side | **Transaction API** | | Show a wallet's positions (LP tokens, CLMM positions, farm stakes) | **Owner API** | | Search LaunchLab tokens, browse leaderboards, fetch per-mint metadata | **LaunchLab Mint API** | | Render a k-line / candlestick chart for a LaunchLab pool | **LaunchLab History API** | | Post or read comments on a LaunchLab launch | **LaunchLab Auth API** → `ray-token` → **LaunchLab Forum API** | | Render a CLMM position NFT image | **Dynamic IPFS API** | | Show futures market settings or asset lists for the Perps UI | **Perps API** | | Maintain compatibility with a v1/v2 path-prefixed client | **API v1 (legacy)** | ## Cross-cutting conventions ### Response envelope Every service except IPFS returns the same JSON envelope: ```json theme={null} { "id": "uuid-v4-per-request", "success": true, "data": { ... } } ``` On failure: ```json theme={null} { "id": "uuid-v4-per-request", "success": false, "msg": "human-readable error string", "data": null } ``` Some services additionally include an `error.code` integer (API v3 uses this for stable error identifiers across minor versions). See each service's overview page for the exact shape. ### Authentication Two patterns appear: * **No auth** — every service except Forum. Hit them anonymously over HTTPS. * **Wallet-signed handshake** — required by **LaunchLab Forum API**. Sign a Solana ed25519 message of the form `time:` with your wallet, send the signature + wallet address to **LaunchLab Auth API** `/request-token`, receive a JWT back, and pass it as the `ray-token` request header on subsequent forum calls. The Mintlify playground accepts `ray-token` in the auth panel before sending forum requests; the value is held in your browser only. ### Rate limits All hosts sit behind Cloudflare with progressive rate limiting per source IP. Published guidance for integrators: Bursts above the published limits return `HTTP 429` with a `Retry-After` header. Aggregators or bots that need higher limits should contact the Raydium team rather than hammering the public hosts — running your own indexer against the on-chain program IDs is also an option for read-heavy workloads. ### Caching and consistency * Most API v3 read endpoints are cached at the edge for 5–60 seconds; specific TTLs are noted on each endpoint's API Reference page. * The cache is invalidated by the indexer on program-touching events it observes. * During large reorgs or congestion, there can be a 1–2 slot divergence between the API's view and on-chain state. **The SDK and direct RPC reads are always more current** — if a client is about to sign a transaction, re-fetch the relevant accounts via RPC, never trust an API value blind. ### Error format Errors come back as HTTP 4xx/5xx with the same envelope (`success: false`, populated `msg`). API v3 additionally includes a stable `error.code`: ```json theme={null} { "id": "uuid-v4-per-request", "success": false, "msg": "Pool not found", "error": { "code": 40401, "message": "Pool not found" } } ``` The `error.code` is stable across minor API versions; treat it as the primary signal in client logic and `msg` as the human-readable surface. ### Mint-pair argument convention Many API v3 endpoints accept `mint1=…&mint2=…` and require `mint1 < mint2` (ascending pubkey byte order). This is so the API can return the same canonical pool regardless of caller's preferred argument order. Sort the two mints client-side before building the URL — endpoint-level docs in [API Reference](/api-reference) repeat this constraint where it applies. ## Recommended client patterns 1. **Hydrate once, refresh lazily.** Pull `GET /main/info` and `GET /mint/list` (both on API v3) at app load and cache locally with a 1-hour TTL. Both are heavily edge-cached and rarely change. 2. **Bulk where the endpoint allows it.** `GET /pools/info/ids?ids=…` accepts a comma-separated list — fetch ten pools in one request, not ten requests. 3. **Avoid hot-path price fetches.** `GET /mint/price` is fine for UI rendering; never loop it in a bot. For trading bots, run an indexer or subscribe to RPC `programSubscribe` events directly. 4. **Mirror or proxy for high throughput.** Anything over the published rate-limit ceiling should be served from your own cache layer, not directly off the public hosts. Aggregators with sustained `>120 req/min` against `transaction-v1` should be running their own quote / route engine. 5. **Re-fetch right before signing.** API responses can be 5–60s stale. For an actually-correct pool snapshot at sign time, re-read the relevant accounts via the SDK or a direct RPC `getMultipleAccounts` call. Treat API values as a lookup hint, not a settlement source. 6. **Use the Transaction API for low-friction integration.** If you do not want to bundle the SDK in your client (mobile native, bot in a constrained environment), the Transaction API will return a base64-encoded versioned transaction for the user to sign. The `swapResponse` it returns embeds a quote — treat it as valid for \~30 seconds. ## Where to go next * **Endpoint reference (interactive)** — [API Reference](/api-reference). Every service has its own group; click any endpoint for parameters, response shape, code samples, and a Try-it panel. * **TypeScript SDK** — [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk). The SDK consumes API v3 internally for several paths; for transaction building it always re-fetches state from RPC, never trusts the API blind. * **Trade API integration** — [`integration-guides/aggregator`](/integration-guides/aggregator). Patterns for wiring Raydium liquidity into a multi-DEX aggregator. * **AI-friendly docs** — [`sdk-api/ai-integration`](/sdk-api/ai-integration). Pointers for AI coding agents that need to call these APIs. # Rust CPI Source: https://docs.raydium.io/sdk-api/rust-cpi Invoking Raydium programs from another Solana program — account list construction, signer seeds, compute budget, and error propagation patterns for CPMM, CLMM, farm v6, and LaunchLab. AMM v4 is covered separately since it has no Anchor crate. CPI ("cross-program invocation") is the mechanism by which one Solana program calls another. Raydium's Anchor programs ship CPI wrapper crates that make the call site look like a typed function call — account structs with validated field names and `cpi::()` helpers. This page documents the general pattern; for product-specific snippets see the code-demos page of each product chapter. ## Cargo dependencies ```toml theme={null} [dependencies] anchor-lang = "0.29" anchor-spl = "0.29" raydium_cp_swap = { git = "https://github.com/raydium-io/raydium-cp-swap", features = ["cpi"] } raydium_amm_v3 = { git = "https://github.com/raydium-io/raydium-clmm", features = ["cpi"] } # AMM v4 and farm v6: no published Anchor CPI crate. See "AMM v4 / farm v6" below. ``` The `cpi` feature flag makes the crates compile to just the CPI surface (account structs + invokers) rather than the full program, so your binary stays small. For working CPI examples that wire up the account structs end-to-end, see [`raydium-io/raydium-cpi-example`](https://github.com/raydium-io/raydium-cpi-example) (covers AMM v4, CPMM, and CLMM). ## Account list construction Every Raydium CPI requires an `Accounts` struct in the calling program. Fields match the program's instruction account order 1-for-1, with field-level validators: ```rust theme={null} use anchor_lang::prelude::*; use anchor_spl::token::{Token, TokenAccount, Mint}; #[derive(Accounts)] pub struct MyProxySwap<'info> { #[account(mut)] pub user: Signer<'info>, /// CHECK: validated by CPMM #[account(mut)] pub pool_state: UncheckedAccount<'info>, /// CHECK: ditto pub amm_config: UncheckedAccount<'info>, /// CHECK: ditto pub pool_authority: UncheckedAccount<'info>, #[account(mut)] pub input_vault: Account<'info, TokenAccount>, #[account(mut)] pub output_vault: Account<'info, TokenAccount>, pub input_mint: Account<'info, Mint>, pub output_mint: Account<'info, Mint>, #[account(mut)] pub user_input_ata: Account<'info, TokenAccount>, #[account(mut)] pub user_output_ata: Account<'info, TokenAccount>, pub cpmm_program: Program<'info, raydium_cp_swap::program::RaydiumCpSwap>, pub token_program: Program<'info, Token>, pub token_program_2022: Program<'info, anchor_spl::token_2022::Token2022>, /// CHECK: observation PDA #[account(mut)] pub observation_state: UncheckedAccount<'info>, } ``` Most of the Raydium-side accounts are `UncheckedAccount` because the callee (Raydium) owns the validation. Your calling program only strictly validates accounts *you* own — user ATAs, your own PDAs. The `/// CHECK:` doc-comment suppresses Anchor's warning about missing checks. ## Building the CPI call Anchor generates one helper per instruction: ```rust theme={null} use raydium_cp_swap::cpi::{self, accounts::Swap as CpmmSwap}; pub fn my_proxy_swap( ctx: Context, amount_in: u64, minimum_amount_out: u64, ) -> Result<()> { let cpi_accounts = CpmmSwap { payer: ctx.accounts.user.to_account_info(), authority: ctx.accounts.user.to_account_info(), amm_config: ctx.accounts.amm_config.to_account_info(), pool_state: ctx.accounts.pool_state.to_account_info(), input_token_account: ctx.accounts.user_input_ata.to_account_info(), output_token_account: ctx.accounts.user_output_ata.to_account_info(), input_vault: ctx.accounts.input_vault.to_account_info(), output_vault: ctx.accounts.output_vault.to_account_info(), input_token_program: ctx.accounts.token_program.to_account_info(), output_token_program: ctx.accounts.token_program.to_account_info(), input_token_mint: ctx.accounts.input_mint.to_account_info(), output_token_mint: ctx.accounts.output_mint.to_account_info(), observation_state: ctx.accounts.observation_state.to_account_info(), }; let cpi_ctx = CpiContext::new( ctx.accounts.cpmm_program.to_account_info(), cpi_accounts, ); cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out)?; Ok(()) } ``` `cpi::swap_base_input` is generated from the IDL; its argument list mirrors the Anchor instruction's argument list. ## Signer seeds (PDA-signed CPI) When your program signs the CPI on behalf of a PDA (common for vaults, escrows, etc.), use `CpiContext::new_with_signer`: ```rust theme={null} let bump = ctx.accounts.my_authority_bump; let signer_seeds: &[&[&[u8]]] = &[&[b"my_authority", &[bump]]]; let cpi_ctx = CpiContext::new_with_signer( ctx.accounts.cpmm_program.to_account_info(), cpi_accounts, signer_seeds, ); cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out)?; ``` The signer seeds must match the PDA's derivation. For any account passed as `authority` (or similar signer role), the Solana runtime checks that the PDA signs via these seeds. ## Remaining accounts Some Raydium instructions take **remaining accounts** — a variable-length list appended after the fixed accounts. The canonical examples: * **CLMM `SwapV2`**: appends 1–8 `TickArrayState` accounts corresponding to the tick arrays the swap might traverse. * **Farm v6 `Deposit`**: appends `(reward_vault, user_reward_ata)` pairs for each live reward stream. Anchor's CPI helpers do not type-check remaining accounts. Pass them via `.with_remaining_accounts(...)`: ```rust theme={null} let cpi_ctx = CpiContext::new(program, accounts) .with_remaining_accounts(ctx.remaining_accounts.to_vec()); ``` Order matters: the receiver program iterates the remaining accounts in the order you pass them. For CLMM, tick arrays must be ordered directionally (first array in swap direction first). For farm v6, reward slots go in slot index order. ## Error propagation Raydium's programs return their own error enums. Anchor wraps them; your calling program sees them as `Err(ProgramError::Custom(code))`. To handle specific errors: ```rust theme={null} use raydium_cp_swap::error::ErrorCode as CpmmErr; match cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out) { Ok(_) => Ok(()), Err(e) => { msg!("CPMM swap failed: {:?}", e); // Re-raise, or convert to your own error type. Err(e) } } ``` The error code number is stable per the IDL policy ([`sdk-api/anchor-idl`](/sdk-api/anchor-idl)). You can test against specific codes by comparing against the numeric value. ## Compute budget in composed CPIs Each CPI frame has overhead (\~1,500 CU for the call itself), and the callee's own CU consumption stacks on top of yours. A transaction that calls CPMM swap from inside your program spends: ``` your_program_cu + ~1_500 (CPI overhead) + ~150_000 (CPMM swap, SPL-token variant) + ~200_000 (if Token-2022 with transfer fee) + ~10_000 (observation update) ``` For stacked routing (your program → aggregator → CPMM + CLMM + farm harvest), budget ≥500k CU. Always set an explicit `ComputeBudgetProgram::set_compute_unit_limit(...)` instruction in the transaction — the default 200k CU limit will silently exhaust. ## AMM v4 — manual Instruction construction AMM v4 has no Anchor crate. Build the `Instruction` by hand: ```rust theme={null} use anchor_lang::solana_program::program::invoke_signed; use anchor_lang::solana_program::instruction::{Instruction, AccountMeta}; const AMM_V4_PROGRAM_ID: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); // SwapBaseIn discriminator is 9. let mut data = vec![9u8]; data.extend_from_slice(&amount_in.to_le_bytes()); data.extend_from_slice(&minimum_amount_out.to_le_bytes()); let ix = Instruction { program_id: AMM_V4_PROGRAM_ID, accounts: vec![ AccountMeta::new_readonly(token_program_id, false), AccountMeta::new(amm_id, false), AccountMeta::new_readonly(amm_authority, false), // ... remaining accounts per products/amm-v4/instructions ... ], data, }; invoke_signed(&ix, &account_infos, signer_seeds)?; ``` See [`products/amm-v4/code-demos`](/products/amm-v4/code-demos) for the full account list. ## Farm v6 — reward-pair remaining accounts Farm v6's `Deposit` / `Withdraw` / `Harvest` use the `(reward_vault_i, user_reward_ata_i)` pair pattern in remaining accounts. Exact sequence: ``` remaining_accounts = [ reward_vault_0, user_reward_ata_0, reward_vault_1, user_reward_ata_1, ... ] ``` One pair per **live** (running or ended-but-unclaimed) reward slot. Omit unused slots; the program dispatches off `farm_state.reward_infos[i].reward_state`. ## Testing a CPI flow Local dev requires the Raydium programs to be available in your test validator. Options: 1. **`anchor test` with program clone** — in `Anchor.toml`: ```toml theme={null} [test.validator] clone = [ { address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK" }, # CPMM { address = "CLMM...program-id..." }, # CLMM { address = "farm-v6-program-id..." }, # farm v6 ] ``` This pulls the deployed bytecode from mainnet into your local validator. 2. **Devnet** — Raydium deploys all programs to devnet with the same program IDs as mainnet. Run `anchor test --provider.cluster devnet` to hit live code. 3. **Local deploy** — clone the Raydium repos and `anchor deploy` to a local validator. Adds test cycle overhead but lets you modify the callee for debugging. ## Pointers * [`products/cpmm/code-demos`](/products/cpmm/code-demos), [`products/clmm/code-demos`](/products/clmm/code-demos), [`products/amm-v4/code-demos`](/products/amm-v4/code-demos), [`products/farm-staking/code-demos`](/products/farm-staking/code-demos) — product-specific CPI examples. * [`sdk-api/anchor-idl`](/sdk-api/anchor-idl) — IDL retrieval and client regeneration. * [`integration-guides/cpi-integration`](/integration-guides/cpi-integration) — higher-level patterns: escrows, vaults, aggregator composition. Sources: * [raydium-cp-swap](https://github.com/raydium-io/raydium-cp-swap) * [raydium-clmm](https://github.com/raydium-io/raydium-clmm) * [Anchor CPI docs](https://www.anchor-lang.com/docs/cross-program-invocations) # Trade API Source: https://docs.raydium.io/sdk-api/trade-api Raydium's server-built swap transaction endpoint: quote, build, sign, send. The integration pattern for apps that want Raydium routing without shipping the SDK — what it returns, how it differs from the SDK, and how aggregators consume it. The **Trade API** is a thin set of endpoints on `transaction-v1.raydium.io` (and some mirrored paths on `api-v3.raydium.io`) that quote a swap, build a signed-ready Solana transaction, and return it in one round trip. It is the same surface the Raydium UI uses. Use it when you want Raydium routing without bundling the TS SDK — backends, Blinks handlers, Telegram bots, third-party apps. ## When to use the Trade API vs the SDK | You want to… | Use | | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | Integrate swaps into a backend that cannot bundle npm packages (e.g. Python bot, Go service, Rust service) | **Trade API** | | Render a swap Blink in a social post | **Trade API** | | Build a browser app where shaving kilobytes matters | **Trade API** | | Embed routing logic inside another Solana program (CPI) | Neither — use [`sdk-api/rust-cpi`](/sdk-api/rust-cpi) | | Build a full DEX-like client with custom route preview, chart overlays, priority-fee heuristics | **TS SDK** | | Need deterministic offline quoting without a network round trip | **TS SDK** (with local pool state) | The SDK is richer; the Trade API is simpler. Both wrap the same underlying CPMM/CLMM/AMM v4 programs, so the resulting on-chain swap is identical. ## The three endpoints ### 1. `GET /compute/swap-base-in` Given an input amount, pick a route and return a quote. ``` GET https://transaction-v1.raydium.io/compute/swap-base-in ?inputMint=So11111111111111111111111111111111111111112 &outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v &amount=1000000000 &slippageBps=50 &txVersion=V0 ``` Response: ```json theme={null} { "id": "b2e4...", "success": true, "version": "V1", "data": { "swapType": "BaseIn", "inputMint": "So11111111111111111111111111111111111111112", "inputAmount": "1000000000", "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "outputAmount": "165234567", "otherAmountThreshold": "164408394", "slippageBps": 50, "priceImpactPct": 0.0012, "referrerAmount": "0", "routePlan": [ { "poolId": "58oQ...", "inputMint": "So11...", "outputMint": "USDC...", "feeAmount": "2500000", "feeMint": "So11..." } ] } } ``` The `id` field is an opaque quote handle passed to the next endpoint. The quote is stable for \~30 seconds; beyond that, re-quote. ### 2. `GET /compute/swap-base-out` Inverted form: "I want to receive exactly N of the output; quote me the required input." ``` GET /compute/swap-base-out ?inputMint= &outputMint= &amount= &slippageBps=50 &txVersion=V0 ``` Symmetric response shape to `swap-base-in`; `amount` field semantics flip. ### 3. `POST /transaction/swap-base-in` and `/transaction/swap-base-out` Takes the quote from step 1 and returns a signed-ready versioned transaction: ``` POST https://transaction-v1.raydium.io/transaction/swap-base-in Content-Type: application/json { "computeUnitPriceMicroLamports": "50000", "swapResponse": { ... paste the data object from swap-base-in ... }, "txVersion": "V0", "wallet": "", "wrapSol": true, "unwrapSol": false, "inputAccount": "", "outputAccount": "" } ``` Response: ```json theme={null} { "id": "9f1c...", "success": true, "version": "V1", "data": [ { "transaction": "" } ] } ``` Multiple transactions may be returned if the swap requires setup (e.g. creating ATAs, wrapping SOL). Sign and send them in order. ## Minimal end-to-end example (Python) ```python theme={null} import base64, requests from solders.transaction import VersionedTransaction from solders.keypair import Keypair from solana.rpc.api import Client rpc = Client("https://api.mainnet-beta.solana.com") kp = Keypair.from_bytes(bytes([...])) # your signer SOL = "So11111111111111111111111111111111111111112" USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" quote = requests.get( "https://transaction-v1.raydium.io/compute/swap-base-in", params={ "inputMint": SOL, "outputMint": USDC, "amount": 1_000_000_000, "slippageBps": 50, "txVersion": "V0", }, ).json() built = requests.post( "https://transaction-v1.raydium.io/transaction/swap-base-in", json={ "computeUnitPriceMicroLamports": "50000", "swapResponse": quote, "txVersion": "V0", "wallet": str(kp.pubkey()), "wrapSol": True, "unwrapSol": False, }, ).json() for entry in built["data"]: raw = base64.b64decode(entry["transaction"]) tx = VersionedTransaction.from_bytes(raw) tx.sign([kp]) sig = rpc.send_raw_transaction(bytes(tx)).value print(f"Sent: {sig}") ``` This is \~20 lines. The equivalent with the TS SDK is \~30 but gives you richer control (custom route picker, Compute Unit budgeting per pool, detailed error metadata). ## Routing and pool selection The Trade API routes across **all** Raydium programs (CPMM, CLMM, AMM v4) and picks the best execution for the quoted size. Characteristics: * **Multi-hop supported.** A SOL→USDC swap can route through wSOL→JUP→USDC if that's cheaper. * **Same-program multi-pool splitting not supported.** A single quote goes through exactly one path; if you want to split size across pools, do it client-side (two quotes, two txs). * **Stable vs concentrated.** The router preferentially uses CLMM when in-range liquidity is adequate, falling back to CPMM for long-tail pairs. * **AMM v4 inclusion.** AMM v4 pools are included in routing but only chosen when they offer better pricing than CPMM/CLMM alternatives. To force routing through a specific pool, use the SDK instead — Trade API does not expose a pool-pin parameter. ## Referrer parameter Append `&referrer=` to the compute endpoint to take a 1% referral cut on the swap. See [`user-flows/referrals-and-blinks`](/user-flows/referrals-and-blinks) for semantics. When present: * `referrerAmount` in the quote response is the absolute amount (in input mint) that will be routed to the referrer. * The final transaction contains an extra SPL token transfer to the referrer's ATA. ## Priority fees `computeUnitPriceMicroLamports` in the build request sets the priority fee for the returned transaction. Rule of thumb: * `50_000` (0.00005 lamports/CU × 200k CU ≈ 0.00001 SOL): minimal, fine for non-congested moments. * `200_000`: moderate congestion. * `1_000_000`: heavy congestion. For adaptive tuning, call `getRecentPrioritizationFees` on your RPC first and pass the median. See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning). ## Transaction versions * `"V0"` returns a versioned (`MessageV0`) transaction with a lookup table for common accounts. Smaller, faster. **Recommended.** * `"LEGACY"` returns a legacy transaction. Larger; only use if your wallet/infra doesn't handle V0. ## Error shapes The API returns HTTP 200 with `success: false` for logical errors, HTTP 4xx/5xx for transport / infra errors. Common logical errors: * `"No route found"` — no path between the two mints at this size. Reduce `amount` or reconsider pair. * `"Insufficient liquidity"` — a route exists but would blow past `slippageBps`. Widen slippage. * `"Quote expired"` — `swapResponse` is >30s old. Re-quote. * `"Unsupported mint"` — mint is not in Raydium's universe (unlisted, or on a deprecated program). ## Rate limits * **Quote endpoints:** 120 req/min per IP. * **Build endpoints:** 60 req/min per IP (higher cost on the server). * Exceeding limits returns HTTP 429 with `Retry-After` header. For higher throughput contact Raydium developer relations; aggregator-tier keys are available. ## Architectural pattern for integrators ``` ┌─────────────┐ quote ┌───────────────┐ build ┌───────────────┐ sign/send ┌──────────┐ │ Your front │──────────►│ Trade API │──────────►│ Trade API │──────────────►│ Solana │ │ end │◄──────────│ /compute/... │◄──────────│ /transaction/ │ │ RPC │ └─────────────┘ └───────────────┘ └───────────────┘ └──────────┘ (stateless) (stateless) ``` Everything is stateless — the only thing you need to thread between the quote call and the build call is the quote response body itself. ## Where to go next * [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — richer programmatic interface with the same underlying programs. * [`sdk-api/rest-api`](/sdk-api/rest-api) — read-side endpoints (pool info, mint info) to complement Trade API's write side. * [`user-flows/swap`](/user-flows/swap) — end-to-end UI swap flow. * [`integration-guides/aggregator`](/integration-guides/aggregator) — pattern for aggregators that route across many DEXes. Sources: * `transaction-v1.raydium.io` live endpoints. * Raydium UI network-tab inspection (same surface consumed). # TypeScript SDK Source: https://docs.raydium.io/sdk-api/typescript-sdk @raydium-io/raydium-sdk-v2 end-to-end — installation, the four module facades, transaction building, and the gotchas that bite every new integrator. **Version banner.** This page documents `@raydium-io/raydium-sdk-v2@0.2.42-alpha`, the version verified for every code demo in this site (2026-04). The SDK is pre-1.0 and the type surface has evolved across releases — pin your version. ## Install ```bash theme={null} npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token # or pnpm add @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token ``` The SDK is written in TypeScript and ships `.d.ts` alongside its JS artifact. Minimum toolchain: Node 18+, TypeScript 5.0+, `moduleResolution: "bundler"` or `"node16"`. ## Initialize The entry point is `Raydium.load`: ```ts theme={null} import { Raydium } from "@raydium-io/raydium-sdk-v2"; import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js"; const connection = new Connection(process.env.RPC_URL ?? clusterApiUrl("mainnet-beta")); const owner = Keypair.fromSecretKey(/* ... */); const raydium = await Raydium.load({ owner, connection, cluster: "mainnet", // "mainnet" | "devnet" disableFeatureCheck: true, // skip the SDK's startup feature-detect API call blockhashCommitment: "confirmed", }); ``` `Raydium.load` is async because it fetches a small `/config` payload from `api-v3.raydium.io` on startup (listing current `AmmConfig` accounts, fee tiers, etc.). Set `disableFeatureCheck: true` in offline environments; you will have to supply those values manually to some builders. ## The four module facades Once loaded, the `raydium` object exposes four module facades, one per product surface: ```ts theme={null} raydium.cpmm // CPMM pools: createPool, addLiquidity, withdrawLiquidity, swap, ... raydium.clmm // CLMM pools: createPool, openPositionFromBase, increasePositionFromBase, // decreaseLiquidity, harvestAllRewards, swap, ... raydium.liquidity // AMM v4 pools: computeAmountOut, swap, addLiquidity, removeLiquidity, ... raydium.farm // Farm v3/v5/v6: deposit, withdraw, harvestAllRewards, create, setRewards raydium.launchpad // LaunchLab: createLaunchpad, buyExactIn, sellExactIn, graduate, ... raydium.trade // Multi-pool routing: quotes, route-execution (beta in 0.2.41) raydium.token // Helpers: get token list, metadata, ATA creation ``` (Yes, five facades in total — "four" is the way Raydium groups them publicly, with `trade` and `token` as supporting utilities.) ## Transaction builders Every mutating function returns a builder rather than executing immediately: ```ts theme={null} const { execute, builder, transaction, innerTransactions, extInfo } = await raydium.cpmm.addLiquidity({ poolInfo, amountInA, amountInB, slippage: 0.005, txVersion: TxVersion.V0, }); ``` Returned fields: * **`execute`** — a convenience function that signs + sends. Equivalent to `builder.execute`. * **`builder`** — the `TxBuilder` instance with all instructions and signers accumulated. Call `.build()` to get a `VersionedTransaction[]`; useful when you need to inject your own instructions or sign with external signers. * **`transaction` / `innerTransactions`** — the raw instruction arrays. Use when building composed multi-program transactions. * **`extInfo`** — product-specific extras. For example, `createPool` returns `extInfo.poolId`; `createLaunchpad` returns the new launch state PDA. `txVersion` controls legacy vs V0 transaction format. V0 (address lookup tables) is the default recommendation — it lets larger swaps fit in a single transaction. ### Why async builders? Almost every builder internally fetches on-chain state: pool info (for quotes), token program ownership (for Token-2022 vs SPL routing), account rent-exemption (for ATA creation), etc. The SDK caches aggressively but the first call for a new pool involves RPC round-trips. Keep a long-lived `raydium` instance to avoid re-fetching. ## CLMM module additions (latest release) The CLMM facade gained surfaces for the new dynamic-fee, single-sided-fee, and limit-order features: * **`raydium.clmm.createCustomizablePool`** — superset of `createPool` that accepts `collectFeeOn`, `enableDynamicFee`, and `dynamicFeeConfigId`. Use this for any new pool that needs the new knobs; classic `createPool` continues to work for default-fee pools. * **`raydium.clmm.openLimitOrder`** — open a single-tick limit order on a pool that supports them. Takes `poolInfo`, `poolKeys`, `limitOrderConfig` (from `/main/clmm-limit-order-config`), `inputMint`, `inputAmount`, and the target `tick`. * **`raydium.clmm.increaseLimitOrder` / `decreaseLimitOrder`** — adjust the unfilled portion of an existing order. Decreasing reverts on a fully-filled order with `InvalidOrderPhase`. * **`raydium.clmm.settleLimitOrder` / `settleAllLimitOrder`** — sweep filled output to the owner's ATA. Either the order's owner or the pool's `limit_order_admin` keeper can call them. * **`raydium.clmm.closeLimitOrder` / `closeAllLimitOrder`** — close fully-settled orders to recover rent. * **`raydium.api.getClmmDynamicConfigs()` / `getClmmLimitOrderConfigs()`** — REST helpers that hit the new `/main/clmm-dynamic-config` and `/main/clmm-limit-order-config` endpoints. A small reorg also moved `utils/` to `libraries/`. Code that imported from `@raydium-io/raydium-sdk-v2/utils/...` should switch to `@raydium-io/raydium-sdk-v2/libraries/...`. The top-level package barrel is unchanged, so most users never see the rename. End-to-end TypeScript walkthroughs live in [`products/clmm/code-demos`](/products/clmm/code-demos). ## Common pitfalls ### 1. Cluster mismatch The SDK's startup config is cluster-specific. Mixing `cluster: "mainnet"` with a devnet `Connection` causes silent mis-routing: the SDK quotes against mainnet `AmmConfig` but sends to devnet. Always pass both. ### 2. Forgetting to pre-create ATAs On first interaction with a mint, the user's Associated Token Account may not exist. The SDK auto-prepends an `AssociatedTokenAccount::create` instruction when it detects a missing ATA, which costs a small amount of rent. If your wallet is low on SOL this will fail silently. Check and fund before retrying. ### 3. Stale `poolInfo` `poolInfo` is a cached snapshot. If the pool state has changed since you fetched it (a large trade moved the price, say), the swap's `minAmountOut` may be computed against the old state and land below the on-chain amount-out, reverting. Re-fetch `poolInfo` immediately before building high-value transactions, or use the SDK's `computeAmountOut` which re-queries reserves. ### 4. Priority fees The SDK does not add compute-unit prices by default. In high-volume windows (new-pool launches, meme-coin events) this means your transaction competes with many others and may not land. Supply an explicit `computeBudgetConfig`: ```ts theme={null} const { execute } = await raydium.cpmm.swap({ poolInfo, inputAmount: new BN(...), swapResult: ..., slippage: 0.005, txVersion: TxVersion.V0, computeBudgetConfig: { units: 250_000, // CU limit microLamports: 50_000, // priority fee per CU }, }); ``` See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) for sizing guidance. ### 5. Slippage tolerance must match the pool type CPMM and AMM v4 are CPMM math (low impact on normal trades). CLMM is piecewise (impact jumps at tick crossings). If you copy a 0.5% slippage tolerance from a CPMM example into a CLMM swap that crosses several ticks, the transaction is likely to revert. The SDK's `computeAmountOut` returns `priceImpact`; size your tolerance above it. ### 6. `BN` vs `number` All amount fields in the SDK are `bn.js` `BN` instances — never JavaScript `number`. Converting amount values via `.toNumber()` silently truncates at `2^53`; for any value above \~9 quadrillion (not uncommon on 9-decimal mints), this produces the wrong result. Keep everything in `BN` until the final UI render. ## Versioning policy * `@raydium-io/raydium-sdk-v2` is the only SDK Raydium maintains. All docs, demos, and integration guidance target it. * An older v1 package (`@raydium-io/raydium-sdk`) exists on npm for historical reasons. Maintenance ended after CPMM and LaunchLab shipped (v1 never gained support for either), and there have been no v1 releases since 2024. Treat v1 as end-of-life: do not use it for new code, and migrate any remaining v1 integrations to v2. * SDK v2 is pre-1.0. Breaking changes between 0.x minor releases are possible; pin the version you've verified against and check the GitHub release notes when upgrading. ## Upgrading When upgrading between SDK minor versions: 1. Re-check the return type of every mutating call — shape changes (e.g. `extInfo`) land frequently. 2. Regenerate `poolInfo` fetch signatures — a field may have been renamed. 3. Re-verify your slippage handling; the SDK has shifted between auto-bound and opt-in bound behaviors across releases. 4. If you use `raydium.trade` (routing), re-verify the route shape — it is the most unstable part of the surface. ## Getting help For SDK and API questions: * **GitHub issues** — file at [github.com/raydium-io/raydium-sdk-V2/issues](https://github.com/raydium-io/raydium-sdk-V2/issues) for bugs and feature requests. The Raydium team monitors actively. * **Discord** — `#dev-support` channel at [discord.gg/raydium](https://discord.gg/raydium) for synchronous help. * **Telegram** — developer chat linked from [raydium.io](https://raydium.io) (avoid unverified Telegram groups). For security issues, do **not** post in public channels — see [`security/disclosure`](/security/disclosure). ## Pointers * [`sdk-api/rest-api`](/sdk-api/rest-api) — the HTTP complement to the SDK. * [`sdk-api/trade-api`](/sdk-api/trade-api) — server-built swap transactions. * [`sdk-api/anchor-idl`](/sdk-api/anchor-idl) — regenerating clients directly from program IDLs. * [`sdk-api/python-integration`](/sdk-api/python-integration) — Python equivalent via `solana-py`. * [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) — sizing `computeBudgetConfig`. Sources: * [Raydium SDK v2 source](https://github.com/raydium-io/raydium-sdk-V2) * Raydium SDK release notes. # Admin keys and multisig Source: https://docs.raydium.io/security/admin-and-multisig The authority surface across Raydium programs — what each admin can do, how the multisig is structured, and how to verify on-chain who currently holds each role. Every Raydium program has at least one privileged role — a key that can upgrade the program, create new configs, or withdraw protocol fees. Minimizing what these roles can do (and gating them behind multisigs with delays) is the primary defense against a compromised admin. This page catalogs the roles and how they're secured in practice. ## Roles by program ### AMM v4 | Role | Authority address | What it can do | | --------------- | ----------------------- | ------------------------------------------------- | | Program upgrade | Squads multisig (3/4) | Deploy new program bytecode | | Pool admin | Treasury multisig (3/5) | Toggle pool status, update fees on existing pools | ### CPMM | Role | Authority address | What it can do | | ------------------------- | ----------------------- | -------------------------------------------------- | | Program upgrade | Squads multisig (3/4) | Deploy new program bytecode | | AmmConfig admin | Treasury multisig (3/5) | Create new AmmConfigs (fee tiers); toggle existing | | Create pool fee recipient | Treasury multisig (3/5) | Receive the one-time create-pool fee | ### CLMM | Role | Authority address | What it can do | | ---------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Program upgrade | Squads multisig (3/4) | Deploy new program bytecode | | AmmConfig admin | Treasury multisig (3/5) | Create/modify AmmConfigs | | DynamicFeeConfig admin | Treasury multisig (3/5) | `CreateDynamicFeeConfig` / `UpdateDynamicFeeConfig` — calibrate the dynamic-fee tiers a `CreateCustomizablePool` call may opt into | | `limit_order_admin` (program-wide) | **Off-chain operational hot wallet, not a multisig** | Settle and close limit orders on behalf of their owners. The keeper key is a single program-wide constant (different value on mainnet vs devnet), checked via `signer == limit_order.owner \|\| signer == limit_order_admin::ID` on `SettleLimitOrder` / `CloseLimitOrder`. Output always lands in the original owner's ATA; the keeper cannot redirect funds, modify orders, or open new ones. | The `limit_order_admin` is a deliberately narrow operational role. It exists so an off-chain keeper can sweep filled orders without the order's owner having to be online. The keeper key is hot (lives on the keeper VM) and is rotated independently of the multisigs above. Concretely, the keeper authority is bounded to: * `SettleLimitOrder` — push the filled output of an order to the **owner's** ATA at the order's limit price. * `CloseLimitOrder` — close a fully-settled order's account to reclaim rent (rent goes to the order owner). It cannot call `OpenLimitOrder`, `IncreaseLimitOrder`, `DecreaseLimitOrder`, mutate any pool field, or sign for any other instruction — those checks are enforced on-chain by the seed and `has_one` constraints in the instruction's `Accounts` struct. A compromised keeper can at worst be unavailable (orders stay parked until the owner settles them themselves) or settle/close legitimately-fillable orders out of order; it cannot move user funds anywhere other than where the owner already authorized them to go. ### Farm v6 | Role | Authority address | What it can do | | ----------------------- | ------------------------- | ------------------------------------------------------------ | | Program upgrade | Squads multisig (3/4) | Deploy new program bytecode | | Farm creator (per farm) | The farm's creator wallet | Fund reward vaults, extend schedules, reclaim unused rewards | Individual farms have no protocol admin — each farm's creator controls only their farm, and the creator's powers are bounded (can't seize user stakes, can't change staking mint). ### LaunchLab | Role | Authority address | What it can do | | --------------------------- | --------------------------- | ------------------------------------------------------------------------- | | Program upgrade | Squads multisig (3/4) | Deploy new program bytecode | | Launch creator (per launch) | The launch's creator wallet | Collect creator fees post-graduation; withdraw un-graduated curve remnant | Launches don't have a protocol admin that can change curves or seize raised funds. ## Program upgrade authority Raydium's programs use the standard Solana **BPF Loader v3** upgrade mechanism. The upgrade authority for all programs is the 3/4 Squads multisig. Why 3/4: enough signers that a single compromise is insufficient; few enough that coordinating a legitimate upgrade is tractable. The four authorities are independent, air-gapped cold-device signers held by core team members. Sequential signing prevents parallel approvals on the same transaction; transactions carry a fixed expiration window. Multisig operations are reviewed periodically in partnership with **Solana's STRIDE Program (Asymmetric Research)**. ### Removing upgrade authority Raydium has not set any program's upgrade authority to null. The protocol operates under the principle that programs need to be upgradeable (to patch bugs, add extensions like Token-2022, fix integration drift). Trade-off: users trust that the 3/4 multisig will only deploy well-reviewed upgrades. For users who want an immutable alternative, the older AMM v4 program has been stable since its last audit; zero upgrades in 18 months. That code path is effectively frozen even though the authority still exists. ## AmmConfig authority Each new AmmConfig creation is permissioned — the 3/5 treasury multisig authorizes new fee tiers and tick spacings. Existing pools reference their AmmConfig by PDA; the pool's fee tier is whatever the AmmConfig says. **Can admins change an existing AmmConfig?** Yes, technically. `updateAmmConfig` is callable by the admin. In practice, modifications to deployed AmmConfigs are avoided because it changes the economics of all pools using that config silently. Protocol policy is to create a new AmmConfig for any change and migrate. **Can admins steal protocol fees via config?** No — the AmmConfig contains fee parameters but not the protocol fee recipient; that's a separate immutable address per pool. ## Protocol fee claim A portion of swap fees (typically 3–12 bps of 25 bps swap fee, depending on config) accrues to a protocol fee vault. The multisig can withdraw these accrued fees. Users never see their LP balance change from this — it's the protocol's pre-allocated share, not LP money. ## Farm creator authority Farms v6 give the creator the power to: * Fund the reward vault (add more tokens). * Extend the schedule (push end time later). * Call `withdrawReward` after end time to reclaim unused vault balance. Farm creators **cannot**: * Withdraw staked user LP. * Change the staking mint. * Change emission rates retroactively (only forward-looking via `setRewards`). * Freeze user harvests. A malicious farm creator can at worst under-fund the vault so the farm runs dry; users' principal stake is always safe. ## Squads multisig configuration Raydium operates two separate Squads multisigs for distinct risk surfaces. Both can be inspected on-chain via the [Squads Protocol](https://app.squads.so) UI. | Multisig | Threshold | Timelock | Scope | | ------------------- | --------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Program upgrade** | **3/4** | **24 hours** | Sole authority for deploying new program bytecode for AMM v4, CPMM, CLMM, LaunchLab, Lock, AMM Routing, Stable Swap, and the legacy Farm/Staking programs. View at [`app.squads.so/.../FytDr…ceZQK`](https://app.squads.so/squads/FytDrVzDybM1TwFQPGb8qaxZR7dBCzNeqT3vtQsceZQK/home). | | **Treasury** | **3/5** | none | Treasury assets, protocol revenue, operational expenses. Also holds Raydium's limited program-admin authority (creating AmmConfigs, sweeping protocol fees, configuring pool parameters) for the time being. View at [`v3.squads.so/dashboard/RVha…dHdtYz09`](https://v3.squads.so/dashboard/RVhaWTdGUGNjTnVFdmdIWk1DTXB3dzJGZW44b0xXQlNKemRnQ3NYM0Rqd20=). | Operational properties of the upgrade multisig: * **24-hour timelock** on any transaction. An upgrade approved today executes no earlier than 24 hours later, giving users time to respond. * **Air-gapped cold-device signing.** Cold devices have network cards physically removed; they connect only to a hardware wallet and read transaction data via QR code from a separate hot device. * **Sequential signing.** Only after one cold device generates and signs a transaction can the next cold device begin its signing process — preventing conflicting or parallel signatures on the same transaction. * **Transaction expiration.** Every transaction carries a fixed expiration window, so stale transactions auto-invalidate. * **TOTP + physical-key enforcement** on the hot devices used for transaction initiation and on-chain broadcast. * **Public transaction queue.** Anyone can monitor pending upgrades on the Squads UI. The treasury multisig has no timelock — its scope is narrower and routine ops (creating AmmConfigs, sweeping fees) need to land same-day. The treasury multisig also holds the limited program-admin authority listed in the per-program tables above; this is an interim arrangement and is reviewed periodically with the project's security partners. ## Verifying authority on-chain The simplest way to verify a program's current upgrade authority: ```bash theme={null} solana program show ``` Output includes: ``` Program Id: CPMMoo8L3F4NbTegBCKVNunggL7H1Zpdmwpwh8KMoZ0F Owner: BPFLoaderUpgradeab1e11111111111111111111111 ProgramData Address: Authority: Last Deployed In Slot: Data Length: bytes ``` If `Authority` is not the expected Squads multisig address, something is wrong. Raydium publishes the expected authority addresses on [`reference/program-addresses`](/reference/program-addresses). For AmmConfig / pool admin roles, fetch the on-chain account and decode: ```ts theme={null} const config = await raydium.cpmm.getAmmConfig(configPda); console.log("AmmConfig admin:", config.admin.toBase58()); // Expected: 3/5 treasury multisig. ``` ## Historical authority changes | Date | Program | Change | | ------- | ------------ | ------------------------------------------------------------------------- | | 2022-12 | AMM v4 | Upgrade authority migrated from single-sig to 3/4 multisig after incident | | 2023-02 | All programs | All operational roles unified under 3/5 treasury multisig | | 2023-11 | CLMM | Added second multisig for reward-only operations to reduce exposure | | 2024-05 | CPMM | Initial deploy with multisig authority from day zero | ## User-side considerations What should you do as a user/LP/integrator? 1. **Check upgrade authority before large allocations.** Confirm it matches the documented multisig. 2. **Monitor multisig activity.** [Squads UI](https://v4.squads.so) shows pending transactions; a scheduled upgrade gives you 24 hours to unwind if you disagree with the change. 3. **Time-lock-aware redemption strategies.** If you're running an auto-compounder, make sure your unwind path doesn't require an instruction that's being changed. 4. **Don't assume program immutability.** Every Raydium program can be upgraded; plan for it. ## Pitfalls for integrators ### 1. Caching authority addresses If you hardcode the upgrade authority or admin multisig address in your code and it later rotates, your verification fails. Fetch from [`reference/program-addresses`](/reference/program-addresses) at runtime or refresh periodically. ### 2. Assuming AmmConfigs are stable A new AmmConfig can be created at any time. Your aggregator/router should re-fetch the full config list periodically (hourly is fine). ### 3. Farm-creator grief vectors If you're depositing into a low-reputation farm, the creator could end the farm early and reclaim reward vault (assuming no user has staked yet). Once users have staked, pro-rata entitlements are enforced by the program; reclaim only gets the leftover after rational end. ## Pointers * [`reference/program-addresses`](/reference/program-addresses) — canonical authority addresses. * [`security/attack-vectors`](/security/attack-vectors) — how admin compromises manifest. * [`ray/treasury`](/ray/treasury) — treasury and fee collection addresses. * [`security/disclosure`](/security/disclosure) — reporting suspected admin issues. Sources: * [Squads Protocol](https://v4.squads.so) — multisig UI. * [Solana BPF Loader docs](https://docs.solana.com/developing/runtime-facilities/programs#bpf-loader) — upgrade mechanism. # Attack vectors Source: https://docs.raydium.io/security/attack-vectors Known attack patterns against AMMs and Raydium's specific exposures — sandwich/MEV, price manipulation, donation attacks, Token-2022 hook abuse, composability bugs — including how Raydium defends against each. An AMM is a juicy target for adversarial code: LPs' funds are in fully-visible pools; every swap changes price deterministically. This page catalogs the attack classes that have been demonstrated against AMMs anywhere, how they apply to Raydium specifically, and what Raydium (and integrators) do to defend. ## 1. Sandwich / MEV attacks ### Attack A bot watches the mempool / gossip stream, sees a user's swap, front-runs with a same-direction buy (pushing price), lets the user's tx execute at the worse price, then back-runs with an opposite sell. The bot profits the spread. ### Exposure * **Most exposed**: low-TVL CPMM pools and AMM v4 pools — even small trades move price meaningfully. * **Less exposed**: deep CLMM pools — within-tick trades don't move price. * **Not exposed**: farm harvests, LP deposits (ratio enforced, not price-sensitive in the same way). ### Defenses * **Jito bundles** ([`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev)) hide the tx from public mempool. * **Tight slippage** — minimum-out that's closer to expected makes sandwiches unprofitable. Below \~0.3%, most sandwiches lose money. * **Smaller trade sizes** — split a \$100k swap into 10× \$10k; each one moves price less. ### Raydium's posture Raydium's core programs don't enforce anti-MEV protections — they're neutral at the program level. Protection happens at the submission layer (Jito, wallets' built-in protection). The UI defaults slippage to 0.5% which is reasonable for most pools. ## 2. Price manipulation ### Attack A large trader temporarily moves a pool's price (by a flash loan or self-funded whale), triggers some downstream action that depends on the price (a liquidation, an oracle-derived borrow, a derivative payout), then returns the price to normal. ### Exposure * **Native Raydium operations**: not exposed. A spot swap in and out just incurs round-trip fees; the trader loses money. * **Integrated programs**: exposed if they read Raydium pool price naively. ### Defenses * **Use TWAPs**, not spot prices, for composability (see [`security/oracle-and-token-risks`](/security/oracle-and-token-risks)). * **CLMM ObservationState** gives a short-window TWAP that's not manipulable without sustained capital commitment. * **Multi-oracle consensus**: if your program reads Raydium and Pyth and Jupiter and only acts when they agree within 1%, flash-loan manipulation of any single source doesn't suffice. ### Raydium's posture CLMM ships ObservationState TWAP support; integrators who ignore it and use spot prices are on their own. Raydium's frontend uses multiple price sources for USD display. ## 3. Donation / inflation attacks ### Attack First LP in a new pool deposits a tiny amount (e.g., 1 token each of 6-decimal mints → 1 unit of LP issued). Then attacker "donates" 1,000,000 tokens directly to the pool vault via SPL Token transfer. Now 1 LP unit represents 500,000 of each mint. Any subsequent LP depositing less than that is rounded to 0 LP units and loses their deposit. ### Exposure * **CPMM / AMM v4**: potentially exposed on newly-created, low-liquidity pools. * **CLMM**: not exposed (no shared LP mint; each position is its own NFT with explicit liquidity value). ### Defenses CPMM's `initialize` instruction locks up a minimum-LP amount to the pool (inspired by Uniswap V2's `MINIMUM_LIQUIDITY` pattern). This means the first LP receives `sqrt(x × y) - MINIMUM_LIQUIDITY`, with the `MINIMUM_LIQUIDITY` (1000 units) burnt to null. A donation attack requires the attacker to donate >> the initial deposit, which becomes uneconomic. Additionally, Raydium's SDK warns loudly when the initial deposit is tiny and guides users toward sensible amounts. ### Raydium's posture The `MINIMUM_LIQUIDITY` lockup ships in CPMM; AMM v4 has a similar mechanism. Users creating pools should seed with at least `10,000+` units of each mint to make donation attacks uneconomic in any case. ## 4. Token-2022 transfer-hook abuse ### Attack A mint's transfer hook is upgradeable. Attacker deploys an innocent hook at mint launch, gets listed on Raydium, accumulates LP from users. Later, upgrades the hook to block all transfers (effectively soft-rugging — users can't withdraw). Attacker makes the pool tradeable only on one direction, buys up LP cheap, unlocks hooks, wins. ### Exposure Pools that include a transfer-hook mint. ### Defenses * **Program-level**: Raydium programs invoke the hook during swaps; if the hook blocks, the swap reverts. This doesn't prevent the attack mechanically. * **UI-level**: Raydium flags pools with transfer-hook mints. * **Integrator-level**: aggregators should skip transfer-hook mints by default and allow-list only verified hooks. ### Raydium's posture Raydium doesn't ban transfer-hook pools (legitimate hooks exist), but tags them clearly. Aggregators filtering on `tags.includes("TRANSFER_HOOK")` can exclude if desired. ## 5. Composability / CPI exploits ### Attack A program composes Raydium via CPI and introduces a bug: e.g., it passes the wrong `observation_state`, the wrong tick arrays for a CLMM swap, or double-spends an account. Attacker identifies the buggy composition and exploits. ### Exposure * **The buggy integrator** — usually the source of the bug. * **Raydium** — only if the bug triggers unintended behavior in Raydium programs themselves. ### Historical examples None of Raydium's programs have been exploited via CPI — Raydium's account validators catch mis-shaped accounts and revert. Exploits in the broader ecosystem have happened via custom-program bugs that *composed* with an AMM but didn't stem from the AMM. ### Defenses * Calling programs should use the Anchor CPI helpers (not hand-built instructions) when possible — type-safety catches most misuse. * Integration tests against mainnet-forked state cover the composition cases. ## 6. Admin / key compromise ### Attack An admin key (upgrade authority, AmmConfig admin, protocol fee claim) is compromised. Attacker deploys a malicious upgrade that drains pools, or modifies AmmConfigs to route fees to an attacker wallet, or drains protocol fees. ### Exposure All roles documented in [`security/admin-and-multisig`](/security/admin-and-multisig). ### Defenses * **3/4 multisig** on upgrade authority requires compromising 4 independent signers. * **24-hour timelock** on upgrades gives users time to unwind before a malicious upgrade activates. * **Operational monitoring** — alerts on any multisig activity via Squads' public queue. ### Historical incident AMM v4's pool authority key was compromised in December 2022 (pre-multisig). Fix: moved all authority to Squads multisig. Post-fix, no incidents. ## 7. Economic attacks on CLMM tick math ### Attack A sophisticated attacker exploits rounding or fee-accounting edge cases in CLMM tick math. Examples that have been found in *other* CLMM implementations (not Raydium): * Fee-growth accounting that rounds against the user, accumulating dust. * Tick crossing that credits/debits the wrong fee\_growth delta. * Integer overflow in `sqrtPrice * liquidity` products. ### Exposure Complex bespoke math. Audits and fuzzing are the primary defense. ### Raydium's posture CLMM has had two independent audits (OtterSec + MadShield) plus ongoing property-based fuzzing. No production-impacting bug found to date. The `sqrt_price_x64` Q64.64 arithmetic uses saturating 128-bit math with unit tests covering boundary ticks. ## 8. Position-NFT confusion ### Attack A user is tricked into signing a transaction that transfers their CLMM position NFT to an attacker. The attacker now owns the position's liquidity. ### Exposure Any position NFT holder. ### Defenses * Wallet UIs should recognize Raydium position NFTs and show them distinctly (not as generic NFTs to "send"). * Users should be wary of signing transactions that transfer NFTs. ### Raydium's posture Position NFTs implement Metaplex's metadata standard; wallet apps that understand CLMM positions display them as liquidity positions rather than tradeable NFTs. Most major Solana wallets surface them specially as of 2026. ## 9. Farm reward-stream manipulation ### Attack A farm creator funds the reward vault, attracts stakers, then calls `restartRewards` with parameters that make pending-reward computation wonky, stealing harvest value. ### Exposure Farms with malicious creators. Farm v6 bounds creator powers tightly; this attack doesn't work. ### Defenses Farm v6's admin instructions (`setRewards`, `restartRewards`, `addReward`) preserve pro-rata entitlements — the reward\_per\_share is adjusted at the moment of the change, so no pre-change accrual is retroactively corrupted. ### Raydium's posture OtterSec's farm audit specifically tested restart-rewards scenarios; no exploit found. ## 10. Simulation-vs-execution divergence ### Attack An attacker constructs a transaction that simulates successfully but reverts on execution (or vice versa). Used to grief wallets that rely on simulation for display. ### Exposure Wallets showing "you will receive X" based on simulation. ### Defenses * Use `simulateTransaction` with the same blockhash as the real submission. * Display expected output as "≈" (approximately) not exact. * Re-simulate immediately before submission. ### Raydium's posture CLMM simulation is deterministic given current pool state; divergence only happens if state changes between simulation and execution (normal case, handled via slippage bounds). ## Summary table | Vector | Raydium-specific | Defended at | Residual risk for users | | ---------------------- | ------------------ | --------------------------------- | ----------------------------- | | Sandwich / MEV | Yes | Submission layer (Jito, slippage) | Low if Jito used | | Price manipulation | Composability only | Use TWAPs | Low if TWAP consumed | | Donation attack | CPMM | MINIMUM\_LIQUIDITY | Low | | Transfer-hook abuse | Token-2022 pools | UI flags | Medium for unverified hooks | | CPI exploits | Integrators' bugs | Raydium's validators revert | Low (bug stays in integrator) | | Admin compromise | All programs | Multisig + timelock | Low (24h to react) | | CLMM tick math | CLMM | Audits + fuzzing | Low | | Position NFT confusion | CLMM | Wallet UX | Low with modern wallets | | Farm manipulation | Farm v6 | Bounded creator powers | Low | | Simulation divergence | Any | Wallet UX | Low | ## What users can do * Default to tight slippage; raise only when needed. * Use Jito-enabled wallets / swap flows. * Verify mint extensions before LP. * Monitor Squads multisig for pending upgrades. * Diversify across pools; don't concentrate all your LP in one new-launch pool. ## What integrators can do * Use ObservationState TWAPs for derivative pricing. * Validate account constraints when composing via CPI. * Filter pools by `tags` field (skip `scam`, `honeypot`, unverified transfer-hook). * Set reasonable slippage bounds; don't accept 0 slippage from user input. * Use `simulateTransaction` with care — document it's an estimate. ## Pointers * [`security/oracle-and-token-risks`](/security/oracle-and-token-risks) — Token-2022 risks in depth. * [`security/admin-and-multisig`](/security/admin-and-multisig) — authority structure. * [`security/disclosure`](/security/disclosure) — bug-bounty program. * [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev) — MEV mitigations. Sources: * [Rekt News](https://rekt.news) — DeFi post-mortems informing this list. * Audit reports linked in [`security/audits`](/security/audits). # Audits Source: https://docs.raydium.io/security/audits Audit history per Raydium program — firm, scope, date, report link — plus fix-status tracking and commentary on what audits do and don't cover. Audits catch some classes of bugs (known attack patterns, access-control mistakes, integer overflow) and miss others (economic design flaws, game-theoretic manipulation, integration bugs with other programs). Raydium's programs have multiple rounds of audits each; this page lists them and discusses what each audit actually verified. ## Per-program audit table | Program | Auditor | Date | Report | | -------------------------------------------------------- | -------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Order-book AMM | [Kudelski Security](https://kudelskisecurity.com/) | Q2 2021 | [View](https://github.com/raydium-io/raydium-docs/blob/master/audit/Kudelski%20Q2%202021/Raydium_Audit.pdf) | | Concentrated liquidity (CLMM) | [OtterSec](https://osec.io/) | Q3 2022 | [View](https://github.com/raydium-io/raydium-docs/blob/master/audit/OtterSec%20Q3%202022/Raydium%20concentrated%20liquidity%20%28CLMM%29%20program.pdf) | | Updated order-book AMM | [OtterSec](https://osec.io/) | Q3 2022 | [View](https://github.com/raydium-io/raydium-docs/blob/master/audit/OtterSec%20Q3%202022/Raydium%20updated%20order-book%20AMM%20program.pdf) | | Staking | [OtterSec](https://osec.io/) | Q3 2022 | [View](https://github.com/raydium-io/raydium-docs/blob/master/audit/OtterSec%20Q3%202022/Raydium%20staking%20program.pdf) | | Order-book AMM & OpenBook migration | [MadShield](https://www.madshield.xyz/) | Q2 2023 | [View](https://github.com/raydium-io/raydium-docs/blob/master/audit/MadSheild%20Q2%202023/Raydium%20updated%20orderbook%20AMM%20program%20%26%20OpenBook%20migration.pdf) | | Constant-product AMM (CPMM) | [MadShield](https://www.madshield.xyz/) | Q1 2024 | [View](https://github.com/raydium-io/raydium-docs/blob/master/audit/MadShield%20Q1%202024/raydium-cp-swap-v-1.0.0.pdf) | | Burn & Earn (liquidity locker) | [Halborn](https://www.halborn.com/) | Q4 2024 | [View](https://github.com/raydium-io/raydium-docs/blob/master/audit/Halborn%20Q4%202024/raydium_liquidity_locking.pdf) | | LaunchLab | [Halborn](https://www.halborn.com/) | Q2 2025 | [View](https://github.com/raydium-io/raydium-docs/blob/master/audit/Halborn%20Q2%202025/raydium_launch.pdf) | | CPMM (update) | [Sec3](https://www.sec3.dev/) | Q3 2025 | [View](https://github.com/raydium-io/raydium-docs/blob/master/audit/Sec3%20Q3%202025/raydium_cp_swap_pr55.pdf) | | CLMM update — Limit Order, Dynamic Fee, Single Asset Fee | [Sec3](https://www.sec3.dev/) | Q2 2026 | [View](https://github.com/raydium-io/raydium-docs/tree/master/audit/Sec3%20Q2%202026) | Members of the [Neodyme](https://neodyme.io/) team have also performed extensive reviews via bug-bounty agreements. All audit reports for Raydium programs are mirrored under [`github.com/raydium-io/raydium-docs/audit/`](https://github.com/raydium-io/raydium-docs/tree/master/audit). Each auditor also publishes on their own site. ## What audits cover A typical Raydium audit (\~3–6 weeks, 2 auditors) covers: * **Access control** — is every privileged operation correctly gated? * **Arithmetic** — overflows, underflows, rounding direction, fixed-point precision. * **Account validation** — does every account have the correct owner, mint, authority? * **Reentrancy-like patterns** — does state update before or after a CPI? * **PDA derivation** — are seeds consistent across all sites? * **Error codes and messages** — do error conditions revert cleanly? * **Code quality** — idiomatic Rust, dead code, unreachable branches. ## What audits don't cover * **Economic game theory** — e.g. "if I can create 1000 pools for free, can I grief the router?" * **MEV / ordering** — sandwich attacks, front-running via validator collusion. * **Off-chain infrastructure** — RPC reliability, indexer correctness, frontend. * **Integrations with other programs** — bugs that only manifest when composed with specific lending, options, or aggregator contracts. * **Emergent behaviors over time** — what happens after 10 million positions? Audits look at small-scale test cases. This is why audit ≠ safety guarantee. Raydium supplements audits with bug bounties, monitoring, and defensive engineering. ## Finding-resolution status Every audit produces a findings list (critical / high / medium / low / informational), with severity counts and per-finding status (Fixed / Acknowledged / Won't fix). Per-finding breakdowns are not duplicated here — read each report directly via the table above. ## Re-audit after significant changes When a program ships a significant upgrade (new instruction, new account field, new extension support), Raydium commissions a re-audit. The Sec3 Q3 2025 review of CPMM and the Sec3 Q2 2026 review of CLMM (Limit Order, Dynamic Fee, Single Asset Fee) listed in the table above are both re-audits of this kind. The re-audit scope is narrower (just the diff), but it's genuinely a re-audit — not just a code review. Reports for re-audits are appended to the primary audit report. ## On-chain verification The deployed program hash should match the audited code hash. Anyone can verify: ```bash theme={null} # Pull the deployed program bytecode. solana program dump program.so # Build the repo at the audited commit. git clone https://github.com/raydium-io/raydium-cp-swap cd raydium-cp-swap git checkout anchor build --verifiable # Compare. sha256sum program.so target/deploy/raydium_cp_swap.so ``` The Anchor verifiable builds produce deterministic bytecode; hashes should match exactly. If they don't, the deployed program is not the audited one — escalate. Raydium publishes the expected hashes per deploy in the repo releases section. ## How to read an audit report A short guide for non-auditors: 1. **Skip to the findings summary** — a table of severity counts. If the "Critical" count is >0 and you see "Open" status, dig in. 2. **Read each finding's description and status.** "Fixed in commit XYZ" means resolved; "Acknowledged" means the team accepted the risk; "Partially fixed" is worth a closer look. 3. **Scan the scope section.** If the audit didn't cover the instruction or account you care about, the absence of findings there isn't evidence of safety. 4. **Skim the auditor's recommendations section.** Often more useful than the findings — surfaces "we couldn't formally prove this but we're uneasy" notes. ## Bug-bounty integration Audits run pre-deploy; bug bounties run continuously post-deploy. Raydium's bounty program ([`security/disclosure`](/security/disclosure)) covers everything audits do plus: * Economic attacks that audits don't cover. * Bugs found in new integrations. * Implementation bugs in SDKs and off-chain components. A whitehat finding in the bounty program typically gets paid out faster than waiting for the next audit cycle; incentives are aligned to disclose rapidly. The active program is hosted on Immunefi: [immunefi.com/bug-bounty/raydium/information](https://immunefi.com/bug-bounty/raydium/information/). ## Historical incidents Raydium's programs have had two notable real-world incidents: ### Pool authority exploit (December 2022) **What:** AMM v4 pool authority private key was compromised, allowing an attacker to drain several pools. **Scope:** Operational key management, not a program bug. The audit had not flagged the code since the code was correct; the key management process was the failure. **Fix:** Multisig migration (all authority roles moved to Squads multisig); additional operational controls. **Lesson:** Audits don't cover key management. See [`security/admin-and-multisig`](/security/admin-and-multisig). ### OpenBook integration freeze (January 2023) **What:** An OpenBook program update changed account semantics; AMM v4's MonitorStep crank couldn't settle PnL until an AMM v4 patch shipped. **Scope:** Integration bug — neither program was wrong in isolation. **Fix:** AMM v4 patch and coordinated deploy. **Lesson:** Audits of program A don't catch bugs in program A's integration with program B. The right tool is integration testing + staged rollouts. ## Pointers * [`security/admin-and-multisig`](/security/admin-and-multisig) — authority structure. * [`security/attack-vectors`](/security/attack-vectors) — known attack patterns. * [`security/disclosure`](/security/disclosure) — bug-bounty policy. Sources: * Per-program source repositories: [`raydium-amm`](https://github.com/raydium-io/raydium-amm), [`raydium-clmm`](https://github.com/raydium-io/raydium-clmm), [`raydium-cp-swap`](https://github.com/raydium-io/raydium-cp-swap). * Audit report mirror: [`raydium-docs/audit`](https://github.com/raydium-io/raydium-docs/tree/master/audit). * Bug-bounty: [Immunefi — Raydium](https://immunefi.com/bug-bounty/raydium/information/). # Responsible disclosure Source: https://docs.raydium.io/security/disclosure Raydium's bug-bounty program: scope (on-chain program code only), severity rubric, contact paths, payout structure, plus the non-bounty channel for reporting SDK / UI / API issues. Raydium runs an active bug-bounty program **covering only the on-chain program code** — the Solana smart contracts at the program IDs listed in [`reference/program-addresses`](/reference/program-addresses). Payouts scale with severity and economic impact, reaching up to \$500,000 at the top of the critical tier. **The SDK, REST APIs, and frontend (raydium.io) are not part of the bounty.** Issues there do not pay out, but reports are still welcome — see [Non-bounty reports (SDK / API / UI)](#non-bounty-reports-sdk-api-ui) below for the contact path. This page is the source of truth for what the bounty covers, how to report, and what to expect from the response process. ## Scope ### In bounty scope (paid) The bounty applies **only to deployed on-chain program code**: * **Deployed programs** at the program IDs in [`reference/program-addresses`](/reference/program-addresses): * AMM v4 (`675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`) * CPMM (`CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C`) * CLMM (`CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK`) * Stable AMM (`5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h`) * LaunchLab (`LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj`) * Farm v3 / v5 / v6 * LP-Lock / Burn & Earn (`LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE`) * AMM Routing * **Smart-contract CPI composability bugs** where a composing program's correct use of a Raydium program causes the Raydium program itself to misbehave. ### Not in bounty scope (welcome to report — see below) * **Raydium SDK v2** (`@raydium-io/raydium-sdk-v2`) on npm. * **REST APIs** (every host listed in the [API Reference](/api-reference) tab — `api-v3.raydium.io`, `transaction-v1.raydium.io`, `launch-*-v1.raydium.io`, etc.). * **Frontend** (`raydium.io`) — including XSS, CSRF, auth-flow bugs, wallet-spoofing, broken UI states. * **Off-chain indexers, image / IPFS gateways, and any other infrastructure** that serves data into the UI. These do not pay out. Reports are still welcome and helpful — submit them through the [Non-bounty channel](#non-bounty-reports-sdk-api-ui) below. ### Out of scope (not eligible at all) * Third-party programs that compose with Raydium (report to their teams). * Third-party aggregators that route through Raydium (e.g. Jupiter). * Off-chain tooling **not maintained by Raydium** (community SDKs in Python, third-party bots, etc.). * Social-engineering attacks against Raydium team members. * Any finding requiring validator-side collusion or majority-stake attacks (this is Solana-layer, not a DeFi bounty target). * DoS via spamming public RPC endpoints. * Automated scanning-tool outputs without a working proof-of-concept. ### Gray zone — discuss first * Bugs in MEV-resistance primitives that interact with external infrastructure. * Token-2022 integration edge cases where the "correct" behavior is ambiguous. * Economic / game-theoretic attacks that don't map cleanly to a code bug. When uncertain, err on the side of reporting. ## Severity rubric Payouts are based on a blend of **severity** and **economic impact**. Examples per tier: ### Critical — \$100,000–\$500,000 * Drain of any user's LP funds via a valid Raydium instruction. * Unbounded mint of LP tokens. * Bypass of program upgrade authority. * Steal-all-protocol-fees bug. * Make the pool's accounting permanently wrong in a way that corrupts future LPs. ### High — \$25,000–\$100,000 * Steal pending rewards from farms or CLMM positions. * Freeze a user's position (they cannot close it) via a malicious tx. * Manipulate pool math such that profitable extraction is possible with moderate capital. * Bypass slippage protection. ### Medium — \$5,000–\$25,000 * Griefable DoS of a specific pool (all swaps revert). * Rounding errors that systematically favor attackers, aggregated over many swaps. * Fee misaccounting that benefits some users at others' expense. * CLMM tick-array corruption requiring manual admin intervention to fix. ### Low — \$500–\$5,000 * Information-disclosure bugs (expose non-public state). * Error-handling flaws that make diagnosis harder. * Edge cases that revert cleanly but should have been handled gracefully. * Typos in error messages that cause confusion. ### Informational — No payout (but acknowledgment) * Code-quality suggestions. * Documentation improvements. * Gas optimizations without security implications. ### Economic-impact multiplier For tier-qualifying bugs, the payout is further weighted by: * **Direct loss potential** — how much TVL is practically extractable? * **Exploitability** — is it trivially callable, or requires specific preconditions? * **Reproducibility** — does the exploit work every time, or only under rare conditions? A critical-severity bug affecting a \$10M pool pays more than a critical-severity bug affecting a \$100k pool, even though both are critical. ## Contact paths ### Primary: Immunefi Raydium's bug bounty is listed on [Immunefi](https://immunefi.com/bug-bounty/raydium/information/). Report through the Immunefi platform: 1. Create an Immunefi account. 2. Navigate to the Raydium bounty page. 3. Submit the finding with full PoC. 4. Triage typically within 24 hours. Immunefi handles escrow and payout once the finding is confirmed. ### Direct (for critical, time-sensitive findings) If the finding is being actively exploited or imminent and you cannot wait for Immunefi triage, the fastest secondary path is: * **Immunefi's emergency button** on the bounty page — escalates within minutes during business hours. * **Encrypted contact through Immunefi** — Immunefi can relay an end-to-end encrypted message to the Raydium team. Avoid public channels (X / Twitter, Telegram, Discord) for security reports — the disclosure itself can trigger exploitation. Use Immunefi for both the report and any direct contact channel established afterward. ### Non-bounty reports (SDK / API / UI) For issues in components **outside the bounty** — the SDK, the REST APIs, the `raydium.io` frontend, or any off-chain infrastructure — there is no payout, but the team still wants to hear about them. Use: * **Email**: `security@raydium.io` for anything with security implications (XSS, CSRF, auth-flow leaks, signed-message replay, wallet spoofing, sensitive data exposed by an API, etc.). Encrypt with the team's PGP key if the bug is sensitive; ask in the email and the team will exchange keys. * **GitHub issues**: for non-security functional bugs in the SDK, the docs, or any Raydium-maintained open-source repo. Open an issue on the relevant repository (e.g. [`raydium-io/raydium-sdk-V2`](https://github.com/raydium-io/raydium-sdk-V2)). * **Discord** (`discord.gg/raydium`): fine for low-impact UI / UX feedback that does not touch security. **Do not** post anything that could enable an exploit if read by a stranger. What you get from non-bounty reports: * **Acknowledgment** in the response within a few business days. * **Cross-repo coordination** if the fix spans the program and the SDK. * **Public credit** (with your consent) in the relevant changelog or release notes. * **Repeat reporters of substantive findings** are sometimes invited to a contributor program; that path is separate from the on-chain bounty and is offered on a discretionary basis. What you do not get: * A scaled payout, regardless of severity. The bounty is for program-code findings. * Coverage under the safe-harbor policy below — that policy specifically refers to bounty-scope research. For SDK / API / UI testing, follow normal responsible-disclosure conventions and standard terms of service. ## Rules of engagement ### Do * Use your own funds on mainnet for proof-of-concept (small amounts). * Develop against devnet or a forked mainnet validator when possible. * Include a working PoC in the report. * Estimate the economic impact to the best of your knowledge. * Propose a fix if you have one in mind. ### Don't * Publicly disclose the vulnerability before a fix is deployed. * Attempt to extract more funds than necessary to demonstrate the bug. * Conduct attacks against other users' funds. * Submit the same finding on multiple platforms (Immunefi, Twitter DM, email). * Attempt social engineering against Raydium team members. * Test authentication or DoS against `raydium.io` infrastructure. Violations of these rules invalidate the bounty. ## Response timeline | Phase | Target time | | ----------------------- | --------------------------------------------- | | Initial triage | ≤ 24 hours | | Severity classification | ≤ 3 business days | | Fix development | 1–30 days (depends on severity + complexity) | | Fix deployment | Subject to 24h timelock for on-chain programs | | Payout | 14 days after fix deployment | For critical findings, the fix track accelerates: the team convenes the 3/4 multisig immediately, drafts a fix, submits for review, queues the timelocked deploy. You can expect updates every 24 hours during a critical response. ## What not to disclose publicly Until a fix is deployed and the Raydium team has coordinated disclosure with you: * Don't tweet about the finding (even vague "I found something big"). * Don't describe the bug class to third parties. * Don't share PoC code with anyone outside Raydium's triage team. After fix deployment + coordinated disclosure window: * Public writeups are welcome and encouraged. * Raydium will cross-promote substantive writeups. * Researchers who consent to be named are credited on the [Immunefi Raydium leaderboard](https://immunefi.com/bug-bounty/raydium/leaderboard/). ## Safe-harbor policy Research conducted within the scope and rules above is explicitly authorized. Raydium: * Will not pursue legal action for good-faith research that follows this policy. * Will not interfere with research activities (e.g., blacklist researcher wallets). * Will collaborate on understanding the finding. Research *outside* scope or rules is not protected by safe harbor. If your research plan is borderline, ask via Immunefi's "Ask Project" channel before testing. ## Notable past disclosures Aggregated statistics for the **program-code bounty** since program inception (2021): * 200+ in-scope reports submitted across all severity tiers. * 18 critical-severity findings paid out, totaling \~\$2M. * 60+ high-severity findings paid out. * Median response time (initial triage): 8 hours. * 0 public disclosures that bypassed the coordinated process. The Hall of Fame lists researchers who consented to be named. Non-bounty reports (SDK / API / UI) are tracked separately and acknowledged in the relevant repository's release notes rather than on the Immunefi leaderboard. ## Related programs * **Solana Foundation Bug Bounty** — covers Solana validator client bugs (sealevel, consensus). Report there for Solana-layer issues. [solana.com/security](https://solana.com/security). * **Squads Protocol Bug Bounty** — covers multisig itself. [squads.so/security](https://squads.so/security). * **Immunefi** — covers many DeFi protocols including Raydium. [immunefi.com](https://immunefi.com). A bug that spans layers (e.g. a Solana validator bug that manifests on Raydium) should be reported to all applicable programs. ## Pointers * [`security/audits`](/security/audits) — prior audit history. * [`security/admin-and-multisig`](/security/admin-and-multisig) — authority structure. * [`security/attack-vectors`](/security/attack-vectors) — known attack classes. Sources: * [Immunefi Raydium bounty page](https://immunefi.com/bug-bounty/raydium/information/) — canonical scope, payout terms, and contact path. # Security & Risk Source: https://docs.raydium.io/security/index Audit history, admin controls, known risk surface, attack vectors, and responsible disclosure. ## Who this chapter is for Auditors, security researchers, risk teams at integrators, and institutional users who need to evaluate Raydium before deploying capital or code against it. ## Chapter contents Full list of audits per program, with audit firm, scope, report links, and follow-up fix status. Which authorities exist per program, what they can change, how they are controlled (multisig, timelock), and how to verify on-chain. Oracle usage and failure modes, Token-2022 extension risks (freeze authority, transfer hook, permanent delegate), and how Raydium mitigates them. Historical and hypothetical vectors: sandwich/MEV, price manipulation, donation attacks, Token-2022 hook abuse, composability risks. Responsible-disclosure policy, bug bounty scope, contact paths, and what *not* to disclose publicly. ## Writing brief * Cite specific on-chain accounts/program IDs for every claim about authority or mitigation. * Do not publish private admin procedures or operational runbooks here. * Keep historical-incident writeups factual and link to post-mortems where they exist. # Oracle and token risks Source: https://docs.raydium.io/security/oracle-and-token-risks How Raydium handles oracle inputs and the specific risks introduced by Token-2022 extensions — freeze authority, transfer hook, permanent delegate, default-frozen — including what the programs protect against and what users must accept. Raydium's core products don't depend on external oracles for pricing — the pool state *is* the oracle. But the API and SDK use external oracles for USD pricing displayed to users, and Token-2022 mints bring a richer set of controls than SPL Token, some of which fundamentally change the trust model of a pool. This page catalogs both. ## Oracles in Raydium ### Internal: the pool is the oracle For AMM v4, CPMM, and CLMM, the protocol's definition of "current price" is derived from pool state alone: * AMM v4 / CPMM: `price = vaultB_balance / vaultA_balance` (accounting for decimals). * CLMM: `price = (sqrtPriceX64 / 2^64)^2 × 10^(decimalsA - decimalsB)`. No external oracle is consulted during a swap, deposit, or withdrawal. This is the "trustless" part of AMM design: if the pool math is correct, no external manipulation of a price feed can corrupt it. ### CLMM `ObservationState` as a TWAP oracle CLMM pools maintain an `ObservationState` account that records historical `sqrt_price` snapshots. Other programs can compose against this to derive a manipulation-resistant time-weighted average price: ```rust theme={null} // A simplified TWAP: average of (current, N slots ago). let obs_now = load_observation(pool, Clock::slot()); let obs_past = load_observation(pool, Clock::slot() - 300); // ~2 min ago let twap = (obs_now.price_cumulative - obs_past.price_cumulative) / (obs_now.slot - obs_past.slot); ``` This is the same pattern Uniswap V3 uses. A short-term price manipulation (a whale temporarily pushing the pool) doesn't corrupt the TWAP because it's averaged over hundreds of slots. Programs that need a safer price feed for CLMM mints (liquidation oracles, options pricing, etc.) should use ObservationState TWAPs rather than instant prices. **Don't use instant CLMM prices for composability.** A single large swap can push spot price 10%+ on a shallow pool; the TWAP dampens this. See [`products/clmm/accounts#observation-state`](/products/clmm/accounts) for the data layout. ### External: USD pricing on frontend/API Raydium's frontend and `api-v3.raydium.io` display USD values (TVL, fee APR, \$ volume). These come from: * **Pyth** as primary oracle for major mints. * **Jupiter's aggregator price** as fallback. * **Pool-derived price** for long-tail mints without external oracle coverage. USD displays are strictly cosmetic — on-chain operations never read Pyth, and no pool math uses USD. If Pyth stops providing data for a mint, the UI shows "—"; the pool keeps functioning. ### Oracle manipulation not applicable to Raydium pools Because the pool state is the oracle, there's no "oracle attack" in the sense that bug-bounty literature means — no external manipulable data source the attacker can corrupt. Economic attacks on pool state (flash-loan-style manipulation) are covered in [`security/attack-vectors`](/security/attack-vectors). ## Token-2022 extension risks SPL Token-2022 (aka "Token Extensions") adds configurable behavior to mints via extensions. Some extensions change the trust properties of pools that include them. Raydium programs handle some automatically and surface others as user warnings. ### Transfer fee **What it is:** A configurable fee (percentage of transfer, up to a `maximum_fee` cap in absolute terms) paid by the sender to the mint authority on every transfer. **Risk:** The fee can be changed by the mint's fee-config authority. If you deposit liquidity when fee is 1%, and the authority raises it to 50%, subsequent swaps return much less than expected. **Mitigation in Raydium:** Pools read the current `transferFeeConfig` at swap time and adjust the math. The pool itself isn't corrupted, but users see worse output. The fee authority can also schedule a delayed fee change; Raydium's UI flags pools with imminent fee changes. **Residual risk:** If a malicious fee authority changes the fee during your in-flight swap, your `minimumAmountOut` protects the downside — the tx reverts. If you trust the mint issuer, this is fine; if you don't, don't LP. ### Transfer hook **What it is:** A transfer invokes a separate program (the "hook") to run custom validation or side-effects. **Risk:** The hook can block any transfer, including the pool's internal transfers during a swap. An upgradeable hook can become malicious later — what was safe at deposit time can become unswappable at withdrawal time. **Mitigation in Raydium:** Raydium lists a hook program ID in the pool state. Integrations should display the hook program ID to users so they can verify it's the expected (non-upgradeable, audited) program. **Residual risk:** If a hook is upgradeable and its authority becomes hostile, the pool can be frozen. Raydium doesn't block pools with transfer hooks, but it does flag them. LP into a transfer-hook pool only if the hook is verified safe. ### Freeze authority **What it is:** A mint's freeze authority can freeze any token account holding that mint, preventing all transfers. **Risk:** A freeze authority with the ability to freeze the pool's vault account effectively shuts down the pool — users can't withdraw, traders can't swap. This applies to SPL Token *and* Token-2022; it's not new with Token-2022, but it's still a risk. **Mitigation in Raydium:** None at the program level — SPL Token's freeze is opaque to the pool. Raydium's UI warns on pools with freezable mints. Users depositing should verify the freeze authority is null or a multisig they trust (USDC has a freeze authority; it's the issuer Circle). **Residual risk:** Accept that freezable mints can be frozen. Major mints (USDC, USDT, USDY) have freeze authorities held by the issuer and used only for regulatory compliance; this is usually acceptable. ### Permanent delegate **What it is:** A Token-2022 extension that designates a permanent delegate who can transfer tokens from any holder without approval. **Risk:** The permanent delegate can drain the pool's vault at any time. **Mitigation in Raydium:** CPMM and CLMM **refuse to create pools** with mints that have a permanent delegate. Initialization reverts. No running Raydium pool has a permanent-delegate mint. **Residual risk:** Zero (as long as the check is correct, which both audits verified). ### Non-transferable **What it is:** Mints that cannot be transferred by holders. **Risk:** Pools depend on transferability to move tokens between user ATAs and pool vaults. Non-transferable mints trivially break pools. **Mitigation in Raydium:** Pool creation reverts on non-transferable mints. Farms also refuse non-transferable staking mints. ### Default-frozen / close-authority / interest-bearing Lower-impact extensions handled by Raydium: * **Default-frozen**: new token accounts need to be thawed before use. Raydium handles this transparently on ATA creation. * **Close-authority**: a designated authority can close token accounts. Pool vaults are owned by the pool's program-derived authority, so close-authority on the *mint* doesn't apply to the vault. * **Interest-bearing**: the displayed balance accrues interest; `amount` stays fixed but `uiAmount` grows. Raydium pool math uses `amount`, not interest-accrued; display adjusts separately. ### Mint authority Not a Token-2022-specific risk, but worth noting: if a mint retains mint authority, the holder can inflate supply at will. For launched tokens, this dilutes LPs at the pool's current price. LaunchLab refuses to create launches unless the mint authority is null. ## Risk labels in the UI Raydium's frontend labels each pool with applicable risk tags: * **TRANSFER\_FEE** — non-zero transfer fee. * **TRANSFER\_HOOK** — transfer-hook extension active. * **FREEZE** — mint has a freeze authority. * **MINT** — mint has a mint authority (supply can inflate). * **CLOSE** — mint has a close authority. Aggregators consuming Raydium's API should surface these labels to users. ## Integrator checklist Before composing with a Raydium pool: * [ ] Check each mint's extensions via `getMint(mint, TOKEN_2022_PROGRAM_ID)`. * [ ] Skip pools where any mint has permanent\_delegate or non\_transferable (these shouldn't exist in Raydium, but defense in depth). * [ ] Check freeze authority for both mints; null or trusted authority only. * [ ] For transfer-hook pools, verify hook program ID against a whitelist. * [ ] Size exposure against transfer-fee scenarios (what if fee goes to max?). * [ ] Use CLMM TWAP (ObservationState) rather than spot price for derivatives pricing. ## Residual-risk acceptance statement Raydium's programs enforce what can be enforced at program level: * Pool creation refuses permanent-delegate / non-transferable mints. * Swap math adjusts correctly for current transfer fees. * ObservationState provides a manipulation-resistant oracle. Residual risks users must accept: * A mint's freeze authority can freeze the pool. * A mint's fee authority can raise transfer fee (up to `maximum_fee` cap) at any time. * A transfer-hook program can be upgraded to malicious code. * An interest-bearing mint's accrual rate can be changed. The right defense is diligence pre-LP: don't deposit into pools with mints that have concentrated authorities you don't trust. Raydium can't decide that trust for you; it can only surface the relevant facts. ## Pointers * [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees) — math adjustments for transfer-fee mints. * [`security/attack-vectors`](/security/attack-vectors) — how these risks manifest as concrete attacks. * [`integration-guides/wallet-integration`](/integration-guides/wallet-integration) — displaying Token-2022 risk labels. * [`products/clmm/accounts`](/products/clmm/accounts) — ObservationState layout. Sources: * [SPL Token-2022 extensions docs](https://spl.solana.com/token-2022/extensions). * Pool-initialization validation logic: `src/raydium/cpmm/instrument.ts`, `src/raydium/clmm/instrument.ts`. * [Pyth](https://pyth.network) — external price oracle used on frontend. # Account model Source: https://docs.raydium.io/solana-fundamentals/account-model Solana's account-centric architecture — accounts hold state, programs are stateless code that operates on accounts. Rent, ownership, program accounts vs data accounts, and what it all means when you're reading Raydium's pool state. Solana's account model is the single most important thing to understand before reading Raydium's code. Unlike Ethereum where state lives alongside contract code, Solana programs are completely stateless: all state lives in separate "accounts" that programs operate on. Every Raydium pool, position, and vault is an account — understanding how those accounts work makes the rest of the documentation make sense. ## The fundamental split: programs vs accounts ### Programs A **program** on Solana is executable code — a compiled binary loaded from a file, deployed to a `Pubkey`, and invokable via transactions. Programs have no associated state; they contain only logic. Raydium's programs: * CPMM: `CPMMoo8L3F4NbTegBCKVNunggL7H1Zpdmwpwh8KMoZ0F` * CLMM: `CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK` * AMM v4: `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8` Each is a fixed binary. The program doesn't "remember" anything between invocations. ### Accounts An **account** is a row of data on-chain. Every account has: * `pubkey` — its address. * `owner` — the program that owns it (controls writes). * `data` — the raw bytes. * `lamports` — SOL balance (1 SOL = 1,000,000,000 lamports). * `rent_epoch` — legacy rent-collection field (ignored since rent-exemption became mandatory). When you query a Raydium pool, you're reading one or more accounts. When a swap runs, the CPMM program reads/writes several accounts — the pool state, the vaults, the observation state, and the user's token accounts. ## Ownership Every account is owned by exactly one program. Only that program's code can modify the account's `data` field. A user can modify `lamports` (send/receive SOL) on an account they can sign for, but modifying `data` requires the owner program to do it on their behalf. Examples: * Your user wallet: owned by the System Program. Lamports live here; you sign to transfer. * Your USDC token account: owned by the SPL Token Program. The token program's `transfer` instruction updates the balance. * A Raydium pool state account: owned by the CPMM program. Only CPMM's instructions can modify the reserves, fees, etc. * A Raydium position NFT's `PersonalPositionState`: owned by the CLMM program. "Owned by" is strict: if program A writes to an account owned by program B, the Solana runtime rejects the transaction. ## Rent and rent-exemption Creating an account consumes storage space. Solana charges rent for that space, but as of 2020 all new accounts must be **rent-exempt** — meaning they hold enough lamports that the rent they'd owe over 2 years is pre-paid. In practice: * Rent-exempt account lives forever. * Closing the account returns the lamports to the closing signer. For a 165-byte account (e.g., SPL Token account), rent-exemption is \~0.00204 SOL. For a 1,440-byte Raydium CPMM pool state, it's \~0.011 SOL. ### Raydium rent costs | Account | Size | Rent | | -------------------------- | --------- | ----------- | | CPMM PoolState | \~1,440 B | \~0.011 SOL | | CLMM PoolState | \~1,500 B | \~0.012 SOL | | CLMM TickArray | \~9,000 B | \~0.063 SOL | | CLMM PersonalPositionState | \~280 B | \~0.003 SOL | | ATA | 165 B | \~0.002 SOL | | Vault (Token Account) | 165 B | \~0.002 SOL | Pool creation requires rent for several accounts at once — which is why CPMM pool creation costs \~0.15 SOL total. ## Data vs executable accounts Accounts come in two flavors: ### Data accounts Hold state (pool reserves, token balances, user positions). `executable = false`. This is the vast majority. ### Executable accounts Hold program bytecode. `executable = true`. These are programs (CPMM, CLMM, etc.). Programs don't have data beyond their bytecode. ### Program-derived accounts (PDAs) A PDA is a data account whose address is *derived deterministically* from a program and some seeds — no private key exists for this address. Only the derivation program can sign on behalf of a PDA via `invoke_signed`. Raydium uses PDAs extensively: * Pool state PDAs: derived from `[poolTypeDiscriminator, mintA, mintB, ammConfig]`. * Vault PDAs: derived from `[pool, mint]`. * Observation state PDA: derived from `[observationSeed, pool]`. PDAs let Raydium create accounts at predictable addresses without managing keys. Anyone can compute the PDA address for a known pool given the seeds. See [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis). ## Transactions and account references Every Solana transaction carries an explicit list of accounts it will read/write. The runtime enforces: * Listed accounts can be read or written (per their `is_writable` flag). * Unlisted accounts cannot be touched. For a Raydium swap, the transaction's account list includes: ``` [readonly] CPMM program [writable] pool state [readonly] amm config [readonly] pool authority (PDA) [writable] input vault [writable] output vault [writable] user input ATA [writable] user output ATA [readonly] input mint [readonly] output mint [readonly] input token program [readonly] output token program [writable] observation state [signer,writable] user ``` This explicit enumeration is why Solana transactions are fast and parallelizable — the runtime can determine non-conflicting txs up front. ## Account size and data layout Every Raydium account has a fixed or bounded size. Layout is defined in code (Rust structs with `#[repr(C)]`) and documented in [`sdk-api/anchor-idl`](/sdk-api/anchor-idl). Anchor programs prepend an 8-byte **discriminator** to every account they create, derived from `hash("account:")[0..8]`. This lets clients identify the type of an account just by reading the first 8 bytes — crucial for `getProgramAccounts` scans that enumerate all accounts of a type. ### Reading a Raydium pool state Via the SDK: ```ts theme={null} const pool = await raydium.cpmm.getPoolInfoFromRpc({ poolId }); console.log(pool.poolInfo); ``` Via raw RPC + layout: ```ts theme={null} const accountInfo = await connection.getAccountInfo(poolId); const data = accountInfo.data; // Skip first 8 bytes (discriminator), then parse according to struct layout. const poolState = CpmmPoolStateLayout.decode(data.slice(8)); ``` The layout is in `src/raydium/cpmm/layout.ts` in the SDK source. ## Worked example: reading a token account Let's read a user's USDC balance. ```ts theme={null} import { Connection, PublicKey } from "@solana/web3.js"; import { getAssociatedTokenAddressSync, AccountLayout } from "@solana/spl-token"; const connection = new Connection("https://api.mainnet-beta.solana.com"); const user = new PublicKey("YourUserWallet..."); const usdcMint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // 1. Compute the ATA address (PDA-style derivation). const ata = getAssociatedTokenAddressSync(usdcMint, user); // 2. Read the account. const accountInfo = await connection.getAccountInfo(ata); if (!accountInfo) { console.log("ATA doesn't exist yet (user has never held USDC)."); return; } // 3. Verify owner is SPL Token program. console.assert(accountInfo.owner.toBase58() === "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); // 4. Decode the data. const parsed = AccountLayout.decode(accountInfo.data); console.log("Balance (smallest units):", parsed.amount.toString()); console.log("Mint:", new PublicKey(parsed.mint).toBase58()); console.log("Owner:", new PublicKey(parsed.owner).toBase58()); ``` This pattern — derive address, fetch account, verify owner, decode — applies to every on-chain read, including Raydium pools. ## Why this matters for Raydium The account model shapes Raydium's design: * **Pool state is a single account** — everything about a pool (mints, reserves, fees, admin) lives in one account owned by the pool program. * **LP tokens are standard SPL token accounts** — Raydium delegates tokenization to the SPL Token program. * **Tick arrays are chunked** — CLMM can't have a single growable array of ticks because accounts have fixed allocated size; instead, it uses chunked `TickArray` PDAs. * **Position NFTs are Metaplex NFTs** — CLMM positions are standard NFTs per Metaplex; position state is a separate PDA. Understanding this lets you answer "where does X live?" questions correctly: * "Where are the pool reserves?" → two vault accounts (token accounts) owned by the SPL Token program, with authority delegated to a PDA of the pool program. * "Where is the tick data for CLMM?" → a series of TickArray PDAs, each covering 60 consecutive ticks. * "Where is my farm stake?" → a `UserLedger` PDA derived from `[user, farmId]`, owned by the farm program. ## Pointers * [`solana-fundamentals/programs-and-anchor`](/solana-fundamentals/programs-and-anchor) — how programs process accounts. * [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis) — PDA derivation and CPI. * [`solana-fundamentals/transactions-and-fees`](/solana-fundamentals/transactions-and-fees) — how accounts are referenced in transactions. * [`sdk-api/anchor-idl`](/sdk-api/anchor-idl) — account layouts for Raydium's programs. Sources: * [Solana Account docs](https://docs.solana.com/developing/programming-model/accounts). * [SPL Token program](https://spl.solana.com/token) — where token accounts live. # Solana Fundamentals Source: https://docs.raydium.io/solana-fundamentals/index The minimum Solana primitives you need to understand the Raydium programs — account model, SPL / Token-2022, PDAs, CPIs, transactions, fees, and toolchain. ## Who this chapter is for Developers and protocol researchers coming from other chains (EVM, Cosmos, Move) or new to Solana. End users can skip this chapter. The goal is pragmatic: give the reader exactly what they need to read the Products and SDK chapters without surprises. This is not a general Solana tutorial — consult the official Solana docs for that. ## Chapter contents Accounts vs. programs, rent, account ownership, data vs. executable accounts, the "account-as-database-row" mental model. Classic SPL Token program vs. Token-2022, mint and token accounts, ATA, transfer-fee and other Token-2022 extensions that affect Raydium. Instructions, transaction size limits, signers, compute budget, priority fees, the two categories of fees Raydium users pay (network + protocol). Program-derived addresses, seeds and bumps, cross-program invocation, invoke\_signed, the patterns Raydium uses for authority delegation. Program deployment, upgradable programs, Anchor framework, IDL, how to read a Raydium IDL file. Solana CLI, SPL CLI, Anchor CLI, @solana/web3.js, @coral-xyz/anchor, solders/solana-py for Python. Minimum working setup. ## Writing brief * Every page gives one worked example that is referenced later in a Raydium context. * Token-2022 gets *first-class* treatment because CPMM supports it; transfer fees and freeze authority change swap math. * Keep code short (`<30` lines) on these pages; full demos go under SDK & API. * Link back to official Solana docs once, not repeatedly. # PDAs and CPIs Source: https://docs.raydium.io/solana-fundamentals/pdas-and-cpis Program-derived addresses and cross-program invocation — the two primitives Raydium uses to manage state deterministically and compose with other programs. Seeds, bumps, invoke vs invoke_signed, and the patterns Raydium's code follows. **PDAs** (program-derived addresses) and **CPIs** (cross-program invocation) are the two primitives that make Raydium possible. PDAs let a program "own" deterministic addresses without private keys — that's how pool authorities and vaults work. CPIs let one program call another — that's how Raydium swaps tokens via the SPL Token program and how integrators compose Raydium into their own flows. Both are worth understanding before reading Raydium's source. ## PDAs: addresses without keys A **Program-Derived Address** is a public key that: * Is not on the ed25519 curve (no private key exists for it). * Is derived deterministically from a program ID and a set of seeds. * Can be signed for by *only* the derivation program, via `invoke_signed`. Every Raydium pool authority, every pool state account, every vault, every farm state — they're all PDAs. ### Derivation A PDA is computed by hashing the program ID with the seeds, then finding a "bump" byte that forces the result off-curve. The first bump (typically starting from 255 and decrementing) that produces an off-curve address wins; this is the **canonical bump**. ```ts theme={null} import { PublicKey } from "@solana/web3.js"; const [poolAuthority, bump] = PublicKey.findProgramAddressSync( [Buffer.from("authority"), poolId.toBuffer()], CPMM_PROGRAM_ID, ); ``` The seeds can be anything — strings, other pubkeys, `u64` values as little-endian bytes. Raydium's convention is a human-readable prefix followed by unique identifiers. ### Raydium PDA patterns Common PDAs in Raydium's programs: | PDA | Seeds | Program | | ------------------------ | ----------------------------------------- | ------- | | AMM authority (AMM v4) | `[b"amm authority"]` + bump | AMM v4 | | Pool state (CPMM) | `[b"pool", amm_config, mint_a, mint_b]` | CPMM | | Pool vault (CPMM) | `[b"pool_vault", pool, mint]` | CPMM | | Authority (CPMM) | `[b"vault_and_lp_mint_auth_seed"]` | CPMM | | Pool state (CLMM) | `[b"pool", amm_config, mint_0, mint_1]` | CLMM | | Tick array (CLMM) | `[b"tick_array", pool, start_tick_index]` | CLMM | | Observation (CLMM) | `[b"observation", pool]` | CLMM | | Personal position (CLMM) | `[b"position", position_nft_mint]` | CLMM | | Farm state (Farm v6) | `[b"pool_farm_state", farm_id]` | Farm v6 | | User ledger (Farm v6) | `[b"user_ledger", farm, user]` | Farm v6 | Users and integrators can compute these without fetching anything — given the public inputs (pool ID, farm ID, user key), the PDA is deterministic. ### Canonical bump Although there can in principle be multiple bumps producing off-curve addresses, Raydium's programs always use the **canonical bump** (found by decrementing from 255). This is stored in the PDA's account data so subsequent transactions can pass it in and skip the (expensive) derivation loop: ```rust theme={null} #[account] pub struct PoolState { pub bump: [u8; 1], // ... rest of pool state } ``` On subsequent transactions, the bump is read from the pool state rather than recomputed. ## CPIs: calling other programs **Cross-Program Invocation** lets a program invoke another program's instructions inline within a single transaction. Raydium uses CPIs extensively: * Swap instructions call SPL Token program to move tokens. * CLMM calls Metaplex to mint the position NFT. * Pool creation calls System Program to allocate accounts. * Farm v6 calls SPL Token to transfer rewards. Integrators also use CPIs to call *into* Raydium — that's how vault strategies, leveraged-LP protocols, and auto-compounders work. See [`integration-guides/cpi-integration`](/integration-guides/cpi-integration). ### invoke vs invoke\_signed The Solana runtime offers two CPI primitives: * **`invoke`**: call another program; the called program inherits the outer transaction's signers. * **`invoke_signed`**: call another program *on behalf of a PDA*; the runtime verifies the PDA's seeds and authorizes the signature. `invoke_signed` is the magic that lets programs hold authority over accounts without managing private keys. ### Example: Raydium transferring from a pool vault A pool vault is a Token Account whose authority is a PDA of the pool program. To transfer tokens out during a swap, the pool program must sign as that PDA: ```rust theme={null} // Seeds for the pool authority let authority_seeds: &[&[u8]] = &[ b"vault_and_lp_mint_auth_seed", &[authority_bump], ]; let signer_seeds: &[&[&[u8]]] = &[authority_seeds]; // Build CPI context let cpi_accounts = Transfer { from: input_vault.to_account_info(), to: user_ata.to_account_info(), authority: pool_authority.to_account_info(), }; let cpi_program = token_program.to_account_info(); let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds); // Execute token::transfer(cpi_ctx, amount)?; ``` The runtime sees `invoke_signed` is called by the CPMM program, verifies that `vault_and_lp_mint_auth_seed + bump` derives to `pool_authority`'s address when hashed with the CPMM program ID, and permits the authority signature on the token transfer. No private key involved. ### Example: integrator calling Raydium CPMM An integrator program (e.g., an escrow) can invoke Raydium's `swap_base_input` via CPI: ```rust theme={null} use raydium_cpmm::cpi::{self, accounts::Swap}; let cpi_accounts = Swap { payer: order.to_account_info(), // PDA, will sign authority: pool_authority_info, amm_config: amm_config_info, pool_state: pool_info, input_token_account: order_input_ata, output_token_account: order_output_ata, input_vault: input_vault_info, output_vault: output_vault_info, input_token_program: input_token_program_info, output_token_program: output_token_program_info, input_token_mint: input_mint_info, output_token_mint: output_mint_info, observation_state: observation_info, }; let seeds = &[b"order", user.key.as_ref(), &[order.bump]]; let cpi_ctx = CpiContext::new_with_signer( cpmm_program.to_account_info(), cpi_accounts, &[seeds], ); cpi::swap_base_input(cpi_ctx, amount_in, min_out)?; ``` This is the canonical integration pattern — see [`integration-guides/cpi-integration`](/integration-guides/cpi-integration) for the full escrow example. ## CPI depth limit Solana caps CPI depth at **4 levels**. A transaction's top-level instruction counts as depth 0; each CPI invocation increments depth. Practical implication: Raydium's own swap already uses 1-2 levels of CPI (Raydium → SPL Token). An integrator calling Raydium uses 2. If that integrator is called by another integrator, it's 3. The 4th level is the limit. Most compositions stay under this easily, but deep nesting (aggregator → router → Raydium → hook) can hit it. Design flat rather than deep. ## Remaining accounts When a Raydium instruction needs a variable number of accounts (e.g., CLMM swap crossing an unknown number of tick arrays), the extra accounts are passed as **remaining accounts** — appended to the fixed-account list, interpreted by position. CPMM's `SwapV2` uses remaining accounts for transfer-hook programs' extra required accounts. Clients fetch the needed accounts and append them: ```ts theme={null} const swapIx = await raydium.cpmm.swap({ /* ... */ // SDK handles remaining accounts automatically }); ``` At the CPI level, integrators must forward remaining accounts through their own instruction: ```rust theme={null} pub struct Swap<'info> { // ... fixed accounts // Plus Remaining accounts forwarded via ctx.remaining_accounts } // Forward remaining_accounts into the CPI cpi::swap_base_input( cpi_ctx.with_remaining_accounts(ctx.remaining_accounts.to_vec()), amount_in, min_out, )?; ``` ## PDA pitfalls ### Wrong seeds → wrong address A bug where seeds are in the wrong order, wrong encoding, or include/exclude an extra byte silently produces a different PDA. The transaction fails ambiguously (the program tries to read an account that doesn't exist). Always unit-test seed derivation against known golden values. ### Not storing bump If you re-derive the bump on every transaction, you pay compute for the derivation loop. Store the canonical bump in the PDA's data and read it from there. ### Confusing canonical vs non-canonical bump Non-canonical bumps (if anyone finds one that yields off-curve) are allowed by `invoke_signed` but rejected by Raydium's programs via `assert_eq!(bump, canonical_bump)`. If someone tries to claim a PDA with a non-canonical bump, the tx fails. ### Passing a PDA as signer when you're not the owning program Only the program whose ID is in the PDA's derivation can `invoke_signed` with its seeds. If you try, the runtime rejects. ## CPI pitfalls ### Forgetting to forward `remaining_accounts` If your outer instruction passes transfer-hook accounts in `remaining_accounts` but the CPI into Raydium doesn't forward them, Raydium fails because it can't find the hook accounts. Always include `with_remaining_accounts` in CPIs that need them. ### Writable flags mismatch An account that the outer instruction marks writable must also be writable in the CPI call if the called program intends to write it. Mismatch → runtime rejection. ### Not accounting for rent CPI to a program that creates an account (e.g., ATA creation) requires the payer to have enough SOL for rent. Failed rent checks appear as obscure errors. ## Worked example: computing Raydium CPMM PDAs ```ts theme={null} import { PublicKey } from "@solana/web3.js"; const CPMM_PROGRAM_ID = new PublicKey("CPMMoo8L3F4NbTegBCKVNunggL7H1Zpdmwpwh8KMoZ0F"); function computeCpmmPdas(ammConfig, mintA, mintB) { const [poolState, poolBump] = PublicKey.findProgramAddressSync( [ Buffer.from("pool"), ammConfig.toBuffer(), mintA.toBuffer(), mintB.toBuffer(), ], CPMM_PROGRAM_ID, ); const [authority] = PublicKey.findProgramAddressSync( [Buffer.from("vault_and_lp_mint_auth_seed")], CPMM_PROGRAM_ID, ); const [vaultA] = PublicKey.findProgramAddressSync( [Buffer.from("pool_vault"), poolState.toBuffer(), mintA.toBuffer()], CPMM_PROGRAM_ID, ); const [vaultB] = PublicKey.findProgramAddressSync( [Buffer.from("pool_vault"), poolState.toBuffer(), mintB.toBuffer()], CPMM_PROGRAM_ID, ); const [observation] = PublicKey.findProgramAddressSync( [Buffer.from("observation"), poolState.toBuffer()], CPMM_PROGRAM_ID, ); return { poolState, authority, vaultA, vaultB, observation, poolBump }; } ``` This is exactly what Raydium SDK does under the hood when you call `getPoolInfoFromRpc({ poolId })` — it derives the associated PDAs without a round-trip. ## Pointers * [`solana-fundamentals/account-model`](/solana-fundamentals/account-model) — how PDAs fit in the account model. * [`solana-fundamentals/programs-and-anchor`](/solana-fundamentals/programs-and-anchor) — Anchor's helpers for declaring PDAs. * [`integration-guides/cpi-integration`](/integration-guides/cpi-integration) — building integrations that CPI into Raydium. * [`sdk-api/rust-cpi`](/sdk-api/rust-cpi) — Raydium's Rust CPI types. Sources: * [Solana PDA docs](https://docs.solana.com/developing/programming-model/calling-between-programs). * [Anchor PDA & CPI docs](https://www.anchor-lang.com/docs/pdas-and-cpis). # Programs and Anchor Source: https://docs.raydium.io/solana-fundamentals/programs-and-anchor How Solana programs are deployed and upgraded, what the Anchor framework adds on top of raw Solana, and how to read Raydium's IDL to generate your own clients. Raydium's newer programs (CPMM, CLMM, Farm v6, LaunchLab) are written in **Anchor** — a Rust framework that builds on Solana's native program model to provide account validation, error handling, and an IDL (interface description). AMM v4 and older farms predate Anchor. Understanding both paradigms helps you read the code, generate clients from the IDL, and debug unexpected errors. ## Program deployment model Every Solana program lives at a `Pubkey`. The program's bytecode is stored in an **executable** account owned by the **BPF Upgradable Loader** (`BPFLoaderUpgradeab1e11111111111111111111111`). A program deployment comprises three accounts: 1. **Program account**: small metadata account at the program's ID. Owner: BPF Upgradable Loader. 2. **ProgramData account**: holds the actual bytecode. Derived as `[program_id, "programdata"]`. 3. **Buffer account** (transient): holds new bytecode during an upgrade. Discarded after the upgrade. The ProgramData account has an **upgrade authority** — a key that can replace the bytecode with a new version. Raydium's upgrade authority is a multisig behind a 24-hour timelock; see [`security/admin-and-multisig`](/security/admin-and-multisig). ### Verifying a deployed program To confirm what's on-chain matches what's in the audit-approved source: ```bash theme={null} # Dump the program from mainnet solana program dump CPMMoo8L3F4NbTegBCKVNunggL7H1Zpdmwpwh8KMoZ0F cpmm-onchain.so # Build from known source cargo build-bpf --manifest-path raydium-cp-swap/programs/cp-amm/Cargo.toml cp target/deploy/raydium_cp_swap.so cpmm-source.so # Compare sha256sum cpmm-onchain.so cpmm-source.so ``` Matching hashes prove you're interacting with the source you think you are. Raydium publishes verified-build instructions in the release notes. ## Anchor: a framework on top of Solana Raw Solana programs are Rust functions with this signature: ```rust theme={null} pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { // Manually parse instruction_data // Manually validate accounts // Manually deserialize account data // Manually check signer/writable flags // ... then do the actual work } ``` Anchor wraps all the boilerplate and lets you write: ```rust theme={null} #[program] pub mod cpmm { use super::*; pub fn swap_base_input( ctx: Context, amount_in: u64, min_out: u64, ) -> Result<()> { // Just the business logic — ctx is pre-validated } } #[derive(Accounts)] pub struct Swap<'info> { pub payer: Signer<'info>, #[account(mut, seeds = [b"pool", config.key().as_ref(), ...], bump)] pub pool_state: AccountLoader<'info, PoolState>, #[account(mut)] pub input_vault: Box>, // ... more accounts } ``` Anchor: * Auto-generates a deterministic 8-byte **discriminator** for each instruction and each account type. * Validates account constraints (owner, seeds, writable, signer, mint-matches, token-program-matches) before your code runs. * Generates an **IDL** — an interface description file that clients use to call the program. * Ships with a Rust, TypeScript, and Python client-side library. ### The 8-byte discriminator Every Anchor account and every Anchor instruction starts with an 8-byte discriminator — the first 8 bytes of SHA-256 of a fixed string: ``` Account discriminator: sha256("account:PoolState")[0..8] Instruction discriminator: sha256("global:swap_base_input")[0..8] ``` When you call an Anchor instruction, the first 8 bytes of the instruction data are this discriminator; Anchor dispatches to the right handler by looking them up. When you read an Anchor account, the first 8 bytes tell you its type — crucial for tools like `getProgramAccounts` that enumerate all accounts of a type. ### Errors Anchor programs define errors via `#[error_code]`: ```rust theme={null} #[error_code] pub enum ErrorCode { #[msg("Slippage tolerance exceeded")] SlippageExceeded, #[msg("Pool is disabled")] PoolDisabled, // ... } ``` Anchor auto-assigns these numeric codes starting from 6000 (0x1770). Raydium's full error-code table is in [`reference/error-codes`](/reference/error-codes). ## The IDL An Anchor **IDL** (Interface Description Language) file is a JSON description of a program: its instructions, accounts, types, errors, and events. It's the equivalent of an Ethereum ABI. Raydium publishes IDLs for all Anchor programs. Fetch live from on-chain: ```bash theme={null} anchor idl fetch CPMMoo8L3F4NbTegBCKVNunggL7H1Zpdmwpwh8KMoZ0F -o cpmm.idl.json ``` Or from the SDK source: `src/raydium/*/idl/*.json`. ### IDL structure ```json theme={null} { "version": "0.1.0", "name": "raydium_cp_swap", "instructions": [ { "name": "swap_base_input", "accounts": [ ... ], "args": [ ... ] }, ... ], "accounts": [ { "name": "PoolState", "type": { ... } }, ... ], "types": [ { "name": "AmmConfig", "type": { ... } }, ... ], "errors": [ { "code": 6000, "name": "SlippageExceeded", "msg": "..." }, ... ], "events": [ { "name": "SwapEvent", "fields": [ ... ] }, ... ] } ``` ### Generating a client from the IDL Anchor's `anchor` CLI generates TypeScript and Rust types: ```bash theme={null} anchor idl build -o target/idl/cpmm.json # TypeScript types auto-generated by Anchor's ts-client # Raydium SDK already includes these ``` Third-party tools like [Kinobi](https://github.com/metaplex-foundation/kinobi) can generate Rust, Python, C, or Go clients from an IDL. ### When the IDL is your friend If you want to build a custom integration that doesn't go through Raydium SDK: 1. Fetch the IDL (live from on-chain or from SDK source). 2. Look up the instruction you want (e.g., `swap_base_input`). 3. Construct the instruction data: 8-byte discriminator + encoded args. 4. Pass accounts in the order the IDL specifies. See [`sdk-api/anchor-idl`](/sdk-api/anchor-idl) for worked examples. ## Pre-Anchor programs: AMM v4 and Farm v3/v5 These programs predate Anchor. They use: * **Manual instruction dispatch**: a `u8` tag in `instruction_data` with a `match` statement. * **Manual account validation**: `if accounts[0].owner != &expected_program { ... }`. * **Borsh-serialized instruction args**: no discriminator, just `instruction_data[1..]`. * **Layout via `#[repr(C, packed)]`**: C-struct binary layout. Raydium SDK v2 ships TypeScript layouts for the non-Anchor AMM v4 instructions so clients can encode/decode without Anchor: ```ts theme={null} import { liquidityStateV4Layout, swapInstructionData } from "@raydium-io/raydium-sdk-v2"; const data = swapInstructionData.encode({ instruction: 9, // swap amountIn: 1_000_000n, minAmountOut: 950_000n, }); ``` The integration pattern is the same — you just don't get Anchor's IDL-driven auto-generation. ## Program upgrade mechanics Only the ProgramData's `upgrade_authority` can upgrade. Steps: 1. Compile the new bytecode. 2. Write it to a buffer account (`solana program write-buffer`). 3. Submit an upgrade instruction: `BpfLoaderUpgradeable::Upgrade { buffer, program, authority }`. 4. The runtime atomically replaces the program's bytecode with the buffer's contents. Raydium gates this behind a **24-hour timelock** implemented in the Squads multisig's settings. An upgrade transaction must wait 24 hours after multisig approval before execution. This protects against rushed / coerced upgrades. See [`security/admin-and-multisig`](/security/admin-and-multisig). ### Making a program immutable An upgrade authority can be set to `None`, at which point the program becomes permanently immutable. Raydium hasn't done this for any product — the team retains the ability to push security fixes. Trade-off: users must trust the multisig + timelock process. ## Programs and rent Deploying a program consumes rent-exempt lamports: * A 50 KB program: \~0.35 SOL in rent. * A 200 KB program: \~1.4 SOL in rent. Closing a program (via `solana program close`) returns the lamports. Raydium programs remain active and aren't scheduled for closure. ## Debugging Anchor programs ### Log output Anchor's `msg!` macro writes to the transaction's log. Simulate a transaction to see logs: ```ts theme={null} const sim = await connection.simulateTransaction(tx); console.log(sim.value.logs); ``` Logs include: * Program invocation (`Program CPMMoo8... invoke [1]`). * `msg!` calls from program code. * Compute unit consumption (`consumed 137842 of 400000 compute units`). * Program success or error. ### Error codes If an Anchor program throws, the log shows: ``` Program CPMMoo8... failed: custom program error: 0x1770 ``` 0x1770 = 6000 decimal = the first Anchor error (e.g., `SlippageExceeded`). Cross-reference with the IDL's `errors` array. See [`reference/error-codes`](/reference/error-codes) for Raydium's full error table. ### Account layout mismatches If you pass the wrong account in the wrong slot, Anchor's account-validation macros return errors like: ``` AnchorError: AccountNotInitialized. Error Number: 3012 ``` Error numbers below 6000 are Anchor's built-in errors (see Anchor's `ErrorCode` enum); errors ≥6000 are the program's custom codes. ## Pointers * [`solana-fundamentals/account-model`](/solana-fundamentals/account-model) — how programs own accounts. * [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis) — PDAs as Anchor declares them. * [`sdk-api/anchor-idl`](/sdk-api/anchor-idl) — fetching and using Raydium's IDLs. * [`reference/program-addresses`](/reference/program-addresses) — program IDs. * [`reference/error-codes`](/reference/error-codes) — error code reference. * [`security/admin-and-multisig`](/security/admin-and-multisig) — upgrade-authority controls. Sources: * [Anchor book](https://book.anchor-lang.com). * [Solana program deployment](https://docs.solana.com/cli/deploy-a-program). * Raydium IDLs (published in SDK `src/raydium/*/idl/*.json`). # SPL Token and Token-2022 Source: https://docs.raydium.io/solana-fundamentals/spl-token-and-token-2022 The two token standards Raydium operates on — the legacy SPL Token program and the extensions-enabled Token-2022 program. What each is, how they differ, and which Raydium products support which. Every tradable asset on Solana — including every Raydium pool's base and quote assets — is a token minted by one of two programs: the legacy **SPL Token** program or its successor, **Token-2022**. They are separate programs at separate addresses, with different account layouts and extension semantics. Raydium supports both, but not everywhere: CPMM, CLMM, and Farm v6 accept Token-2022 mints; AMM v4 does not. Understanding the split is essential before integrating with any pool. ## The two programs | | SPL Token | Token-2022 | | ---------------------------- | --------------------------------------------- | --------------------------------------------- | | Program ID | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` | | Launched | 2020 | 2022 | | Account size (token account) | 165 B | 165 B + extensions (variable) | | Extensions | No | Yes — 17+ official extensions | | Legacy compatibility | Full | Opt-in per mint | Both are maintained by the Solana Labs team (now Anza) and live under the `solana-program-library` repo. ## Why two programs? SPL Token was frozen for forward compatibility — its bytecode is effectively immutable, making a clean baseline for the entire ecosystem. As use cases grew (stablecoins wanting transfer fees, institutional mints needing freeze authorities with nuance, NFTs needing metadata pointers), the Solana team introduced **Token-2022** as a separate, extensible program rather than upgrading SPL Token. This preserves existing integrations and lets each mint opt into exactly the extensions it needs. Token-2022 is a strict superset in functionality, not in address space: the two programs coexist, and a mint at a given address belongs to exactly one of them. ## Account structure ### Mint account Defines a token's identity. SPL Token mint (82 bytes): ``` u32 mint_authority_option Pubkey mint_authority u64 supply u8 decimals bool is_initialized u32 freeze_authority_option Pubkey freeze_authority ``` Token-2022 mint: same base layout, plus zero or more **extension TLV** (type-length-value) records appended after the base. ### Token account Holds a balance of a specific mint for a specific owner. SPL Token account (165 bytes): ``` Pubkey mint Pubkey owner u64 amount u32 delegate_option Pubkey delegate u8 state // initialized, frozen u32 is_native_option u64 is_native u64 delegated_amount u32 close_authority_option Pubkey close_authority ``` Token-2022 account: same base, plus extension TLV records if any extensions are active. ## Token-2022 extensions Extensions are modular features that can be attached to mints or accounts. Each extension is a separate TLV record. The key ones for Raydium: ### Transfer fee The mint can charge a percentage fee on every transfer. Fee goes to a configured withdraw authority. Supported by Raydium CPMM and CLMM via `SwapV2` — the program accounts for the fee when computing exchange rates, so the pool math stays coherent. ```rust theme={null} let extension = TransferFeeConfig { transfer_fee_config_authority, withdraw_withheld_authority, withheld_amount: 0, older_transfer_fee: ..., newer_transfer_fee: ..., }; ``` ### Transfer hook The mint points to a program that the runtime invokes on every transfer. The hook program can reject the transfer or perform side effects (update a compliance state, log, etc.). Raydium CPMM/CLMM invoke the hook via `SwapV2` — the transaction includes the hook program and any extra accounts it needs. ### Interest-bearing The on-chain balance accrues interest at a configured rate. Display-only (balances appear higher over time) rather than actual mint; the underlying supply is unchanged. ### Mint close authority Allows the mint to be closed once supply reaches zero. ### Permanent delegate A designated wallet can transfer or burn tokens from any account unconditionally. **Raydium blocks pool creation for mints with this extension** — it's incompatible with the invariant that pool reserves can't be seized. ### Non-transferable Tokens cannot be moved from the account they're minted into. **Raydium blocks pool creation** — an untradable asset can't be an LP pool's base or quote. ### Default account state New token accounts for this mint are frozen by default and must be unfrozen by the freeze authority. Usable but rare. ### Confidential transfer Balance and transfer amounts are encrypted. Raydium does not support confidential transfer mints (pool math requires cleartext balances). ### Metadata pointer + token metadata Replaces Metaplex metadata for Token-2022 mints. Supported for Raydium pool listings. ### Group / Member pointer Declares a mint as belonging to a group (e.g., an NFT collection). Informational; Raydium uses this for display. See the [official Token-2022 extensions page](https://spl.solana.com/token-2022/extensions) for the full list. ## Which Raydium products support what | Product | SPL Token | Token-2022 | Notes | | --------- | --------- | ---------- | ----------------------------------------------- | | AMM v4 | Yes | No | OpenBook integration requires SPL Token | | CPMM | Yes | Yes | Requires `SwapV2` for Token-2022 pools | | CLMM | Yes | Yes | Requires `SwapV2` for Token-2022 pools | | Farm v6 | Yes | Yes | Supported for both stake mint and reward mints | | LaunchLab | Yes | Yes | Graduated CPMM pools inherit Token-2022 support | Mint eligibility for Raydium pools — all extensions allowed unless listed: * **Blocked**: non-transferable, permanent delegate, confidential transfer, default account state (in rejected configurations). * **Allowed with caveats** (LP must accept risk): transfer fee, transfer hook, freeze authority active. * **Fully allowed**: interest-bearing, metadata pointer, group pointer, mint close authority. The `getPoolInfoFromRpc` response includes the mint's extension flags — clients should check before LPing. ## Token account standards ### Associated Token Account (ATA) Both programs share the **Associated Token Account** convention: a PDA derived from `[owner, programId, mint]` via the Associated Token Program (`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`). Almost every user token account on Solana is an ATA. ```ts theme={null} import { getAssociatedTokenAddressSync } from "@solana/spl-token"; // SPL Token const ata = getAssociatedTokenAddressSync(mint, owner); // Token-2022 const ata22 = getAssociatedTokenAddressSync( mint, owner, false, // allowOwnerOffCurve TOKEN_2022_PROGRAM_ID, ); ``` The ATA program creates accounts owned by the appropriate token program based on which program owns the mint. ### Non-ATA token accounts A wallet can have multiple token accounts for a single mint; ATA is just the convention. Pool vaults, for instance, are not ATAs — they're PDAs of the pool program, holding the pool's reserves. ## Detecting which program a mint belongs to Every mint's account has an `owner` field pointing to either SPL Token or Token-2022: ```ts theme={null} const mintInfo = await connection.getAccountInfo(mintPubkey); if (mintInfo.owner.equals(TOKEN_PROGRAM_ID)) { console.log("SPL Token mint"); } else if (mintInfo.owner.equals(TOKEN_2022_PROGRAM_ID)) { console.log("Token-2022 mint"); } ``` The Raydium SDK handles this detection automatically — `getPoolInfoFromRpc` returns the appropriate `programId` per token so clients can construct correct ATAs. ## Swap instructions by program Raydium's CPMM and CLMM each have two swap instructions: | Instruction | Supported mints | | --------------------------------- | ----------------------------- | | `Swap` / `SwapBaseInput` (legacy) | SPL Token only | | `SwapV2` / `SwapBaseInputV2` | Both SPL Token and Token-2022 | `SwapV2` takes additional accounts: mint accounts for both sides, the token program for each side (since they may differ), and — for transfer-hook mints — the hook program and its required accounts. Clients should always use `SwapV2` when at least one side is Token-2022; `SwapV2` also works for SPL-only pools, but the legacy `Swap` is cheaper in compute. The SDK picks the right variant automatically. ## Migrating an SPL Token project to Token-2022 Token-2022 is not a drop-in replacement at the mint level — a mint at address X is either SPL or Token-2022, and that's fixed at creation. To "migrate" you must: 1. Create a new mint under Token-2022 with the extensions you want. 2. Provide a swap/wrap mechanism for holders of the old SPL mint to exchange for the new one. 3. Update all LP pools, farms, and integrations to reference the new mint. This is heavy. Most projects launched under SPL stay under SPL unless a specific extension need forces the move. ## Worked example: creating a Token-2022 mint with transfer fee ```ts theme={null} import { Connection, Keypair, SystemProgram, Transaction, sendAndConfirmTransaction, } from "@solana/web3.js"; import { TOKEN_2022_PROGRAM_ID, ExtensionType, createInitializeMintInstruction, getMintLen, createInitializeTransferFeeConfigInstruction, } from "@solana/spl-token"; const connection = new Connection("https://api.mainnet-beta.solana.com"); const payer = Keypair.generate(); const mint = Keypair.generate(); const extensions = [ExtensionType.TransferFeeConfig]; const mintLen = getMintLen(extensions); const rentLamports = await connection.getMinimumBalanceForRentExemption(mintLen); const tx = new Transaction().add( SystemProgram.createAccount({ fromPubkey: payer.publicKey, newAccountPubkey: mint.publicKey, space: mintLen, lamports: rentLamports, programId: TOKEN_2022_PROGRAM_ID, }), createInitializeTransferFeeConfigInstruction( mint.publicKey, payer.publicKey, // transfer fee authority payer.publicKey, // withdraw-withheld authority 50, // 50 bps = 0.5% BigInt(1_000_000), // max fee per transfer (smallest units) TOKEN_2022_PROGRAM_ID, ), createInitializeMintInstruction( mint.publicKey, 9, // decimals payer.publicKey, // mint authority null, // freeze authority TOKEN_2022_PROGRAM_ID, ), ); await sendAndConfirmTransaction(connection, tx, [payer, mint]); ``` This mint can be LPed into a Raydium CPMM pool; swappers will pay the 0.5% transfer fee on top of the pool's swap fee. ## Security considerations Before LPing into or swapping through a Token-2022 mint: * **Check `freeze_authority`**. If non-null and held by a centralized party, they can freeze your ATA (and arguably the pool vault). * **Check `transfer_hook`**. The hook program can block transfers arbitrarily — DYOR on the hook's source. * **Check `transfer_fee`**. Account for the fee in expected swap output. * **Check `permanent_delegate` and `non_transferable`**. Raydium's program rejects these, but verify if building a custom integration. See [`security/oracle-and-token-risks`](/security/oracle-and-token-risks) for the full risk-acceptance framework. ## Pointers * [`solana-fundamentals/account-model`](/solana-fundamentals/account-model) — the general account model. * [`reference/token-2022-support`](/reference/token-2022-support) — Token-2022 specifics for CPMM and CLMM. * [`security/oracle-and-token-risks`](/security/oracle-and-token-risks) — risks from malicious mint extensions. Sources: * [SPL Token docs](https://spl.solana.com/token). * [Token-2022 docs](https://spl.solana.com/token-2022) — full extension list. * [Associated Token Account](https://spl.solana.com/associated-token-account). # Toolchain Source: https://docs.raydium.io/solana-fundamentals/toolchain Minimum working setup for reading Raydium on-chain state, building transactions, and developing against the programs. Solana CLI, Anchor, SDKs in TypeScript and Python, and devnet/localnet tips. Everything in this documentation assumes a working Solana development toolchain. This page lists the tools, the minimum versions known to work with Raydium as of April 2026, and the specific setup gotchas. Start here before running any of the code examples in later chapters. ## Core tools ### Solana CLI The canonical command-line interface for interacting with Solana clusters — wallet management, RPC calls, program deployment. Install: ```bash theme={null} sh -c "$(curl -sSfL https://release.solana.com/v1.18.22/install)" # Or latest stable: sh -c "$(curl -sSfL https://release.solana.com/stable/install)" ``` Verify: ```bash theme={null} solana --version # → solana-cli 1.18.22 (or later) ``` Configure for mainnet: ```bash theme={null} solana config set --url https://api.mainnet-beta.solana.com solana config set --keypair ~/.config/solana/id.json solana-keygen new # create a wallet if you don't have one solana address # your public key ``` For Raydium testing, you'll almost always want a funded devnet wallet: ```bash theme={null} solana config set --url https://api.devnet.solana.com solana airdrop 2 # 2 SOL for testing ``` ### SPL Token CLI The official CLI for SPL Token / Token-2022 operations — creating mints, minting tokens, transferring. Install (included with Solana CLI): ```bash theme={null} spl-token --version ``` Common ops: ```bash theme={null} spl-token create-token # create an SPL Token mint spl-token create-token --program-id TOKEN_2022 ... # create a Token-2022 mint spl-token create-account # create an ATA spl-token mint 1000 # mint 1000 tokens to your ATA spl-token transfer 100 # transfer 100 spl-token display # inspect ``` ### Anchor CLI Needed for fetching Raydium IDLs, building clients, and verifying programs. Install: ```bash theme={null} cargo install --git https://github.com/coral-xyz/anchor avm --locked avm install 0.30.1 avm use 0.30.1 anchor --version ``` Common ops: ```bash theme={null} anchor idl fetch -o program.idl.json # fetch the on-chain IDL anchor idl init -f idl.json # upload an IDL anchor build # build a program from source ``` ## TypeScript setup The primary integration path for Raydium. ### Packages ```bash theme={null} npm install \ @solana/web3.js \ @solana/spl-token \ @coral-xyz/anchor \ @raydium-io/raydium-sdk-v2 \ bn.js \ decimal.js ``` Versions known to work together as of April 2026: | Package | Version | | ---------------------------- | ------------- | | `@solana/web3.js` | ≥1.95 | | `@solana/spl-token` | ≥0.4 | | `@coral-xyz/anchor` | ≥0.30 | | `@raydium-io/raydium-sdk-v2` | ≥0.2.42-alpha | ### Minimal script ```ts theme={null} import { Connection, Keypair } from "@solana/web3.js"; import { Raydium } from "@raydium-io/raydium-sdk-v2"; const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); const owner = Keypair.fromSecretKey(/* your key */); const raydium = await Raydium.load({ connection, owner, cluster: "mainnet", disableFeatureCheck: true, }); const poolInfo = await raydium.api.fetchPoolById({ id: "..." }); console.log(poolInfo); ``` See [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) for the full SDK reference. ## Rust setup For CPI integration and on-chain programs. ### Toolchain ```bash theme={null} rustup install 1.76.0 # or whatever Anchor current requires rustup component add rustfmt clippy solana-install init 1.18.22 # BPF toolchain included ``` ### Program Cargo.toml for a Raydium CPI integration ```toml theme={null} [dependencies] anchor-lang = "0.30.1" anchor-spl = "0.30.1" raydium-cp-swap = { version = "0.2", features = ["cpi"] } raydium-clmm = { version = "0.1", features = ["cpi"] } ``` The `"cpi"` feature imports the `cpi` module with CPI helpers (so you can `raydium_cp_swap::cpi::swap_base_input(...)`). See [`sdk-api/rust-cpi`](/sdk-api/rust-cpi). ## Python setup Secondary integration path — common for bot developers. ### Packages ```bash theme={null} pip install solders solana-py anchorpy pip install raydium-py # community Raydium SDK (not official) ``` Versions: | Package | Version | | ------------ | ------- | | `solders` | ≥0.21 | | `solana-py` | ≥0.34 | | `anchorpy` | ≥0.21 | | `raydium-py` | ≥0.2.1 | ### Minimal script ```python theme={null} from solana.rpc.async_api import AsyncClient from solders.keypair import Keypair from anchorpy import Provider, Wallet, Program async def main(): conn = AsyncClient("https://api.mainnet-beta.solana.com") owner = Keypair() wallet = Wallet(owner) provider = Provider(conn, wallet) # Load IDL and program with open("cpmm.idl.json") as f: idl = json.load(f) program = Program(idl, CPMM_PROGRAM_ID, provider) pool = await program.account["PoolState"].fetch(pool_id) print(pool) ``` See [`sdk-api/python-integration`](/sdk-api/python-integration). ## RPC endpoints Public mainnet RPC (`api.mainnet-beta.solana.com`) is heavily rate-limited and throttles under any load. For non-trivial usage, get a private endpoint: | Provider | Notes | | ----------------- | ------------------------------------------- | | Helius | Most popular; generous free tier. | | Triton | Enterprise; premium pricing, stable. | | QuickNode | Solid; sub-per-second latency. | | Your own RPC node | Costs \~\$500/mo in hardware; full control. | Configure: ```ts theme={null} const connection = new Connection("https://mainnet.helius-rpc.com/?api-key=..."); ``` Some RPC operations need extended capabilities: * `getProgramAccounts` — unrestricted scans are expensive; some providers gate or charge per scan. * `getPriorityFeeEstimate` — Helius-specific endpoint; others have equivalents. * `geyser` / WebSocket streaming — needed for low-latency bots; not all providers expose it. ## Devnet and localnet ### Devnet Solana's public test cluster. Raydium has a partial devnet deployment: * Some CPMM pools exist for testing. * AMM v4 has historical devnet pools. * CLMM has a few demo pools. * Farm v6 is deployed. ```bash theme={null} solana config set --url https://api.devnet.solana.com solana airdrop 2 ``` The SDK supports devnet via `cluster: "devnet"`: ```ts theme={null} const raydium = await Raydium.load({ connection, owner, cluster: "devnet" }); ``` **Caveat**: devnet pools have thin liquidity and may have stale state. Don't rely on devnet for price discovery; use it for building-and-testing-instruction flows only. ### Localnet with a forked mainnet state For realistic testing, fork mainnet state into a local validator: ```bash theme={null} solana-test-validator \ --url https://api.mainnet-beta.solana.com \ --clone CPMMoo8L3F4NbTegBCKVNunggL7H1Zpdmwpwh8KMoZ0F \ --clone CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK \ --clone \ --clone ``` This clones specific program/account state to localhost, where you can transact freely. Ideal for: * Unit-testing your CPI integration against a real Raydium program. * Replaying a transaction with a modified parameter to debug. * Stress-testing with large synthetic volumes. Raydium's team uses forked localnet extensively for regression testing prior to mainnet deployment. ## Project templates ### TypeScript integration starter ``` my-raydium-app/ ├── package.json ├── tsconfig.json ├── src/ │ ├── swap.ts # swap example │ ├── add-liquidity.ts │ └── read-pool.ts └── .env # RPC URL, wallet path ``` Minimal `package.json`: ```json theme={null} { "name": "my-raydium-app", "type": "module", "scripts": { "swap": "tsx src/swap.ts" }, "dependencies": { "@raydium-io/raydium-sdk-v2": "^0.2.42-alpha", "@solana/web3.js": "^1.95", "@solana/spl-token": "^0.4" }, "devDependencies": { "@types/node": "^20", "tsx": "^4", "typescript": "^5" } } ``` ### Anchor CPI program starter ``` my-raydium-cpi/ ├── Anchor.toml ├── Cargo.toml ├── programs/ │ └── my-cpi/ │ ├── Cargo.toml │ └── src/lib.rs └── tests/ └── my-cpi.ts ``` With Anchor 0.30: ```bash theme={null} anchor init my-raydium-cpi cd my-raydium-cpi # add raydium-cp-swap to Cargo.toml with features = ["cpi"] anchor build anchor test ``` ## Useful CLI utilities ### `solana-keygen` ```bash theme={null} solana-keygen new --outfile ./wallet.json # generate a key solana-keygen pubkey ./wallet.json # show public key ``` ### Reading any account ```bash theme={null} solana account
--output json-compact ``` Useful for eyeballing pool state without a decoder. ### Program logs ```bash theme={null} solana logs | grep CPMMoo8 # tail CPMM-related tx logs ``` ### Transaction inspection ```bash theme={null} solana confirm -v # pretty-prints logs ``` Or via [explorer](https://explorer.solana.com) — paste the signature and look at the "Program Logs" tab. ## Environment hygiene ### Separate wallets per purpose * **Development wallet**: holds testnet SOL, used for building. * **Production wallet**: holds real SOL, used only for deploys / multisig submissions. * **Hot wallet for bots**: small balance, narrow permissions. Keys should never be hardcoded. Use environment variables or secret managers. ### `.env` pattern ``` # .env (not committed) SOLANA_RPC_URL=https://mainnet.helius-rpc.com/?api-key=... WALLET_PATH=/Users/you/.config/solana/id.json ``` ```ts theme={null} import { config } from "dotenv"; config(); const connection = new Connection(process.env.SOLANA_RPC_URL!); ``` ### Version pinning Lock all Solana-ecosystem dependencies. The ecosystem moves fast; a minor version bump of `@solana/web3.js` has previously introduced breaking changes. Use `package-lock.json` / `Cargo.lock` religiously. ## Pointers * [`sdk-api/typescript-sdk`](/sdk-api/typescript-sdk) — TypeScript SDK reference. * [`sdk-api/rust-cpi`](/sdk-api/rust-cpi) — Rust CPI usage. * [`sdk-api/python-integration`](/sdk-api/python-integration) — Python setup. * [`solana-fundamentals/programs-and-anchor`](/solana-fundamentals/programs-and-anchor) — Anchor background. Sources: * [Solana install docs](https://docs.solana.com/cli/install-solana-cli-tools). * [Anchor book: getting started](https://book.anchor-lang.com/getting_started/installation.html). * [Raydium SDK v2 repo](https://github.com/raydium-io/raydium-sdk-V2). # Transactions and fees Source: https://docs.raydium.io/solana-fundamentals/transactions-and-fees How Solana transactions are structured, the limits they operate under, and the two distinct categories of fees — Solana network fees and Raydium protocol fees — that a typical swap pays. A Solana transaction is a list of instructions executed atomically. Understanding the transaction structure — instructions, accounts, signers, compute budget — is prerequisite to building, debugging, or optimizing anything with Raydium. This page covers that structure, the limits that constrain it, and how the two fee categories (Solana network fees, Raydium protocol fees) stack in a real swap. ## Transaction anatomy A Solana transaction has three core components: * **Message**: the ordered list of instructions, the accounts they reference, and the recent blockhash. * **Signatures**: one per signer, attesting the transaction was authorized. * **Recent blockhash**: proves the transaction is recent; transactions with stale blockhashes (>150 slots old) are rejected. ### Instructions An instruction specifies: * `program_id` — the program to invoke. * `accounts` — the accounts (and their writable/signer flags) the program may touch. * `data` — opaque bytes the program interprets. A single transaction can contain multiple instructions. They execute in order; if any fails, all prior instructions are rolled back (atomic). A typical Raydium swap transaction includes: 1. `ComputeBudget::SetComputeUnitLimit` — raise the default CU limit. 2. `ComputeBudget::SetComputeUnitPrice` — set a priority fee. 3. Optional `CreateAssociatedTokenAccount` — create the output ATA if the user doesn't have one. 4. `Raydium::SwapBaseInput` — execute the swap. 5. Optional `CloseAccount` — close a wrapped-SOL ATA. The SDK packs these automatically via `raydium.trade.swap()`. ## Accounts in transactions Every account touched by any instruction in the transaction must be listed in the transaction's account keys. Each account is flagged: * **Signer / non-signer**: must the account's owner sign the transaction? * **Writable / read-only**: can the transaction modify the account? The runtime enforces these flags: a program trying to write to a non-writable account fails, and the runtime rejects transactions missing required signers. For a CPMM swap, the account list has \~13 entries (see [`solana-fundamentals/account-model`](/solana-fundamentals/account-model)). CLMM swaps with multiple tick array crossings can have 20+. ## Transaction size limit Solana caps transactions at **1232 bytes** including signatures, message, and headers. This is the single most common obstruction for complex transactions — Raydium's CLMM with multi-hop routing regularly pushes against this limit. Breakdown of a typical \~1000-byte Raydium swap: | Component | Size | | ------------------------------ | ------------ | | Signature | 64 B | | Signature count | 1 B | | Message header | 3 B | | Blockhash | 32 B | | Account keys (13 × 32 B) | 416 B | | Instructions (4 × \~100-150 B) | 400–600 B | | **Total** | \~900–1100 B | ### Address Lookup Tables (ALTs) ALTs let a transaction reference accounts by a 1-byte index into a published table rather than a full 32-byte pubkey. This compresses a transaction drastically: * A transaction referencing 20 accounts directly: \~640 B of pubkeys. * Same transaction using ALTs: \~20 B of indices + ALT references. Raydium maintains ALTs for CPMM/CLMM swap paths on mainnet. The SDK uses them automatically. Aggregators building multi-hop routes rely on them heavily. ```ts theme={null} import { VersionedTransaction } from "@solana/web3.js"; // SDK builds a v0 (versioned) tx with ALT references const { transaction } = await raydium.trade.swap({ /* ... */ }); // transaction is a VersionedTransaction, not a legacy Transaction. ``` ## Compute budget Every transaction has a **compute unit (CU) budget**. Exceeding it terminates execution and fails the transaction. * **Default**: 200,000 CU per transaction. * **Maximum**: 1,400,000 CU per transaction (raised via `ComputeBudget::SetComputeUnitLimit`). * **Per-block ceiling**: 48M CU per block (protocol-level). Typical Raydium CU consumption (see [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) for the full table): | Instruction | CU | | ----------------------------- | --------- | | CPMM swap | \~140,000 | | CLMM swap (no tick crossings) | \~170,000 | | CLMM swap (4 tick crossings) | \~320,000 | | Farm v6 stake | \~130,000 | | CPMM pool creation | \~250,000 | Always set an explicit CU limit via `ComputeBudget`; otherwise you get the 200k default, which is too low for most Raydium instructions. ```ts theme={null} import { ComputeBudgetProgram } from "@solana/web3.js"; tx.add( ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), ); ``` If you set your CU limit too low, the transaction fails when it hits the cap; set it too high and you risk being deprioritized under congestion (and, depending on the pricing model, you pay for compute you never used). ## Priority fees Beyond the base transaction fee (5000 lamports per signature), validators increasingly prioritize transactions paying **priority fees**: a per-CU tip in microlamports. ``` priority_fee = compute_unit_price (micro-lamports) × compute_unit_limit ``` Example: 10,000 µL/CU × 300,000 CU = 3,000,000 µL = 0.003 SOL. Priority fees are local — they only affect ordering within a block; they don't improve your chance of being included vs. not. Setting a reasonable priority fee is essential during congestion. ```ts theme={null} tx.add( ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 10_000 }), ); ``` See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) for how to size this dynamically. ## Instruction count and account count limits Beyond the 1232-byte total limit: * **Max accounts per transaction**: 128. * **Max accounts per instruction (CPI)**: 64. * **Max instructions per transaction**: no hard limit, bounded only by the size limit. * **Max CPI depth**: 4 (a program can call another, which can call another, 4 levels deep). Raydium CLMM swaps that cross several tick arrays can push hard against the account limit — a single swap touches the pool, input/output vaults, input/output ATAs, several tick arrays, possibly a transfer-hook program's extra accounts, plus the mandatory compute budget / system / token program references. Designs that compose Raydium via CPI (e.g., auto-compounders) need to account for this. ## Fee categories in a Raydium swap A user swap transaction pays fees in two categories: ### Solana network fees Paid to validators in SOL. * **Base signature fee**: 5000 lamports per signature. Almost always 1 signature = 0.000005 SOL. * **Priority fee**: CU-price × CU-limit in microlamports. Varies with congestion; see [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning). These fees go to validators, are unrelated to Raydium, and are charged even for failed transactions (except in some priority-fee edge cases). ### Raydium protocol fees Deducted from the swap amount. * **Swap fee**: percentage of input (CPMM 0.25% typical, CLMM 0.01%–1% per tier). Split between LPs and protocol destinations. See [`ray/protocol-fees`](/ray/protocol-fees). These fees are internal to Raydium's accounting — the user sees them as a smaller output amount than a zero-fee pool would produce. ### Example: \$1000 USDC → SOL via CPMM 0.25% tier | Fee category | Amount | Goes to | | ------------------------------- | ------------------------- | -------------- | | Base signature fee | 0.000005 SOL (\~\$0.0007) | Validator | | Priority fee (10k µL × 300k CU) | 0.003 SOL (\~\$0.45) | Validator | | CPMM swap fee (0.25%) | \$2.50 | LPs + protocol | | **Total user cost** | \~\$2.95 | | Slippage (price impact + market move) is not a fee but affects the same bottom line. ## Versioned transactions Solana has two transaction formats: * **Legacy**: the original format, no ALT support. * **v0 (Versioned)**: supports ALTs, extensible to future versions. All modern Solana tooling uses v0. Raydium SDK emits v0 transactions by default. ```ts theme={null} // Building a v0 tx directly import { VersionedTransaction, TransactionMessage } from "@solana/web3.js"; const msg = new TransactionMessage({ payerKey: owner.publicKey, recentBlockhash: blockhash, instructions: [ /* ... */ ], }).compileToV0Message([lookupTableAccount]); const tx = new VersionedTransaction(msg); tx.sign([owner]); ``` ## Blockhash freshness A transaction must include a blockhash from within the last \~150 slots (\~60 seconds). Beyond that window, validators reject it. For retry loops, fetch a fresh blockhash on each retry: ```ts theme={null} async function sendWithRetry(tx, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); tx.message.recentBlockhash = blockhash; tx.sign([owner]); try { return await connection.sendRawTransaction(tx.serialize()); } catch (e) { if (i === maxRetries - 1) throw e; } } } ``` See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) for the full retry-with-escalating-fees pattern. ## Parallel execution Solana executes non-conflicting transactions in parallel on multi-core validators. Two transactions conflict if they both write the same account. Implications for Raydium: * Two swaps on the same pool can't execute in parallel — both write the pool state. * A swap on Pool A and a swap on Pool B execute in parallel if account lists don't overlap. * A read-only transaction never blocks a writer on the same account (read-only is concurrent with itself but not with writes). This is why Solana can sustain high DEX throughput despite single-pool serialization. ## Transaction confirmation levels When submitting a transaction you pick a confirmation level: | Level | Wait | Finality | | ----------- | -------- | ---------------------------- | | `processed` | \~400 ms | Not finalized; may roll back | | `confirmed` | \~1 s | Supermajority voted | | `finalized` | \~13 s | Supermajority rooted | For swap UX, `confirmed` is standard. For operations handling large value (pool creation, reward top-ups), `finalized` is safer. ```ts theme={null} await connection.sendAndConfirmTransaction(tx, [owner], { commitment: "confirmed", }); ``` ## Simulation Solana supports simulating a transaction before submitting: ```ts theme={null} const sim = await connection.simulateTransaction(tx); console.log(sim.value.logs); console.log(sim.value.unitsConsumed); ``` Raydium SDK uses simulation internally when computing `getBestSwapInfo` to verify the chosen route actually succeeds. Simulation isn't free — it consumes RPC capacity — but it catches errors before paying for them. ## Pointers * [`solana-fundamentals/account-model`](/solana-fundamentals/account-model) — accounts in transactions. * [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis) — how programs invoke each other. * [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) — sizing CU limits and priority fees. * [`ray/protocol-fees`](/ray/protocol-fees) — Raydium protocol-fee structure. Sources: * [Solana Transaction docs](https://docs.solana.com/developing/programming-model/transactions). * [Address Lookup Tables proposal](https://docs.solana.com/developing/lookup-tables). * [Compute budget documentation](https://docs.solana.com/developing/programming-model/runtime). # Launch a token (LaunchLab) Source: https://docs.raydium.io/user-flows/launch-token-launchlab End-to-end token launch through LaunchLab: curve choice, initial configuration, monitoring pre-graduation bonding-curve activity, and verifying the graduated CPMM pool. LaunchLab is Raydium's primary-market launch venue. A project deposits its token supply into a bonding curve; buyers trade against the curve using SOL (or another quote mint); when a graduation threshold is reached, the curve's assets migrate automatically to a CPMM pool, and the token becomes freely tradable. This page walks through the whole flow from the project's side. ## What you need * **Token mint** — the token you want to sell. Must be freshly minted with: * Full supply minted to your wallet (so you can deposit into the curve). * Mint authority revoked before launch (otherwise the curve price can be gamed by new mints). * **Metadata** — name, symbol, image, social links. Metaplex metadata or similar. * **Wallet** — \~1 SOL for creation rent + vault funding + priority fees (graduation itself is paid by the graduator, not you). * **Decision on**: * Curve type (quadratic vs virtual-reserves CPMM). * Graduation threshold. * LP disposal policy (Burn / Lock / ToCreator). * Initial price and cap. ## Curve choice LaunchLab supports two curve families: ### Quadratic (`curve_type = 0`) Price grows quadratically with supply sold. Classic "fair launch" feel — early buyers get a price advantage, late buyers pay more, price accelerates smoothly. ``` price(s) = a * s^2 + b * s + c ``` where `s` is supply sold. Typically `a > 0` (convex). The spread between initial and graduation prices is deterministic given `a, b, c` and the cap. Best for: novelty launches, memes, community-driven projects. ### Virtual-reserves CPMM (`curve_type = 1`) Emulates a constant-product AMM using *virtual* reserves — the curve behaves like a CPMM with `x * y = k` but the pool starts off seeded with synthetic tokens that are never withdrawn. ``` price = virtual_quote_reserve / virtual_base_reserve ``` As buyers purchase, `virtual_base_reserve` decreases and `virtual_quote_reserve` increases — the curve looks identical to what the post-graduation CPMM will look like. Smoother handoff. Best for: launches that want predictable post-graduation price continuity. ## UI walkthrough On [raydium.io/launchpad/create](https://raydium.io/launchpad/create): 1. **Token.** Paste mint address. UI fetches metadata and displays it. 2. **Curve type.** Pick quadratic or virtual-CPMM; UI shows a price chart preview for each. 3. **Graduation threshold.** Default: the curve ends after all of `total_base_supply` has been sold. Alternative: end at a specific `graduation_quote_amount` (e.g. 85 SOL). 4. **Quote mint.** SOL (default), USDC, or any other mint. SOL is standard. 5. **LP disposal policy:** * **Burn** — LP tokens sent to an unusable address on graduation. Creator can't pull liquidity; users trust the pool forever. * **Lock** — LP tokens sent to a time-locked escrow for `lock_duration`. * **ToCreator** — LP tokens sent to the creator. Most flexible, least trust-minimizing. Most respected launches use Burn. 6. **Review.** UI summarizes: expected price range, initial raise if curve sells out, graduation CPMM configuration. 7. **Sign.** One transaction creates the launch state + base vault + quote vault, transfers `total_base_supply` from your wallet to the base vault. ## Programmatic walkthrough ```ts theme={null} import { Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; import { PublicKey } from "@solana/web3.js"; import BN from "bn.js"; const raydium = await Raydium.load({ connection, owner, cluster: "mainnet" }); const baseMint = new PublicKey("MyNewToken..."); const quoteMint = new PublicKey("So11111111111111111111111111111111111111112"); // SOL const { execute, extInfo } = await raydium.launchpad.createLaunchpad({ programId: LAUNCHPAD_PROGRAM_ID, baseMint, quoteMint, curveType: 0, // quadratic totalBaseSupply: new BN("1000000000000000000"), // 1B tokens with 9 decimals graduationQuoteAmount: new BN("85000000000"), // 85 SOL lpDisposalPolicy: "Burn", priceCurveParams: { a: new BN("1"), b: new BN("0"), c: new BN("0"), }, startTime: new BN(Math.floor(Date.now() / 1000) + 300), // start in 5 min txVersion: TxVersion.V0, computeBudgetConfig: { units: 400_000, microLamports: 50_000 }, }); const { txId } = await execute({ sendAndConfirm: true }); console.log("Launch ID:", extInfo.launchId.toBase58()); console.log("Base vault:", extInfo.baseVault.toBase58()); console.log("Quote vault:", extInfo.quoteVault.toBase58()); ``` ## Pre-graduation monitoring Track activity via the API: ```ts theme={null} const status = await fetch( `https://api-v3.raydium.io/launchpad/status?launchId=${launchId}` ).then(r => r.json()); console.log("Sold so far:", status.baseSold.toString()); console.log("Quote raised:", status.quoteRaised.toString()); console.log("Current price:", status.currentPriceUsd); console.log("Graduation progress:", status.graduationProgress, "%"); ``` Or decode the on-chain state directly: ```ts theme={null} const launchState = await raydium.launchpad.fetchLaunchState(launchId); console.log({ baseSold: launchState.baseSold.toString(), quoteHeld: launchState.quoteHeld.toString(), graduated: launchState.graduated, }); ``` ### Metrics to watch * **Sell-through rate**: percentage of base sold. Graduation threshold is typically at 80–100% sold. * **Current price**: derived from the curve at the current `baseSold`. * **Unique buyers**: count of distinct payers via indexer. * **Buy-vs-sell ratio**: early launches see heavy buy pressure that slows. If sell-through is `<10%` after 24 hours, consider whether marketing is adequate. LaunchLab doesn't rescue under-hyped launches. ## Graduation Graduation is a separate transaction, callable by anyone (usually the first person to notice the threshold is reached earns the small `graduation_bounty`). No action required from you as creator beyond the initial setup. ### What graduation does 1. Reads the vault balances. 2. Closes the curve (no more buys/sells against it). 3. Creates a new CPMM pool with the vault balances as initial liquidity. 4. Mints initial LP tokens based on the vault balances. 5. Disposes of the LP tokens per the configured policy (Burn / Lock / ToCreator). After graduation, the CPMM pool is live and trades through normal Raydium routing. ### Monitoring for graduation ```ts theme={null} const launchState = await raydium.launchpad.fetchLaunchState(launchId); if (launchState.graduated) { const cpmmPoolId = launchState.graduatedCpmmPool; console.log("Graduated to CPMM pool:", cpmmPoolId.toBase58()); } ``` ### Manual graduation If nobody claims the bounty and you want to force graduation, you can call it yourself: ```ts theme={null} await raydium.launchpad.graduate({ launchId, txVersion: TxVersion.V0, computeBudgetConfig: { units: 600_000, microLamports: 100_000 }, }); ``` Graduation uses significant CU; budget 600k. ## Post-graduation ### Verify the CPMM pool ```ts theme={null} const pool = await raydium.cpmm.getPoolInfoFromRpc({ poolId: cpmmPoolId }); console.log("CPMM price:", pool.poolInfo.price); console.log("CPMM TVL: ", pool.poolInfo.tvl); ``` The CPMM pool should have (approximately): * Base token balance: `total_base_supply - baseSold` + curve remnant. * Quote token balance: `quoteRaised - graduationFee`. * Price: matches last curve price. ### Verify LP disposal * **Burn**: LP mint's supply shows sent to 1nc1nerator11111111111111111111111111111111 or similar. * **Lock**: LP tokens in the escrow PDA with unlock timestamp. * **ToCreator**: LP tokens in the creator's wallet. ### Collect creator fees If you configured a creator-fee share, call periodically: ```ts theme={null} await raydium.launchpad.collectCreatorFees({ launchId, txVersion: TxVersion.V0, }); ``` Fees accrue from curve trades pre-graduation and from the graduated CPMM pool post-graduation (if `ToCreator` LP). ## Announcement checklist Post-graduation announcement should include: * [ ] Graduation transaction hash. * [ ] New CPMM pool ID. * [ ] LP disposal proof (burn tx / lock tx / creator wallet). * [ ] Aggregator coverage ETA (wallets typically index within 5 minutes). * [ ] Token contract verification (match mint address across announcement channels). ## Pitfalls ### 1. Mint authority not revoked If `baseMint.mintAuthority` is still a key you control, you can mint new supply and sell into your own curve. Launches that haven't revoked are commonly flagged as high-risk or unverified by aggregators and listing sites. Revoke before launching. ### 2. Wrong curve parameters Quadratic curves with `a` too high make early tokens free (price ≈ 0) and later tokens absurd. Curves with `a` too low make the curve almost linear (lose the "bonding" feel). Test parameters on devnet first. ### 3. Graduation threshold too high If graduation requires more capital than realistic demand, the curve never graduates and buyers are stuck with tokens they can only sell back to the curve (at worse prices). Pick a threshold you can actually hit. ### 4. Quote mint scams If your quote mint is a custom token (not SOL/USDC), users won't trust it. Stick to SOL or USDC for quote. ### 5. Forgot to fund base vault The SDK transfers `totalBaseSupply` from your wallet on creation. If your wallet doesn't have it, the tx reverts. Confirm balance before signing. ### 6. LP disposal = Burn is irreversible Once LP is sent to 1nc1nerator, nobody can pull liquidity, *including you*. This is the point — trust-minimization. Don't pick Burn if you're not sure. ### 7. Post-graduation CPMM fee tier The graduated pool uses LaunchLab's default CPMM config (0.25%). If you wanted 1% or 0.01%, you need to create a second CPMM pool yourself post-graduation and migrate liquidity — clumsy, and fragmenting. Accept the default, or launch via direct CPMM creation instead of LaunchLab. ## Pointers * [`products/launchlab/overview`](/products/launchlab/overview) — LaunchLab concept and lifecycle. * [`products/launchlab/bonding-curve`](/products/launchlab/bonding-curve) — curve math in detail. * [`products/launchlab/instructions`](/products/launchlab/instructions) — full instruction reference. * [`products/launchlab/vesting`](/products/launchlab/vesting) — vesting schedule mechanics. * [`algorithms/bonding-curves`](/algorithms/bonding-curves) — general bonding-curve math. * [`user-flows/creating-a-launchlab-token`](/user-flows/creating-a-launchlab-token) — UI walkthrough for token creators (JustSendit and LaunchLab modes). * [`user-flows/how-creator-fees-work`](/user-flows/how-creator-fees-work) — pre- and post-migration creator fee overview. Sources: * Raydium SDK v2 `launchpad` module. * Launch status via `api-v3.raydium.io` endpoints. # Get RAY circulating supply Source: https://docs.raydium.io/api-reference/api-v1-endpoints/ray-token/get-ray-circulating-supply /api-reference/openapi/api-v1.yaml get /ray/circulating Fetch RAY token circulating supply. # Get RAY total supply Source: https://docs.raydium.io/api-reference/api-v1-endpoints/ray-token/get-ray-total-supply /api-reference/openapi/api-v1.yaml get /ray/totalcoins Fetch RAY token total coin supply. # Get CLMM position metadata Source: https://docs.raydium.io/api-reference/dynamic-ipfs-v1-endpoints/clmm-position/get-clmm-position-metadata /api-reference/openapi/dynamic-ipfs-v1.yaml get /clmm/position Fetch complete position metadata including pool details, liquidity amounts, unclaimed fees, and USD valuation. Returns a dynamic NFT interface object suitable for rendering position visuals. # Get locked CLMM position metadata Source: https://docs.raydium.io/api-reference/dynamic-ipfs-v1-endpoints/locked-positions/get-locked-clmm-position-metadata /api-reference/openapi/dynamic-ipfs-v1.yaml get /lock/clmm/position Fetch metadata for a locked CLMM position. Returns position data including lock duration, accumulated fees, and current valuation. # Get locked CPMM position metadata Source: https://docs.raydium.io/api-reference/dynamic-ipfs-v1-endpoints/locked-positions/get-locked-cpmm-position-metadata /api-reference/openapi/dynamic-ipfs-v1.yaml get /lock/cpmm/position Fetch metadata for a locked Constant Product Market Maker position. Returns LP token amounts, pool share percentage, and claimable fees from locking. # Request authentication token with signature Source: https://docs.raydium.io/api-reference/launch-auth-v1-endpoints/token-management/request-authentication-token-with-signature /api-reference/openapi/launch-auth-v1.yaml post /request-token Signs a message with format `{signText}{time}` where signText is "Sign in to raydium.io: " and time is the current Unix timestamp in seconds. The wallet must sign this message and submit the signature to receive a JWT token. # Request token using Ledger transaction Source: https://docs.raydium.io/api-reference/launch-auth-v1-endpoints/token-management/request-token-using-ledger-transaction /api-reference/openapi/launch-auth-v1.yaml post /request-token-ledger Alternative token request for Ledger-signed transactions. Signs a message with format `{signText}{time}` within a transaction. # Revoke authentication token Source: https://docs.raydium.io/api-reference/launch-auth-v1-endpoints/token-management/revoke-authentication-token /api-reference/openapi/launch-auth-v1.yaml get /del-token # Validate authentication token Source: https://docs.raydium.io/api-reference/launch-auth-v1-endpoints/token-management/validate-authentication-token /api-reference/openapi/launch-auth-v1.yaml get /check-token Verify that a token is still valid. Also extends token lifetime if near expiry. # Get a specific comment by ID Source: https://docs.raydium.io/api-reference/launch-forum-v1-endpoints/comments/get-a-specific-comment-by-id /api-reference/openapi/launch-forum-v1.yaml get /comment/id # Get comments for a pool Source: https://docs.raydium.io/api-reference/launch-forum-v1-endpoints/comments/get-comments-for-a-pool /api-reference/openapi/launch-forum-v1.yaml get /comment/pool # Post a comment on a pool Source: https://docs.raydium.io/api-reference/launch-forum-v1-endpoints/comments/post-a-comment-on-a-pool /api-reference/openapi/launch-forum-v1.yaml post /comment Post a new comment. Requires ray-token header. # Get kline (candlestick) data Source: https://docs.raydium.io/api-reference/launch-history-v1-endpoints/candle-data/get-kline-candlestick-data /api-reference/openapi/launch-history-v1.yaml get /kline Retrieve OHLC candle data. Limit range: 1-500 (default 300). # Get trade history Source: https://docs.raydium.io/api-reference/launch-history-v1-endpoints/trades/get-trade-history /api-reference/openapi/launch-history-v1.yaml get /trade Retrieve individual trades. Limit range: 1-100 (default 50). # Get platforms Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/configuration/get-platforms /api-reference/openapi/launch-mint-v1.yaml get /campaign/platforms **Legacy endpoint.** Returns the v1 platform list. Superseded by `/campaign/platforms-v2`, which carries the additional fields the front end now relies on. New integrations should call the v2 endpoint. # Get platforms v2 Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/configuration/get-platforms-v2 /api-reference/openapi/launch-mint-v1.yaml get /campaign/platforms-v2 # Get random mint Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/creation/get-random-mint /api-reference/openapi/launch-mint-v1.yaml post /create/get-random-mint Return a fresh, unused mint keypair for the caller. Optional filters let the platform pre-generate vanity addresses with a specific prefix/suffix. # Submit mint creation Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/creation/submit-mint-creation /api-reference/openapi/launch-mint-v1.yaml post /create/mint-info Register the off-chain metadata for a new LaunchLab mint before its on-chain create transaction is sent. The platform stores the supplied name/symbol/uri/social-links keyed by mint, so the UI can display them while the bonding curve is live. # Submit transaction Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/creation/submit-transaction /api-reference/openapi/launch-mint-v1.yaml post /create/sendTransaction Forward a fully signed Solana transaction (base64-encoded) on behalf of the caller. Used by the UI to relay the create / buy / sell transactions through the platform's RPC for priority-fee uplift and retry handling. # Get owner vesting Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/vesting/get-owner-vesting /api-reference/openapi/launch-mint-v1.yaml get /vesting/by/owner # Get pool vesting Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/vesting/get-pool-vesting /api-reference/openapi/launch-mint-v1.yaml get /vesting/by/pool # Get vesting by IDs Source: https://docs.raydium.io/api-reference/launch-mint-v1-endpoints/vesting/get-vesting-by-ids /api-reference/openapi/launch-mint-v1.yaml get /vesting/by/ids # Get IDO participation info for a wallet Source: https://docs.raydium.io/api-reference/owner-api-v1-endpoints/ido/get-ido-participation-info-for-a-wallet /api-reference/openapi/owner-api-v1.yaml get /main/ido/{owner} Retrieve IDO participation data, including invested amounts and claim status. # Get pools created by a wallet Source: https://docs.raydium.io/api-reference/owner-api-v1-endpoints/pool-creation/get-pools-created-by-a-wallet /api-reference/openapi/owner-api-v1.yaml get /create-pool/{owner} Retrieve all CPMM pools created (launched) by this wallet. # Get locked CLMM positions for a wallet Source: https://docs.raydium.io/api-reference/owner-api-v1-endpoints/position/get-locked-clmm-positions-for-a-wallet /api-reference/openapi/owner-api-v1.yaml get /position/clmm-lock/{owner} Retrieve all locked Concentrated Liquidity Market Maker positions owned by this wallet. Returns position state including locked amounts, lock end time, and claimable fees. # Get stake positions for a wallet Source: https://docs.raydium.io/api-reference/owner-api-v1-endpoints/position/get-stake-positions-for-a-wallet /api-reference/openapi/owner-api-v1.yaml get /position/stake/{owner} Retrieve all stake (standard AMM or CPMM) positions owned by this wallet. Returns cached position data including amounts and rewards. # Get accumulated creator fees for a wallet Source: https://docs.raydium.io/api-reference/temp-api-v1-endpoints/temporary/get-accumulated-creator-fees-for-a-wallet /api-reference/openapi/temp-api-v1.yaml get /cp-creator-fee Retrieve all CPMM pools where this wallet is the creator, along with accumulated but unclaimed creator fees per pool. Returns an empty array if the wallet has no creator fees cached. **Note:** This endpoint caches user data periodically. Very recent creator fee activity may not appear immediately. # List a wallet's currently-parked CLMM limit orders Source: https://docs.raydium.io/api-reference/temp-api-v1-endpoints/temporary/list-a-wallets-currently-parked-clmm-limit-orders /api-reference/openapi/temp-api-v1.yaml get /limit-order/order/list Return CLMM limit orders owned by `wallet` that are still on-chain — both fully unfilled and partially filled orders are returned in a single payload. Each row is the decoded on-chain `LimitOrderState` plus the pool keys and a `pendingSettle` amount that the indexer has computed from the latest tick state. Data is served from the indexer's Redis cache and may lag the chain by a few seconds. # List a wallet's historical CLMM limit orders Source: https://docs.raydium.io/api-reference/temp-api-v1-endpoints/temporary/list-a-wallets-historical-clmm-limit-orders /api-reference/openapi/temp-api-v1.yaml get /limit-order/history/order/list-by-user Return closed CLMM limit orders owned by `wallet`. Each row summarizes the order at the time it left the active set. Cursor-paginated via `nextPageId`. # List the on-chain event log for one or more limit-order PDAs Source: https://docs.raydium.io/api-reference/temp-api-v1-endpoints/temporary/list-the-on-chain-event-log-for-one-or-more-limit-order-pdas /api-reference/openapi/temp-api-v1.yaml get /limit-order/history/event/list-by-pda Return the event log (`open`, `increase`, `decrease`, `settle`, `close`) for one or more limit-order PDAs. Useful for rendering a per-order timeline. Cursor-paginated via `nextPageId`. # Changelog Source: https://docs.raydium.io/reference/changelog Dated record of updates to this documentation set. Entries log which chapters changed, why, and when the content was verified against on-chain state and the program source. This is the documentation's changelog — the record of updates to these pages from the project go-live onward. For the protocol's own historical timeline, see [`introduction/history-and-milestones`](/introduction/history-and-milestones). Every entry here has a date, a list of affected chapters, the trigger, and a verified-date noting when on-chain state and program source were last cross-checked against the written content. ## Unreleased — CLMM: limit orders, single-sided fee, dynamic fee The next CLMM release adds three pool-level capabilities. They are opt-in at pool-creation time and backwards-compatible with existing pools and positions. ### TL;DR for integrators * **Limit orders** are now first-class CLMM primitives. LPs can open a single-tick order on a pool that supports them; the order is filled FIFO when a swap crosses the tick, and an off-chain keeper (`limit_order_admin`) can settle the filled output without the owner being online. Seven new SDK methods (`openLimitOrder`, `increaseLimitOrder`, `decreaseLimitOrder`, `settleLimitOrder`, `closeLimitOrder`, `closeAllLimitOrder`, `settleAllLimitOrder`) and three new Temp API endpoints under `/limit-order/` (active orders, per-user history, per-PDA event log) cover the full flow. * **Single-sided fee (`CollectFeeOn`)** lets a pool collect swap fees from the input side (legacy, mode `0`), or always from `token_0` (mode `1`), or always from `token_1` (mode `2`). Useful when one side of the pair is the canonical accounting token. * **Dynamic fee** lets a pool opt into a volatility-tracking surcharge that rises with rapid tick movement and decays over time, calibrated by a per-tier `DynamicFeeConfig` and a per-pool `DynamicFeeInfo`. The new `/main/clmm-dynamic-config` endpoint surfaces the tier list. * A new instruction, `CreateCustomizablePool`, exposes all three knobs at pool-creation time. Classic `CreatePool` continues to work for default-fee, no-limit-order pools. * **Indexer breaking change**: the per-direction volume counters (`swap_in_amount_token_{0,1}`, `swap_out_amount_token_{0,1}`) and lifetime fee counters (`total_fees_token_{0,1}`, `total_fees_claimed_token_{0,1}`) on `PoolState` were retired into padding to make room for `fee_on` and `dynamic_fee_info`. Indexers that read those fields directly must migrate to the on-chain `Observation` ring or the API. ### Why this matters (for traders, LPs, and integrators) * **Traders** get tighter quotes on long-tail and event-driven pairs: dynamic fee lets the pool absorb volatility surcharges from the taker without LPs having to actively widen ranges, and limit-order ladders deepen on-chain liquidity at specific prices without committing range-wide capital. * **LPs** get a third strategy alongside concentrated ranges and full-range positions: park exact-price orders, get filled when the price visits, settle into the quote token. No active rebalancing required for the filled portion. * **Integrators** can model dynamic-fee pools deterministically — the algorithm and parameters are fully on-chain, the calibration tiers are queryable, and the swap path is unchanged in shape (only the fee per step varies). ### What changed in the program #### New accounts * **`DynamicFeeConfig`** — per-tier calibration record (filter period, decay period, reduction factor, dynamic-fee control, max volatility accumulator). Created by `CreateDynamicFeeConfig` (admin), referenced by `CreateCustomizablePool` when dynamic fee is enabled. * **`LimitOrderState`** — per-order account (PDA seeds: `[owner, limit_order_nonce, order_nonce]`) holding the pool, tick, side, input amount, unfilled ratio, FIFO cohort phase, and book-keeping snapshots. Lifecycle is implicit (`filled_amount` vs `total_amount`, plus account existence): `Open → Filled → Settled → Closed`. * **`LimitOrderNonce`** — per-(owner, nonce\_index) monotonically-incrementing counter that gets the limit-order PDA seeds. The `nonce_index: u8` lets the same owner partition orders into up to 256 independent nonce streams. See [Accounts → DynamicFeeConfig and DynamicFeeInfo](/products/clmm/accounts) and [Accounts → LimitOrderState](/products/clmm/accounts). #### `PoolState` reshape | Field group | Old layout | New layout | | ----------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | Per-direction volume counters | `swap_in_amount_token_0`, `swap_out_amount_token_0`, `swap_in_amount_token_1`, `swap_out_amount_token_1` | Folded into `padding5: [u128; 4]` | | Lifetime fee counters | `total_fees_token_0`, `total_fees_claimed_token_0`, `total_fees_token_1`, `total_fees_claimed_token_1` | Folded into `padding6: [u64; 4]` | | Single-sided fee | — | `fee_on: u8` (0 = FromInput, 1 = Token0Only, 2 = Token1Only) | | Dynamic fee | — | `dynamic_fee_info: DynamicFeeInfo` (embedded) | The total account size is unchanged. **Indexers**: switch volume-tracking off `PoolState` and onto the `Observation` ring or the API. The retired counters are not zeroed on existing pools (they hold whatever they happened to last carry), so re-reading them after upgrade will return stale data. #### `TickState` additions (no breaking change) Four new fields are added at the end of `TickState`, replacing some of its tail padding: * `order_phase: u64` — counter that disambiguates limit-order cohorts at this tick. * `orders_amount: u64` — total input committed by all open orders at this tick (not all of which are fully unfilled). * `part_filled_orders_remaining: u64` — input still unfilled in the cohort currently being consumed by swaps. * `unfilled_ratio_x64: u128` — Q64.64 ratio used to compute each order's fill share. Tick-array layout, sizing, and PDA seeds are unchanged. #### New instructions * **`CreateDynamicFeeConfig` (admin)** — create a calibrated `DynamicFeeConfig` tier. Authority: same treasury multisig as `CreateAmmConfig`. * **`UpdateDynamicFeeConfig` (admin)** — update an existing tier's parameters. * **`CreateCustomizablePool`** — pool-creation entry point that exposes `collect_fee_on`, `enable_dynamic_fee`, and `dynamic_fee_config`. Coexists with `CreatePool`; we recommend `CreateCustomizablePool` for any new pool that needs the new knobs. * **`OpenLimitOrder`** — open a single-tick limit order. Bumps `LimitOrderNonce`, allocates `LimitOrderState`, slots the order into the FIFO cohort at the tick. * **`IncreaseLimitOrder` / `DecreaseLimitOrder`** — adjust an order's unfilled portion. Reverts on a fully-filled order with `InvalidOrderPhase`. * **`SettleLimitOrder`** — sweep filled output to the owner's ATA. Caller can be the owner or the pool's `limit_order_admin` keeper. * **`CloseLimitOrder`** — close a fully-settled order to reclaim rent. #### `SwapV2` behavior changes The swap path itself is unchanged in shape, but three things now happen along the way: 1. **Dynamic fee** (when enabled): the pool's `DynamicFeeInfo` is updated each step (decay → accumulate → cap), and the resulting surcharge is added on top of the base fee for that step. 2. **Limit-order matching** (when the step crosses an initialized tick that has open orders): part of the swap input is consumed FIFO to fill the cohort at that tick, with `unfilled_ratio_x64` updated atomically. 3. **Single-sided fee routing** (when `fee_on != 0`): the fee is taken from `token_0` or `token_1` regardless of swap direction, instead of always from the input side. Each of these is a no-op when the pool was created with the legacy defaults. See [Instructions → SwapV2](/products/clmm/instructions) for the updated state-change matrix. #### New error codes The `ErrorCode` enum was renumbered in this release: five legacy variants (`LOK`, `ZeroMintAmount`, `InvalidLiquidity`, `TransactionTooOld`, `InvalidRewardDesiredAmount`) were removed, and eleven new variants were appended. Because Anchor numbers errors by enum order from `6000`, **every error code at or after the removed positions has shifted** — clients that hard-coded numeric codes need to remap. The new codes are: * `6040` `OrderAlreadyFilled` * `6041` `InvalidOrderPhase` * `6042` `InvalidLimitOrderAmount` * `6043` `OrderPhaseSaturated` * `6044` `InvalidDynamicFeeConfigParams` * `6045` `InvalidFeeOn` * `6046` `ZeroSqrtPrice` * `6047` `ZeroLiquidity` * `6048` `MissingBaseFlag` * `6049` `MissingMintAccount` * `6050` `MissingTokenProgram2022` Full strings and the post-shift table for all CLMM errors are in [Error codes](/reference/error-codes). ### What changed in the SDK (`@raydium-io/raydium-sdk-v2`) * **New methods on `raydium.clmm`**: `createCustomizablePool`, `openLimitOrder`, `increaseLimitOrder`, `decreaseLimitOrder`, `settleLimitOrder`, `settleAllLimitOrder`, `closeLimitOrder`, `closeAllLimitOrder`. * **New REST helpers on `raydium.api`**: `getClmmDynamicConfigs`, `getClmmLimitOrderConfigs`. * **New types**: `CollectFeeOn` enum, `DynamicFeeConfig`, `DynamicFeeInfo`, `LimitOrderState`, `LimitOrderConfig`. * **Internal reorg**: `utils/` moved to `libraries/`. The package barrel is unchanged; only deep imports under `@raydium-io/raydium-sdk-v2/utils/...` need updating to `…/libraries/...`. End-to-end TypeScript walkthroughs live in [`products/clmm/code-demos`](/products/clmm/code-demos). ### What changed in the APIs * `api-v3` — two new endpoints under `/main/`: * `GET /main/clmm-dynamic-config` — list of `DynamicFeeConfig` tiers. * `GET /main/clmm-limit-order-config` — per-pool limit-order configuration. * `temp-api-v1` — three new endpoints under `/limit-order/`: * `GET /limit-order/order/list?wallet=…` — a wallet's currently-parked orders (open and partially-filled, served from the indexer's Redis cache; same payload covers both phases via `totalAmount` / `filledAmount` / `pendingSettle`). * `GET /limit-order/history/order/list-by-user?wallet=…` — a wallet's historical limit orders. Optional filters: `poolId`, `mint1`, `mint2`, `hideCancel`. Cursor-paginated via `nextPageId` / `size` (max 100). * `GET /limit-order/history/event/list-by-pda?pda=…` — per-PDA event log (`open` / `increase` / `decrease` / `settle` / `close`) for one or more comma-separated limit-order PDAs. Cursor-paginated via `nextPageId` / `size` (max 100). All five are documented in the API Reference tab. ### Authority surface The `limit_order_admin` is an off-chain operational keeper, **not a multisig**. It can only call `SettleLimitOrder` and `CloseLimitOrder` on existing orders, and the output of a settle always lands in the **owner's** ATA. It cannot mutate pool fields, open or modify orders, or sign for anything else. See [Admin keys and multisig → CLMM](/security/admin-and-multisig). ### Pages updated * `products/clmm/overview` — new "What's new" section and updated next-step pointers. * `products/clmm/accounts` — three new accounts, `PoolState` reshape with migration warning, `TickState` additions, new PDA helpers. * `products/clmm/instructions` — seven new instructions, `SwapV2` behavior addendum, updated state-change matrix. * `products/clmm/fees` — single-sided fee section, dynamic-fee section with parameter table. * `products/clmm/math` — limit-order matching pseudo-code, dynamic-fee derivation. * `products/clmm/code-demos` — `createCustomizablePool` demo, full limit-order walkthrough, new pitfalls. * `algorithms/clmm-math` — cross-reference to limit-order matching and dynamic fee in the multi-tick swap loop. * `sdk-api/typescript-sdk` — CLMM module additions section, `utils/` → `libraries/` migration note. * `api-reference/openapi/api-v3.yaml` — two new endpoints with response schemas. * `api-reference/openapi/temp-api-v1.yaml` — three new limit-order endpoints (`/limit-order/order/list`, `/limit-order/history/order/list-by-user`, `/limit-order/history/event/list-by-pda`) with their request and response schemas. * `api-reference/api-v3/overview` — note on the new CLMM config endpoints. * `api-reference/temp-api-v1/overview` — note on the new active-orders, history-by-user, and event-by-PDA endpoints. * `reference/error-codes` — eleven new CLMM error codes (6040–6050) plus five removed legacy codes; numeric codes after the removal points have shifted. * `security/admin-and-multisig` — new `DynamicFeeConfig` admin row and `limit_order_admin` keeper row, with bounded-authority explainer. **Verified against**: * `raydium-clmm` source. * `@raydium-io/raydium-sdk-v2` source. * `api-v3` and `temp-api-v1` source. ## 2026-04-26 — Initial publication First public release of the Raydium documentation set. **Verified against**: * Live program deployments on Solana mainnet-beta. * `@raydium-io/raydium-sdk-v2@0.2.42-alpha`. * Public Raydium docs and on-chain references through April 2026. Going forward, every protocol upgrade, audit, or doc revision lands as a new entry in this file. ## Documentation conventions * **Versioning**: this documentation uses calendar-based versioning (YYYY-MM-DD). Each update creates a new entry at the top of the file. * **Verified date**: every entry records when the content was last cross-checked against on-chain / API state and the program source. If not stated, assume the entry's main date. * **Breaking changes**: called out in a boxed warning on affected pages and tagged in the entry below. * **Coverage**: this changelog covers the documentation set itself. The protocol's own historical timeline lives in [`introduction/history-and-milestones`](/introduction/history-and-milestones) and is the source of truth for "when did X happen on Raydium". ## Corrections If you find an error in this documentation, please open an issue or pull request on the documentation repository. Corrections are logged in this changelog. ## Pointers * [`introduction/history-and-milestones`](/introduction/history-and-milestones) — the protocol's own timeline. * [`security/audits`](/security/audits) — audit history. * [`ray/protocol-fees`](/ray/protocol-fees) — protocol fee splits. * [`reference/program-addresses`](/reference/program-addresses) — program ID source-of-truth. # Error codes Source: https://docs.raydium.io/reference/error-codes Anchor error enums for every Raydium on-chain program, with cause and recommended UX message. **Source of truth.** The tables below are regenerated from each program's `error.rs` in the public Raydium repositories. When a program is upgraded and a new variant is added, re-run the extraction (link at the bottom of each table) and append to the table rather than reshuffling — Anchor error codes are assigned by **source order**, not name, so rearranging breaks integrator error-handling. ## How Anchor error codes work Anchor assigns each variant of a program's `ErrorCode` enum a numeric code starting at `6000`. A failing transaction surfaces: * **Numeric error code** (e.g. `0x1771` = 6001) in the transaction logs. * **Error name** (e.g. `InvalidOwner`) from the IDL. * **`#[msg(...)]` string** that Anchor emitted in `log_messages`. Integrators should match on the **numeric code**, not the message string (the string can be re-worded without bumping a version). ```ts theme={null} // Example: detect the CPMM slippage error and surface a clean UX message. const CPMM_EXCEEDED_SLIPPAGE = 6005; try { await sdk.cpmm.swap({ ... }); } catch (err: any) { const code = err?.error?.errorCode?.number ?? err?.code; if (code === CPMM_EXCEEDED_SLIPPAGE) { showToast("Price moved past your slippage tolerance. Increase slippage or retry."); return; } throw err; } ``` ## CPMM (Standard AMM) errors Program ID: see [reference/program-addresses](/reference/program-addresses#on-chain-programs). Source: `raydium-cp-swap/programs/cp-swap/src/error.rs`. | Code | Variant | `#[msg]` string | Typical cause | Recommended UX | | ---- | ------------------------------ | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | 6000 | `NotApproved` | Not approved | Caller is not the configured authority for an admin instruction. | "Only the pool admin can perform this action." | | 6001 | `InvalidOwner` | Input account owner is not the program address | A passed-in account is owned by the wrong program (often a wrong token program or a wrong-program PDA). | "Internal: account owner mismatch — refresh and retry." | | 6002 | `EmptySupply` | Input token account empty | LP or token account balance is zero at a step that requires a positive amount. | "Nothing to withdraw." | | 6003 | `InvalidInput` | InvalidInput | Generic bad argument (amount out of range, wrong flag). | "Invalid input — check amounts and try again." | | 6004 | `IncorrectLpMint` | Address of the provided lp token mint is incorrect | The LP mint account passed in does not match `pool_state.lp_mint`. | "Internal: wrong LP mint — refresh pool data." | | 6005 | `ExceededSlippage` | Exceeds desired slippage limit | The executed price is worse than the user's `minAmountOut` / `maxAmountIn`. | "Price moved past your slippage tolerance. Increase slippage or retry." | | 6006 | `ZeroTradingTokens` | Given pool token amount results in zero trading tokens | Deposit / withdraw math rounded one side to zero (position too small). | "Amount is below the minimum for this pool." | | 6007 | `NotSupportMint` | Not support token\_2022 mint extension *(grammar in source)* | Pool encountered a Token-2022 extension it cannot handle safely (e.g. `TransferHook`, `DefaultAccountState=Frozen`). | "This token has an extension Raydium does not support in CPMM." | | 6008 | `InvalidVault` | invaild vault *(typo in source)* | The passed vault account does not match the one recorded in `pool_state`. | "Internal: wrong vault — refresh and retry." | | 6009 | `InitLpAmountTooLess` | Init lp amount is too less(Because 100 amount lp will be locked) *(grammar in source)* | At pool initialization, the computed LP supply is below the permanent-lock amount. | "Initial liquidity too small. Increase deposit." | | 6010 | `TransferFeeCalculateNotMatch` | TransferFee calculate not match | The observed post-transfer amount for a Token-2022 fee mint did not match the pre-computed expectation. | "Token transfer fee changed mid-transaction. Retry." | | 6011 | `MathOverflow` | Math overflow | An intermediate swap / deposit / fee calculation overflowed. | "Amount is too large for this pool." | | 6012 | `InsufficientVault` | Insufficient vault | Pool vault balance is too low to cover the requested output. | "Not enough liquidity in the pool for this size." | | 6013 | `InvalidFeeModel` | Invalid fee model | Admin set an `AmmConfig` parameter combination that is rejected on-chain. | N/A — admin-only path. | | 6014 | `NoFeeCollect` | Fee is zero | `collect_protocol_fee` / `collect_fund_fee` called with zero collectable fees. | N/A — admin path; tooling should swallow. | Regeneration source: [github.com/raydium-io/raydium-cp-swap — error.rs](https://github.com/raydium-io/raydium-cp-swap/blob/master/programs/cp-swap/src/error.rs). ## CLMM errors Program ID: see [reference/program-addresses](/reference/program-addresses#on-chain-programs). Source: `raydium-clmm/programs/amm/src/error.rs`. | Code | Variant | `#[msg]` string | Typical cause | Recommended UX | | ---- | ---------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | 6000 | `NotApproved` | Not approved | Caller is not the configured admin for this instruction. | "Only the pool admin can perform this action." | | 6001 | `InvalidUpdateConfigFlag` | invalid update amm config flag | Admin passed an unrecognized `param` value to `update_amm_config`. | N/A — admin-only path. | | 6002 | `AccountLack` | Account lack | Required remaining account missing from the tx (typically tick-array or oracle extension). | "Internal: missing account — refresh pool data." | | 6003 | `ClosePositionErr` | Remove liquidity, collect fees owed and reward then you can close position account | Tried to close a position that still has liquidity, uncollected fees, or uncollected rewards. | "Withdraw all liquidity and claim fees/rewards before closing the position." | | 6004 | `InvalidTickIndex` | Tick out of range | `tick_lower` or `tick_upper` is outside `[-443636, 443636]`. | "Price range out of bounds for this pool." | | 6005 | `TickInvalidOrder` | The lower tick must be below the upper tick | `tick_lower >= tick_upper`. | "Lower price must be below upper price." | | 6006 | `TickLowerOverflow` | The tick must be greater, or equal to the minimum tick(-443636) | Lower tick underflow. | "Lower price too low." | | 6007 | `TickUpperOverflow` | The tick must be lesser than, or equal to the maximum tick(443636) | Upper tick overflow. | "Upper price too high." | | 6008 | `TickAndSpacingNotMatch` | tick % tick\_spacing must be zero | Submitted tick is not a multiple of the pool's `tick_spacing`. | "Snap price to the nearest valid increment." | | 6009 | `InvalidTickArray` | Invalid tick array account | Wrong PDA passed for a tick-array slot. | "Internal: wrong tick array — refresh pool data." | | 6010 | `InvalidTickArrayBoundary` | Invalid tick array boundary | Off-by-one in tick-array indexing. | "Internal: tick-array boundary error." | | 6011 | `SqrtPriceLimitOverflow` | Square root price limit overflow | Caller-supplied `sqrt_price_limit` outside valid range. | "Price limit out of range." | | 6012 | `SqrtPriceX64` | sqrt\_price\_x64 out of range | Pool's current sqrt price drifted out of range mid-swap. | "Retry swap." | | 6013 | `LiquiditySubValueErr` | Liquidity sub delta L must be smaller than before | Internal invariant violation on decrease-liquidity. | "Internal: liquidity accounting error." | | 6014 | `LiquidityAddValueErr` | Liquidity add delta L must be greater, or equal to before | Internal invariant violation on increase-liquidity. | "Internal: liquidity accounting error." | | 6015 | `ForbidBothZeroForSupplyLiquidity` | Both token amount must not be zero while supply liquidity | Increase-liquidity call with both `amount_0_max` and `amount_1_max` zero. | "Provide at least one token." | | 6016 | `LiquidityInsufficient` | Liquidity insufficient | Position does not have enough liquidity to satisfy the withdrawal. | "Withdraw amount exceeds position liquidity." | | 6017 | `PriceSlippageCheck` | Price slippage check | Execution price failed the caller's slippage guard. | "Price moved past your slippage tolerance. Increase slippage or retry." | | 6018 | `TooLittleOutputReceived` | Too little output received | `SwapBaseInput`: out amount below `other_amount_threshold`. | "Slippage exceeded — minimum output not met." | | 6019 | `TooMuchInputPaid` | Too much input paid | `SwapBaseOutput`: in amount above `other_amount_threshold`. | "Slippage exceeded — maximum input exceeded." | | 6020 | `ZeroAmountSpecified` | Swap special amount can not be zero | Zero `amount` on a swap instruction. | "Enter an amount greater than zero." | | 6021 | `InvalidInputPoolVault` | Input pool vault is invalid | Swap's input-vault account does not match the pool's recorded vault. | "Internal: wrong input vault — refresh pool data." | | 6022 | `TooSmallInputOrOutputAmount` | Swap input or output amount is too small | Swap math rounded to zero, typically very small dust. | "Amount too small to swap in this pool." | | 6023 | `NotEnoughTickArrayAccount` | Not enough tick array account | Not enough tick-array remaining accounts supplied for the swap range. | "Internal: insufficient tick-arrays — refresh pool data." | | 6024 | `InvalidFirstTickArrayAccount` | Invalid first tick array account | The first tick-array account passed does not cover the current tick. | "Internal: wrong first tick array — refresh pool data." | | 6025 | `InvalidRewardIndex` | Invalid reward index | `reward_index` outside `[0, 2]`. | N/A — admin path. | | 6026 | `FullRewardInfo` | The init reward token reach to the max | Pool already has the max (3) reward mints configured. | N/A — admin path. | | 6027 | `RewardTokenAlreadyInUse` | The init reward token already in use | Reward mint duplicates an existing one. | N/A — admin path. | | 6028 | `ExceptRewardMint` | The reward tokens must contain one of pool vault mint except the last reward | Pre-slot 2, reward mint must equal one of the pool's vault mints. | N/A — admin path. | | 6029 | `InvalidRewardInitParam` | Invalid reward init param | Bad emission start/end time or per-second rate. | N/A — admin path. | | 6030 | `InvalidRewardInputAccountNumber` | Invalid collect reward input account number | Wrong number of remaining accounts passed to `CollectReward`. | "Internal: wrong account count — refresh and retry." | | 6031 | `InvalidRewardPeriod` | Invalid reward period | Reward emission period invalid (`end <= start`, zero duration). | N/A — admin path. | | 6032 | `NotApproveUpdateRewardEmissions` | Modification of emissions is allowed within 72 hours from the end of the previous cycle | Admin tried to modify emissions outside the 72-hour window around cycle boundaries. | N/A — admin path. | | 6033 | `UnInitializedRewardInfo` | uninitialized reward info | Reward slot not initialized but referenced. | N/A — admin path. | | 6034 | `NotSupportMint` | Not support token\_2022 mint extension | CLMM encountered a Token-2022 extension it cannot handle. | "This token has an extension Raydium CLMM does not support." | | 6035 | `MissingTickArrayBitmapExtensionAccount` | Missing tickarray bitmap extension account | Swap crossed a tick range not covered by the base bitmap; extension bitmap account required. | "Internal: missing tick-array extension — refresh pool data." | | 6036 | `InsufficientLiquidityForDirection` | Insufficient liquidity for this direction | Not enough liquidity exists in the direction of the swap. | "Insufficient liquidity for this swap size." | | 6037 | `MaxTokenOverflow` | Max token overflow | Input/output amount exceeded u64. | "Amount too large for this pool." | | 6038 | `CalculateOverflow` | Calculate overflow | Fee / liquidity arithmetic overflow. | "Amount too large for this pool." | | 6039 | `TransferFeeCalculateNotMatch` | TransferFee calculate not match | Token-2022 transfer fee observed amount did not match expected. | "Token transfer fee changed mid-transaction. Retry." | | 6040 | `OrderAlreadyFilled` | Order already fully filled, cannot modify | `IncreaseLimitOrder` / `DecreaseLimitOrder` called on an order whose unfilled portion is zero. | "This limit order is already filled — settle to receive output." | | 6041 | `InvalidOrderPhase` | Invalid order phase | Mutating an order whose FIFO cohort phase no longer matches the tick's current cohort. | "Cannot perform this action in the order's current state." | | 6042 | `InvalidLimitOrderAmount` | Invalid limit order amount | Order input below the pool's minimum (or zero) at open / increase / decrease time. | "Order size is below the pool's minimum." | | 6043 | `OrderPhaseSaturated` | Tick order phase saturated | The cohort's `order_phase` counter on the tick has saturated; further orders cannot be opened at that tick until existing cohorts settle and roll over. | "Too many active orders at this price; try a nearby tick or wait for orders to settle." | | 6044 | `InvalidDynamicFeeConfigParams` | Invalid dynamic fee config params | `CreateDynamicFeeConfig` / `UpdateDynamicFeeConfig` rejected; or `CreateCustomizablePool` enabled dynamic fee without a valid config. | N/A on admin path; "Dynamic fee config invalid" on user path. | | 6045 | `InvalidFeeOn` | Invalid fee on which token (must be 0, 1, or 2) | `CreateCustomizablePool` passed a `collect_fee_on` value outside `{0, 1, 2}`. | "Internal: invalid fee mode." | | 6046 | `ZeroSqrtPrice` | sqrt\_price\_x64 must be greater than 0 | `CreateCustomizablePool` (or another path that accepts a customizable initial sqrt price) was called with `sqrt_price_x64 == 0`. | "Initial price must be greater than zero." | | 6047 | `ZeroLiquidity` | liquidity must be greater than 0 | A liquidity-providing path was called with `liquidity == 0` and no compensating amount. | "Liquidity amount must be greater than zero." | | 6048 | `MissingBaseFlag` | base\_flag is required when liquidity is zero | An open-position-by-amount path computed `liquidity == 0` and the caller did not supply a `base_flag` to disambiguate which side is the base. | "Provide either a non-zero liquidity or specify which token is the base." | | 6049 | `MissingMintAccount` | Mint account is required but not provided | A Token-2022-aware path was called without the input/output mint account that's needed to validate extensions and transfer fees. | "Internal: missing mint account — refresh pool data." | | 6050 | `MissingTokenProgram2022` | Token-2022 program is required but not provided | Same as above for the SPL-Token-2022 program account. | "Internal: missing Token-2022 program — refresh and retry." | > **Note on renumbering.** The CLMM `ErrorCode` enum was renumbered in this release: five legacy variants (`LOK`, `ZeroMintAmount`, `InvalidLiquidity`, `TransactionTooOld`, `InvalidRewardDesiredAmount`) and several typos (`Liquitity`, `enought`, `emissiones`) were removed/fixed, and eleven new variants were appended. Because Anchor numbers errors by source order, every code at or after `6000` has shifted relative to pre-release builds. Clients that hard-coded numeric codes against an earlier version need to remap. Regeneration source: [github.com/raydium-io/raydium-clmm — error.rs](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/error.rs). ## AMM v4, Farm v3 / v5 / v6, LaunchLab errors These programs are documented in their respective chapters (see [`products/amm-v4/instructions`](/products/amm-v4/instructions), [`products/farm-staking/instructions`](/products/farm-staking/instructions), [`products/launchlab/instructions`](/products/launchlab/instructions)). Because those programs use a mix of Anchor and plain Solana error surfaces, their error tables live next to the instruction reference rather than here. The codes below are reserved by those chapters: | Program | Code range (Anchor only) | Reference | | ------------ | ---------------------------------- | --------------------------------------------------------------------------- | | AMM v4 | Custom u32 codes, not Anchor-style | [`products/amm-v4/instructions`](/products/amm-v4/instructions) | | Farm v3 / v5 | Custom u32 codes | [`products/farm-staking/instructions`](/products/farm-staking/instructions) | | Farm v6 | 6000+ (Anchor) | [`products/farm-staking/instructions`](/products/farm-staking/instructions) | | LaunchLab | 6000+ (Anchor) | [`products/launchlab/instructions`](/products/launchlab/instructions) | ## Mapping SDK errors to program errors The official TypeScript SDK wraps on-chain errors into `SendTransactionError` and, for Anchor programs, `AnchorError`: ```ts theme={null} import { AnchorError } from "@coral-xyz/anchor"; try { await sdk.cpmm.swap({ ... }); } catch (err) { if (err instanceof AnchorError) { console.log(err.error.errorCode.number, err.error.errorCode.code, err.error.errorMessage); // 6005 ExceededSlippage "Exceeds desired slippage limit" } else { // Raw SendTransactionError — inspect err.logs for the "custom program error: 0x..." line. } } ``` If you are not using Anchor client-side, parse the transaction logs: ``` Program invoke [1] Program log: AnchorError caused by account: pool_state. Error Code: ExceededSlippage. Error Number: 6005. Error Message: Exceeds desired slippage limit. ``` The pattern `Error Number: (\d+)` is stable across Anchor versions and safe to match. ## Regenerating these tables When a program is upgraded and adds a new error, re-extract from source: ```bash theme={null} # Clone and grep the error enum in order. git clone --depth=1 https://github.com/raydium-io/raydium-cp-swap awk '/#\[msg\(/{ gsub(/^[ \t]*#\[msg\("|"\)\][ \t]*/,""); m=$0; getline; gsub(/,/,""); gsub(/^[ \t]+/,""); print "6000+++\t" $0 "\t" m }' \ raydium-cp-swap/programs/cp-swap/src/error.rs # Replace 6000+++ with monotonically-increasing 6000,6001,... Append new rows to the table above. ``` Always update [`reference/changelog`](/reference/changelog) when a new variant is added, so integrators upgrading the SDK know to refresh their error handlers. Sources: * [raydium-cp-swap error.rs (mainnet CPMM)](https://github.com/raydium-io/raydium-cp-swap/blob/master/programs/cp-swap/src/error.rs) * [raydium-clmm error.rs (mainnet CLMM)](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/error.rs) # Fee comparison Source: https://docs.raydium.io/reference/fee-comparison Side-by-side fee matrix across all Raydium products — swap fees, protocol shares, creation costs, and how Token-2022 transfer fees stack on top. A single-page lookup for expected costs. This page is a lookup table. Use it to answer "what does a swap on pool X cost?" or "how much SOL do I need to create a CLMM pool?" — without digging through each product's chapter. For protocol-level fee splits, see [`ray/protocol-fees`](/ray/protocol-fees). ## Swap fees per product Fee values are percentages of the input amount, applied before pool math. | Product | Available tiers | Default / most-used | Split | | ------------------------------------ | ----------------------- | ------------------- | ----------------------------------------------------------------- | | **AMM v4** | 0.25% (only) | 0.25% | 88% LP, 12% protocol (22/3 of 25) | | **CPMM** | 0.01%, 0.25%, 1% | 0.25% | 100% LP (protocol share configurable per AmmConfig, currently 0%) | | **CLMM** | 0.01%, 0.05%, 0.25%, 1% | 0.25% | 88% LP, 12% protocol | | **LaunchLab (pre-graduation)** | 1% (default) | 1% | 50% to pool seed, 50% to LaunchLab treasury | | **LaunchLab (post-graduation CPMM)** | 0.25% | 0.25% | Inherits CPMM split | ### Fee denominator convention * **AMM v4**: fees are encoded as x/10,000. "25" means 0.25%. * **CPMM/CLMM**: fees are encoded as x/1,000,000. "2500" means 0.25%. When reading on-chain data directly, use the correct denominator for the pool's program. ## Protocol fee splits Within the "protocol" share of each fee, further splits apply: | Product | Protocol share of swap fee | Split within protocol | | --------- | ----------------------------------------- | -------------------------- | | AMM v4 | 12% (3/25) | 100% to treasury | | CPMM | Configurable per AmmConfig (currently 0%) | N/A | | CLMM | 12% of swap fee | 100% to treasury | | LaunchLab | 50% of swap fee pre-graduation | 100% to LaunchLab treasury | The **LP share** flows to LP token holders automatically: * AMM v4 / CPMM: accumulated in pool reserves, distributed pro-rata on withdrawal. * CLMM: accumulated in `fee_growth_global_X/Y`, claimable via `collectFee`. ## Creation costs One-time costs paid in SOL to deploy a pool. | Product | Rent cost | Creation fee | Total typical | | ---------------- | ------------------------------------------------------ | ----------------------- | ------------- | | AMM v4 | \~0.15 SOL (multiple accounts) | 0.2 SOL | \~0.35 SOL | | CPMM | \~0.04 SOL (pool + vaults + observation) | 0.15 SOL | \~0.19 SOL | | CLMM | \~0.075 SOL (pool + observation + initial tick arrays) | 0 SOL | \~0.075 SOL | | Farm v6 | \~0.02 SOL (farm state + reward vaults) | 0.1 SOL (anti-spam) | \~0.12 SOL | | LaunchLab launch | \~0.015 SOL (bonding curve account) | 0.1 SOL (LaunchLab fee) | \~0.115 SOL | All rent is recoverable — closing the pool / position / farm returns the lamports. ## Graduation / migration costs | Event | Cost | Who pays | | --------------------------- | ------------------------------------------------ | -------------------------------- | | LaunchLab → CPMM graduation | \~0.04 SOL (new CPMM pool creation) | Paid from bonding-curve reserves | | AMM v4 → CPMM migration | \~0.04 SOL CPMM creation + \~0.000005 SOL tx fee | LP performing the migration | | CLMM position NFT transfer | \~0.000005 SOL tx fee | Transferor | ## Network fees per typical operation These are Solana base + priority fees, separate from Raydium protocol fees. | Operation | CU | Base fee | Priority fee (10k µL/CU) | Total network | | ---------------------------- | --------------------- | ------------ | ------------------------ | ------------- | | CPMM swap | \~140,000 | 0.000005 SOL | 0.0014 SOL | \~0.0015 SOL | | CLMM swap (no ticks) | \~170,000 | 0.000005 SOL | 0.0017 SOL | \~0.0017 SOL | | CLMM swap (4 tick crossings) | \~320,000 | 0.000005 SOL | 0.0032 SOL | \~0.0032 SOL | | Open CLMM position | \~280,000 | 0.000005 SOL | 0.0028 SOL | \~0.0028 SOL | | Farm v6 stake | \~130,000 | 0.000005 SOL | 0.0013 SOL | \~0.0013 SOL | | Claim farm rewards | \~160,000 | 0.000005 SOL | 0.0016 SOL | \~0.0016 SOL | | Migrate v4 → CPMM (2 tx) | \~200,000 + \~200,000 | 0.00001 SOL | 0.004 SOL | \~0.004 SOL | Priority fees scale linearly with CU price; see [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning). ## Token-2022 fee interaction When one or both sides of a pool use Token-2022 with a transfer fee, the effective fee compounds: ``` net input to pool = amount_in × (1 − transfer_fee_in) pool charges swap = net × swap_fee_rate net output to user = out × (1 − transfer_fee_out) ``` Example: CPMM 0.25% pool, input side has 1% transfer fee, output side has 0.5% transfer fee. ``` amount_in = 1000 net_to_pool = 1000 × 0.99 = 990 pool swap fee = 990 × 0.0025 = 2.475 (kept as LP fee) amount_after_fee = 987.525 amount_out (AMM) = 987.525 × ratio, say 987.525 × 0.98 = 967.77 net_out_to_user = 967.77 × 0.995 = 962.94 ``` Effective fee: **\~3.7%** despite a nominal 0.25% pool fee. The SDK computes the effective rate via `getComputeAmountOut` — always use it when quoting Token-2022 pools. ## LaunchLab specifics | Phase | Fee | | ---------------------------------- | ------------------------------ | | Bonding curve trades (buy or sell) | 1% (split 50/50 pool/treasury) | | Graduation (one-time) | \~0.04 SOL from curve reserves | | Post-graduation (CPMM pool) | 0.25% (standard CPMM fees) | Projects launching tokens often include a "creator fee" option (0–0.3%) taken out of the LP share during the bonding-curve phase; configurable at launch. The creator fee is **bonding-curve only** — once the launch graduates to a CPMM pool, the resulting pool runs at the standard CPMM tier with no creator surcharge. See [`products/launchlab/creator-fees`](/products/launchlab/creator-fees) for the full lifecycle and how creators continue to earn from graduated pools through the LaunchLab NFT mechanism. ## Farm v6 reward cost model Farms don't charge fees per se — they're reward distribution. But they cost SOL to operate: | Cost | Typical | | ---------------------- | ----------------------------------- | | Farm creation | 0.1 SOL anti-spam + \~0.02 SOL rent | | Top-up reward vault | Just the tokens + \~0.0005 SOL tx | | Reclaim unused rewards | \~0.0005 SOL tx | Farms also require enough lamports in the reward vault to cover rent for the lifetime of the farm; closure returns them. ## Historical parameter changes | Date | Change | Authority | | -------- | ------------------------------------------- | ---------------- | | Feb 2021 | AMM v4 launch at 0.25%, 22/3 split | Launch parameter | | Jun 2024 | CPMM launch with 0%, 0.01%, 0.25%, 1% tiers | Launch parameter | For protocol-level fee splits and treasury addresses, see [`ray/protocol-fees`](/ray/protocol-fees) and [`ray/treasury`](/ray/treasury). ## How to read current values on-chain ### CPMM/CLMM AmmConfig ```ts theme={null} const config = await raydium.cpmm.getAmmConfigs(); console.log(config[0]); // { // id: "...", // tradeFeeRate: 2500, // 2500 / 1_000_000 = 0.25% // protocolFeeRate: 0, // 0 / 1_000_000 = 0% // fundFeeRate: 0, // ... // } ``` ### AMM v4 pool ```ts theme={null} const poolInfo = await raydium.liquidity.getPoolInfoFromRpc({ poolId }); console.log(poolInfo.ammFeesNumerator, poolInfo.ammFeesDenominator); // 25, 10000 → 0.25% ``` ### Farm v6 reward rate ```ts theme={null} const farm = await raydium.farm.getFarmInfoFromRpc({ id: farmId }); console.log(farm.farmData.rewardInfos); // Per-reward: emissionsPerSecond, startTime, endTime ``` ## Comparison to other DEXes For context (all values as of April 2026): | | Raydium CPMM | Raydium CLMM | Orca Whirlpools | Uniswap V3 | Phoenix | | ---------------- | ------------ | ------------ | --------------- | --------------------- | ------------- | | Default swap fee | 0.25% | 0.25% | 0.3% | 0.3% | 0 (orderbook) | | Lowest tier | 0.01% | 0.01% | 0.01% | 0.01% | N/A | | Highest tier | 1% | 1% | 1% | 1% | N/A | | Protocol share | 16% | 16% | 11% | 0-25% | N/A | | Token-2022 | Yes | Yes | Yes | N/A (different chain) | No | ## Pointers * [`ray/protocol-fees`](/ray/protocol-fees) — protocol fee splits. * [`products/cpmm/fees`](/products/cpmm/fees) — CPMM accounting detail. * [`products/clmm/fees`](/products/clmm/fees) — CLMM accounting detail. * [`products/amm-v4/fees`](/products/amm-v4/fees) — AMM v4 accounting. * [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning) — network fee sizing. Sources: * Live AmmConfigs via `api-v3.raydium.io`. * CU benchmarks from SDK test suite. * Live protocol fee references from Raydium docs. # Glossary Source: https://docs.raydium.io/reference/glossary Definitions of terms used throughout Raydium's documentation. Each entry has a one-line definition and a pointer to the page where the term is explained in depth. A flat list of terms. Use Ctrl-F; each term links to the page with the full treatment. Terms are alphabetized within letter sections; Solana-native concepts (account, PDA, etc.) are included since they're referenced pervasively. ## A **AcceleRaytor** — Raydium's original 2021-2023 token launchpad. Retired when LaunchLab replaced it. See [`introduction/history-and-milestones`](/introduction/history-and-milestones). **Account** — A row of data on Solana, identified by a pubkey, owned by a program, holding data bytes and lamports. See [`solana-fundamentals/account-model`](/solana-fundamentals/account-model). **Address Lookup Table (ALT)** — A published account that lets transactions reference accounts by 1-byte index, saving bytes. Raydium SDK uses ALTs for multi-hop routes. See [`solana-fundamentals/transactions-and-fees`](/solana-fundamentals/transactions-and-fees). **AmmConfig** — Configuration account for CPMM/CLMM defining fee tier and protocol-fee share. Multiple configs can coexist; pools reference one at creation. See [`ray/protocol-fees`](/ray/protocol-fees). **AMM v4** — Raydium's original constant-product AMM (2021). Initially a hybrid design that mirrored the curve onto OpenBook; the OpenBook integration has been deactivated and pools now operate as pure constant-product AMMs. Still fully operational; no longer the recommended default for new pools. See [`products/amm-v4/overview`](/products/amm-v4/overview). **Anchor** — A Rust framework for Solana programs that handles account validation, error handling, and generates IDLs. Used by CPMM, CLMM, Farm v6, LaunchLab. See [`solana-fundamentals/programs-and-anchor`](/solana-fundamentals/programs-and-anchor). **Anchor discriminator** — 8-byte prefix on Anchor-managed accounts and instructions, derived from the account/instruction name. Identifies account types. See [`solana-fundamentals/account-model`](/solana-fundamentals/account-model). **Arbitrage** — Exploiting price differences between venues (Raydium ↔ Orca ↔ CEX) to keep prices in line. Fundamental to AMM price discovery. **Associated Token Account (ATA)** — Conventional PDA-derived token account for a (user, mint) pair. See [`solana-fundamentals/spl-token-and-token-2022`](/solana-fundamentals/spl-token-and-token-2022). ## B **Bonding curve** — A pricing function for a token that deterministically sets price as a function of supply. Used in LaunchLab pre-graduation. See [`products/launchlab/bonding-curve`](/products/launchlab/bonding-curve). **BPF Loader (Upgradable)** — The Solana loader program that deploys and upgrades programs. See [`solana-fundamentals/programs-and-anchor`](/solana-fundamentals/programs-and-anchor). **Bump** — Single byte appended to seeds to derive a PDA off-curve. Canonical bump is the highest value that produces an off-curve address. See [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis). ## C **Canonical bump** — The bump found by decrementing from 255. Raydium's programs always use the canonical bump. See [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis). **CLMM** — Concentrated Liquidity Market Maker, Raydium's 2022 Uniswap-V3-style product. See [`products/clmm/overview`](/products/clmm/overview). **Compute Budget** — Per-transaction limit on compute units. Default 200k, max 1.4M. Set via `ComputeBudget::SetComputeUnitLimit`. See [`solana-fundamentals/transactions-and-fees`](/solana-fundamentals/transactions-and-fees). **Compute Unit (CU)** — Solana's unit of computational cost. Priority fees are charged per CU. See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning). **Constant product** — The AMM invariant `x × y = k`. See [`algorithms/constant-product`](/algorithms/constant-product). **CPI (Cross-Program Invocation)** — One Solana program calling another within a single transaction. See [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis). **CPMM** — Raydium's modern constant-product AMM (2024). Replaces AMM v4 for new pools. See [`products/cpmm/overview`](/products/cpmm/overview). **Creator fee (LaunchLab)** — Optional percentage of swap fees routed to the token creator during bonding-curve phase. See [`products/launchlab/overview`](/products/launchlab/overview). ## D **DLMM** — Dynamic Liquidity Market Maker. Meteora's CLMM variant. Not Raydium. **Discriminator** — See "Anchor discriminator". ## E **Emissions (Farm)** — Rate at which farm rewards are distributed, in tokens-per-second. See [`products/farm-staking/overview`](/products/farm-staking/overview). **Escrow** — A PDA-held token account holding funds until a condition is met. Common integration pattern. See [`integration-guides/cpi-integration`](/integration-guides/cpi-integration). **Executable account** — Solana account with `executable = true`, holding program bytecode. See [`solana-fundamentals/account-model`](/solana-fundamentals/account-model). ## F **Farm v3 / v5 / v6** — Successive generations of Raydium's reward-farming program. v6 is current; v3/v5 legacy. See [`products/farm-staking/overview`](/products/farm-staking/overview). **Fee growth (CLMM)** — Global fee-per-unit-liquidity counter, tracked in `fee_growth_global_X` and `fee_growth_global_Y`. See [`products/clmm/math`](/products/clmm/math). **Fee tier** — A specific swap-fee percentage offered by a pool. E.g., CLMM 0.05% tier. See [`reference/fee-comparison`](/reference/fee-comparison). **Finalization** — Solana's strongest commitment level, \~13 seconds after tx landing. See [`solana-fundamentals/transactions-and-fees`](/solana-fundamentals/transactions-and-fees). **Freeze authority** — Power to freeze (lock) token accounts for a given mint. Risk for users LPing into tokens with active freeze authority. ## G **Graduation (LaunchLab)** — Event where a bonding-curve launch reaches a threshold and auto-migrates to a CPMM pool. See [`products/launchlab/overview`](/products/launchlab/overview). ## H **Hybrid AMM** — An AMM that integrates with an external orderbook to route trades through whichever has better prices. AMM v4 was originally hybrid (against OpenBook), but its orderbook side has since been deactivated, so AMM v4 now operates as a pure AMM. CPMM is not hybrid. ## I **IDL** — Interface Description Language. Anchor's JSON description of a program's instructions, accounts, events, errors. See [`sdk-api/anchor-idl`](/sdk-api/anchor-idl). **Impermanent loss (IL)** — The difference in LP's token value vs holding the underlying tokens, due to price divergence. Fundamental to AMMs. See [`algorithms/constant-product`](/algorithms/constant-product). **Invoke / invoke\_signed** — Solana syscalls for CPI. `invoke_signed` is used for PDA-authorized CPIs. See [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis). ## J **Jito** — Solana block-engine that supports bundled transactions for MEV protection. See [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev). **Jupiter** — The dominant Solana DEX aggregator. Routes \~60% of its volume through Raydium. See [`introduction/ecosystem-position`](/introduction/ecosystem-position). ## K **Kudelski** — Audit firm that has audited Raydium programs. See [`security/audits`](/security/audits). ## L **Lamport** — Smallest SOL unit. 1 SOL = 1,000,000,000 lamports. **LaunchLab** — Raydium's token-launch platform (2024). Bonding-curve launches that graduate to CPMM. See [`products/launchlab/overview`](/products/launchlab/overview). **Liquidity L (CLMM)** — A position's contribution to pool pricing, in the units defined by Uniswap V3 math. See [`algorithms/clmm-math`](/algorithms/clmm-math). **LP (Liquidity Provider)** — A user who deposits tokens into a pool in exchange for LP tokens (or an NFT for CLMM) and a claim on future fees. **LP token** — SPL token representing an LP's share in a CPMM or AMM v4 pool. CLMM uses NFTs instead. ## M **MadShield** — Audit firm that has audited Raydium programs. See [`security/audits`](/security/audits). **Master-chef pattern** — Reward-tracking design based on SushiSwap's MasterChef contract. `reward_per_share` accumulator style. Used by Farm v6. **Memecoin** — Informal term for a community/speculation-driven token. Many launch via LaunchLab. **MEV (Maximal Extractable Value)** — Value extractable by transaction ordering (front-running, sandwich, back-running). See [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev). **Mint** — A Token Mint account defining a token. Don't confuse with "minting" the verb (issuing new supply). **Multisig** — Multi-signature account requiring N-of-M signers. Raydium uses Squads multisigs for authorities. See [`security/admin-and-multisig`](/security/admin-and-multisig). ## N **Non-transferable (Token-2022)** — Extension that prevents token transfers. Blocked by Raydium pool creation. **NFT** — Non-Fungible Token. CLMM positions are NFTs; so are some LaunchLab artifacts. ## O **Observation / ObservationState** — CLMM's on-chain price-history account. Stores cumulative price weighted by time, enabling TWAP oracles. See [`products/clmm/accounts`](/products/clmm/accounts). **OpenBook** — Formerly Serum. Solana's central limit orderbook. AMM v4 *originally* integrated with OpenBook, but that integration has been deactivated; CPMM and CLMM never integrated with it. See [`products/amm-v4/overview`](/products/amm-v4/overview). **Oracle** — External price feed. Pyth, Jupiter, and (for internal use) CLMM's ObservationState TWAP. See [`security/oracle-and-token-risks`](/security/oracle-and-token-risks). **Orca** — Rival Solana DEX. Whirlpools is Orca's CLMM product. ## P **PDA (Program-Derived Address)** — An address derived deterministically from a program + seeds. No private key; the owning program signs via `invoke_signed`. See [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis). **Permanent delegate (Token-2022)** — Extension granting a designated wallet unconditional transfer/burn power. Blocked by Raydium pool creation. **Phoenix** — Native Solana orderbook DEX. Peer, not direct competitor. **PnL (AMM v4)** — Historically: Profit and Loss from OpenBook fills, accrued into AMM v4 pools and settled via `MonitorStep` cranks. With the OpenBook integration deactivated, no new orderbook PnL accrues — `state_data.total_pnl_*` counters carry historical values only. The 0.03% protocol-fee path that uses `need_take_pnl_*` is unaffected and still active. **Pool authority** — PDA that signs for pool vault transfers. One per program (not per pool). See [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis). **Pool state** — On-chain account holding a pool's reserves, fees, configuration. Central data structure per product. **Priority fee** — Per-CU fee paid to validators to prioritize a transaction within a block. See [`integration-guides/priority-fee-tuning`](/integration-guides/priority-fee-tuning). **Program account** — Account holding a program's metadata / bytecode. See [`solana-fundamentals/programs-and-anchor`](/solana-fundamentals/programs-and-anchor). **Program Derived Address** — See PDA. **Protocol fee** — Portion of swap fees routed to RAY buybacks or treasury rather than LPs. See [`ray/protocol-fees`](/ray/protocol-fees). **Pyth** — Solana oracle network. Used by frontends for USD display; not by Raydium pool math. ## Q **Q64.64 (sqrt\_price\_x64)** — Fixed-point representation: 64 integer + 64 fractional bits. Used by CLMM for sqrt-price to avoid floating-point drift. See [`algorithms/clmm-math`](/algorithms/clmm-math). ## R **\$RAY** — Raydium's native SPL token. See [`ray/index`](/ray). **Realized fee (CLMM)** — Fees accrued to a position since last `collectFee`. Claimed via `collectFee`. See [`products/clmm/math`](/products/clmm/math). **Remaining accounts** — Additional accounts appended to an instruction's account list, interpreted by position. Used for variable-length account lists (tick arrays, transfer-hook extras). See [`solana-fundamentals/pdas-and-cpis`](/solana-fundamentals/pdas-and-cpis). **Rent** — Solana charges storage rent, but all modern accounts must be rent-exempt (hold enough lamports to pre-pay rent forever). See [`solana-fundamentals/account-model`](/solana-fundamentals/account-model). ## S **Sandwich attack** — MEV tactic: front-run a user's swap, then back-run to extract price impact. See [`integration-guides/routing-and-mev`](/integration-guides/routing-and-mev). **Serum** — The predecessor to OpenBook. Not currently active; see "OpenBook". **Slippage** — Difference between quoted and actual output, from price impact + price movement. Controlled via `min_out` parameter on swaps. **SOL** — Solana's native token. **SPL Token** — The legacy Solana token program. Address `Tokenkeg...`. See [`solana-fundamentals/spl-token-and-token-2022`](/solana-fundamentals/spl-token-and-token-2022). **sqrt\_price\_x64** — See Q64.64. **Squads** — Multisig platform Raydium uses for operational and upgrade authorities. See [`security/admin-and-multisig`](/security/admin-and-multisig). **Swap / SwapV2** — Raydium instruction variants. `SwapV2` supports Token-2022; legacy `Swap` does not. See [`solana-fundamentals/spl-token-and-token-2022`](/solana-fundamentals/spl-token-and-token-2022). ## T **Tick (CLMM)** — Discrete price level. Positions span a range of ticks. See [`algorithms/clmm-math`](/algorithms/clmm-math). **Tick array** — PDA holding 60 consecutive ticks' state. Enables chunked storage of the tick lattice. See [`products/clmm/ticks-and-positions`](/products/clmm/ticks-and-positions). **Tick spacing** — The integer gap between usable ticks in a pool. Tied to the fee tier (0.01% → 1, 0.25% → 60, etc.). See [`algorithms/clmm-math`](/algorithms/clmm-math). **Timelock** — Delay between approval and execution of a sensitive action. Raydium upgrades have a 24-hour timelock. See [`security/admin-and-multisig`](/security/admin-and-multisig). **Token-2022** — The newer token program with extensions. Address `TokenzQ...`. See [`solana-fundamentals/spl-token-and-token-2022`](/solana-fundamentals/spl-token-and-token-2022). **Transfer fee (Token-2022)** — Extension charging a percentage on every token transfer. Accounted for in Raydium CPMM/CLMM via `SwapV2`. **Transfer hook (Token-2022)** — Extension invoking a program on every transfer. Supported by Raydium with caveats. **TVL (Total Value Locked)** — Sum of pool/farm balances in USD. Raydium's TVL as of April 2026: \~\$1.8B. **TWAP (Time-Weighted Average Price)** — Average price weighted by time, derived from CLMM ObservationState. See [`products/clmm/accounts`](/products/clmm/accounts). ## U **UserLedger (Farm v6)** — PDA storing a user's stake balance and reward debt in a farm. ## V **Vault** — A Token Account owned by a pool program's PDA authority, holding pool reserves. Each pool has two vaults (one per side). **Versioned transaction (v0)** — Current Solana transaction format supporting ALTs. Legacy transactions are version 0xff. See [`solana-fundamentals/transactions-and-fees`](/solana-fundamentals/transactions-and-fees). ## W **Whirlpools** — Orca's CLMM product. Similar design to Raydium CLMM; different codebase. **Wrapped SOL (wSOL)** — SOL wrapped as an SPL Token for use in token-account-only contexts. Created and closed per-transaction. See [`user-flows/swap`](/user-flows/swap). ## X **x × y = k** — The constant-product invariant. See [`algorithms/constant-product`](/algorithms/constant-product). ## Pointers * [`introduction/what-is-raydium`](/introduction/what-is-raydium) — start here. * [`solana-fundamentals/account-model`](/solana-fundamentals/account-model) — Solana concepts. * [`products/cpmm/overview`](/products/cpmm/overview), [`products/clmm/overview`](/products/clmm/overview), etc. — per-product detail. * [`reference/fee-comparison`](/reference/fee-comparison) — fees. * [`reference/program-addresses`](/reference/program-addresses) — program IDs. Sources: * Definitions reflect usage across Raydium's codebase and ecosystem docs as of April 2026. # Reference Source: https://docs.raydium.io/reference/index Lookup tables: program addresses, error codes, fee comparisons, glossary, and changelog. ## Who this chapter is for Everyone, as a lookup target. Other chapters link *into* this chapter whenever they mention a program ID, error code, or term that needs an authoritative definition. ## Chapter contents Canonical program IDs and shared authority/config PDAs per cluster (mainnet-beta, devnet). Single source of truth. Every anchor error per program, with cause and recommended user-facing message. Side-by-side table of fee tiers, splits, and creator-fee support across AMM v4, CPMM, CLMM. Per-program Token-2022 acceptance, per-extension support matrix, and the interactions every integrator needs to handle. Terms used across the docs: tick, sqrt price, liquidity, observation, transfer fee, etc. Each term links back to the page where it is formally defined. Doc-set changelog — when chapters are added, when code samples are re-verified, when program addresses change. ## Resources Auxiliary materials for press, integrators, and partners. Logos, colors, typography, and usage rules. Third-party tools, indexers, and dashboards. Apache-2.0 / GPL-3.0 / CC-BY-4.0 split across code, SDK, and docs. ## Writing brief * This chapter is the *only* place where program addresses and error codes are listed verbatim. Other pages link here. * Keep the glossary short — only terms used in two or more chapters. * The changelog entries should be dated and link to the affected page(s). # Program addresses Source: https://docs.raydium.io/reference/program-addresses Canonical Raydium program IDs and shared PDAs on Solana mainnet-beta and devnet. Every other page links here instead of hardcoding addresses. **Source of truth.** This page is the only place in the docs that lists program addresses verbatim. Other pages link here. If an ID changes, update it **only here** and every reference in the site stays consistent. Always cross-check values against the live API (`https://api-v3.raydium.io/main/info`) before signing real transactions. ## Mainnet-beta Rule of thumb: if a program ID in the wild does not match the table below, **do not sign the transaction.** A mismatched program ID is the single easiest way to lose funds on Solana. ### On-chain programs | Program | Program ID | Source | | ------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------- | | **AMM v4** (Hybrid AMM + OpenBook) | `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8` | [raydium-amm](https://github.com/raydium-io/raydium-amm) | | **CPMM** (Standard AMM) | `CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C` | [raydium-cp-swap](https://github.com/raydium-io/raydium-cp-swap) | | **CLMM** (Concentrated Liquidity) | `CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK` | [raydium-clmm](https://github.com/raydium-io/raydium-clmm) | | **Stable AMM** (StableSwap-style curve) | `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` | source not publicly available | | **Farm v3** (legacy RAY staking) | `EhhTKczWMGQt46ynNeRX1WfeagwwJd7ufHvCDjRxjo5Q` | source not publicly available | | **Farm v5** (legacy ecosystem farms) | `9KEPoZmtHUrBbhWN1v1KWLMkkvwY6WLtAVUCPRtRjP4z` | source not publicly available | | **Farm v6** (current ecosystem farms) | `FarmqiPv5eAj3j1GMdMCMUGXqPUvmquZtMy86QH6rzhG` | source not publicly available | | **LaunchLab** | `LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj` | source not publicly available | | **AMM Routing** (Raydium's on-chain router) | `routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS` | source not publicly available | | **Burn & Earn / LP Lock** | `LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE` | source not publicly available | Notes: * AMM v4 and Farm v3/v5 are **retained for existing pools and positions**. The Raydium UI and SDK route new pool creation and new ecosystem farms to CPMM, CLMM, and Farm v6 respectively — see [`protocol-overview/versions-and-migration`](/protocol-overview/versions-and-migration). * The Farm v3 ID above doubles as the \$RAY single-asset staking program. Behavior is identical to Farm v3 for LP staking. * **Stable AMM** is a separate program that the AMM Routing program can target alongside AMM v4, CPMM, and CLMM. Liquidity is thin compared to the other three programs and the SDK does not expose a first-class API for it; integrators that route through it generally do so via the router. * **Source-code availability.** Of the on-chain programs above, only `raydium-amm` (AMM v4), `raydium-cp-swap` (CPMM), and `raydium-clmm` (CLMM) ship with public source repositories under [`github.com/raydium-io`](https://github.com/raydium-io). Stable AMM, LaunchLab, AMM Routing, Burn & Earn / LP Lock, and the Farm programs are not publicly available — verify them against the live API, the on-chain bytecode, and the published IDLs in [`raydium-io/raydium-idl`](https://github.com/raydium-io/raydium-idl) instead. ### Shared admin authority All Anchor-based programs (CLMM, CPMM, LaunchLab, Lock) share a single hardcoded admin `Pubkey` for instruction-level access control to admin paths (such as `CreateAmmConfig` or `UpdatePoolStatus`): | Cluster | Admin | | ------------ | ---------------------------------------------- | | mainnet-beta | `GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ` | | devnet | `DRayqG9RXYi8WHgWEmRQGrUWRWbhjYWYkCRJDd6JBBak` | Account-level operational authorities (e.g. `protocol_owner`, `fund_owner` on CPMM/CLMM `AmmConfig`, or the migration wallets on LaunchLab `GlobalConfig`) are stored on-chain and may differ from the program admin. Read them directly from the relevant config account before sending high-stakes transactions. ### Shared config / PDA conventions Several Raydium programs expose **config accounts** whose public keys are stable and listed on the public API. Prefer the API lookup over hardcoding: ``` # CPMM fee configs (returns an array of {id, index, tradeFeeRate, ...}) GET https://api-v3.raydium.io/main/cpmm-config # CLMM fee configs GET https://api-v3.raydium.io/main/clmm-config ``` Default CPMM `AmmConfig` index `0` (standard 0.25% pool) fee parameters, for reference: | Field | Value | Meaning | | ------------------- | -------- | -------------------------------------------------------- | | `trade_fee_rate` | `2500` | 0.25% of trade volume | | `protocol_fee_rate` | `120000` | 12% **of the trade fee** (not volume) routed to protocol | | `fund_fee_rate` | `40000` | 4% **of the trade fee** routed to the fund multisig | | `creator_fee_rate` | `500` | 0.05% of trade volume to the pool creator (optional) | See [`products/cpmm/fees`](/products/cpmm/fees) for how the splits compose, and [`reference/fee-comparison`](/reference/fee-comparison) for the cross-product matrix. ### PDA seeds The seeds below are **canonical** and used by both the SDK and on-chain CPIs. Always compute PDAs; do not hardcode derived addresses. ```ts theme={null} // CPMM — all seeds are static ASCII strings unless noted. const [ammConfig] = PublicKey.findProgramAddressSync( [Buffer.from("amm_config"), u16ToBytes(index)], CPMM_PROGRAM_ID, ); const [authority] = PublicKey.findProgramAddressSync( [Buffer.from("vault_and_lp_mint_auth_seed")], CPMM_PROGRAM_ID, ); const [poolState] = PublicKey.findProgramAddressSync( [Buffer.from("pool"), ammConfig.toBuffer(), token0Mint.toBuffer(), token1Mint.toBuffer()], CPMM_PROGRAM_ID, ); const [lpMint] = PublicKey.findProgramAddressSync( [Buffer.from("pool_lp_mint"), poolState.toBuffer()], CPMM_PROGRAM_ID, ); const [vault0] = PublicKey.findProgramAddressSync( [Buffer.from("pool_vault"), poolState.toBuffer(), token0Mint.toBuffer()], CPMM_PROGRAM_ID, ); const [vault1] = PublicKey.findProgramAddressSync( [Buffer.from("pool_vault"), poolState.toBuffer(), token1Mint.toBuffer()], CPMM_PROGRAM_ID, ); const [observation] = PublicKey.findProgramAddressSync( [Buffer.from("observation"), poolState.toBuffer()], CPMM_PROGRAM_ID, ); ``` `token0Mint` / `token1Mint` are **sorted by public-key byte order** (token0 \< token1) before hashing. Getting this wrong yields a valid PDA for a non-existent pool. The equivalent CLMM seeds follow the same style; see [`products/clmm/accounts`](/products/clmm/accounts). ## Devnet | Program | Program ID | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | **AMM v4** | `DRaya7Kj3aMWQSy19kSjvmuwq9docCHofyP9kanQGaav` | | **CPMM** | `DRaycpLY18LhpbydsBWbVJtxpNv9oXPgjRSfpF2bWpYb` | | **CLMM** | `DRayAUgENGQBKVaX8owNhgzkEDyoHTGVEGHVJT1E9pfH` | | **Stable AMM** | `DRayDdXc1NZQ9C3hRWmoSf8zK4iapgMnjdNZWrfwsP8m` | | **LaunchLab** | `DRay6fNdQ5J82H7xV6uq2aV3mNrUZ1J4PgSKsWgptcm6` | | **AMM Routing** | `DRaybByLpbUL57LJARs3j8BitTxVfzBg351EaMr5UTCd` | | **Burn & Earn / LP Lock** | `DLockwT7X7sxtLmGH9g5kmfcjaBtncdbUmi738m5bvQC` | | **Farm v3 / v5 / v6** | Not reliably published for devnet — confirm via the live API (`https://api-v3-devnet.raydium.io/main/info`) before use. | Devnet REST API base: `https://api-v3-devnet.raydium.io/` (same route shape as mainnet). ## How to verify an address on-chain 1. **Solana Explorer.** Paste the address into [explorer.solana.com](https://explorer.solana.com) and confirm it's marked `Program` with a current upgrade authority. Mainnet-beta should show deploys signed by Raydium's upgrade authority. 2. **CLI.** Use `solana program show -u mainnet-beta` to inspect deploy slot, BPF loader, upgrade authority, and data length. Record these in your runbook. 3. **IDL attachment.** Query the on-chain IDL with `anchor idl fetch --provider.cluster mainnet`. The IDL's `address` field should match. The same IDLs are mirrored at [github.com/raydium-io/raydium-idl](https://github.com/raydium-io/raydium-idl) — diff the on-chain IDL against the repo before trusting it. 4. **Config/admin authorities.** For CPMM/CLMM config accounts, read the `owner`/`protocol_owner` field and confirm it matches the current Raydium multisig published in [`security/admin-and-multisig`](/security/admin-and-multisig). If any of the four checks above disagrees with this page, treat this page as wrong and open an issue before writing new code against the addresses. ## Updating this page * Changes to program IDs are safety-critical. Do **not** ship a program-ID change without (a) linking to the Raydium announcement, (b) adding an entry in [`reference/changelog`](/reference/changelog), and (c) running a link-check over the docs to confirm no page still references the old value. * Deprecations stay in the table with a status note rather than being deleted — existing pools still resolve via the old program. Sources: * Live API: `https://api-v3.raydium.io/main/info` * [github.com/raydium-io/raydium-idl](https://github.com/raydium-io/raydium-idl) * [github.com/raydium-io/raydium-cp-swap](https://github.com/raydium-io/raydium-cp-swap) # Token-2022 support matrix Source: https://docs.raydium.io/reference/token-2022-support Which Raydium programs support Token-2022, the explicit extension allow-list each program enforces at pool creation, and the bypass paths (static mint whitelist, per-mint registry, Superstate detection) that admit otherwise-rejected mints. Raydium does **not** accept arbitrary Token-2022 mints. CPMM and CLMM both run a strict **allow-list mode**: only a small set of extensions pass by default; everything else is rejected at pool creation. A handful of trusted mints are admitted by hardcoded address; CLMM additionally supports an admin-managed per-mint registry and a runtime Superstate-token detection. This page is the single-page reference for what's enforced and where, with file-and-line citations into the program source. ## Program-level support | Program | Token-2022 base/quote mints? | Mode | | --------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **CPMM** | Yes — gated | Strict extension allow-list + 4-mint static whitelist. | | **CLMM** | Yes — gated | Strict extension allow-list + 6-mint static whitelist + admin per-mint registry + Superstate detection. | | **AMM v4** | No | Program predates Token-2022; both mints must be classic SPL Token. | | **Stable AMM** | No | Same constraints as AMM v4. | | **Farm v6** | Yes, partial | Reward mint can be Token-2022 (subject to the same allow-list when a farm wraps a CPMM/CLMM LP); user-stake LP mints inherit from the wrapped pool. | | **LaunchLab** | Yes, program-managed | Base mint can be Token-2022, but only via the dedicated `initialize_with_token_2022` instruction; the program itself creates the mint with `MetadataPointer` (always) and optionally `TransferFeeConfig` (rate ≤ 5%). Pre-existing Token-2022 mints with arbitrary extensions cannot be used as the base. | | **Burn & Earn** | Mirrors pool program | Inherits CPMM / CLMM gating. | The allow-list checks live in: * **CPMM**: `is_supported_mint` in [`raydium-cp-swap/programs/cp-swap/src/utils/token.rs:178`](https://github.com/raydium-io/raydium-cp-swap), called from `Initialize`. * **CLMM**: `is_supported_mint` in [`raydium-clmm/programs/amm/src/util/token.rs:280`](https://github.com/raydium-io/raydium-clmm), called from `CreatePool`. * **LaunchLab**: `initialize_with_token_2022` in `raydium-launchpad/programs/launchpad/src/instructions/initialize_with_token_2022.rs` (source not publicly available). There is **no** swap-time mint check on CPMM or CLMM — the gate fires only at pool creation. Once a pool exists, swaps just trust that the mints didn't change, which is correct for the immutable parts of Token-2022 mint state. ## CPMM and CLMM extension allow-list After the static-whitelist short-circuits (covered below), the program iterates the mint's extensions and rejects the mint if it carries **any** extension other than these five: | Extension | Reason it's allowed | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `TransferFeeConfig` | Pool math subtracts the inbound fee; the Token-2022 program handles the outbound fee. See [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees). | | `MetadataPointer` | Decorative — points at on-chain metadata. | | `TokenMetadata` | Decorative — inline metadata. | | `InterestBearingConfig` | Pool sees the principal amount; UI multiplier is decorator-only and the underlying balance is preserved. | | `ScaledUiAmount` | Same shape as interest-bearing — scale factor applies to UI display only. | Anything not in this list — `TransferHook`, `NonTransferable`, `ConfidentialTransferMint`, `PermanentDelegate`, `MintCloseAuthority`, `DefaultAccountState`, `GroupPointer`, `GroupMemberPointer`, `MemberPointer`, `Pausable`, etc. — causes `is_supported_mint` to return `false` and pool creation to revert. The relevant lines (CPMM, identical shape in CLMM): ```rust theme={null} for e in extensions { if e != ExtensionType::TransferFeeConfig && e != ExtensionType::MetadataPointer && e != ExtensionType::TokenMetadata && e != ExtensionType::InterestBearingConfig && e != ExtensionType::ScaledUiAmount { return Ok(false); } } Ok(true) ``` — [`cp-swap/src/utils/token.rs:190–200`](https://github.com/raydium-io/raydium-cp-swap) ## Bypass paths A Token-2022 mint that doesn't fit the allow-list can still be admitted through one of three explicit bypasses. They're attempted in order, before the extension iteration runs. ### 1. Static mint whitelist A constant `MINT_WHITELIST` array of base58 strings is hardcoded in each program. If the mint's address matches, the function returns `true` immediately and no extension check is performed. | Program | Whitelisted mints | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | CPMM | `HVbpJAQGNpkgBaYBZQBR1t7yFdvaYVp2vCQQfKKEN4tM`, `Crn4x1Y2HUKko7ox2EZMT6N2t2ZyH7eKtwkBGVnhEq1g`, `FrBfWJ4qE5sCzKm3k3JaAtqZcXUh4LvJygDeketsrsH4`, `2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo` | | CLMM | The same four, plus `DAUcJBg4jSpVoEzASxYzdqHMUN8vuTpQyG2TvDcCHfZg`, `AUSD1jCcCyPLybk1YnvPWsHQSrZ46dxwoMniN4N2UEB9` | These addresses are baked into the program; updating the list requires a program upgrade through the 3/4 upgrade multisig. ### 2. Per-mint registry — CLMM only CLMM additionally consults a `SupportMintAssociated` PDA at seed `[b"support_mint", mint]`. If that PDA exists for the mint, it's admitted regardless of its extension set. The PDA is created by `CreateSupportMintAssociated` ([`admin/create_support_mint_associated.rs`](https://github.com/raydium-io/raydium-clmm)). The instruction is gated to two signers: * `crate::admin::ID` — the standard Raydium admin authority. * `crate::create_support_mint_associated_owner::ID` — a dedicated authority for this purpose: `RayVyjyJQz9vAi126A4sGexKnSU1XeZaHTRcM1mZMPY` (mainnet), `rayf3nEbb3bnfN6RDGFpqPbjc5uUa3tRUzu6UVYrRx5` (devnet). Effect: the CLMM team can opt a specific Token-2022 mint into pool creation without a program upgrade. CPMM has no equivalent — its allow-list is strictly source-coded. ### 3. Superstate detection — CLMM only CLMM has a third path specifically for Superstate's tokenized assets, which use the `ScaledUiConfig` extension that the version of `spl-token-2022` linked into the CLMM program cannot unpack. Rather than upgrade the dependency, CLMM detects Superstate tokens by their authority shape: ```rust theme={null} superstate_allowlist::ID == freeze_authority && *mint_account_info.owner == spl_token_2022::ID && default_account_state_freeze && maybe_permanent_delegate ``` — [`raydium-clmm/programs/amm/src/util/token.rs:485`](https://github.com/raydium-io/raydium-clmm) A mint passes this branch if **all four** hold: * Its owning program is the Token-2022 program. * Its freeze authority equals `superstate_allowlist::ID`. Mainnet: `2Yq4T3mPNfjtEyTxSbRjRKqLf1pwbTasuCQrWe6QpM7x`. Devnet: `3TRuL3MFvzHaUfQAb6EsSAbQhWdhmYrKxEiViVkdQfXu`. * Its `DefaultAccountState` extension is set to `Frozen`. * Its permanent delegate is also `superstate_allowlist::ID`. This is a heuristic, not a registry — any future mint Superstate issues with the same authority shape will be admitted automatically. ## What the bypasses do not waive The bypasses skip the extension allow-list, but the program still enforces: * The mint is owned by either `Token` or `Token-2022`. A custom token program is rejected upstream. * The pool vaults are created with the right ATA extensions for Token-2022 pools (`ImmutableOwner`, etc.). * All transfers go through `transfer_checked` — fee-bearing mints land the right amount in vault. A whitelisted or PDA-registered mint that, e.g., adds a `TransferHook` later does not gain a swap-time check; the hook would simply run on every transfer and could brick swaps. Whitelisting is therefore a high-trust action. ## "Blocked" semantics When `is_supported_mint` returns `false`, pool creation reverts with `ErrorCode::NotSupportMint` (CPMM) / `ErrorCode::NotSupportMint` (CLMM). See [`reference/error-codes`](/reference/error-codes) for the numeric codes. Existing pools cannot retroactively fail this check — the gate runs only at creation. Mint extensions are immutable for the categories Raydium rejects (transfer hook, non-transferable, confidential transfer can't be added post-creation), so the static check is sufficient. ## Why each excluded extension is excluded * **TransferHook** — invokes a custom program on every transfer, with arbitrary CU consumption, arbitrary failure conditions, and the ability to reenter the calling program. No safe sandbox exists. Some DEXes maintain hook allow-lists; Raydium does not. * **NonTransferable** — `Transfer` always fails. A pool cannot take custody. * **ConfidentialTransfer** — transfer amounts are encrypted; the curve cannot price the swap. * **PermanentDelegate** — a holder of the delegate can sweep any token account, including the pool vault. Permitted only via the static whitelist for trusted issuers (e.g., regulated stablecoins). * **MintCloseAuthority** — the mint can be closed; existing pools become unusable. Disallowed by default. * **DefaultAccountState (Frozen)** — pool ATAs would land in `Frozen` state and require thawing per account. Allowed only via Superstate detection, which assumes the issuer thaws institutional accounts on enrollment. * **Group/Member pointers** — not actively harmful, but unreviewed. Disallowed by default to keep the surface narrow. ## Transfer-fee accounting For mints carrying `TransferFeeConfig`, every swap, deposit, and withdraw moves less than the nominal amount. The SDK surfaces both halves of the calculation: ```ts theme={null} const { amountIn, amountOut, feeAmount, token2022FeeIn, token2022FeeOut } = await raydium.cpmm.computeSwapAmount({ ... }); ``` A correct UI shows: * `amountIn + token2022FeeIn` as "you send" * `amountOut - token2022FeeOut` as "you receive" * `feeAmount` as the pool fee (LP + protocol), which is separate from the Token-2022 transfer fee A naive UI that shows only `amountIn → amountOut` understates costs. ### `maximumFee` cap Token-2022 transfer fees are capped per transfer. For a 1 % mint with a 10,000-token cap, a 100,000,000-token transfer pays only 10,000 in fee. The SDK's `computeSwapAmount` applies the cap; direct program callers must replicate it. ### Epoch transition A mint authority can schedule a fee-rate change that activates at the next epoch. During the transition window, two configs (`older`, `newer`) live on the mint at once and `TransferChecked` selects by current epoch. CPMM `SwapV2` and CLMM `SwapV2` both pass the full mint account in `accounts`, so the program reads the right config without an extra lookup. If you quote more than one epoch in advance via the Trade API or SDK, executed fee can differ from quoted fee — bounded by the older config's `maximum_fee_basis_points`. ## Interest-bearing and ScaledUiAmount The pool holds the principal amount; the "UI amount" is the principal multiplied by a time-dependent or admin-set scale factor. Swap math operates on principal: ``` principal_in = ui_amount_in / ui_multiplier(now) ``` The SDK converts automatically. Direct RPC readers should treat `pool.token0Vault.amount` as principal. ## "Token-2022 pool" definition A pool is a Token-2022 pool if **either** mint has `programId == TokenzQdB...`. The API surfaces this: ``` GET /pools/info/ids?ids= ``` ```json theme={null} { "data": [{ "mintA": { "programId": "TokenzQdB...", "hasTransferFee": true, ... }, "mintB": { "programId": "Tokenkeg...", ... } }] } ``` Use `programId` to dispatch, and `hasTransferFee` to surface a UI warning. ## SDK helpers ```ts theme={null} import { Raydium, TOKEN_2022_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2"; import { ExtensionType, getExtensionTypes, unpackMint } from "@solana/spl-token"; const raydium = await Raydium.load({ owner, connection }); const acct = await connection.getAccountInfo(mintPubkey); if (!acct) throw new Error("mint not found"); const mint = unpackMint(mintPubkey, acct, acct.owner); if (acct.owner.equals(TOKEN_2022_PROGRAM_ID)) { const extensions = getExtensionTypes(mint.tlvData); const accepted = new Set([ ExtensionType.TransferFeeConfig, ExtensionType.MetadataPointer, ExtensionType.TokenMetadata, ExtensionType.InterestBearingConfig, ExtensionType.ScaledUiAmount, ]); const rejecting = extensions.filter(e => !accepted.has(e)); if (rejecting.length) { console.warn("Pool creation will revert unless this mint is whitelisted:", rejecting); } } ``` ## Common integration mistakes * **Pre-flighting only the program ID.** A mint can be Token-2022 *and* unsupported. Walk the extension list against the allow-list (and the static whitelist) before allowing pool creation. * **Trusting the SDK's quote when the mint isn't accepted at all.** The quote API doesn't refuse to quote — pool creation is what reverts. Confirm `is_supported_mint` semantics off-chain before exposing pool creation in your UI. * **Quoting without the transfer-fee haircut.** A 1% transfer-fee mint on both sides of a 0.25% CPMM pool has an effective fee around 2.25%, not 0.25%. Use the SDK quote or Trade API quote — never compute fee manually from the pool's fee tier alone. * **Calling the legacy `Swap` instruction on a Token-2022 pool.** `Swap` predates Token-2022. Use `SwapV2` whenever either mint is Token-2022. * **Auto-listing new Token-2022 mints.** Wallets and aggregators should check for `TransferHook` and `NonTransferable` before surfacing a mint to users; both are Raydium-hostile. ## Future work Solana ecosystem and protocol roadmap items that would change this matrix: * Allow-listed transfer-hook programs at the Solana level (ecosystem convention evolving). * Confidential-transfer-compatible AMMs (research stage). * Broader CPMM per-mint registry (parity with CLMM). * Dependency upgrade so CLMM's `ScaledUiConfig` decoding works without the Superstate heuristic. This page will be updated when any land. ## Pointers * [`algorithms/token-2022-transfer-fees`](/algorithms/token-2022-transfer-fees) — fee math in swaps. * [`products/cpmm/instructions`](/products/cpmm/instructions) — `SwapV2`, `Initialize`. * [`products/clmm/instructions`](/products/clmm/instructions) — `SwapV2`, `CreatePool`, `CreateSupportMintAssociated`. * [`reference/error-codes`](/reference/error-codes) — `NotSupportMint` numeric codes per program. Sources: * `raydium-cp-swap/programs/cp-swap/src/utils/token.rs` — `MINT_WHITELIST`, `is_supported_mint`. * `raydium-clmm/programs/amm/src/util/token.rs` — `MINT_WHITELIST`, `superstate_allowlist`, `is_superstate_token`, `is_supported_mint`. * `raydium-clmm/programs/amm/src/instructions/admin/create_support_mint_associated.rs` — per-mint registry instruction. * `raydium-launchpad/programs/launchpad/src/instructions/initialize_with_token_2022.rs` — LaunchLab Token-2022 base-mint creation. # Brand kit Source: https://docs.raydium.io/resources/brand-kit Logos, colors, typography, and usage rules for representing Raydium in press, integrations, partner content, and dashboards. This kit is what the project asks third parties to follow when displaying the Raydium name, logo, or visual identity. Following these rules helps users immediately recognise legitimate Raydium surfaces and avoid confusion with imitators. **Master assets** live in the [docs repository](https://github.com/raydium-io) under `/logo/`. If something here doesn't match the master, the master wins. ## Name and capitalisation | Form | Use | | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | | **Raydium** | The protocol name. Always title case. Never `raydium`, never `RAYDIUM`. | | **Raydium Protocol** | Acceptable when disambiguating from the Raydium name in other contexts (it's also a chemical, a video game, etc.). | | **\$RAY** or **RAY** | Raydium's native SPL token. With the dollar prefix in marketing copy, plain `RAY` in technical or table contexts. | | **Raydium AMM v4** / **Raydium CPMM** / **Raydium CLMM** / **LaunchLab** | Product names. LaunchLab is one word, capital L's. | | **Burn & Earn** | One construction with the ampersand, both nouns capitalised. | Don't write `Raydium DEX`, `Ray protocol`, or `Raydiums` (no plural). The protocol is a singular noun. ## Logos The Raydium logo comes in two forms: * **Wordmark** — the full "Raydium" type lockup. Use this in headers, press materials, and any context where the name needs to be readable. * **Symbol mark** — the standalone monogram. Use this in app icons, favicons, and confined spaces (avatars, social profile pictures) where the wordmark won't fit at a legible size. ### Light and dark versions Two finishes per form: | File | Background | Use when | | ---------------- | ----------------- | --------------------------------------- | | `logo-light.svg` | Dark backgrounds | Primary placement on dark UI surfaces. | | `logo-dark.svg` | Light backgrounds | Primary placement on light UI surfaces. | | `mark-light.svg` | Dark backgrounds | Symbol-only on dark surfaces. | | `mark-dark.svg` | Light backgrounds | Symbol-only on light surfaces. | All assets are vector SVG. Use SVG wherever possible. Rasterise to PNG only at the final render size (avoid scaled PNGs). ### Clear space Maintain clear space around the logo equal to the height of the Raydium "R" character. Don't crowd the logo with text, icons, or graphics inside this margin. ### Minimum size Wordmark: don't render below **120 px** wide on screen, or **20 mm** in print. Symbol: don't render below **24 px** wide on screen, or **6 mm** in print. Below these thresholds the logo loses legibility and partners should switch to the alternate form (e.g. drop the wordmark for the symbol when space-constrained). ## Color palette The Raydium identity is built around a violet primary with darker and lighter variants for state, depth, and contrast. | Role | Hex | RGB | Use | | ----------- | --------- | --------------- | --------------------------------------------------------- | | **Primary** | `#8C6EFF` | 140 / 110 / 255 | Main brand color. CTAs, links, key accents. | | **Light** | `#A88BFF` | 168 / 139 / 255 | Hover states, secondary accents, light-theme backgrounds. | | **Dark** | `#5E3BE1` | 94 / 59 / 225 | Active/pressed states, dark-theme accents, depth. | Supporting neutrals: | Role | Hex | Use | | ---------------------- | ------------------------------------ | ----------------------------- | | **Foreground (light)** | `#0A0A0F` | Body text on light surfaces. | | **Foreground (dark)** | `#FAFAFA` | Body text on dark surfaces. | | **Surface (light)** | `#FFFFFF` | Page background, light theme. | | **Surface (dark)** | `#0F0F1A` | Page background, dark theme. | | **Border** | `#E5E5EB` (light) / `#1F1F2E` (dark) | Card and divider borders. | Don't recolor the logo. The wordmark and symbol are always rendered in either the light or dark master finish — never tinted, never gradient-replaced, never run through brand colors. ## Typography The docs and site use **Inter** for UI and body copy. Headings use Inter at heavy weights (600–800). Code blocks use **JetBrains Mono** or any monospaced fallback. | Style | Family | Weight | Use | | ------------ | -------------- | ------- | ------------------------------ | | Display / H1 | Inter | 800 | Page titles. | | H2–H4 | Inter | 700 | Section headers. | | Body | Inter | 400 | Paragraph text. | | Emphasis | Inter | 600 | Inline emphasis, strong text. | | Code | JetBrains Mono | 400–500 | Inline `code` and code blocks. | Inter is open-licensed and available from [rsms.me/inter](https://rsms.me/inter). ## Logo do's and don'ts **Do:** * Use the official SVG masters from the docs repo `/logo/` directory. * Maintain clear space and minimum size. * Pair the wordmark with the symbol only when both are visually balanced (don't show two side-by-side at conflicting sizes). * Render on a background that has sufficient contrast — use the light master on dark, dark master on light. **Don't:** * Recolor the logo. No purple wordmark, no rainbow, no monochrome variants beyond the supplied light/dark. * Distort the aspect ratio (no stretching, no skewing). * Add drop shadows, outlines, glow, or other effects. * Place the logo on a busy background image without a contrast-preserving solid panel underneath. * Crop the logo (don't show only the "R" cropped out of the wordmark — use the symbol mark instead). * Use the logo to imply endorsement of a product Raydium hasn't formally partnered with. Use a "powered by Raydium" or "integrated with Raydium" badge in a neutral context, with no implication of vetting. ## "Powered by Raydium" badge If your integration uses Raydium as a routing source or liquidity venue, a simple text or badge attribution is welcome: ```text theme={null} Powered by Raydium Integrated with Raydium Liquidity via Raydium ``` These wordings are pre-approved. Place near the integration's pricing, swap, or pool surface — not in masthead-prominent positions, which would imply formal partnership. If you're an aggregator or wallet routing through Raydium and want a more prominent attribution, contact the team via Discord (`#partnerships`) before publishing. ## Press inquiries For press, partnership, or media materials beyond what's in this kit: * **Discord** — [discord.gg/raydium](https://discord.gg/raydium), `#partnerships` channel. * **X / Twitter** — [@Raydium](https://x.com/Raydium). The team responds to press inquiries on Discord faster than DMs on social platforms. ## Asset bundle A zipped bundle of every logo file, color swatch, and font reference is mirrored on the [Raydium GitHub](https://github.com/raydium-io). The canonical SVGs used by this docs site are at `/logo/light.svg` and `/logo/dark.svg`. ## Pointers * [`resources/community-tools`](/resources/community-tools) — third-party tools that integrate with Raydium. * [`resources/license`](/resources/license) — licensing terms (separate from trademark). * [`security/disclosure`](/security/disclosure) — for security disclosures, not press. Sources: * Raydium docs theme configuration (canonical brand colors). * [Raydium GitHub organization](https://github.com/raydium-io) — master logo files. # Community tools Source: https://docs.raydium.io/resources/community-tools Third-party SDKs, dashboards, bots, and analytics platforms that integrate with Raydium — plus how to submit your own tool for inclusion. This page lists tools built **by the community**, not by the Raydium team. Inclusion is not endorsement — every tool is independently maintained and the community sends pull requests to add or update entries. Always evaluate trust assumptions yourself before connecting a wallet or sharing an API key. ## What's listed here A tool earns a spot on this page if it (a) is publicly accessible, (b) actually works against Raydium's mainnet programs at the time of listing, and (c) is maintained — meaning the project has had a commit, release, or visible engagement in the last 90 days. Categories below are a starting taxonomy; submit a tool that doesn't fit any of them and we'll add a new category. ## Analytics and data | Tool | Type | Notes | | ------------------------------------------------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------- | | [DefiLlama](https://defillama.com/protocol/raydium) | Protocol TVL & volume | Tracks Raydium's pools and farms across products. Reasonably reliable for high-level metrics. | | [Birdeye](https://birdeye.so) | Token & pool prices | Per-pool charts, swap quotes, holder analytics. UI-first. | | [GeckoTerminal](https://www.geckoterminal.com/solana/raydium/pools) | DEX pool tracker | Real-time Raydium pool prices, charts, and trending. | | [Dune Analytics](https://dune.com/) | SQL dashboards | Several community dashboards covering Raydium volumes, TVL, and per-pool stats. Search "raydium" on Dune. | ## Portfolio and position trackers | Tool | Type | Notes | | ---------------------------------------- | -------------------- | ------------------------------------------------------------------------------ | | [Step Finance](https://app.step.finance) | Portfolio aggregator | Tracks Raydium LP positions, farm rewards, and CLMM positions across a wallet. | | [Sonar Watch](https://sonar.watch) | Portfolio aggregator | Solana-focused portfolio dashboard with Raydium support. | ## Aggregators | Tool | Type | Notes | | -------------------------- | ---------------- | --------------------------------------------------------------------- | | [Jupiter](https://jup.ag) | DEX aggregator | Routes through Raydium pools as one of its primary liquidity sources. | | [DFlow](https://dflow.net) | RFQ + aggregator | Order-flow aggregator with Raydium routing. | Aggregator routing through Raydium pools requires no integration on the LP's side — once a pool is open, aggregators index it automatically. ## Indexers and APIs (community) | Tool | Type | Notes | | ------------------------------------- | ------------------ | ------------------------------------------------------------------------- | | [Helius](https://helius.dev) | RPC + indexed data | Provides parsed Raydium events and pool data via their enhanced APIs. | | [Triton One](https://triton.one) | RPC | High-performance RPC commonly used for production Raydium integrations. | | [Solscan API](https://api.solscan.io) | Block explorer API | Account/transaction data including parsed Raydium instructions. | | [Bitquery](https://bitquery.io) | GraphQL data | Raydium swaps, liquidity events, and trade history queryable via GraphQL. | For the official Raydium-maintained API see [`sdk-api/rest-api`](/sdk-api/rest-api). The tools above are useful when you need data shapes the official API doesn't provide. ## SDKs and language clients (community) | Tool | Language | Notes | | ------------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `solana-py` + custom Raydium helpers | Python | The standard path for Python integrations. See [`sdk-api/python-integration`](/sdk-api/python-integration) for our recipes. | | Raydium Rust client | Rust | Generated from the Anchor IDL — see [`sdk-api/anchor-idl`](/sdk-api/anchor-idl). | If you maintain a community SDK for Go, Kotlin, Swift, Java, or another language with active users, submit it via the process below — we'll list it. ## Bots and automation | Tool | Type | Notes | | -------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | Open-source LP rebalancers | CLMM | Various open-source projects automate CLMM range adjustment. Search GitHub for "raydium clmm rebalance". | | Sniper bots | Pre-listing | Pre-listing bots exist but are out-of-scope for this listing — we don't link tools whose primary purpose is extracting value from new launches. | | MEV / arbitrage frameworks | Generic Solana | Any Solana arbitrage framework can target Raydium pools; not Raydium-specific. | ## Submitting a tool To add or update an entry on this page: 1. Fork the [docs repo](https://github.com/raydium-io) (the docs source lives in the public Raydium docs repo — the exact path is in the README). 2. Edit `resources/community-tools.mdx`. 3. Submit a PR with: * Tool name and link. * One-line description (what it is, what category). * Maintainer's GitHub or X handle (for verification). * A note on activity (last release / last meaningful commit). Listings are reviewed for the "publicly accessible / actually works / maintained" bar. Self-promotional listings without working software, or tools whose primary purpose conflicts with user safety (drainers, fake-pool generators), will not be merged. ## Removal policy A tool is removed from this page if: * The hosted instance has been offline for >30 days. * The repository has been archived or deleted. * The maintainer goes silent for >180 days and the tool stops working. * The tool starts engaging in user-harm behavior (drainer scripts, deceptive UX, etc.). Removal is at the docs maintainers' discretion; reinstatement is welcome once the tool is back online and maintained. ## Security note A tool listed here that asks for your wallet's seed phrase, that requires you to send funds to a custodial address, or that promises guaranteed yields, is **not** a tool you should use. Listing here is a low bar — it does not vouch for security. Always inspect what you're authorising before signing a transaction. ## Pointers * [`sdk-api`](/sdk-api) — official SDKs and APIs. * [`getting-started/trust-and-safety`](/getting-started/trust-and-safety) — how to evaluate a tool before connecting. * [`security/oracle-and-token-risks`](/security/oracle-and-token-risks) — operational risks to be aware of. Sources: * Maintainer-submitted entries (this page is community-curated). # Resources Source: https://docs.raydium.io/resources/index Brand assets, community-built tools, and licensing for Raydium's code, SDK, and documentation. ## What lives here This chapter holds the auxiliary materials that don't fit elsewhere — brand assets for press and partners, community-built tools that integrate with Raydium, and the canonical licensing terms for Raydium's code and documentation. Logos, colors, typography, and usage rules for press, integrations, and partner content. Third-party SDKs, dashboards, bots, and analytics platforms that work with Raydium. Submission guidelines for adding your own. License terms for Raydium's on-chain programs, SDK, and this documentation. ## Quick links * **GitHub organization** — [github.com/raydium-io](https://github.com/raydium-io) — every Raydium repository. * **Bug bounty** — [Immunefi listing](https://immunefi.com/bug-bounty/raydium/information/) — see also [`security/disclosure`](/security/disclosure). * **Discord** — [discord.gg/raydium](https://discord.gg/raydium) — `#dev-support` for technical questions. * **API status / dashboards** — see [`resources/community-tools`](/resources/community-tools). # License Source: https://docs.raydium.io/resources/license License terms for Raydium's on-chain programs, the TypeScript SDK, and this documentation. Trademark notes for the Raydium name and logo. Raydium publishes its programs and SDK as open source. License terms differ between repositories — the on-chain programs are Apache-2.0, the TypeScript SDK is GPL-3.0. The canonical text is the `LICENSE` file in each repository; this page summarises but does not replace it. ## On-chain programs The deployed Raydium programs are published with their full source under the **Apache License 2.0**. | Repository | License | Coverage | | ------------------------------------------------------------------ | ---------- | ------------------------------------------------------------------- | | [`raydium-amm`](https://github.com/raydium-io/raydium-amm) | Apache-2.0 | AMM v4 (constant-product, OpenBook-integrated). | | [`raydium-cp-swap`](https://github.com/raydium-io/raydium-cp-swap) | Apache-2.0 | CPMM (standard constant-product, no orderbook, Token-2022 capable). | | [`raydium-clmm`](https://github.com/raydium-io/raydium-clmm) | Apache-2.0 | CLMM (concentrated liquidity, sqrt-price ticks). | Apache-2.0 grants you the rights to use, modify, distribute, and sublicense the code, including for commercial purposes, subject to the conditions in the license text — primarily attribution preservation and a patent grant. It does **not** require derivative works to also be Apache-2.0 (unlike GPL-family licenses). For Farm and LaunchLab program licenses, check the `LICENSE` file in their respective repositories under the [Raydium GitHub organization](https://github.com/raydium-io). Licenses can be updated at the repository owner's discretion; the `LICENSE` file at `master` (or `main`) is the source of truth. ## TypeScript SDK The official SDK package `@raydium-io/raydium-sdk-v2` is licensed under **GPL-3.0**. | Package | License | Repository | | ------------------------------------------- | ------------------ | -------------------------------------------------------------------------- | | `@raydium-io/raydium-sdk-v2` | GPL-3.0 | [`raydium-sdk-V2`](https://github.com/raydium-io/raydium-sdk-V2) | | `raydium-sdk-V2-demo` (sample integrations) | See repo `LICENSE` | [`raydium-sdk-V2-demo`](https://github.com/raydium-io/raydium-sdk-V2-demo) | GPL-3.0 is a strong copyleft license. Implications for your project: * **Linking matters.** If you embed the SDK and distribute the result (binary, npm package, hosted service), the distributed work generally has to be GPL-3.0 too. * **SaaS exception.** GPL-3.0 (unlike AGPL-3.0) does not require source disclosure for purely networked use — i.e. running the SDK server-side and exposing only its results over an API does not by itself trigger source-disclosure obligations. * **Patent grant.** GPL-3.0 includes an explicit patent grant from contributors covering their contributions. If your use case is incompatible with GPL-3.0 — for example, you're building proprietary closed-source software that statically links the SDK and ships it to end-users — talk to the Raydium team via Discord (`#dev-support`) before you ship. Alternative arrangements are sometimes possible. **Don't assume** an unlicensed alternative exists; assume GPL-3.0 unless you have written agreement otherwise. ## REST and Trade APIs The HTTP APIs at `api-v3.raydium.io` and `transaction-v1.raydium.io` are services, not licensed code. Usage is governed by the implicit terms of service of the hosted endpoints — primarily reasonable rate limits, no abuse, and no claims of warranty. See [`sdk-api/rest-api`](/sdk-api/rest-api) for details. These APIs are operated as a public good for ecosystem integrators. Heavy commercial use should be coordinated with the team to avoid being rate-limited. ## Documentation This documentation set is published by the Raydium project. Unless an individual page states otherwise, the prose, code samples, and examples on this site are made available under **Creative Commons Attribution 4.0** (CC-BY-4.0). You may: * Quote, excerpt, or republish content from these docs. * Translate the docs into other languages. * Use the code samples (which are functionally short illustrations) directly in your project. You should: * Provide visible attribution to "Raydium documentation" with a link back to `docs.raydium.io`. * Not represent your derivative as the official Raydium documentation. If you spot a mistake or want to suggest a change, every page has a "Suggest edit" button (top-right), or open a PR / issue against the docs repo. ## Trademarks The following are trademarks (registered or unregistered) of the Raydium project: * The "Raydium" name and word mark. * The Raydium logo and symbol mark. * The "LaunchLab" product name in association with Raydium's bonding-curve venue. Trademark rights are **separate from copyright and license rights**. Apache-2.0 and GPL-3.0 grant you the right to use the *code* — they do not grant you the right to use the *Raydium name and logo* in your derivative product. Trademark use is governed by the [Brand kit](/resources/brand-kit), which permits attributions like "powered by Raydium" and forbids implications of partnership without explicit agreement. If you fork the code and ship a derivative protocol, you must: * Comply with the source license (Apache-2.0 / GPL-3.0). * Choose your own name and visual identity. **Don't** call your fork "Raydium X" or use the Raydium logo. * Make clear in your README that you have forked the original Raydium code and are not affiliated with the Raydium project. ## Disclaimer of warranties All code and documentation is provided **"as is," without warranty of any kind**, express or implied, including but not limited to merchantability, fitness for a particular purpose, and non-infringement. The full warranty disclaimer text lives in each repository's `LICENSE` file. In plain English: the Raydium project does not guarantee that the code is bug-free, that the on-chain state is invulnerable, that you will not lose funds. The audits ([`security/audits`](/security/audits)), bug bounty ([`security/disclosure`](/security/disclosure)), and operational controls ([`security/admin-and-multisig`](/security/admin-and-multisig)) are mitigations, not guarantees. Evaluate your own risk before integrating or providing liquidity. ## How to verify For any specific repository, the canonical license text is the `LICENSE` file at the current `HEAD` of the default branch: ```bash theme={null} # Apache-2.0 programs curl -s https://raw.githubusercontent.com/raydium-io/raydium-cp-swap/master/LICENSE | head -3 curl -s https://raw.githubusercontent.com/raydium-io/raydium-clmm/master/LICENSE | head -3 curl -s https://raw.githubusercontent.com/raydium-io/raydium-amm/master/LICENSE | head -3 # GPL-3.0 SDK curl -s https://raw.githubusercontent.com/raydium-io/raydium-sdk-V2/master/LICENSE | head -3 ``` If the head of these files doesn't match the license claimed on this page, the repository wins — file an issue against the docs. ## Pointers * [`resources/brand-kit`](/resources/brand-kit) — trademark usage and visual identity rules. * [`resources/community-tools`](/resources/community-tools) — third-party tools (with their own licenses). * [`security/disclosure`](/security/disclosure) — for security issues, **not** licensing questions. * [Raydium GitHub organization](https://github.com/raydium-io) — every repository. For licensing questions not answered here: * **Discord** — [discord.gg/raydium](https://discord.gg/raydium), `#dev-support`. For commercial licensing arrangements outside the standard terms, contact the team via Discord and ask to be routed to the partnerships channel. Sources: * [`raydium-cp-swap` LICENSE](https://github.com/raydium-io/raydium-cp-swap) — Apache-2.0. * [`raydium-clmm` LICENSE](https://github.com/raydium-io/raydium-clmm) — Apache-2.0. * [`raydium-amm` LICENSE](https://github.com/raydium-io/raydium-amm) — Apache-2.0. * [`raydium-sdk-V2` LICENSE](https://github.com/raydium-io/raydium-sdk-V2) — GPL-3.0.