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

# Demo code CLMM

> Ví dụ TypeScript đầy đủ: tạo pool CLMM, mở vị thế trong khoảng giá tùy chọn, điều chỉnh thanh khoản, swap, và thu phí cùng phần thưởng.

<Info>
  **Trang này được dịch tự động bằng AI. Phiên bản tiếng Anh là bản chính thức.**

  [Xem bản tiếng Anh →](/products/clmm/code-demos)
</Info>

<Info>
  **Thông tin phiên bản.** Tất cả các demo trên trang này sử dụng `@raydium-io/raydium-sdk-v2@0.2.42-alpha` trên Solana mainnet-beta, xác minh vào 2026-04. Các Program ID lấy từ [`reference/program-addresses`](/vi/reference/program-addresses) thông qua SDK.
</Info>

## Cài đặt

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

Mỗi demo trên trang này tương ứng với một file trong [`raydium-sdk-V2-demo/src/clmm`](https://github.com/raydium-io/raydium-sdk-V2-demo/tree/master/src/clmm); đường link GitHub được đặt ngay cạnh từng phần. Phần khởi tạo tuân theo file `config.ts.template` của demo repo ([nguồn](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/config.ts.template)) — `disableFeatureCheck: true` là cài đặt được khuyến nghị cho mọi tích hợp không tầm thường:

```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",
});
export const txVersion = TxVersion.V0;
```

## Tạo pool CLMM

Nguồn: [`src/clmm/createPool.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPool.ts)

```ts theme={null}
import { PublicKey } from "@solana/web3.js";
import { CLMM_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";
import BN from "bn.js";
import Decimal from "decimal.js";

const mintA = await raydium.token.getTokenInfo(
  new PublicKey("So11111111111111111111111111111111111111112"));   // wSOL
const mintB = await raydium.token.getTokenInfo(
  new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"));   // USDC

const ammConfigs = await raydium.api.getClmmConfigs();
const ammConfig  = ammConfigs.find((c) => c.index === 1)!;        // 0.05% tier

const initialPrice = new Decimal(160);        // 160 USDC per SOL
const { execute, extInfo } = await raydium.clmm.createPool({
  programId: CLMM_PROGRAM_ID,
  mint1:     mintA,
  mint2:     mintB,
  ammConfig,
  initialPrice,
  startTime: new BN(0),
  txVersion: TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Pool:", extInfo.address.id.toBase58(), "tx:", txId);
```

SDK sẽ thực hiện:

* Sắp xếp `mint1`/`mint2` theo thứ tự byte trước khi tính toán địa chỉ.
* Tính `sqrt_price_x64 = floor(sqrt(initialPrice × 10^(dB−dA)) × 2^64)`.
* Tạo các account `observation` và `tick_array_bitmap_extension`.
* Thanh toán phí tạo pool được xác định bởi `ammConfig`.

## Mở vị thế trong khoảng giá tùy chọn

Nguồn: [`src/clmm/createPosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/createPosition.ts)

```ts theme={null}
import { PoolUtils, TickUtils } from "@raydium-io/raydium-sdk-v2";

const poolId = new PublicKey("<POOL_ID>");
const { poolInfo, poolKeys, rpcData } = await raydium.clmm.getPoolInfoFromRpc(poolId);

// Choose a price range. Here: ±10% of current.
const currentPrice = new Decimal(poolInfo.price);
const lowerPrice   = currentPrice.mul(0.9);
const upperPrice   = currentPrice.mul(1.1);

// Snap to valid ticks for this pool's tick_spacing.
const { tick: tickLower } = TickUtils.getPriceAndTick({
  poolInfo, price: lowerPrice, baseIn: true,
});
const { tick: tickUpper } = TickUtils.getPriceAndTick({
  poolInfo, price: upperPrice, baseIn: true,
});

// How much of each token to deposit.
const inputAmount = new BN(10_000_000);  // 0.01 SOL
const inputMint   = poolInfo.mintA.address;

const res = PoolUtils.getLiquidityAmountOutFromAmountIn({
  poolInfo,
  slippage: 0.01,
  inputA: true,
  tickUpper,
  tickLower,
  amount: inputAmount,
  add: true,
  amountHasFee: true,
  epochInfo: await raydium.fetchEpochInfo(),
});

const { execute } = await raydium.clmm.openPositionFromBase({
  poolInfo,
  poolKeys,
  tickUpper,
  tickLower,
  base: "MintA",
  ownerInfo: { useSOLBalance: true },
  baseAmount: inputAmount,
  otherAmountMax: res.amountSlippageB.amount,
  txVersion: TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Position opened, tx:", txId);
```

SDK tự động xác định các tick array mà khoảng giá chạm đến và đóng gói các lệnh `InitTickArray` nếu có bất kỳ array nào chưa được khởi tạo.

## Tăng thanh khoản cho vị thế hiện có

Nguồn: [`src/clmm/increaseLiquidity.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/increaseLiquidity.ts)

```ts theme={null}
const positionNftMint = new PublicKey("<POSITION_NFT_MINT>");

const positionAccount = await raydium.clmm.getPositionInfo({
  nftMint: positionNftMint,
});

const { execute } = await raydium.clmm.increasePositionFromBase({
  poolInfo,
  poolKeys,
  ownerPosition: positionAccount,
  base: "MintA",
  baseAmount: new BN(5_000_000),
  otherAmountMax: new BN(1_000_000_000),
  txVersion: TxVersion.V0,
});

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

## Giảm thanh khoản (và thu phí đồng thời)

Nguồn: [`src/clmm/decreaseLiquidity.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/decreaseLiquidity.ts) và [`src/clmm/closePosition.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/closePosition.ts)

```ts theme={null}
const { execute } = await raydium.clmm.decreaseLiquidity({
  poolInfo,
  poolKeys,
  ownerPosition: positionAccount,
  liquidity: positionAccount.liquidity.divn(2),   // halve
  amountMinA: new BN(0),
  amountMinB: new BN(0),
  closePosition: false,
  txVersion: TxVersion.V0,
});

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

Để **chỉ thu phí**, hãy gọi `decreaseLiquidity` với `liquidity = new BN(0)`. Tác dụng phụ của lệnh này là thanh toán `tokens_fees_owed_{0,1}` và chuyển chúng ra ngoài.

Để đóng hoàn toàn vị thế sau khi đã về zero thanh khoản và phí, truyền `closePosition: true` vào lần gọi `decreaseLiquidity` cuối cùng. SDK sẽ thêm lệnh `ClosePosition` và đốt NFT.

## Thu phần thưởng

Nguồn: [`src/clmm/harvestAllRewards.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/harvestAllRewards.ts)

```ts theme={null}
const { execute } = await raydium.clmm.harvestAllRewards({
  ownerInfo: { useSOLBalance: true },
  allPoolInfo: { [poolInfo.id]: poolInfo },
  allPositions: { [poolInfo.id]: [positionAccount] },
  txVersion: TxVersion.V0,
});

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

`harvestAllRewards` duyệt qua mọi vị thế trên mọi pool được truyền vào, gom nhóm các lệnh `CollectReward` (và mọi `UpdateRewardInfos`), rồi tách chúng thành nhiều giao dịch nếu cần.

## Swap

Nguồn: [`src/clmm/swap.ts`](https://github.com/raydium-io/raydium-sdk-V2-demo/blob/master/src/clmm/swap.ts)

```ts theme={null}
import { PoolUtils } from "@raydium-io/raydium-sdk-v2";

const amountIn = new BN(10_000_000);
const baseIn   = true;               // swap A (SOL) → B (USDC)
const slippage = 0.005;

const { minAmountOut, remainingAccounts, priceImpact } =
  PoolUtils.computeAmountOutFormat({
    poolInfo,
    tickArrayCache: await raydium.clmm.fetchTickArrays({ poolInfo }),
    amountIn,
    tokenOut: poolInfo.mintB,
    slippage,
    epochInfo: await raydium.fetchEpochInfo(),
  });

const { execute } = await raydium.clmm.swap({
  poolInfo,
  poolKeys,
  inputMint: new PublicKey(poolInfo.mintA.address),
  amountIn,
  amountOutMin: minAmountOut.amount,
  observationId: poolKeys.observationId,
  remainingAccounts,
  txVersion: TxVersion.V0,
});

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

`computeAmountOutFormat` duyệt bản đồ tick off-chain bằng cùng logic với chương trình on-chain và trả về:

* lượng token nhận được dự kiến,
* lượng token nhận tối thiểu sau slippage,
* danh sách các account tick-array mà swap thực tế sẽ chạm vào (`remainingAccounts`).

Luôn truyền `remainingAccounts` được trả về từ bước mô phỏng: nếu truyền quá ít, swap sẽ revert giữa chừng với lỗi `TickArrayNotFound`; nếu truyền dữ liệu cũ, sẽ lãng phí compute.

## Tạo pool CLMM tùy chỉnh

`createCustomizablePool` là entry point mới cho phép bật tắt dynamic-fee và single-sided-fee ngay tại thời điểm tạo pool. Tham số đầu vào giống với `createPool` nhưng có thêm ba trường mới:

```ts theme={null}
import { CLMM_PROGRAM_ID, CollectFeeOn } from "@raydium-io/raydium-sdk-v2";

const dynamicFeeConfigs = await raydium.api.getClmmDynamicConfigs();    // GET /main/clmm-dynamic-config
const dynamicFeeConfig  = dynamicFeeConfigs.find((c) => c.index === 0); // pick a calibration tier

const { execute, extInfo } = await raydium.clmm.createCustomizablePool({
  programId: CLMM_PROGRAM_ID,
  mint1:     mintA,
  mint2:     mintB,
  ammConfig,
  initialPrice,
  startTime: new BN(0),
  // New fields:
  collectFeeOn:        CollectFeeOn.Token1Only,   // 0 = FromInput, 1 = Token0Only, 2 = Token1Only
  enableDynamicFee:    true,
  dynamicFeeConfigId:  dynamicFeeConfig?.id,      // omit when enableDynamicFee is false
  txVersion: TxVersion.V0,
});

await execute({ sendAndConfirm: true });
console.log("Customizable pool:", extInfo.address.id.toBase58());
```

`createPool` vẫn hoạt động bình thường cho trường hợp phí mặc định, không có limit order và không có dynamic fee. Hãy dùng `createCustomizablePool` khi bạn cần bất kỳ một trong ba tùy chọn mới trên. Xem [`products/clmm/instructions`](/vi/products/clmm/instructions) để biết danh sách account on-chain.

## Lệnh limit order

Một limit order đặt token đầu vào của người dùng tại một tick duy nhất và được khớp theo thứ tự FIFO khi một swap vượt qua tick đó. Token đầu ra được chuyển vào ATA của chủ sở hữu tại thời điểm thanh toán; chủ sở hữu không cần trực tuyến để lệnh được khớp.

### Mở limit order

```ts theme={null}
import { TickUtils } from "@raydium-io/raydium-sdk-v2";

const limitConfigs = await raydium.api.getClmmLimitOrderConfigs(); // GET /main/clmm-limit-order-config
const limitConfig  = limitConfigs.find((c) => c.poolId === poolInfo.id);

// Limit price MUST be quantized to tick_spacing.
const targetPrice = new Decimal(180);                              // sell SOL at 180 USDC
const { tick: limitTick } = TickUtils.getPriceAndTick({
  poolInfo, price: targetPrice, baseIn: true,
});

const { execute } = await raydium.clmm.openLimitOrder({
  poolInfo,
  poolKeys,
  limitOrderConfig: limitConfig,
  inputMint: poolInfo.mintA.address,         // selling SOL
  inputAmount: new BN(50_000_000),           // 0.05 SOL
  tick: limitTick,
  txVersion: TxVersion.V0,
});

const { txId } = await execute({ sendAndConfirm: true });
console.log("Limit order opened, tx:", txId);
```

SDK tính toán PDA `LimitOrderState` từ `(pool, owner, tick, nonce)`, tăng `LimitOrderNonce` theo cặp (pool, owner), và chèn lệnh vào nhóm FIFO tại tick đó.

### Tăng / giảm lệnh đang mở

```ts theme={null}
await raydium.clmm.increaseLimitOrder({
  poolInfo,
  poolKeys,
  limitOrderId: <LIMIT_ORDER_PUBKEY>,
  addAmount: new BN(20_000_000),
  txVersion: TxVersion.V0,
}).then((b) => b.execute({ sendAndConfirm: true }));

await raydium.clmm.decreaseLimitOrder({
  poolInfo,
  poolKeys,
  limitOrderId: <LIMIT_ORDER_PUBKEY>,
  removeAmount: new BN(10_000_000),
  txVersion: TxVersion.V0,
}).then((b) => b.execute({ sendAndConfirm: true }));
```

`decreaseLimitOrder` chỉ có thể xóa khỏi phần **chưa được khớp** của lệnh; phần đã khớp bị khóa cho đến khi thanh toán. Cả hai lệnh đều revert với lỗi `InvalidOrderPhase` nếu lệnh đã được khớp hoàn toàn.

### Thanh toán lệnh đã khớp

```ts theme={null}
await raydium.clmm.settleLimitOrder({
  poolInfo,
  poolKeys,
  limitOrderId: <LIMIT_ORDER_PUBKEY>,
  txVersion: TxVersion.V0,
}).then((b) => b.execute({ sendAndConfirm: true }));
```

`settleLimitOrder` đọc `unfilled_ratio_x64` của lệnh so với bộ theo dõi nhóm, tính toán lượng đầu ra đã khớp, rồi chuyển vào ATA của chủ sở hữu. Chủ sở hữu có thể tự gọi hàm này; `limit_order_admin` (một keeper vận hành off-chain) cũng có thể gọi thay mặt chủ sở hữu — token đầu ra vẫn đến tay chủ sở hữu.

Để đóng các lệnh đã thanh toán hoàn toàn nhằm thu hồi rent, dùng `closeLimitOrder` (đơn lẻ) hoặc `closeAllLimitOrder` (theo lô). Để thanh toán nhiều lệnh cùng lúc, `settleAllLimitOrder` đóng gói nhiều lệnh `SettleLimitOrder` nhất có thể vào một giao dịch v0.

### Xem danh sách lệnh đang chờ của một ví (off-chain)

```ts theme={null}
// API helper. See api-reference/temp-api-v1.
const active = await fetch(
  `https://temp-api-v1.raydium.io/limit-order/order/list?wallet=<your-wallet-pubkey>`,
).then((r) => r.json());
```

Endpoint lệnh đang hoạt động trả về cả lệnh chưa khớp lẫn khớp một phần trong một payload (`totalAmount` / `filledAmount` / `pendingSettle` phân biệt các trạng thái). Để xem lịch sử lệnh đã đóng, dùng `/limit-order/history/order/list-by-user?wallet=…` (theo ví, phân trang bằng `nextPageId`); để xem toàn bộ nhật ký sự kiện của một lệnh cụ thể, dùng `/limit-order/history/event/list-by-pda?pda=…`.

## Skeleton CPI bằng Rust

```rust theme={null}
use anchor_lang::prelude::*;
use raydium_amm_v3::cpi;
use raydium_amm_v3::program::AmmV3;
use raydium_amm_v3::cpi::accounts::SwapV2;

#[derive(Accounts)]
pub struct ProxyClmmSwap<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    /// CHECK:
    pub amm_config: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub pool_state: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub input_token_account: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub output_token_account: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub input_vault: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub output_vault: UncheckedAccount<'info>,
    #[account(mut)]
    /// CHECK:
    pub observation_state: UncheckedAccount<'info>,
    /// CHECK:
    pub token_program: UncheckedAccount<'info>,
    /// CHECK:
    pub token_program_2022: UncheckedAccount<'info>,
    /// CHECK:
    pub memo_program: UncheckedAccount<'info>,
    /// CHECK:
    pub input_vault_mint: UncheckedAccount<'info>,
    /// CHECK:
    pub output_vault_mint: UncheckedAccount<'info>,
    pub clmm_program: Program<'info, AmmV3>,
    // `remaining_accounts` carries the tick_array and bitmap_extension accounts.
}

pub fn proxy_swap(
    ctx: Context<ProxyClmmSwap>,
    amount: u64,
    other_amount_threshold: u64,
    sqrt_price_limit_x64: u128,
    is_base_input: bool,
) -> Result<()> {
    let cpi_accounts = SwapV2 {
        payer:                 ctx.accounts.payer.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(),
        observation_state:     ctx.accounts.observation_state.to_account_info(),
        token_program:         ctx.accounts.token_program.to_account_info(),
        token_program_2022:    ctx.accounts.token_program_2022.to_account_info(),
        memo_program:          ctx.accounts.memo_program.to_account_info(),
        input_vault_mint:      ctx.accounts.input_vault_mint.to_account_info(),
        output_vault_mint:     ctx.accounts.output_vault_mint.to_account_info(),
    };
    let cpi_ctx = CpiContext::new(ctx.accounts.clmm_program.to_account_info(), cpi_accounts)
        .with_remaining_accounts(ctx.remaining_accounts.to_vec());
    cpi::swap_v2(cpi_ctx, amount, other_amount_threshold, sqrt_price_limit_x64, is_base_input)
}
```

Thứ tự remaining account cho `SwapV2`:

```
[tick_array_bitmap_extension?, tick_array_0, tick_array_1, …]
```

Nếu swap không cần đến extension, hãy bỏ qua; ngược lại, nó phải là remaining account đầu tiên.

## Các lỗi thường gặp

* **Tick endpoint không khớp với tick\_spacing** → `InvalidTickIndex`. Luôn căn chỉnh bằng `TickUtils.getPriceAndTick`.
* **Không cung cấp đủ tick array trong `SwapV2`** → `TickArrayNotFound`. Dùng `computeAmountOutFormat` để lấy danh sách đầy đủ.
* **Vị thế full-range thiếu bitmap extension** → PDA của extension phải có quyền ghi; SDK xử lý điều này tự động.
* **Nhầm lẫn `sqrt_price_x64` với `price`** → Nhầm lẫn này gây sai lệch theo hệ số 2 rất khó phát hiện. Khi không chắc, hãy để SDK tính từ giá dạng con người đọc được.
* **Thu phần thưởng quá thường xuyên** → Mỗi lần thu tốn một giao dịch. Hãy gom nhóm bằng `harvestAllRewards` cho nhiều vị thế cùng lúc.
* **Đốt NFT khi mint vẫn còn nợ rent** → `ClosePosition` cũng đóng luôn NFT mint và ATA; đừng đóng chúng riêng lẻ vì chương trình sẽ revert.
* **Mở limit order tại tick không đúng bội số của tick\_spacing** → `InvalidTickIndex`. Luôn căn chỉnh bằng `TickUtils.getPriceAndTick`.
* **Gọi `decreaseLimitOrder` trên lệnh đã khớp hoàn toàn** → `InvalidOrderPhase`. Thay vào đó hãy dùng `settleLimitOrder` rồi `closeLimitOrder`.
* **Quên `dynamicFeeConfigId` trong khi truyền `enableDynamicFee: true`** → `CreateCustomizablePool` sẽ revert với lỗi `InvalidDynamicFeeConfigParams`. Hãy tắt dynamic fee hoặc chọn một config từ `/main/clmm-dynamic-config`.

## Tiếp theo

* [`sdk-api/typescript-sdk`](/vi/sdk-api/typescript-sdk) — toàn bộ bề mặt SDK.
* [`sdk-api/rest-api`](/vi/sdk-api/rest-api) — các endpoint báo giá và metadata pool.
* [`user-flows/create-clmm-pool`](/vi/user-flows/create-clmm-pool) — hướng dẫn không dùng code.
* [`integration-guides/aggregator`](/vi/integration-guides/aggregator) — định tuyến CLMM trong một đường dẫn swap.

Nguồn tham khảo:

* [Raydium SDK v2](https://github.com/raydium-io/raydium-sdk-V2)
* [Raydium SDK v2 demos](https://github.com/raydium-io/raydium-sdk-V2-demo)
* [`raydium-io/raydium-clmm`](https://github.com/raydium-io/raydium-clmm)
