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

# AMM v4 程式碼示例

> TypeScript 範例，展示如何對現有 AMM v4 池進行交換和提供流動性。新池建立不在本範圍內 — 請改用 CPMM。

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

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

<Info>
  **版本說明。** 所有示例針對 `@raydium-io/raydium-sdk-v2@0.2.42-alpha`，在 Solana mainnet-beta 上驗證，時間為 2026-04。程式 ID：`675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8`（見 [`reference/program-addresses`](/zh-Hant/reference/program-addresses)）。
</Info>

<Info>
  **本處未展示新池建立。** Raydium UI 不再提供 AMM v4 池建立 — 新交易對預設為 [CPMM](/zh-Hant/products/cpmm/code-demos)。AMM v4 程式本身仍接受鏈上 `Initialize2`；只是不是推薦的路徑。下面的示例涵蓋每個整合者仍需的即時池操作：交換、存入、提取。
</Info>

## 設定

```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" });
```

## 按 ID 取得池

```ts theme={null}
import { PublicKey } from "@solana/web3.js";

const poolId = new PublicKey("<AMM_V4_POOL_ID>");

// 取得 SDK 標準化的池物件。對於 AMM v4，這包括指令建構器需要的 OpenBook
// 帳戶。
const data = await raydium.liquidity.getPoolInfoFromRpc({ poolId });
const { poolInfo, poolKeys, poolRpcData } = data;

console.log("交易對：", poolInfo.mintA.symbol, "/", poolInfo.mintB.symbol);
console.log("版本：", poolInfo.version);       // AMM v4 為 4
console.log("市場：", poolKeys.marketId.toBase58());
```

`poolKeys` 是指令建構器使用的結構。它按程式預期的順序包含每個 AMM v4 和 OpenBook 帳戶。

## 交換（基礎輸入）

```ts theme={null}
import BN from "bn.js";

const amountIn = new BN(1_000_000);            // 1 USDC（6 位小數報價）
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("交換交易：", txId);
```

SDK 透過 V2 進入點路由 AMM v4 交換，這些進入點不包含 OpenBook 帳戶。（在 2026-07 升級後，市場帳戶即使在舊版 v1 路徑上也不再被驗證。）

## 交換（基礎輸出）

```ts theme={null}
const amountOut = new BN(1_000_000_000);       // 1 SOL（9 位小數基礎）
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 });
```

## 增加流動性

```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"` 告訴 SDK 你提供了確切的 `amountInA`，而 `amountInB` 應最多為 `maxAnotherAmount`。池的帳面流動性在按比例計算前結算，因此存入比率與最新儲備相符。

## 移除流動性

```ts theme={null}
const lpAmount = new BN(50_000);               // 要銷毀的 LP

const { execute } = await raydium.liquidity.removeLiquidity({
  poolInfo,
  poolKeys,
  lpAmount,
  baseAmountMin: new BN(0),
  quoteAmountMin: new BN(0),
  txVersion: TxVersion.V0,
});

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

滑點最小值保護池狀態在你的預報價和著陸時間之間變化。

## 計算單位 / 優先費用調整

AMM v4 交換在計算上很繁重，因為每條指令都驗證完整的 OpenBook 狀態。典型交換使用 180k–250k CU，取決於需要在過程中結算多少開放訂單。始終傳遞計算單位限制：

```ts theme={null}
import { ComputeBudgetProgram } from "@solana/web3.js";

const { execute, innerTransactions } = await raydium.liquidity.swap({
  /* ...params... */
  computeBudgetConfig: {
    units: 400_000,
    microLamports: 50_000,       // 優先費用
  },
});
```

如果你省略 `computeBudgetConfig`，SDK 仍可能使用自己的預設值；檢查 `innerTransactions` 以確認。見 [`integration-guides/priority-fee-tuning`](/zh-Hant/integration-guides/priority-fee-tuning)。

## 直接 Rust CPI

如果你必須從自己的 Anchor 程式 CPI 進入 AMM v4，你需要逐字模擬 `SwapBaseIn` 的帳戶列表。最小草圖：

```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<ProxyAmmV4Swap>,
    amount_in: u64,
    minimum_amount_out: u64,
) -> Result<()> {
    // SwapBaseIn 在 AMM v4 上的指令判別器是 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(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 不提供用於 CPI 的 Anchor crate。上面的草圖使用手動構造的 `Instruction`。

<Note>
  上面的 CPI 草圖使用**舊版 v1 `SwapBaseIn` 佈局**（標籤 9，17 個帳戶）用於讀取/重現現有交易。自 2026-07 升級以來，市場帳戶被接受但被忽略。**對於新程式碼，優先使用 `SwapBaseInV2` / `SwapBaseOutV2`**（標籤 16 / 17），它們完全刪除市場帳戶（和 `amm_open_orders`）— 僅傳遞 8 個帳戶：`token_program`、`amm`、`amm_authority`、兩個池保管庫、兩個使用者代幣帳戶和 `user_owner`。
</Note>

## 陷阱

* **v1 交換上的帳戶計數錯誤。** 舊版 `SwapBaseIn` / `SwapBaseOut` 仍需要完整的 17 帳戶（或 18 帳戶）列表 — 不匹配的*計數*會以 `WrongAccountsNumber` 回復。市場帳戶的*內容*不再被驗證，但你仍必須佔據它們的位置。優先使用 V2 進入點以避免這種情況。
* **讀取原始保管庫餘額。** 儲備現在僅限保管庫；減去應計 PnL（`need_take_pnl_*`）。SDK 報價或 `api-v3.raydium.io/pools/info/ids` 為你處理此問題。
* **呼叫已移除的指令。** `MonitorStep`、`MigrateToOpenBook`、`WithdrawSrm`、`SimulateInfo`、`AdminCancelOrders` 和舊版 `Initialize` / `PreInitialize` 現在會回復。使用 `Initialize2` 進行池建立；沒有要呼叫的 crank。
* **Token-2022 mints。** 不支援。AMM v4 池無法針對 Token-2022 mint 建立；任何 Token-2022 交易對應在 CPMM 或 CLMM 上。

## 接下來去哪裡

* [`products/amm-v4/instructions`](/zh-Hant/products/amm-v4/instructions) — 這些示例背後的指令級表面。
* [`user-flows/migrate-amm-v4-to-cpmm`](/zh-Hant/user-flows/migrate-amm-v4-to-cpmm) — 如果你是考慮遷移的 LP。
* [`integration-guides/priority-fee-tuning`](/zh-Hant/integration-guides/priority-fee-tuning) — 繁重 AMM v4 交換的優先費用調整。

來源：

* [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2)
* [Raydium AMM 程式](https://github.com/raydium-io/raydium-amm)
