Skip to main content
Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →
CPI («cross-program invocation» — кросс-программный вызов) — это механизм, посредством которого одна программа Solana вызывает другую. Большинство программ Raydium поставляются с крейтами-обёртками Anchor CPI, которые делают место вызова похожим на типизированный вызов функции с структурами аккаунтов, имеющими проверенные имена полей и помощники cpi::<ix>(). На этой странице один раз описан общий паттерн, затем различия для каждой программы. Для работающих примеров на TypeScript см. страницу code-demos каждого раздела продукта.

Какой паттерн применяется к какой программе

Если вы интегрируете CPMM, CLMM или LaunchLab, сначала прочитайте общий паттерн, затем перейдите к разделу вашей программы для списка аккаунтов и любых различий. Farm v6 и AMM v4 достаточно отличаются, чтобы их разделы стоило читать отдельно.

Зависимости Cargo

Ключ зависимости должен точно совпадать с [package] name целевого репо, включая дефисы. 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 имеет одну lifetime, а не четыре. 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 — это модули крейта interface; 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) владеет валидацией. Ваша вызывающая программа строго валидирует только аккаунты, которыми вы владеете, такие как пользовательские ATA и ваши собственные PDA. Комментарий /// CHECK: подавляет предупреждение Anchor об отсутствующих проверках. Единственное исключение со стороны Raydium — это сама cpmm_program: это вызываемая программа, а не аккаунт данных, который Raydium валидирует внутри, поэтому он типизирован как Program<T> и получает автоматическую проверку адреса Anchor вместо ручной /// CHECK:. Эта в основном UncheckedAccount форма, где Raydium валидирует свои собственные аккаунты, одинакова для CLMM и LaunchLab. Этот пример предполагает, что оба mint — это классический 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 выше, имена и порядок полей этой структуры фиксированы raydium-cp-swap’s собственным IDL и должны совпадать точно:
cpi::swap_base_input генерируется из IDL; его список аргументов отражает список аргументов инструкции Anchor. Каждая подтверждённая программа Raydium на основе Anchor (CPMM, CLMM, LaunchLab) генерирует свои помощники cpi::<ix>() одинаково, с именем функции, совпадающим с именем инструкции в snake_case. Распространяется ли это на Farm v6, не подтверждено; см. его раздел.

Seeds подписантов (CPI, подписанный PDA)

Когда ваша программа подписывает CPI от имени PDA (обычно для хранилищ, escrow и т. д.), используйте CpiContext::new_with_signer:
Seeds подписанта должны совпадать с выведением PDA. Для любого аккаунта, переданного как authority (или аналогичная роль подписанта), среда выполнения Solana проверяет, что PDA подписывает через эти seeds.

Оставшиеся аккаунты

Некоторые инструкции 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 возвращает свой собственный enum ошибок; Anchor оборачивает их, поэтому ваша вызывающая программа видит их как Err(ProgramError::Custom(code)). Для обработки конкретных ошибок:
Подставьте соответствующий тип ошибки для программы, которую вы вызываете (raydium_clmm::error::ErrorCode для CLMM и так далее). Номера кодов ошибок стабильны согласно политике IDL (sdk-api/anchor-idl), поэтому вы можете тестировать против конкретных кодов, сравнивая с числовым значением. Полные таблицы ошибок: CPMM, CLMM, AMM v4, Farm v6 и LaunchLab.

Бюджет вычислений в составных CPI

Каждый кадр 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 swap ~26,000; CLMM swap ~41,000; CLMM swap_v2 ~48,000 (43,838–52,887), растёт с каждым пересечением тика.Более ранняя версия этой страницы сообщала ~47,700 CU для CPI прокси-свопа. Эта цифра была всей транзакцией (computeUnitsConsumed), которая включает вашу собственную программу, кадр CPI и любую настройку ATA — не стоимость вызываемого. Обе полезны, но это не одно и то же число, поэтому сравнивайте подобное с подобным. Измерьте вашу собственную транзакцию, а не бюджетируйте по числу из документации.
CPI 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 не предлагает путь Anchor CPI. Нет крейта 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 модуль), декодируя реальные транзакции напрямую (см. ниже) или дампируя и дизассемблируя развёрнутую программу. Для формы инструкции с нулевыми аргументами, согласованной с вызовом harvest или claim, реальный порядок аккаунтов — это фиксированный префикс (token_program, аккаунт состояния фермы, PDA авторитета хранилища, первое хранилище вознаграждения этого PDA, второй PDA, вызывающий и ATA вызывающего для этого первого mint вознаграждения), за которым следуют пары (reward_vault_i, user_reward_ata_i) в remaining_accounts для каждого потока вознаграждения после первого. Соглашение о парировании реально, но оно начинается только со второго потока вознаграждения: хранилище и ATA первого потока — это фиксированные аккаунты, а не смежные друг с другом и вообще не часть remaining_accounts.

Тестирование потока CPI

Локальная разработка требует, чтобы программы Raydium были доступны в вашем тестовом валидаторе. Три варианта:
  1. anchor test с клонированием программы. Вытягивает развёрнутый bytecode mainnet в ваш локальный валидатор; см. Клонирование программ в локальный валидатор ниже для конфигурации Anchor.toml и двух вещей, которые спотыкают тесты создания пула специально.
  2. Devnet. Raydium развёртывает большинство программ на devnet, но с разными ID программ, чем 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 (или эквивалент для других крейтов), не предполагайте, что ID mainnet также работает на devnet. Запустите anchor test --provider.cluster devnet, чтобы попасть в живой код, как только у вас будут правильные адреса.
  3. Локальное развёртывание. Клонируйте репо Raydium (CPMM, CLMM; исходный код LaunchLab недоступен для этого варианта) и anchor deploy в локальный валидатор. Добавляет накладные расходы цикла тестирования, но позволяет вам изменять вызываемого для отладки.
Запустите с anchor test или anchor build сначала и anchor test --skip-build после, если вы итерируете по файлу теста без изменения программы.

Клонирование программ в локальный валидатор

Это работает по ID программы независимо от того, доступен ли исходный код программы публично, поэтому 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) между созданием пула и первым свопом достаточен. Это специфично для тестирования; человек, запускающий две отдельные ручные команды, обычно не заметит, так как ввод и запуск процесса уже едят более секунды.

Указатели

Источники: