Перейти к основному содержанию

Documentation Index

Fetch the complete documentation index at: https://docs.raydium.io/llms.txt

Use this file to discover all available pages before exploring further.

Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →
Идентификаторы программ и seeds PDA для CPMM перечислены в reference/program-addresses. На этой странице рассматривается назначение каждого аккаунта и инварианты, которые он поддерживает, а не жёстко заданные адреса.

Шесть аккаунтов пула CPMM

Каждый пул CPMM полностью описывается шестью адресами, производными программы (PDA) под программой CPMM, плюс один общий аккаунт AmmConfig, на который он ссылается. Имея два mint’а, вы можете детерминировано вычислить всё остальное без обращения к сети.
АккаунтSeed(s)ВладелецНазначение
authority"vault_and_lp_mint_auth_seed"CPMMПодписант для каждого перемещения средств из хранилища и каждой операции mint/burn LP. Общий для всех пулов CPMM.
poolState"pool", ammConfig, token0Mint, token1Mint или любая пара ключей, подписанная создателемCPMMСтруктура состояния пула — пара mint’ов, балансы хранилищ, предложение LP, начисленные комиссии, указатель наблюдения. Инструкция Initialize CPMM принимает либо каноническую PDA, полученную из четырёх seeds, либо произвольную пару ключей, подписанную создателем. Путь со случайной парой ключей существует для предотвращения front-running атаки, где злоумышленник отслеживает mempool и спешит занять каноническую PDA раньше легитимного создателя.
lpMint"pool_lp_mint", poolStateSPL TokenLP токен пула. Предложение = общее выданное LP. Полномочия на mint = PDA полномочий CPMM.
vault0"pool_vault", poolState, token0MintSPL Token / Token-2022Хранит баланс пула token0. Принадлежит PDA полномочий.
vault1"pool_vault", poolState, token1MintSPL Token / Token-2022Хранит баланс пула token1. Принадлежит PDA полномочий.
observation"observation", poolStateCPMMКольцевой буфер образцов цены для TWAP. Записывается при каждом обмене.
И общая конфигурация:
АккаунтSeed(s)ВладелецНазначение
ammConfig"amm_config", index: u16CPMMСодержит ставки торговой комиссии, комиссии протокола, фонда, создателя и ключи администратора. Один на каждый «ярус комиссий». Poolstate привязывается к одному при создании и не может измениться позже.

Вычисление пула, имея только два mint’а

import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";

const CPMM_PROGRAM_ID = new PublicKey(
  "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"
); // mainnet — см. reference/program-addresses

function u16ToBytes(n: number): Buffer {
  const b = Buffer.alloc(2);
  b.writeUInt16BE(n);
  return b;
}

// token0 < token1 по порядку байтов. Ошибка здесь приводит к валидной PDA,
// которая указывает на несуществующий пул.
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,
  };
}
Всегда сортируйте mint’ы перед вычислением PDA пула. Seed хеширует два mint’а в порядке байтов, а не в порядке, введённом пользователем. Два пула с (A, B) и (B, A) столкнулись бы в цепи — сортировка — это способ, которым программа делает отображение каноническим.
ID пула — это не всегда каноническая PDA. Initialize принимает произвольную пару ключей, подписанную владельцем, как pool_state в дополнение к приведённой выше PDA. Если переданный аккаунт не совпадает с канонической PDA, программа требует, чтобы он был подписан — т. е. создатель передаёт свежую пару ключей, которой они подписывают. Это защита от front-run’а: любая третья сторона, спешащая захватить каноническую PDA, может быть обойдена легитимным создателем, использующим вместо этого случайную пару ключей. Нижестоящие PDA (lpMint, vault0, vault1, observation) по-прежнему производны из poolState.key(), поэтому они остаются уникальными для любого используемого адреса. При индексировании пулов всегда обнаруживайте ID пула из состояния в цепи (например, аккаунты PoolState под программой CPMM), а не путём вычисления канонической PDA — последний пропустит пулы со случайными ключами.

Разметка аккаунтов

Полные определения на Rust находятся в исходном коде raydium-cp-swap. Приведённые ниже поля — это те, которые вы будете читать из интеграции.

PoolState

// programs/cp-swap/src/states/pool.rs
pub struct PoolState {
    pub amm_config: Pubkey,               // привязывает этот пул к AmmConfig
    pub pool_creator: Pubkey,             // кто запустил 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 или Token-2022 программа
    pub token_1_program: Pubkey,

    pub observation_key: Pubkey,          // == observation PDA
    pub auth_bump: u8,
    pub status: u8,                       // битовая маска: deposit | withdraw | swap
    pub lp_mint_decimals: u8,
    pub mint_0_decimals: u8,
    pub mint_1_decimals: u8,

    pub lp_supply: u64,                   // зеркало предложения lp_mint
    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; обмены отклоняются до этого времени
    pub recent_epoch: u64,

    // Состояние комиссии создателя (добавлено после исходной разметки):
    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],
}
Что действительно читать:
  • lp_supply — внутреннее зеркало пула общего предложения LP mint’а. Используйте его для математики доли LP; значение должно совпадать с предложением mint’а в цепи, но его чтение из PoolState избегает дополнительной выборки аккаунта.
  • protocol_fees_token{0,1}, fund_fees_token{0,1}начисленные комиссии, ещё не собранные. Они не влияют на ценообразование обмена; они остаются в хранилищах до вызова CollectProtocolFee / CollectFundFee.
  • status — битовая маска, контролирующая, разрешены ли Swap, Deposit, Withdraw. Обновляется администратором через UpdatePoolStatus. SDK проверяет это перед созданием транзакции; если вы делаете CPI напрямую, проверьте сами.
  • token0_program / token1_program — программа токена, в которую нужно делать CPI для каждого хранилища. Один может быть классическим SPL Token, а другой Token-2022; они независимы.
  • open_time — временная метка Unix. Обмены до этого времени не работают. Депозиты разрешены до open_time, так что пул может быть инициализирован.
  • creator_fee_on / enable_creator_fee — вместе контролируют, активна ли опциональная комиссия создателя для этого пула и с какой стороны обмена она собирается. enable_creator_fee == false полностью обнуляет путь комиссии создателя. Когда включено, creator_fee_on выбирает: 0 = снять комиссию с любого токена, который является входом обмена (BothToken); 1 = снять комиссию только из token_0 (пропустить на обменах token_1 → token_0); 2 = снять комиссию только из token_1. Устанавливается при создании пула через InitializeWithPermission; не может измениться позже.
  • creator_fees_token_{0,1} — начисленные комиссии создателя, собираемые через CollectCreatorFee.

AmmConfig

pub struct AmmConfig {
    pub bump: u8,
    pub disable_create_pool: bool,
    pub index: u16,                       // совпадает с seed
    pub trade_fee_rate: u64,              // напр., 2500 = 0.25%
    pub protocol_fee_rate: u64,           // доля торговой комиссии протоколу
    pub fund_fee_rate: u64,               // доля торговой комиссии фонду
    pub create_pool_fee: u64,             // оплачено один раз при инициализации (в SOL или токене)
    pub protocol_owner: Pubkey,           // может вызвать CollectProtocolFee
    pub fund_owner: Pubkey,               // может вызвать CollectFundFee
    pub creator_fee_rate: u64,            // опциональная ставка комиссии создателя пула (1/1_000_000 объёма)
    pub padding: [u64; 15],
}
Три вещи, о которых следует проявить осторожность:
  1. trade_fee_rate и creator_fee_rate — доли объёма, обе обозначены в единицах 1/1_000_000. 2500 означает 0.25% объёма торговли. protocol_fee_rate и fund_fee_rate — доли торговой комиссии (не объёма), с тем же знаменателем 1/1_000_000. Комиссия создателя не является долей торговой комиссии — это её собственная независимая ставка. Полная арифметика в products/cpmm/fees.
  2. index — это u16, поэтому seed hash использует 2 байта big-endian. Ошибка на один байт в порядке байтов — распространённая ошибка интеграции.
  3. AmmConfig неизменяем на уровне пула. Пул указывает на один AmmConfig при создании и никогда не переключается. Изменения комиссии распространяются, потому что пул читает конфиг при каждом обмене — но пул не может быть перемещён между ярусами комиссий.
Замечание о комиссиях создателей: сама ставка (creator_fee_rate) находится на AmmConfig и общая для всего яруса комиссий. Начисляет ли конкретный пул её (enable_creator_fee) и с какой стороны обмена она попадает (creator_fee_on) находится на PoolState. Комиссия создателя независима от торговой комиссии — это её собственная ставка, начисляемая на её собственные счётчики (creator_fees_token_{0,1}), и никогда не уменьшает доли LP / протокола / фонда торговой комиссии. Сбор осуществляется через CollectCreatorFee. Полную механику см. в products/cpmm/fees.

Permission

Небольшой аккаунт контроля доступа, используемый InitializeWithPermission. Программа CPMM поддерживает путь создания пула с разрешением, чтобы другие программы (например, LaunchLab при выводе токена в CPMM) могли доказать, что они имеют право создать пул против данного AmmConfig.
pub struct Permission {
    pub authority: Pubkey,    // кто имеет право вызывать InitializeWithPermission
    pub padding: [u64; 8],
}
Permission PDA создаётся администратором CPMM через CreatePermissionPda и отзывается через ClosePermissionPda. Конечные пользователи не взаимодействуют с этим аккаунтом напрямую — это вспомогательная система для cross-program потоков.

