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.
ما يفعله هذا الكود. ينشئ مجموعة CLMM جديدة بطبقة الرسم التي تختارها، ثم يفتح مركزًا مركزًا أوليًا. معاملتان، سكريبت واحد. الكود مأخوذ من العروض التوضيحية الرسمية في raydium-sdk-V2-demo/src/clmm وتم تكييفه ليعمل كملف Node واحد.
الإعداد
تأكد من أنك قرأت متطلبات البدء السريع وأن لديك RPC_URL وKEYPAIR والتبعيات مثبتة.
ينطوي إنشاء مجموعة CLMM على رسم لمرة واحدة بالإضافة إلى إيجار مصفوفة القرادة لكل مركز أولي. ستحتاج أيضًا إلى كلا mint البذرة في محفظتك — فتح مركز عندما يكون السعر داخل النطاق المختار يتطلب السيولة على كلا الجانبين.
الخطوة 1 — config.ts
احفظ باسم config.ts. هذا له نفس شكل src/config.ts.template في مستودع العرض التوضيحي — disableFeatureCheck مجبر على true (موصى به لأي تكامل غير تافه حتى لا يحظر SDK استدعاء كشف الميزات عند بدء التشغيل):
// 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],
},
});
};
الخطوة 2 — createPool.ts
احفظ بجانب config.ts. المصدر: 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();
الخطوة 3 — createPosition.ts
المصدر: 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();
الخطوة 4 — utils.ts
المصدر: 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);
شغّله
# 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
ما الذي حدث للتو
المعاملة 1 — raydium.clmm.createPool هيّأت:
- حالة المجموعة في PDA الكنسي لـ
(mint1, mint2, ammConfig)،
token_0_vault و token_1_vault (مرتبة حسب ترتيب بايت mint)،
- حلقة المخزن المؤقت
observation،
- خريطة مصفوفة القرادة المضمنة،
وعيّنت sqrt_price_x64 الأولية من initialPrice الخاص بك.
المعاملة 2 — raydium.clmm.openPositionFromBase فتحت مركزًا مركزًا:
- ضربت NFT موضع لمحفظتك (NFT هو الموضع؛ نقله ينقل الموضع)،
- خصصت مصفوفات القرادة في الحدود الدنيا والعليا (إيجار لمرة واحدة إذا كان أول موضع في تلك النطاقات؛ لا تُغلق مصفوفات القرادة أبدًا بواسطة البرنامج، لذلك لا تدفع المواضع اللاحقة في نفس المصفوفات إيجارًا إضافيًا)،
- أودعت
inputAmount من mint1 ومقدار الزوج المطابق من mint2 (محسوب بواسطة PoolUtils.getLiquidityAmountOutFromAmountIn)،
- أرصدت الموضع بسيولة متناسبة مع عرض النطاق.
كلما ضاق النطاق، زادت الكفاءة الرأسمالية لكل دولار من TVL — وزادت خسارة عدم الدوام عندما ينجرف السعر خارج النطاق. النطاق المستخدم أعلاه ([0.000001, 100000]) هو فعليًا النطاق الكامل؛ ضيّقه لتركيز الرسوم بالقرب من السعر الفوري الحالي.
اختيار طبقة الرسم
clmmConfigs[0] هي طبقة الرسم الأقل. تُنشر المجموعة الكاملة في GET https://api-v3.raydium.io/main/clmm-config:
| الفهرس | tradeFeeRate | فسح القرادة | الاستخدام عندما |
|---|
| 0 | 100 (1bp) | 1 | مستقر / مستقر، خسارة عدم الدوام منخفضة جدًا متوقعة |
| 1 | 500 (5bp) | 10 | الأصول المترابطة بشدة (مثل staked سائل مقابل الأساس) |
| 2 | 2_500 (25bp) | 60 | زوج رمز قياسي، blue-chip + مستقر |
| 3 | 10_000 (1.00%) | 120 | زوج متقلب أو رقيق حيث يكون خطر IL مرتفعًا |
انظر user-flows/choosing-a-pool-type للحصول على مصفوفة قرار كاملة.
الأخطاء الشائعة
Pool already exists for this config — مجموعة CLMM موجودة بالفعل لهذا الثلاثي (mint1, mint2, ammConfig). ابحث عن معرف المجموعة الموجود وتخطَّ الخطوة 2.
Insufficient funds for amount B — محفظتك تحتوي على المقدار المطلوب من mintA لكن لا تحتوي على mintB المطابقة. فتح مركز عندما يكون السعر داخل النطاق يتطلب السيولة على كلا الجانبين.
Tick out of range — lowerPrice أو upperPrice الخاص بك يقع خارج نطاق السعر الممثل. استخدم نطاقًا أكثر معقولية بالنسبة للسعر الحالي.
- سعر قديم — قد تكون الاقتباس من API قديمة من 5 إلى 60 ثانية. إذا فشلت
executePosition على الانزلاق، فقم بإلغاء التعليق عن كتلة getRpcClmmPoolInfo في createPosition.ts لإعادة جلب السعر المباشر الحي بحلول الوقت المناسب قبل التوقيع.
تحفظات
- NFT الموضع هو مقبضك الوحيد. فقدان NFT أو نقله، فقدان الوصول إلى الموضع. تعامل معه مثل المفتاح.
- المواضع خارج النطاق لا تحقق أي رسوم. إذا تحرك السعر خارج
[lowerPrice, upperPrice]، يكون موضعك متوقفًا بالكامل في أحد الأصول ولا يحقق شيئًا حتى تعيد التوازن.
- إيجار مصفوفة القرادة ذو اتجاه واحد. أول موضع يلمس مصفوفة قرادة لم تهيأ أبدًا يدفع إيجارها؛ البرنامج لا يعرض مسارًا لإغلاق مصفوفات القرادة، لذلك هذا الإيجار دائم. المواضع اللاحقة في نفس المصفوفة مجانية.
التالي
المصادر: