> ## 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 操作的端到端 TypeScript 範例 — 建立流動性池、交換、存入、提取、收集費用 — 加上最小的 Rust CPI 框架。

<Info>
  **本頁內容由 AI 自動翻譯，所有內容以英文版本為準。**

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

<Info>
  **版本資訊。** 所有 TypeScript 範例以 `@raydium-io/raydium-sdk-v2@0.2.42-alpha` 針對 Solana mainnet-beta 為目標，已於 2026-04 年驗證。Rust CPI 框架以 `raydium-cp-swap` 的 `master` 分支、Anchor `0.30.x` 為目標。程式 ID 從 [`reference/program-addresses`](/zh-Hant/reference/program-addresses) 的常數中提取。
</Info>

## 前置條件

```bash theme={null}
npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token bn.js decimal.js
```

本頁面的每個範例都對應到 [`raydium-sdk-V2-demo/src/cpmm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/cpmm) 中的一個檔案；GitHub 連結位於每個章節旁邊。初始化流程遵循示範倉庫的 `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 } 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",
});
```

`Raydium` 實例是 SDK 的外觀 — 下面的每個範例都會使用它。它會延遲從 `api-v3.raydium.io` 取得代幣清單和費用設定；你可以在離線環境中用自己的資料予以初始化。

## 建立 CPMM 流動性池

原始碼：[`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. 選擇費用級別。index=0 是 0.25% 級別。
const feeConfigs = await raydium.api.getCpmmConfigs();
const feeConfig  = feeConfigs.find((c) => c.index === 0)!;

// 2. 取得 mint 中繼資料，使 SDK 可以處理 Token-2022 擴展功能。
const mintAInfo = await raydium.token.getTokenInfo(mintA);
const mintBInfo = await raydium.token.getTokenInfo(mintB);

// 3. 建立交易。
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（假設 9 位小數）
  mintBAmount:     new BN(   160_000_000),  // 160 USDC（按 160/SOL）
  startTime:       new BN(0),               // 立即開啟
  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);
```

SDK 悄悄處理的幾件事：

* 在推導 PDA 之前將 mint 排序為 token0/token1 順序。
* 向 `poolFeeAccount` 支付一次性的 `create_pool_fee`。
* 如果缺失，建立呼叫者的關聯代幣帳戶。
* 根據每一側選擇正確的代幣程式（SPL Token 或 Token-2022）。

確認後，你可以使用以下方式取得即時池狀態：

```ts theme={null}
const { poolKeys, poolInfo, rpcData } = await raydium.cpmm.getPoolInfoFromRpc(
  extInfo.address.poolId,
);
```

## 交換（基礎輸入）

原始碼：[`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("<POOL_ID>");

// 1. 直接從 RPC 載入目前的池狀態（不是從 API）。
const { poolInfo, poolKeys, rpcData } = await raydium.cpmm.getPoolInfoFromRpc(poolId);

const inputMint  = new PublicKey(poolInfo.mintA.address); // 交換 A → B
const amountIn   = new BN(100_000_000);                   // 0.1 SOL
const slippage   = 0.005;                                 // 0.5%

// 2. 在本地報價。SDK 的 CurveCalculator 鏡像鏈上數學運算，
//    包括任何一側 Token-2022 轉帳費用。
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. 建立並送出。
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);
```

注意：SDK **一定會** 在 `getPoolInfoFromRpc` 內部重新從 RPC 取得池狀態。不要根據 `api-v3.raydium.io` 報價進行即將簽署的交易 — 過期一個區塊的報價可能在著陸時進入 `ExceededSlippage`。

## 交換（基礎輸出）

原始碼：[`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 是輸入，A 是輸出？取決於你的方向
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 });
```

## 存入流動性

原始碼：[`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);           // 所需的 LP mint 數量
const slippage = 0.01;

const { execute } = await raydium.cpmm.addLiquidity({
  poolInfo,
  poolKeys,
  lpAmount,
  slippage,
  baseIn: true,           // 從 mintA 側報價
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

SDK 使用池的目前準備金將 `lpAmount` 轉換為 `needed_token_0` 和 `needed_token_1`，每個都按 `1 + slippage` 膨脹以用於指令的 `maximum_*` 引數，並在必要時建立 ATA。

## 提取流動性

原始碼：[`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
const slippage = 0.01;

const { execute } = await raydium.cpmm.withdrawLiquidity({
  poolInfo,
  poolKeys,
  lpAmount,
  slippage,
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
```

## 收集協議/基金/創建者費用

原始碼：[`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)

這些指令受管理員或創建者限制，通常由 Raydium 多簽或池創建者持有的簽署者呼叫。SDK 將其公開為原始建構工具：

```ts theme={null}
import {
  makeCollectProtocolFeeInstruction,
  makeCollectFundFeeInstruction,
  makeCollectCreatorFeeInstruction,
} from "@raydium-io/raydium-sdk-v2";

// PDA 和授權在池建立時設定；查看 reference/program-addresses
// 以取得規範的種子。SDK 會公開助手程式（如果你偏好的話）。
```

離線你可以直接從 `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 框架

如果你想從自己的 Anchor 程式呼叫 CPMM — 例如，代表存款人執行交換的保險庫 — CPI 上下文看起來像這樣。帳戶順序遵循 [`products/cpmm/instructions`](/zh-Hant/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<ProxySwap>,
    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)
}
```

如果你的 CPI 以 PDA 身份簽署（例如，你代表存款人管理保險庫），將 `CpiContext::new` 換成 `CpiContext::new_with_signer` 並傳入你的種子。

## 常見陷阱

在開啟支援票務前的簡短檢查清單：

* **排序的 mint。** 如果你推導的 `poolState` PDA 與鏈上池不匹配，你可能忘記排序 mint。
* **過時的 API 報價。** 絕不要將來自 `api-v3.raydium.io` 的準備金值傳入 `CurveCalculator.swap`。從 RPC 取得。
* **錯誤的代幣程式。** Token-2022 mint 的保險庫由 Token-2022 程式擁有，而不是 SPL Token。始終使用池的 `token_0_program` / `token_1_program` 欄位。
* **轉帳費用 mint 的滑點計算不足。** 如果池的任一側是 Token-2022 轉帳費用 mint，你的 `minimum_amount_out` 必須以使用者實際接收的內容為單位，而不是保險庫發送的內容。
* **交換上的 `NotApproved`。** 檢查 `PoolState.status` — 管理員可能已暫停該池上的交換。查看 [`products/cpmm/instructions`](/zh-Hant/products/cpmm/instructions) 以取得狀態位元遮罩。

## 後續步驟

* [`sdk-api/typescript-sdk`](/zh-Hant/sdk-api/typescript-sdk) — 完整 SDK 參考。
* [`sdk-api/rest-api`](/zh-Hant/sdk-api/rest-api) — 報價和池中繼資料端點。
* [`user-flows/create-cpmm-pool`](/zh-Hant/user-flows/create-cpmm-pool) — 相同流程的非程式碼說明。
* [`integration-guides/aggregator`](/zh-Hant/integration-guides/aggregator) — 如果你作為多跳路徑的一部分路由 CPMM。

來源：

* [Raydium SDK v2 — `@raydium-io/raydium-sdk-v2`](https://github.com/raydium-io/raydium-sdk-V2)
* [Raydium SDK v2 範例 — `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)
