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 должны совпадать с тем, что привязывает целевой крейт, и по состоянию на 2026-09 два публичных крейта Raydium не согласны:
Вы не можете зависеть от обоих крейтов из одной программы прямо сейчас. Каждый привязывает Anchor с =, поэтому Cargo пришлось бы связать две несовместимые копии трейтов Anchor в один бинарник, и сборка не удаётся. Если ваша программа вызывает CPI как в CPMM, так и в CLMM, вам нужно либо разделить её на две программы, либо отбросить типизированный крейт CPI для одной из них и вручную закодировать эту инструкцию (паттерн, показанный для AMM v4, работает для любой программы). Перепроверьте оба файла Cargo.toml перед началом — ожидается, что это разрешится, когда CLMM перейдёт на Anchor 1.x.
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), поэтому рассматривайте его как тот же паттерн, а не ту же форму. См. раздел каждой программы вместо предположения, что список аккаунтов из этого пошагового руководства переносится напрямую.

Построение списка аккаунтов

Каждый Raydium CPI требует структуру 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, неподтверждено; см. его раздел.

Signer seeds (CPI, подписанный PDA)

Когда ваша программа подписывает CPI от имени PDA (обычно для хранилищ, условных депозитов и т. д.), используйте CpiContext::new_with_signer:
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.
Измеренный, а не оценённый. CPMM swap_base_input на 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) между созданием пула и первым свопом достаточен. Это специфично для тестирования; человек, запускающий две отдельные ручные команды, обычно не заметит, так как ввод и запуск процесса уже едят более секунды.

Указатели

Источники: