Chuyển đến nội dung chính

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.

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 →
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 thông qua SDK.

Cài đặt

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; đườ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) — disableFeatureCheck: true là cài đặt được khuyến nghị cho mọi tích hợp không tầm thường:
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
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 observationtick_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
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
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.tssrc/clmm/closePosition.ts
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
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
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:
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 để 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

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ở

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

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)

// 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

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_spacingInvalidTickIndex. Luôn căn chỉnh bằng TickUtils.getPriceAndTick.
  • Không cung cấp đủ tick array trong SwapV2TickArrayNotFound. 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ợ rentClosePosition 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_spacingInvalidTickIndex. Luôn căn chỉnh bằng TickUtils.getPriceAndTick.
  • Gọi decreaseLimitOrder trên lệnh đã khớp hoàn toànInvalidOrderPhase. Thay vào đó hãy dùng settleLimitOrder rồi closeLimitOrder.
  • Quên dynamicFeeConfigId trong khi truyền enableDynamicFee: trueCreateCustomizablePool 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

Nguồn tham khảo: