الانتقال إلى المحتوى الرئيسي

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.

هذه الصفحة مُترجَمة آليًا بواسطة الذكاء الاصطناعي. النسخة الإنجليزية هي المرجع المعتمد.عرض النسخة الإنجليزية →
لافتة الإصدار. جميع العروض التوضيحية تستهدف @raydium-io/raydium-sdk-v2@0.2.42-alpha على شبكة Solana mainnet-beta، تم التحقق منها في أبريل 2026. تأتي معرّفات البرامج من reference/program-addresses عبر SDK.

الإعداد

npm install @raydium-io/raydium-sdk-v2 @solana/web3.js @solana/spl-token bn.js decimal.js
كل عرض توضيحي في هذه الصفحة يعكس ملفًا موجودًا في raydium-sdk-V2-demo/src/clmm؛ ورابط GitHub مذكور بجانب كل قسم. يتبع الإعداد الأولي ملف config.ts.template الخاص بمستودع العروض التوضيحية (المصدر) — يُنصح باستخدام disableFeatureCheck: true في أي تكامل غير بسيط:
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;

إنشاء pool من نوع CLMM

المصدر: 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 بما يلي:
  • ترتيب mint1/mint2 وفق الترتيب البايتي قبل الاشتقاق.
  • حساب sqrt_price_x64 = floor(sqrt(initialPrice × 10^(dB−dA)) × 2^64).
  • إنشاء حسابَي observation وtick_array_bitmap_extension.
  • دفع رسوم إنشاء pool المحددة في ammConfig.

فتح مركز ضمن نطاق سعري محدد

المصدر: 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 تلقائيًا مصفوفات الـ tick التي يمسّها النطاق المحدد، ويضم تعليمات InitTickArray إذا كانت أي منها غير مُهيَّأة.

زيادة السيولة على مركز قائم

المصدر: 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 });

تقليص السيولة (مع تحصيل الرسوم في آنٍ واحد)

المصدر: src/clmm/decreaseLiquidity.ts وsrc/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 });
لتحصيل الرسوم فقط، استدعِ decreaseLiquidity مع تمرير liquidity = new BN(0). الأثر الجانبي للتعليمة هو تسوية tokens_fees_owed_{0,1} وتحويلها خارجًا. لإغلاق المركز كليًا بعد تصفير السيولة والرسوم، مرّر closePosition: true في آخر استدعاء لـdecreaseLiquidity. سيضيف SDK تعليمة ClosePosition ويحرق NFT.

تحصيل المكافآت

المصدر: 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 على كل مركز في كل pool يُمرَّر إليها، وتجمع تعليمات CollectReward (وأي UpdateRewardInfos) في دُفعات، وتوزعها على معاملات متعددة عند الحاجة.

Swap (مبادلة)

المصدر: 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 خريطة الـ tick خارج السلسلة باستخدام المنطق ذاته المعتمد في البرنامج على السلسلة، وتُعيد:
  • الكمية المتوقعة ناتجةً عن العملية،
  • الحد الأدنى للكمية الناتجة بعد احتساب slippage،
  • قائمة بحسابات مصفوفات tick التي ستمسّها عملية swap الفعلية (remainingAccounts).
احرص دائمًا على تمرير remainingAccounts المُعادة من المحاكاة: إن كانت أقل من اللازم، ستعود العملية بخطأ TickArrayNotFound في منتصف المسير؛ وإن كانت قديمة، فهذا هدر لموارد الحساب.

إنشاء pool من نوع CLMM قابل للتخصيص

createCustomizablePool هو نقطة الدخول الجديدة التي تتيح ضبط الرسوم الديناميكية وخيار الرسوم أحادية الجانب عند إنشاء pool. يأخذ نفس معاملات createPool مع ثلاثة إضافات:
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 عملها للمسار الافتراضي الذي لا يحتاج رسومًا ديناميكية أو أوامر حد أو تخصيصًا إضافيًا. استخدم createCustomizablePool كلما احتجت إلى أي من الخيارات الثلاثة الجديدة. راجع products/clmm/instructions للاطلاع على قائمة الحسابات على السلسلة.

أوامر الحد (Limit orders)

يضع أمر الحد مدخلات المستخدم عند tick واحد ويُنفَّذ وفق مبدأ FIFO عندما تعبر عملية swap ذلك الـ tick. تُرسَل المخرجات إلى ATA الخاص بالمالك عند وقت التسوية؛ ولا يحتاج المالك أن يكون متصلًا بالشبكة لتنفيذ أمره.

فتح أمر حد

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 الـ PDA الخاص بـLimitOrderState من (pool, owner, tick, nonce)، ويزيد LimitOrderNonce الخاص بكل زوج (pool, owner)، ويدرج الأمر في مجموعة FIFO عند ذلك الـ tick.

زيادة / تقليص أمر مفتوح

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 سحب إلا من الجزء غير المنفَّذ من الأمر؛ أما الجزء المنفَّذ فيظل مقيدًا حتى وقت التسوية. تعود كلتا التعليمتين بخطأ InvalidOrderPhase إذا كان الأمر قد اكتمل تنفيذه بالكامل.

تسوية أمر مكتمل التنفيذ

await raydium.clmm.settleLimitOrder({
  poolInfo,
  poolKeys,
  limitOrderId: <LIMIT_ORDER_PUBKEY>,
  txVersion: TxVersion.V0,
}).then((b) => b.execute({ sendAndConfirm: true }));
تقرأ settleLimitOrder النسبة unfilled_ratio_x64 الخاصة بالأمر مقابل متتبع المجموعة، وتحسب المخرجات المنفَّذة، ثم تحولها إلى ATA المالك. يمكن للمالك استدعاء هذه الدالة بنفسه؛ كما يمكن لـlimit_order_admin (وهو حارس تشغيلي خارج السلسلة) استدعاؤها نيابةً عن المالك — والمخرجات تذهب إلى المالك في كلتا الحالتين. لإغلاق الأوامر المُسوَّاة بالكامل واسترداد الإيجار، استخدم closeLimitOrder (لأمر واحد) أو closeAllLimitOrder (دُفعي). ولتسوية عدة أوامر دفعةً واحدة، تحشو settleAllLimitOrder أكبر عدد ممكن من استدعاءات SettleLimitOrder في معاملة واحدة من نوع v0.

عرض الأوامر المعلّقة لمحفظة ما (خارج السلسلة)

// 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());
تُعيد نقطة نهاية الأوامر النشطة كلًا من الأوامر غير المنفَّذة والمنفَّذة جزئيًا في حمولة واحدة (تُميّز الحقول totalAmount / filledAmount / pendingSettle بين المراحل المختلفة). للاطلاع على سجل الأوامر المُغلقة، استخدم /limit-order/history/order/list-by-user?wallet=… (مقسّم بالصفحات عبر nextPageId)؛ وللاطلاع على سجل أحداث أمر بعينه، استخدم /limit-order/history/event/list-by-pda?pda=….

هيكل CPI بلغة 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)
}
ترتيب الحسابات المتبقية لـSwapV2:
[tick_array_bitmap_extension?, tick_array_0, tick_array_1, …]
إن لم تحتج عملية swap إلى الامتداد، أغفله؛ وإلا فهو أول حساب في قائمة الحسابات المتبقية.

الأخطاء الشائعة

  • تحديد نقاط tick لا تتوافق مع التباعدInvalidTickIndex. احرص دائمًا على المحاذاة عبر TickUtils.getPriceAndTick.
  • تمرير عدد غير كافٍ من مصفوفات tick في SwapV2TickArrayNotFound. استخدم computeAmountOutFormat للحصول على القائمة الكاملة.
  • إنشاء مركز بنطاق كامل دون إضافة امتداد bitmap ← يجب أن يكون PDA الامتداد قابلًا للكتابة؛ يتولى SDK هذا تلقائيًا.
  • الخلط بين sqrt_price_x64 والسعر ← هذا الالتباس يُفضي إلى خطأ عامل 2 يصعب اكتشافه. عند الشك، دع SDK يحسب السعر انطلاقًا من قيمة يقرأها الإنسان.
  • تحصيل المكافآت بشكل متكرر جدًا ← كل تحصيل يكلف معاملة. استخدم harvestAllRewards لدمج عدة مراكز في دُفعة واحدة.
  • حرق NFT ولا يزال الإيجار مستحقًا على الـ mint ← تُغلق ClosePosition أيضًا mint الـ NFT وحساب ATA الخاص به؛ لا تُغلقهما منفصلَين وإلا سيعود البرنامج بخطأ.
  • فتح أمر حد عند tick لا يتوافق مع التباعدInvalidTickIndex. احرص دائمًا على التكميم عبر TickUtils.getPriceAndTick.
  • استدعاء decreaseLimitOrder على أمر اكتمل تنفيذهInvalidOrderPhase. استخدم settleLimitOrder ثم closeLimitOrder عوضًا عن ذلك.
  • نسيان dynamicFeeConfigId مع تمرير enableDynamicFee: true ← سيعود CreateCustomizablePool بخطأ InvalidDynamicFeeConfigParams. إما أن تُعطّل الرسوم الديناميكية، أو تختار إعدادًا من /main/clmm-dynamic-config.

الخطوات التالية

المصادر: