> ## 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, 토큰 vault, observation ring buffer.

<Info>
  **이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.**

  [영문판 보기 →](/products/cpmm/accounts)
</Info>

<Info>
  CPMM의 프로그램 ID와 PDA seed는 [`reference/program-addresses`](/ko/reference/program-addresses)에 정식으로 나열되어 있습니다. 이 페이지는 **각 계정의 용도와 유지해야 할 불변성**에 초점을 맞추며, 하드코딩된 주소는 다루지 않습니다.
</Info>

## CPMM 풀의 여섯 계정

모든 CPMM 풀은 CPMM 프로그램 아래에 여섯 개의 PDA(program-derived address)와 이것이 참조하는 하나의 공유 `AmmConfig` 계정으로 완전히 기술됩니다. 두 mint만 있으면 네트워크에 접근하지 않고도 모든 항목을 결정적으로 유도할 수 있습니다.

| 계정            | Seed                                                                          | 소유자                    | 용도                                                                                                                                                                                                                                                             |
| ------------- | ----------------------------------------------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `authority`   | `"vault_and_lp_mint_auth_seed"`                                               | CPMM                   | 모든 vault 이동과 모든 LP mint/burn을 서명하는 주체입니다. **모든** CPMM 풀에서 공유됩니다.                                                                                                                                                                                               |
| `poolState`   | `"pool"`, `ammConfig`, `token0Mint`, `token1Mint` **또는** 서명자가 제공한 임의의 keypair | CPMM                   | 풀의 상태 struct — mint 쌍, vault 잔액, LP 공급량, 수수료 적립, observation 포인터. CPMM `Initialize` 명령어는 네 개의 seed에서 유도한 정식 PDA **또는** 생성자가 서명한 임의의 keypair를 모두 받을 수 있습니다. 랜덤 keypair 경로는 악의적인 사용자가 mempool을 감시하다가 정당한 생성자보다 먼저 정식 PDA를 차지하려는 front-running 공격을 방어하기 위해 존재합니다. |
| `lpMint`      | `"pool_lp_mint"`, `poolState`                                                 | SPL Token              | 풀의 LP 토큰입니다. 공급량 = 총 유출 LP. Mint authority = 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에 사용되는 가격 샘플의 ring buffer입니다. 모든 스왑에서 기록됩니다.                                                                                                                                                                                                               |

그리고 공유 config:

| 계정          | Seed                         | 소유자  | 용도                                                                                                 |
| ----------- | ---------------------------- | ---- | -------------------------------------------------------------------------------------------------- |
| `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를 정렬합니다.** Seed는 사용자 순서가 아닌 바이트 순서로 두 mint를 해싱합니다. `(A, B)`와 `(B, A)`를 가진 두 풀은 온체인에서 충돌합니다. 정렬은 프로그램이 매핑을 정식으로 만드는 방법입니다.
</Tip>

<Note>
  **풀 ID가 항상 정식 PDA는 아닙니다.** `Initialize`는 위의 PDA 외에도 임의의 서명자 keypair를 `pool_state`로 받습니다. 전달된 계정이 정식 PDA와 일치하지 않으면 프로그램은 이것이 서명자여야 합니다. 즉, 생성자가 서명할 새로운 keypair를 전달합니다. 이것이 front-run 방어입니다: 정식 PDA를 확보하려는 제3자는 정당한 생성자가 대신 랜덤 keypair를 사용함으로써 우회될 수 있습니다. 하위 PDA(`lpMint`, `vault0`, `vault1`, `observation`)는 여전히 `poolState.key()`에서 유도되므로 어떤 주소가 사용되었든 고유하게 유지됩니다. 풀을 인덱싱할 때는 정식 PDA를 유도하지 말고 항상 온체인 상태에서 풀 ID를 발견합니다(예: CPMM 프로그램 아래의 `PoolState` 계정). 후자는 랜덤 keypair 풀을 놓칩니다.
</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`에서 읽으면 추가 계정 fetch를 피할 수 있습니다.
* **`protocol_fees_token{0,1}`**, **`fund_fees_token{0,1}`** — 아직 수거되지 않은 *적립된* 수수료입니다. 이들은 스왑 가격 책정에 영향을 주지 않습니다. `CollectProtocolFee` / `CollectFundFee`가 호출될 때까지 vault에 남아있습니다.
* **`status`** — `Swap`, `Deposit`, `Withdraw`가 허용되는지 여부를 제어하는 비트마스크입니다. 관리자가 `UpdatePoolStatus`를 통해 업데이트합니다. SDK는 트랜잭션을 구축하기 전에 이를 확인합니다. CPI로 직접 이동하는 경우 직접 확인합니다.
* **`token0_program` / `token1_program`** — 각 vault에 대해 CPI할 토큰 프로그램입니다. 하나는 classic SPL Token이고 다른 하나는 Token-2022일 수 있으며, 독립적입니다.
* **`open_time`** — Unix 타임스탬프입니다. 이 시간 이전의 스왑은 실패합니다. Deposit은 풀을 시드할 수 있도록 `open_time` 전에 허용됩니다.
* **`creator_fee_on` / `enable_creator_fee`** — 함께 선택 창작자 수수료가 이 풀에 대해 활성화되어 있는지, 그리고 스왑의 어느 쪽에서 수거되는지를 제어합니다. `enable_creator_fee == false`는 creator-fee 경로를 완전히 제로화합니다. 활성화되면 `creator_fee_on`은 다음을 선택합니다: `0` = 스왑 입력인 토큰 중 어느 것이든 수수료를 가져갑니다 (`BothToken`); `1` = `token_0`에서만 수수료를 가져갑니다 (`token_1 → token_0` 스왑은 건너뜀); `2` = `token_1`에서만 수수료를 가져갑니다. 풀 생성 시 `InitializeWithPermission`을 통해 설정됩니다. 나중에 변경할 수 없습니다.
* **`creator_fees_token_{0,1}`** — 적립된 creator 수수료는 `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` 분모입니다. Creator 수수료는 거래 수수료의 분수가 **아닙니다**. 자체 독립적인 비율입니다. 전체 산술은 [`products/cpmm/fees`](/ko/products/cpmm/fees)에 있습니다.
2. \*\*`index`는 `u16`\*\*이므로 seed 해시는 2바이트 big-endian을 사용합니다. 바이트 순서의 off-by-one은 일반적인 통합 버그입니다.
3. **`AmmConfig`는 풀 수준에서 불변입니다.** 풀은 생성 시 하나의 `AmmConfig`를 가리키며 절대 전환되지 않습니다. 수수료 변경은 풀이 각 스왑마다 config를 읽기 때문에 전파됩니다. 하지만 풀은 수수료 계층 간에 이동할 수 없습니다.

Creator 수수료에 대한 참고: **비율** 자체(`creator_fee_rate`)는 `AmmConfig`에 있으며 수수료 계층 전체에서 공유됩니다. 특정 풀이 실제로 이를 청구하는지(`enable_creator_fee`)와 스왑의 어느 쪽에 착지하는지(`creator_fee_on`)는 `PoolState`에 있습니다. Creator 수수료는 거래 수수료와 **독립적**입니다. 자체 비율이며, 자체 카운터(`creator_fees_token_{0,1}`)에 적립되며, LP / 프로토콜 / 펀드 거래 수수료 공유를 절대 줄이지 않습니다. 수거는 `CollectCreatorFee`를 통해 수행됩니다. 전체 메커니즘은 [`products/cpmm/fees`](/ko/products/cpmm/fees)를 참조합니다.

### `Permission`

`InitializeWithPermission`이 사용하는 작은 액세스 제어 계정입니다. CPMM 프로그램은 다른 프로그램(예: 토큰을 CPMM으로 승격시킬 때 LaunchLab)이 주어진 `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`를 통해 취소합니다. 최종 사용자는 이 계정과 직접 상호작용하지 않습니다. 이는 크로스 프로그램 흐름을 위한 기반입니다.

### Vault와 Token-2022

`vault0`과 `vault1`은 CPMM authority PDA가 소유하며, 토큰 프로그램 소유자(`token_program`)는 풀 생성 시 mint의 프로그램으로 결정되는 SPL Token 또는 Token-2022입니다. 풀은 두 경우를 투명하게 처리합니다. `Swap` / `Deposit` / `Withdraw` 명령어 계정에서 각 쪽에 올바른 토큰 프로그램 ID를 전달합니다.

CPMM은 풀 생성 시 엄격한 **확장 허용 목록**을 적용합니다 (`utils/token.rs`의 `is_supported_mint`). Token-2022 mint는 **모든** 확장이 이 목록에 있을 경우에만 CPMM 풀에서 사용할 수 있습니다:

* **`TransferFeeConfig`.** 모든 이전에서 mint가 적용합니다. 풀은 `SwapBaseInput` 입금의 수신 쪽이고 출금의 전송 쪽입니다. 프로그램은 vault에 도착하는 **순** 금액을 계산하고 그에 따라 곡선을 설정합니다. [`algorithms/token-2022-transfer-fees`](/ko/algorithms/token-2022-transfer-fees)를 참조합니다.
* \*\*`MetadataPointer`와 **`TokenMetadata`.** Mint의 표준 메타데이터입니다. 스왑 수학에 영향을 주지 않습니다.
* **`InterestBearingConfig`.** Mint의 UI 금액이 이자를 적립합니다. Vault는 원시 금액을 저장합니다. 곡선은 원시 금액에서만 작동합니다. APR을 표시하는 UI는 Token-2022 헬퍼를 호출하여 UI 금액을 렌더링해야 합니다.
* **`ScaledUiAmount`.** UI 표시 스케일링 확장입니다. `InterestBearingConfig`과 동일한 처리입니다. 곡선은 원시 금액을 사용합니다.

다른 확장(`PermanentDelegate`, `TransferHook`, `DefaultAccountState`, `NonTransferable`, `ConfidentialTransfer`, `Group`/`GroupMember`, `MintCloseAuthority` 등)은 `Initialize`가 `NotSupportMint`로 거부합니다. 예외는 확장 검사를 우회하는 프로그램의 작은 하드코딩된 **mint 화이트리스트**(소수의 특정 pubkey)입니다. 특정 mint를 사건별로 온보딩하는 데 사용됩니다.

검증된 확장 목록과 mint 화이트리스트는 `programs/cp-swap/src/utils/token.rs` 아래의 CP-Swap 소스에 있으며 향후 프로그램 업그레이드와 함께 변경될 수 있습니다.

### Observation

Observation 계정은 각각 `block_timestamp`와 **누적 가격**을 저장하는 `ObservationState` 항목의 ring buffer입니다. 모든 스왑에서 프로그램은 마지막 항목 이후 충분한 시간이 지났으면 새로운 observation을 추가합니다. TWAP는 두 개의 observation을 읽고 `Δ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],
}
```

Ring buffer는 **100 observation**에 맞게 크기가 조정되었습니다. 각 observation은 40바이트이므로 배열만 4,000바이트입니다. 완전한 `ObservationState` PDA는 주변 필드와 discriminator 이후 약 4,100바이트입니다.

두 개의 소비자 규칙:

* **단일 observation을 가격으로 사용하지 마십시오.** 이는 현물 가격이 아닌 *누적*입니다. TWAP를 계산하려면 두 개를 사용합니다.
* **최소한 하나 블록 떨어진 observation을 선택합니다.** 동일 블록 내의 스왑은 새로운 observation을 생성하지 않을 수 있습니다. 연속으로 읽으면 동일 기록을 반환할 수 있습니다.

더 많은 수학은 [`products/clmm/accounts`](/ko/products/clmm/accounts)에 있습니다.

## 계정 생명주기

| 이벤트                  | 생성된 계정                                                   | 삭제된 계정 |
| -------------------- | -------------------------------------------------------- | ------ |
| `Initialize`         | `poolState`, `lpMint`, `vault0`, `vault1`, `observation` | —      |
| `Deposit`            | — (사용자 LP ATA 생성 가능)                                     | —      |
| `Withdraw`           | —                                                        | —      |
| `Swap`               | — (사용자 대상 ATA 생성 가능)                                     | —      |
| `CollectProtocolFee` | —                                                        | —      |
| `CollectFundFee`     | —                                                        | —      |
| `UpdatePoolStatus`   | —                                                        | —      |

CPMM 풀과 그 PDA는 **절대 닫히지 않습니다**. 0 유동성에서도 `poolState`는 유지됩니다. 이는 의도적입니다: 나중에 동일 풀을 다시 시드하면 역사적 observation buffer를 보존하고 PDA 유도가 안정적으로 유지됩니다.

## 어디서 무엇을 읽을지

* **명령어 계정 목록** (각 명령어에 대해 위의 어떤 것이 쓰기 가능/서명자인지): [`products/cpmm/instructions`](/ko/products/cpmm/instructions).
* **수수료 적립 의미론**: [`products/cpmm/fees`](/ko/products/cpmm/fees).
* **스왑 수학 / observation 업데이트 규칙**: [`products/cpmm/math`](/ko/products/cpmm/math).
* **정식 seed / 프로그램 ID**: [`reference/program-addresses`](/ko/reference/program-addresses).

출처:

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