Хранилища и Token-2022

vault0 и vault1 принадлежат CPMM authority PDA, а их владелец токен-программы (token_program) — либо SPL Token, либо Token-2022, определённый при создании пула по программе mint’а. Пул прозрачно обрабатывает оба случая — вы передаёте правильный ID программы токена для каждой стороны в аккаунты инструкции Swap / Deposit / Withdraw. CPMM применяет строгий список разрешённых расширений при создании пула (is_supported_mint в utils/token.rs). Mint Token-2022 может быть использован в пуле CPMM, только если каждое расширение, которое он содержит, находится в этом списке:
  • TransferFeeConfig. Применяется mint’ом при каждом переводе. Пул находится на стороне получателя для депозитов SwapBaseInput и на стороне отправителя для вывода. Программа вычисляет чистое количество, попадающее в хранилище, и устанавливает кривую соответственно. См. algorithms/token-2022-transfer-fees.
  • MetadataPointer и TokenMetadata. Стандартные метаданные on-mint. Не влияет на математику обмена.
  • InterestBearingConfig. UI количество mint’а накапливает проценты. Хранилище хранит сырые количества; кривая работает только с сырыми количествами. UI, отображающие APR, должны вызывать помощников Token-2022 для рендеринга UI количества.
  • ScaledUiAmount. Расширение масштабирования отображения UI. Та же обработка, что и InterestBearingConfig — кривая использует сырые количества.
Любое другое расширение — PermanentDelegate, TransferHook, DefaultAccountState, NonTransferable, ConfidentialTransfer, Group/GroupMember, MintCloseAuthority и т. д. — вызовет отклонение Initialize с ошибкой NotSupportMint. Исключение — небольшой жёстко закодированный whitelist mint’ов в программе (несколько определённых pubkey), которые обходят проверку расширений; он используется для подключения определённых mint’ов в каждом конкретном случае. Список проверенных расширений и whitelist mint’ов находятся в исходном коде CP-Swap под programs/cp-swap/src/utils/token.rs и могут изменяться при будущих обновлениях программы.

Observation

Аккаунт observation — это кольцевой буфер записей ObservationState, каждая из которых хранит block_timestamp и кумулятивную цену. При каждом обмене программа добавляет новое наблюдение, если прошло достаточно времени с последнего. TWAP вычисляются путём чтения двух наблюдений и деления Δcumulative / Δtime.
// OBSERVATION_NUM жёстко закодирована в программе как 100.
pub const OBSERVATION_NUM: usize = 100;

pub struct Observation {
    pub block_timestamp:              u64,
    pub cumulative_token_0_price_x32: u128,   // Q32.32, верхние 64 бита зарезервированы для переполнения
    pub cumulative_token_1_price_x32: u128,
}

pub struct ObservationState {
    pub initialized:           bool,
    pub observation_index:     u16,                            // циклический индекс
    pub pool_id:               Pubkey,
    pub observations:          [Observation; OBSERVATION_NUM], // 100 записей
    pub last_update_timestamp: u64,                            // временная метка самого последнего добавления
    pub padding:               [u64; 3],
}
Кольцевой буфер рассчитан на 100 наблюдений. Каждое наблюдение — 40 байт, поэтому один только массив — 4 000 байт; полный PDA ObservationState — около 4 100 байт после окружающих полей и дискриминатора. Два правила для потребителей:
  • Не используйте одно наблюдение как цену. Это кумулятивное значение, а не спотовая цена. Используйте два из них для вычисления TWAP.
  • Выбирайте наблюдения на расстоянии как минимум одного блока. Обмены внутри одного блока могут не создать новое наблюдение; чтение подряд может вернуть одну и ту же запись.
Более полная математика в products/clmm/accounts.

Жизненный цикл аккаунтов

СобытиеСозданные аккаунтыУничтоженные аккаунты
InitializepoolState, lpMint, vault0, vault1, observation
Deposit— (может создать пользовательскую LP ATA)
Withdraw
Swap— (может создать пользовательскую целевую ATA)
CollectProtocolFee
CollectFundFee
UpdatePoolStatus
Пулы CPMM и их PDA никогда не закрываются. Даже при нулевой ликвидности poolState остаётся. Это намеренно: повторное инициализирование того же пула позже сохраняет его исторический буфер наблюдений, и его вычисление PDA остаётся стабильным.

Где что читать

  • Списки аккаунтов инструкций (какие из приведённых выше являются writable/signer для каждой инструкции): products/cpmm/instructions.
  • Семантика начисления комиссий: products/cpmm/fees.
  • Математика обмена / правило обновления наблюдения: products/cpmm/math.
  • Канонические seeds / идентификаторы программ: reference/program-addresses.
Источники: