> ## 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.

# CPMM 账户

> CPMM 池涉及的每个 PDA 和共享账户：PoolState、AmmConfig、authority、LP mint、代币金库和观测环形缓冲区。

<Info>
  **本页内容由 AI 自动翻译，所有内容以英文版本为准。**

  [查看英文版 →](/products/cpmm/accounts)
</Info>

<Info>
  CPMM 的程序 ID 和 PDA 种子列在 [`reference/program-addresses`](/zh/reference/program-addresses) 中。本页重点介绍**每个账户的用途和它维护的不变量**，而不是硬编码的地址。
</Info>

## CPMM 池的六个账户

每个 CPMM 池由 CPMM 程序下的六个程序派生地址（PDA）完全描述，加上它引用的一个共享 `AmmConfig` 账户。一旦你有两个 mint，就可以不接触网络直接确定性地派生所有内容。

| 账户            | 种子                                                                | 所有者                    | 用途                                                                                                                                              |
| ------------- | ----------------------------------------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `authority`   | `"vault_and_lp_mint_auth_seed"`                                   | CPMM                   | 用于签署每次金库移动和每次 LP 铸造/销毁。在**所有** CPMM 池之间共享。                                                                                                      |
| `poolState`   | `"pool"`、`ammConfig`、`token0Mint`、`token1Mint` **或**任何签署者提供的随机密钥对 | CPMM                   | 池的状态结构——mint 对、金库余额、LP 供应、费用累计、观测指针。CPMM `Initialize` 指令接受从四个种子派生的规范 PDA **或**由创建者签署的任意密钥对。随机密钥对路径存在是为了防止一种前置攻击，即对手监视内存池并竞速占据规范 PDA 之前合法创建者这样做。 |
| `lpMint`      | `"pool_lp_mint"`、`poolState`                                      | SPL Token              | 池的 LP 代币。供应量 = 总 LP 未清偿。铸造权限 = CPMM authority PDA。                                                                                              |
| `vault0`      | `"pool_vault"`、`poolState`、`token0Mint`                           | SPL Token / Token-2022 | 持有池的 token0 余额。由 authority PDA 拥有。                                                                                                              |
| `vault1`      | `"pool_vault"`、`poolState`、`token1Mint`                           | SPL Token / Token-2022 | 持有池的 token1 余额。由 authority PDA 拥有。                                                                                                              |
| `observation` | `"observation"`、`poolState`                                       | CPMM                   | TWAP 使用的价格样本环形缓冲区。在每次交换时写入。                                                                                                                     |

以及共享配置：

| 账户          | 种子                          | 所有者  | 用途                                                            |
| ----------- | --------------------------- | ---- | ------------------------------------------------------------- |
| `ammConfig` | `"amm_config"`、`index: u16` | CPMM | 持有交易/协议/基金/创建者费率和管理员密钥。每个"费用等级"一个。PoolState 在创建时绑定到一个，之后无法更改。 |

## 仅从两个 mint 派生池

```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,
  };
}
```

<Tip>
  \*\*在派生池 PDA 之前始终排序 mint。\*\*种子按字节顺序哈希两个 mint，而不是按用户顺序。具有 `(A, B)` 和 `(B, A)` 的两个池在链上会碰撞——排序是程序使映射规范化的方式。
</Tip>

<Note>
  **池 ID 并不总是规范 PDA。**`Initialize` 除了上面的 PDA 外，还接受任意签署者密钥对作为 `pool_state`。如果传递的账户与规范 PDA 不匹配，程序要求它是签署者——即创建者传递一个他们用签名的新密钥对。这是前置攻击防御：任何第三方竞速抢占规范 PDA 都可以被合法创建者使用随机密钥对绕过。下游 PDA（`lpMint`、`vault0`、`vault1`、`observation`）仍从 `poolState.key()` 派生，因此它们对于任何使用的地址都是唯一的。当你索引池时，始终从链上状态发现池 ID（例如 CPMM 程序下的 `PoolState` 账户），而不是派生规范 PDA——后者会遗漏随机密钥对池。
</Note>

## 账户布局

完整的 Rust 定义位于 [`raydium-cp-swap`](https://github.com/raydium-io/raydium-cp-swap) 源代码中。下面的字段是你将从集成中读取的字段。

### `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],
}
```

要实际读取的内容：

* **`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`

```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],
}
```

有三件事需要小心：

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`](/zh/products/cpmm/fees) 中。
2. **`index` 是 `u16`**，所以种子哈希使用 2 字节大端序。字节顺序偏差一是常见的集成错误。
3. \*\*`AmmConfig` 在池级别是不可变的。\*\*池在创建时指向一个 `AmmConfig`，不会切换。费用更改会传播，因为池在每次交换时读取配置——但池不能在费用等级之间移动。

关于创建者费用的说明：**费率本身**（`creator_fee_rate`）位于 `AmmConfig` 上，在费用等级之间共享。特定池是否真的收费（`enable_creator_fee`）以及它在交换的哪一侧（`creator_fee_on`）位于 `PoolState` 上。创建者费用**独立于**交易费——它是自己的费率，累计到自己的计数器（`creator_fees_token_{0,1}`），从不减少 LP / 协议 / 基金在交易费中的份额。清扫通过 `CollectCreatorFee`。有关完整机制，请参阅 [`products/cpmm/fees`](/zh/products/cpmm/fees)。

### `Permission`

由 `InitializeWithPermission` 使用的小型访问控制账户。CPMM 程序支持一个许可池创建路径，以便其他程序（例如 LaunchLab 当将代币升级为 CPMM 时）可以证明他们有权针对给定 `AmmConfig` 创建池。

```rust theme={null}
pub struct Permission {
    pub authority: Pubkey,    // who is allowed to call InitializeWithPermission
    pub padding: [u64; 8],
}
```

Permission PDA 由 CPMM 管理员通过 `CreatePermissionPda` 创建，通过 `ClosePermissionPda` 撤销。最终用户不直接与此账户交互——它是跨程序流的管道。

### 金库和 Token-2022

`vault0` 和 `vault1` 由 CPMM authority PDA 拥有，它们的代币程序所有者（`token_program`）是 SPL Token 或 Token-2022，由池创建时 mint 的程序确定。池透明地处理这两种情况——在 `Swap` / `Deposit` / `Withdraw` 指令账户中为每一侧传递正确的代币程序 ID。

CPMM 在池创建时强制执行严格的**扩展允许清单**（`utils/token.rs` 中的 `is_supported_mint`）。Token-2022 mint 只有在它携带的**每个**扩展都在此清单上时才能用于 CPMM 池：

* **`TransferFeeConfig`。**由 mint 在每次转账时应用。池在 `SwapBaseInput` 存款接收端，在取款发送端。程序计算到达金库的**净**金额并相应地设置曲线。请参阅 [`algorithms/token-2022-transfer-fees`](/zh/algorithms/token-2022-transfer-fees)。
* \*\*`MetadataPointer` 和 `TokenMetadata`。\*\*标准链上 mint 元数据。对交换数学无影响。
* **`InterestBearingConfig`。** Mint 的 UI 金额累计利息。金库存储原始金额；曲线仅在原始金额上操作。显示 APR 的 UI 应调用 Token-2022 辅助程序来呈现 UI 金额。
* **`ScaledUiAmount`。** UI 显示缩放扩展。与 `InterestBearingConfig` 相同的处理——曲线使用原始金额。

任何其他扩展——`PermanentDelegate`、`TransferHook`、`DefaultAccountState`、`NonTransferable`、`ConfidentialTransfer`、`Group`/`GroupMember`、`MintCloseAuthority` 等——导致 `Initialize` 以 `NotSupportMint` 拒绝。例外是程序中的小型硬编码 **mint 白名单**（少数特定公钥），绕过扩展检查；它用于逐个批准特定 mint。

经过审查的扩展列表和 mint 白名单位于 CP-Swap 源的 `programs/cp-swap/src/utils/token.rs` 下，可能会通过未来的程序升级更改。

### 观测

观测账户是 `ObservationState` 条目的环形缓冲区，每个条目存储 `block_timestamp` 和**累积价格**。在每次交换时，如果自上次以来足够的时间已过，程序会追加新的观测。TWAP 通过读取两个观测并除以 `Δ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],
}
```

环形缓冲区的大小为**100 个观测**。每个观测 40 字节，所以数组本身是 4000 字节；完整的 `ObservationState` PDA 在周围字段和鉴别器之后约 4100 字节。

两个消费者规则：

* \*\*不要将单个观测用作价格。\*\*它是*累积*，不是现货价格。使用其中两个来计算 TWAP。
* \*\*选择相隔至少一个区块的观测。\*\*同一区块内的交换可能不会产生新的观测；逐个读取可能会返回相同的记录。

更多数学在 [`products/clmm/accounts`](/zh/products/clmm/accounts) 中。

## 账户生命周期

| 事件                   | 创建的账户                                                | 销毁的账户 |
| -------------------- | ---------------------------------------------------- | ----- |
| `Initialize`         | `poolState`、`lpMint`、`vault0`、`vault1`、`observation` | —     |
| `Deposit`            | —（可能创建用户 LP ATA）                                     | —     |
| `Withdraw`           | —                                                    | —     |
| `Swap`               | —（可能创建用户目标 ATA）                                      | —     |
| `CollectProtocolFee` | —                                                    | —     |
| `CollectFundFee`     | —                                                    | —     |
| `UpdatePoolStatus`   | —                                                    | —     |

CPMM 池及其 PDA **从不关闭**。即使在零流动性时，`poolState` 仍然存在。这是故意的：稍后重新投入相同的池保留其历史观测缓冲区，其 PDA 派生保持稳定。

## 在哪里读取什么

* **指令账户列表**（上述每个指令的可写/签署者）：[`products/cpmm/instructions`](/zh/products/cpmm/instructions)。
* **费用累计语义**：[`products/cpmm/fees`](/zh/products/cpmm/fees)。
* **交换数学 / 观测更新规则**：[`products/cpmm/math`](/zh/products/cpmm/math)。
* **规范种子 / 程序 ID**：[`reference/program-addresses`](/zh/reference/program-addresses)。

源代码：

* [`raydium-io/raydium-cp-swap` — PoolState、AmmConfig、ObservationState](https://github.com/raydium-io/raydium-cp-swap)
