Skip to main content
هذه الصفحة مُترجَمة آليًا بواسطة الذكاء الاصطناعي. النسخة الإنجليزية هي المرجع المعتمد.عرض النسخة الإنجليزية →
CPI (“cross-program invocation” أو الاستدعاء بين البرامج) هي الآلية التي يستدعي بها برنامج Solana واحد برنامجًا آخر. تأتي معظم برامج Raydium مع صناديق غلاف CPI من Anchor التي تجعل موقع الاستدعاء يبدو وكأنه استدعاء دالة مكتوبة، مع هياكل حسابات لها أسماء حقول تم التحقق منها و مساعدات cpi::<ix>(). توثق هذه الصفحة النمط العام مرة واحدة، ثم الاختلافات لكل برنامج. للحصول على TypeScript قابل للتشغيل، انظر صفحة code-demos من فصل كل منتج.

أي نمط ينطبق على أي برنامج

إذا كنت تدمج CPMM أو CLMM أو LaunchLab، اقرأ النمط العام أولاً، ثم انتقل إلى قسم برنامجك للحصول على قائمة الحسابات وأي اختلافات. Farm v6 و AMM v4 مختلفان بما يكفي لضمان قراءة أقسامهما بشكل مستقل.

تبعيات Cargo

يجب أن يطابق مفتاح التبعية اسم [package] في المستودع المستهدف بالضبط، مع الواصلات. لا يعامل Cargo raydium_cp_swap كمكافئ لـ raydium-cp-swap عند حل تبعية git.
branch = "master" يتتبع أحدث مصدر منشور؛ ثبّت على rev = "<commit>" محدد إذا كنت بحاجة إلى بناء قابل للتكرار. يُنصح به بمجرد تجاوزك مرحلة النماذج الأولية، لأن تغيير تخطيط الحساب في master سيكسر بناءك بدون تحذير. علم الميزة cpi يجعل الصناديق تُترجم إلى سطح CPI فقط (هياكل الحسابات + المستدعيات) بدلاً من البرنامج الكامل، لذا يبقى ملفك الثنائي صغيرًا. يجب أن يطابق anchor-lang / anchor-spl ما يثبته الصندوق المستهدف:
خذ كلا الصندوقين من نفس خط Anchor. كلا فرعي الترقية يثبتان =1.0.2، لذا يمكن لبرنامج واحد أن يستدعي CPI إلى CPMM و CLMM من صندوق واحد. خلط الخطوط يكسر البناء: على سبيل المثال، raydium-cp-swap على chore/upgrade-anchor مع raydium-clmm على master. سيضطر Cargo إلى ربط نسختين غير متوافقتين من سمات Anchor في ملف ثنائي واحد. إذا كنت عالقًا في زوج مختلط، قسّم البرنامج إلى اثنين، أو أسقط صندوق CPI المكتوب لأحد الجانبين وقم بترميز تلك التعليمة يدويًا (النمط الموضح لـ AMM v4 يعمل مع أي برنامج). أعد التحقق من ملفي Cargo.toml قبل البدء، لأن الفروع ستنتقل إلى master في النهاية.
غيّر Anchor 1.0 شيئين يلمسهما كل موقع استدعاء CPI. إذا كنت تنقل تكاملاً عاملاً من 0.3x:
  • CpiContext::new يأخذ Pubkey، وليس AccountInfo. CpiContext::new(ctx.accounts.cpmm_program.to_account_info(), accts) يصبح CpiContext::new(*ctx.accounts.cpmm_program.key, accts). نفس الشيء لـ new_with_signer. حقل الهيكل الآن program_id: Pubkey.
  • Context له عمر واحد، وليس أربعة. Context<'_, '_, 'info, 'info, MyProxySwap<'info>> يصبح Context<'info, MyProxySwap<'info>>.
على جانب العميل، anchor-client’s RequestBuilder::instructions() الآن يعيد Vec<Instruction> بدلاً من Result<Vec<Instruction>> (أسقط ?)، و CommitmentConfig انتقل خارج solana-sdk — خذه من anchor_client بدلاً من ذلك. spl-associated-token-account 8.0 يعيد تصدير مساعداته من صندوق spl-associated-token-account-interface الجديد. get_associated_token_address و ID لا تزال قابلة للوصول في جذر الصندوق (spl_associated_token_account::{get_associated_token_address, ID})، لكن مساعدات العنوان مهملة هناك — فضّل الاعتماد على spl-associated-token-account-interface مباشرة واستيراد spl_associated_token_account_interface::address::get_associated_token_address و spl_associated_token_account_interface::program::ID. لاحظ ::address و ::program هي وحدات من صندوق الواجهة؛ spl_associated_token_account::address::… لا تُحل.
للحصول على أمثلة CPI عاملة تربط هياكل الحسابات من البداية إلى النهاية، انظر raydium-io/raydium-cpi-example (يغطي AMM v4 و CPMM و CLMM). أحدث فرع لها هو anchor-0.31.0 — لا يوجد فرع Anchor 1.x حتى الآن، لذا تعامل مع هذا المستودع كمرجع لربط هيكل الحسابات، وليس لأقفال الإصدار التي تفرضها هذه الصفحة.

النمط العام لـ Anchor CPI

يمشي هذا القسم عبر CPMM من البداية إلى النهاية كمثال معمول به: هيكل Accounts، CpiContext، cpi::<ix>(). يتبع CLMM نفس الشكل بالضبط، مع قائمة حسابات مختلفة ومتطلب حسابات متبقية. يتبع LaunchLab نفس الميكانيكا لكن قائمة حساباته تحمل عدة حسابات بدون مكافئ CPMM/CLMM (global_config، platform_config، event_authority، program)، لذا تعامل معها كنفس النمط، وليس نفس الشكل. انظر قسم كل برنامج الخاص به بدلاً من افتراض أن قائمة الحسابات من هذا الشرح تنتقل مباشرة.

بناء قائمة الحسابات

يتطلب كل CPI من Raydium هيكل Accounts في البرنامج الاستدعاء. حقوله هي أي حسابات يحتاجها تعليمتك، مع محققات على مستوى الحقل؛ لا يجب أن يطابق ترتيب إعلانهم ترتيب حسابات تعليمة Raydium الخاصة، لأن عميلك المُنشأ من IDL يعالجها بالاسم، وليس بالموضع:
معظم حسابات جانب Raydium هي UncheckedAccount لأن المستدعى (Raydium) يمتلك التحقق. برنامجك الاستدعاء يتحقق فقط بصرامة من الحسابات التي تمتلكها، مثل ATAs المستخدم و PDAs الخاصة بك. تعليق /// CHECK: يقمع تحذير Anchor حول الفحوصات المفقودة. الاستثناء الوحيد من جانب Raydium هو cpmm_program نفسه: إنه البرنامج الذي يتم استدعاؤه وليس حساب بيانات يتحقق منه Raydium داخليًا، لذا يُكتب كـ Program<T> ويحصل على فحص عنوان Anchor التلقائي بدلاً من فحص /// CHECK: يدوي. هذا الشكل الذي يحتوي على UncheckedAccount في الغالب، حيث يتحقق Raydium من حساباته الخاصة، هو نفسه لـ CLMM و LaunchLab. يفترض هذا المثال أن كلا الـ mints كلاسيكي SPL Token؛ إذا كان أي جانب يمكن أن يكون mint Token-2022، أضف حقل token_program_2022: Program<'info, anchor_spl::token_2022::Token2022> وأمرره كـ input_token_program/output_token_program لذلك الجانب في استدعاء CPI أدناه بدلاً من token_program.

بناء استدعاء CPI

ينشئ Anchor مساعدًا واحدًا لكل تعليمة، جنبًا إلى جنب مع هيكل حسابات CPI (cpi::accounts::Swap، مستعار CpmmSwap أدناه). بخلاف هيكل MyProxySwap الخاص بك أعلاه، أسماء حقول هذا وترتيبه ثابتان بواسطة IDL raydium-cp-swap الخاص به ويجب أن يطابقا بالضبط:
cpi::swap_base_input يُنشأ من IDL؛ قائمة حجته تعكس قائمة حجة تعليمة Anchor. كل برنامج Raydium قائم على Anchor مؤكد (CPMM و CLMM و LaunchLab) ينشئ مساعدات cpi::<ix>() الخاصة به بنفس الطريقة، مع اسم الدالة يطابق اسم التعليمة في snake_case. ما إذا كان هذا يمتد إلى Farm v6 غير مؤكد؛ انظر قسمه.

بذور الموقّع (CPI موقّع بـ PDA)

عندما يوقّع برنامجك CPI نيابة عن PDA (شائع للأقبية والضمانات، إلخ)، استخدم CpiContext::new_with_signer:
يجب أن تطابق بذور الموقّع اشتقاق PDA. لأي حساب يُمرر كـ authority (أو دور موقّع مشابه)، يتحقق وقت تشغيل Solana من أن PDA يوقّع عبر هذه البذور.

الحسابات المتبقية

تأخذ بعض تعليمات Raydium حسابات متبقية، قائمة بطول متغير مُلحقة بعد الحسابات الثابتة. لا تتحقق مساعدات CPI من Anchor من الحسابات المتبقية؛ مررها عبر .with_remaining_accounts(...):
الترتيب مهم دائمًا، لأن برنامج المستقبل يكرر الحسابات المتبقية بالترتيب الذي تمررها. ترتيبان مؤكدان:
  • CLMM SwapV2: مصفوفات التجزئة، مرتبة اتجاهيًا.
  • Farm v6: أزواج (reward_vault, user_reward_ata)، لكن فقط من تدفق المكافأة الثاني فما بعده؛ انظر Farm v6 لما يُظهره فك تشفير معاملة حقيقية.

تطبيق النمط: CLMM

يتبع SwapV2 النمط العام أعلاه مع قائمة حسابات مختلفة ومتطلب حسابات متبقية لمصفوفات التجزئة. وحدة #[program] للصندوق تُسمى raydium_clmm، وهي أيضًا مسار Rust use الخاص بها.
هيكل حسابات CPI يُسمى SwapSingleV2، وليس SwapV2. SwapV2 هو اسم التعليمة على السلسلة.
احسب قائمة مصفوفة التجزئة بنفس الطريقة التي يفعلها SDK، عبر اقتباس مقابل حالة المجموعة الحالية، بدلاً من تخمين عدد ثابت؛ مبادلة تتجاوز المصفوفات التي مررتها تعود مع TickArrayNotFound (انظر products/clmm/instructions للجدول الكامل للحسابات وقائمة الأخطاء). مررها في اتجاه المسار السعري: أول مصفوفة في اتجاه المبادلة أولاً.

تطبيق النمط: LaunchLab

LaunchLab قائم على Anchor و IDL منشور: raydium_launchpad/raydium_launchpad.json في مستودع raydium-idl العام. معرّف البيانات الوصفية الداخلية لـ IDL هذا هو raydium_launchpad، اسم تقني للبرنامج الأساسي، وليس اسم بديل للمنتج. بخلاف CPMM و CLMM، مع ذلك، مصدر البرنامج الخاص به غير متاح للعموم (انظر reference/program-addresses). لا توجد تبعية git = "..." للإشارة إلى Cargo، ولا مصدر للتأكد من ما قد يكون مسار Rust use الفعلي للصندوق. أنشئ ربط من IDL المنشور باستخدام ماكرو declare_program! من Anchor. احفظ JSON IDL كـ idls/raydium_launchpad.json في صندوقك (يبحث Cargo عن مجلد idls/ نسبة إلى CARGO_MANIFEST_DIR)، ثم declare_program!(raydium_launchpad); ينشئ هياكل raydium_launchpad::cpi::accounts::<Ix> ودوال cpi::<ix>() مباشرة من IDL، بدون مصدر برنامج مطلوب. اسم هيكل الحسابات المُنشأ هو دائمًا اسم التعليمة في PascalCase (buy_exact_in → BuyExactIn)، وأسماء الحقول تطابق أسماء الحسابات في IDL بالضبط، قائمة الحسابات نفسها المستخدمة بالفعل في MyProxyBuy أدناه. يتبع شكل CPI النمط العام. قائمة الحسابات والحجج أدناه تأتي من تعليمة buy_exact_in في IDL على السلسلة، وليس من products/launchlab/instructions.mdx:
بعد التخرج، البرنامج المستهدف هو CPMM أو AMM v4 حسب pool_state.migrate_type، الذي تقول products/launchlab/accounts.mdx أنه يُعيّن في وقت Initialize. قائمة حسابات CPI الخاصة بك يجب أن تكون مُعدة لأي منهما، أو تحتاج إلى قراءة migrate_type من PoolState أولاً والتفرع.

نشر الأخطاء

يعيد كل برنامج Raydium قائم على Anchor تعداد أخطاء خاص به؛ يلفه Anchor، لذا يرى برنامجك الاستدعاء Err(ProgramError::Custom(code)). للتعامل مع أخطاء محددة:
استبدل نوع الخطأ ذا الصلة للبرنامج الذي تستدعيه (raydium_clmm::error::ErrorCode لـ CLMM، وهكذا). أرقام رموز الأخطاء مستقرة وفقًا لسياسة IDL (sdk-api/anchor-idl)، لذا يمكنك الاختبار مقابل رموز محددة بمقارنة القيمة الرقمية. جداول الأخطاء الكاملة: CPMM، CLMM، AMM v4 و Farm v6 و LaunchLab.

ميزانية الحساب في CPIs المركبة

لكل إطار CPI نفقات عامة، واستهلاك CU الخاص بالمستدعى يتراكم فوق ملكك، لذا معاملة تستدعي Raydium من داخل برنامجك تحتاج إلى ميزانية حساب صريحة بدلاً من الاعتماد على افتراضي 200k CU.
مقاس، وليس مقدر. swap_base_input من CPMM على mainnet يستهلك ~23,000 CU في برنامج CPMM نفسه — عينة 2026-09-09 عبر ثماني مبادلات حية على مجموعة عالية الحجم (22,721–23,052)، مقروءة من سطر السجل Program CPMMoo8… consumed N of M compute units. للمقارنة: مبادلة AMM v4 ~26,000؛ CLMM swap ~41,000؛ CLMM swap_v2 ~48,000 (43,838–52,887)، ترتفع مع كل عبور تجزئة.نسخة سابقة من هذه الصفحة أبلغت عن ~47,700 CU لـ CPI مبادلة وكيل. كان هذا الرقم المعاملة الكاملة (computeUnitsConsumed)، التي تشمل برنامجك الخاص والإطار CPI وأي إعداد ATA — وليس تكلفة المستدعى. كلاهما مفيد، لكنهما ليسا نفس الرقم، لذا قارن مثل مع مثل. قس معاملتك الخاصة بدلاً من ميزانية من أي رقم.
تكلف CPIs من CLMM و LaunchLab أكثر (CLMM بالخصوص يمشي مصفوفات تجزئة إضافية عبر remaining_accounts، مضيفًا CU لكل مصفوفة)، لكن فقط الرقم CPMM أعلاه هو قيمة مقاسة. اضبط دائمًا تعليمة ComputeBudgetProgram::set_compute_unit_limit(...) صريحة بحجم من قياسك الخاص، وليس رقمًا منسوخًا من التوثيق، لأن حد 200k CU الافتراضي سيستنزف بصمت وتكاليف لكل تعليمة تتحول مع ترقية البرامج.

AMM v4: بناء التعليمة اليدوي

يسبق AMM v4 Anchor وليس لديه صندوق CPI، مما يجعله البرنامج الوحيد في هذه الوثيقة الذي لا يتبع النمط العام أعلاه. بناء Instruction يدويًا:
انظر products/amm-v4/code-demos للحصول على قائمة الحسابات الكاملة.

Farm v6

استخدم TS SDK إذا كان هذا خيارًا لتكاملك. raydium.farm.deposit(...) (انظر products/farm-staking/code-demos) يتم ممارسته من قبل عروض توضيحية حقيقية ولا يعتمد على ما إذا كان صندوق Rust Anchor موجودًا لهذا البرنامج.
Farm v6 لا يوفر مسار CPI من Anchor. لا يوجد صندوق raydium_farm_v6 على crates.io، لا مستودع مصدر عام، ولا IDL على السلسلة — البرنامج ليس لديه حساب anchor:idl قديم ولا إدخال في برنامج Program Metadata (انظر sdk-api/anchor-idl). تعامل معه كبرنامج غير Anchor وبناء تعليماته يدويًا، كما هو موضح أدناه.
إذا كنت بحاجة إلى Rust CPI على أي حال، على سبيل المثال التركيب من برنامج آخر على السلسلة، بناء Instruction يدويًا، بنفس الطريقة مثل AMM v4: اشتق قائمة الحسابات الحقيقية ومعرّفات التعليمات بشكل مستقل، على سبيل المثال بفك تشفير تخطيطات TypeScript من SDK (raydium-sdk-V2’s farm module)، فك تشفير معاملات حقيقية مباشرة (انظر أدناه)، أو تفريغ وفك تجميع البرنامج المنشور. لشكل التعليمة بدون حجة متسق مع استدعاء حصاد أو مطالبة، ترتيب الحساب الحقيقي هو بادئة ثابتة (token_program، حساب حالة المزرعة، PDA سلطة الخزان، أول خزان مكافأة لـ PDA، PDA ثانية، المستدعي، و ATA المستدعي لأول رمز مكافأة)، متبوعًا بأزواج (reward_vault_i, user_reward_ata_i) في remaining_accounts لكل تدفق مكافأة بعد الأول. اتفاقية الاقتران حقيقية، لكنها تبدأ فقط في تدفق المكافأة الثاني: خزان وATA تدفق المكافأة الأول ثابتة، وليست مجاورة لبعضها البعض، وليست جزءًا من remaining_accounts على الإطلاق.

اختبار تدفق CPI

يتطلب dev المحلي توفر برامج Raydium في محقق الاختبار المحلي. ثلاثة خيارات:
  1. anchor test مع استنساخ البرنامج. يسحب bytecode mainnet المنشور إلى محقق محلي؛ انظر استنساخ البرامج إلى محقق محلي أدناه لتكوين Anchor.toml وشيئين يعثران على اختبارات إنشاء المجموعة بالتحديد.
  2. Devnet. ينشر Raydium معظم البرامج إلى devnet، لكن في معرّفات برامج مختلفة عن mainnet لكل برنامج (CPMM و CLMM و AMM v4 و Stable AMM و LaunchLab لكل منها عنوان devnet مميز؛ انظر جدول Devnet في reference/program-addresses). لا يتم نشر Farm v3/v5/v6 بشكل موثوق على devnet؛ API الحي (https://api-v3-devnet.raydium.io/main/info) لديه الصورة الحالية. إذا استخدمت ثوابت DEVNET_PROGRAM_ID المجمعة من raydium_clmm (أو ما يعادلها للبرامج الأخرى)، لا تفترض أن معرّف mainnet يعمل أيضًا على devnet. شغّل anchor test --provider.cluster devnet للوصول إلى الكود الحي بمجرد حصولك على العناوين الصحيحة.
  3. نشر محلي. استنسخ مستودعات Raydium (CPMM و CLMM؛ مصدر LaunchLab غير متاح لهذا الخيار) و anchor deploy إلى محقق محلي. يضيف نفقات عامة لدورة الاختبار لكن يسمح لك بتعديل المستدعى للتصحيح.
شغّل مع anchor test، أو anchor build أولاً و anchor test --skip-build بعد ذلك إذا كنت تكرر على ملف الاختبار بدون تغيير البرنامج.

استنساخ البرامج إلى محقق محلي

يعمل هذا بمعرّف البرنامج بغض النظر عما إذا كان مصدر البرنامج عامًا، لذا يستنسخ LaunchLab بنفس الطريقة CPMM و CLMM حتى لو لم يكن مصدره متاحًا. reference/program-addresses هو مصدر الحقيقة لكل عنوان هنا.
استنساخ البرنامج ليس كافيًا إذا كان اختبارك أيضًا ينشئ مجموعة (بدلاً من المبادلة مقابل واحدة موجودة بالفعل). تعليمة initialize من CPMM تتحقق من حسابات amm_config و create_pool_fee مقابل بيانات حقيقية على السلسلة، لذا تحتاج إلى استنساخ تلك أيضًا، أو initialize يفشل تمامًا. لـ CPMM بالتحديد: استنسخ AmmConfig لفئة الرسوم التي تريدها (احصل على عنوانها من GET https://api-v3.raydium.io/main/cpmm-config، الفهرس 0 هو فئة 0.25%) و حساب رمز استقبال الرسوم، تم التحقق من قبل العنوان الدقيق، وليس تم إنشاؤه على الطاير، لذا يجب أن يكون موجودًا بالفعل.
مجموعة أنشأها اختبارك للتو ليست قابلة للمبادلة في نفس اللحظة. تعليمة initialize من CPMM تتجاوز بصمت open_time المطلوب الذي ليس بدقة في المستقبل (if open_time <= block_timestamp { open_time = block_timestamp + 1 })، لذا حتى startTime: 0 (“افتح فورًا،” حسب SDK) يترك فجوة حقيقية ≥1 ثانية قبل أن تقبل المجموعة المبادلات. اختبار ينشئ مجموعة ويبادل مقابلها بدون تأخير سيضرب NotApproved. await قصير (1–2s) بين إنشاء المجموعة والمبادلة الأولى كافٍ. هذا خاص بالاختبار؛ إنسان يشغل أمرين منفصلين يدويًا لن يلاحظ عادة، لأن الكتابة وبدء العملية تأكل بالفعل أكثر من ثانية.

مؤشرات

المصادر: