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 →
Chức năng của đoạn mã này. Tạo pool CLMM mới tại mức phí bạn chọn, sau đó mở vị thế tập trung ban đầu. Hai transaction, một kịch bản. Mã được trích từ các demo chính thức trong raydium-sdk-V2-demo/src/clmm và điều chỉnh thành một tệp có thể chạy được trên Node.

Chuẩn bị

Hãy chắc chắn bạn đã đọc Điều kiện tiên quyết Bắt đầu nhanh và có RPC_URL, KEYPAIR, cũng như các thư viện phụ thuộc đã được cài đặt. Tạo pool CLMM yêu cầu phí một lần cộng với chi phí thuê cho mỗi mảng tick cho vị thế ban đầu. Bạn cũng cần cả hai mint hạt giống trong ví của mình — mở vị thế khi giá nằm trong phạm vi đã chọn yêu cầu thanthanh khoảy trên cả hai phía.

Bước 1 — config.ts

Lưu dưới dạng config.ts. Hình dạng này giống với tệp src/config.ts.template của kho demo — disableFeatureCheck được buộc phải là true (được khuyến nghị cho bất kỳ tích hợp phi tầm thường nào để SDK không bị chặn bởi lệnh gọi phát hiện tính năng khởi động của nó):
// config.ts
import { Raydium, TxVersion, parseTokenAccountResp } from "@raydium-io/raydium-sdk-v2";
import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js";
import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
import bs58 from "bs58";
import fs from "node:fs";

export const owner: Keypair = Keypair.fromSecretKey(
  // accept either a JSON-array keypair file (same shape Solana CLI writes) or a bs58 secret in env
  process.env.KEYPAIR_BS58
    ? bs58.decode(process.env.KEYPAIR_BS58)
    : new Uint8Array(JSON.parse(fs.readFileSync(process.env.KEYPAIR!, "utf8"))),
);

export const connection = new Connection(
  process.env.RPC_URL ?? clusterApiUrl("mainnet-beta"),
  "confirmed",
);
export const txVersion = TxVersion.V0;
const cluster = "mainnet" as "mainnet" | "devnet";

let raydium: Raydium | undefined;
export const initSdk = async (params?: { loadToken?: boolean }) => {
  if (raydium) return raydium;
  raydium = await Raydium.load({
    owner,
    connection,
    cluster,
    disableFeatureCheck: true,
    disableLoadToken: !params?.loadToken,
    blockhashCommitment: "finalized",
  });
  return raydium;
};

export const fetchTokenAccountData = async () => {
  const solAccountResp = await connection.getAccountInfo(owner.publicKey);
  const tokenAccountResp = await connection.getTokenAccountsByOwner(owner.publicKey, {
    programId: TOKEN_PROGRAM_ID,
  });
  const token2022Req = await connection.getTokenAccountsByOwner(owner.publicKey, {
    programId: TOKEN_2022_PROGRAM_ID,
  });
  return parseTokenAccountResp({
    owner: owner.publicKey,
    solAccountResp,
    tokenAccountResp: {
      context: tokenAccountResp.context,
      value: [...tokenAccountResp.value, ...token2022Req.value],
    },
  });
};

Bước 2 — createPool.ts

Lưu cùng với config.ts. Nguồn: src/clmm/createPool.ts.
// createPool.ts
import { CLMM_PROGRAM_ID, DEVNET_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";
import { PublicKey } from "@solana/web3.js";
import Decimal from "decimal.js";
import { initSdk, txVersion } from "./config";

export const createPool = async () => {
  const raydium = await initSdk({ loadToken: true });

  // RAY
  const mint1 = await raydium.token.getTokenInfo("4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R");
  // USDT
  const mint2 = await raydium.token.getTokenInfo("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");

  // Fee tiers come from the live API. On devnet the published `id` field is wrong;
  // re-derive the PDA before passing it to the SDK.
  const clmmConfigs = await raydium.api.getClmmConfigs();

  const { execute } = await raydium.clmm.createPool({
    programId: CLMM_PROGRAM_ID,
    // programId: DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID,
    mint1,
    mint2,
    ammConfig: {
      ...clmmConfigs[0],
      id: new PublicKey(clmmConfigs[0].id),
      fundOwner: "",
      description: "",
    },
    initialPrice: new Decimal(1),
    txVersion,
    // optional: set up priority fee here
    // computeBudgetConfig: { units: 600000, microLamports: 46591500 },
  });

  const { txId } = await execute({ sendAndConfirm: true });
  console.log("clmm pool created:", { txId: `https://explorer.solana.com/tx/${txId}` });
  process.exit();
};

createPool();

Bước 3 — createPosition.ts

Nguồn: src/clmm/createPosition.ts.
// createPosition.ts
import {
  ApiV3PoolInfoConcentratedItem,
  TickUtils,
  PoolUtils,
  ClmmKeys,
} from "@raydium-io/raydium-sdk-v2";
import BN from "bn.js";
import Decimal from "decimal.js";
import { initSdk, txVersion } from "./config";
import { isValidClmm } from "./utils";

export const createPosition = async () => {
  const raydium = await initSdk();

  let poolInfo: ApiV3PoolInfoConcentratedItem;
  // RAY-USDC pool
  const poolId = "61R1ndXxvsWXXkWSyNkCxnzwd3zUNB8Q2ibmkiLPC8ht";
  let poolKeys: ClmmKeys | undefined;

  if (raydium.cluster === "mainnet") {
    const data = await raydium.api.fetchPoolById({ ids: poolId });
    poolInfo = data[0] as ApiV3PoolInfoConcentratedItem;
    if (!isValidClmm(poolInfo.programId)) throw new Error("target pool is not CLMM pool");
  } else {
    const data = await raydium.clmm.getPoolInfoFromRpc(poolId);
    poolInfo = data.poolInfo;
    poolKeys = data.poolKeys;
  }

  // Optional: pull on-chain real-time price to avoid slippage errors from a stale API quote.
  // const rpcData = await raydium.clmm.getRpcClmmPoolInfo({ poolId: poolInfo.id });
  // poolInfo.price = rpcData.currentPrice;

  const inputAmount = 0.000001; // RAY amount
  const [startPrice, endPrice] = [0.000001, 100000];

  const { tick: lowerTick } = TickUtils.getPriceAndTick({
    poolInfo,
    price: new Decimal(startPrice),
    baseIn: true,
  });
  const { tick: upperTick } = TickUtils.getPriceAndTick({
    poolInfo,
    price: new Decimal(endPrice),
    baseIn: true,
  });

  const epochInfo = await raydium.fetchEpochInfo();
  const res = await PoolUtils.getLiquidityAmountOutFromAmountIn({
    poolInfo,
    slippage: 0,
    inputA: true,
    tickUpper: Math.max(lowerTick, upperTick),
    tickLower: Math.min(lowerTick, upperTick),
    amount: new BN(new Decimal(inputAmount || "0").mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    add: true,
    amountHasFee: true,
    epochInfo,
  });

  const { execute, extInfo } = await raydium.clmm.openPositionFromBase({
    poolInfo,
    poolKeys,
    tickUpper: Math.max(lowerTick, upperTick),
    tickLower: Math.min(lowerTick, upperTick),
    base: "MintA",
    ownerInfo: { useSOLBalance: true },
    baseAmount: new BN(new Decimal(inputAmount || "0").mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    otherAmountMax: res.amountSlippageB.amount,
    txVersion,
    computeBudgetConfig: { units: 600000, microLamports: 100000 },
  });

  const { txId } = await execute({ sendAndConfirm: true });
  console.log("clmm position opened:", { txId, nft: extInfo.nftMint.toBase58() });
  process.exit();
};

createPosition();

Bước 4 — utils.ts

Nguồn: src/clmm/utils.ts.
// utils.ts
import { CLMM_PROGRAM_ID, DEVNET_PROGRAM_ID } from "@raydium-io/raydium-sdk-v2";

const VALID_PROGRAM_IDS = new Set<string>([
  CLMM_PROGRAM_ID.toBase58(),
  DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID.toBase58(),
]);

export const isValidClmm = (programId: string) => VALID_PROGRAM_IDS.has(programId);

Chạy thử

# create the pool first
RPC_URL="https://api.mainnet-beta.solana.com" \
KEYPAIR="$HOME/.config/solana/id.json" \
npx tsx createPool.ts

# then open an initial position against the new pool id
# (edit poolId at the top of createPosition.ts to point at your new pool)
RPC_URL="https://api.mainnet-beta.solana.com" \
KEYPAIR="$HOME/.config/solana/id.json" \
npx tsx createPosition.ts

Vừa xảy ra điều gì

Transaction 1 — raydium.clmm.createPool đã khởi tạo:
  • trạng thái pool tại PDA chính tắc cho (mint1, mint2, ammConfig),
  • token_0_vaulttoken_1_vault (được sắp xếp theo thứ tự byte của mint),
  • vòng đệm observation,
  • bitmap mảng tick nội tuyến,
và đặt sqrt_price_x64 ban đầu từ initialPrice của bạn. Transaction 2 — raydium.clmm.openPositionFromBase đã mở vị thế tập trung:
  • đã mint NFT vị thế vào ví của bạn (NFT vị thế; chuyển nhượng nó có nghĩa là chuyển nhượng vị thế),
  • đã phân bổ mảng tick tại các cận dưới và trên (phí thuê một lần nếu là vị thế đầu tiên trong các phạm vi đó; các mảng tick không bao giờ được đóng bởi chương trình, vì vậy các vị thế tiếp theo trong các mảng giống nhau không có chi phí thuê thêm),
  • đã gửi inputAmount của mint1 và lượng cặp phù hợp của mint2 (được tính bằng PoolUtils.getLiquidityAmountOutFromAmountIn),
  • đã ghi công vị thế với thanhanh khoảy tỷ lệ với độ rộng của phạm vi.
Phạm vi càng hẹp, hiệu quả vốn trên mỗi đơn vị TVL càng cao — và tổn thất vô thường càng lớn khi giá trôi ra ngoài phạm vi. Phạm vi được sử dụng ở trên ([0.000001, 100000]) là toàn phạm vi hiệu quả; thu hẹp nó để tập trung phí gần giá spot hiện tại.

Chọn mức phí

clmmConfigs[0] là mức phí thấp nhất. Bộ đầy đủ được công bố tại GET https://api-v3.raydium.io/main/clmm-config:
Chỉ sốtradeFeeRateKhoảng cách tickSử dụng khi
0100 (1bp)1Stablecoin / stablecoin, dự kiến tổn thất vô thường rất thấp
1500 (5bp)10Tài sản tương quan cao (ví dụ: liquid-staked so với cơ sở)
22_500 (25bp)60Cặp token tiêu chuẩn, blue-chip + stablecoin
310_000 (1.00%)120Cặp dễ bay hơi hoặc mỏng nơi rủi ro IL cao
Xem user-flows/choosing-a-pool-type để có ma trận quyết định đầy đủ.

Lỗi phổ biến

  • Pool already exists for this config — Pool CLMM đã tồn tại cho bộ ba (mint1, mint2, ammConfig) này. Tra cứu ID pool hiện có và bỏ qua Bước 2.
  • Insufficient funds for amount B — Ví của bạn có lượng mintA được yêu cầu nhưng không có mintB tương ứng. Mở vị thế khi giá nằm trong phạm vi yêu cầu thanhanh khoảy trên cả hai phía.
  • Tick out of rangelowerPrice hoặc upperPrice của bạn nằm ngoài phạm vi giá có thể biểu diễn được. Sử dụng phạm vi hợp lý hơn so với giá hiện tại.
  • Giá bị lỗi thời — Báo giá từ API có thể bị lỗi thời 5–60 giây. Nếu executePosition không thành công do slippage, hãy bỏ comment khối getRpcClmmPoolInfo trong createPosition.ts để lấy lại giá trực tiếp ngay trước khi ký.

Cảnh báo

  • Position NFT là cách truy cập duy nhất của bạn. Mất NFT hoặc chuyển nhượng nó, mất quyền truy cập vào vị thế. Hãy coi nó như một chìa khóa.
  • Vị thế ngoài phạm vi không kiếm được phí. Nếu giá di chuyển ra ngoài [lowerPrice, upperPrice], vị thế của bạn được đỗ hoàn toàn ở một tài sản và không kiếm được gì cho đến khi bạn cân bằng lại.
  • Phí thuê mảng tick là một chiều. Vị thế đầu tiên chạm vào mảng tick chưa được khởi tạo sẽ trả phí thuê của nó; chương trình không cung cấp đường dẫn để đóng mảng tick, vì vậy phí thuê đó là vĩnh viễn. Các vị thế tiếp theo trong cùng mảng là miễn phí.

Tiếp theo

Nguồn: