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 также переместил свои помощники: get_associated_token_address теперь находится под ::address, а ID программы — ::program::ID.
Для рабочих примеров CPI, которые подключают аккаунт-структуры от начала до конца, см. raydium-io/raydium-cpi-example (охватывает AMM v4, CPMM и CLMM).

Общий паттерн 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 выше, имена полей и порядок этой структуры зафиксированы IDL самого raydium-cp-swap и должны совпадать точно:
cpi::swap_base_input генерируется из IDL; его список аргументов отражает список аргументов инструкции Anchor. Каждая подтверждённая программа Raydium на основе Anchor (CPMM, CLMM, LaunchLab) генерирует свои помощники cpi::<ix>() одинаково, с именем функции, совпадающим с именем инструкции в snake_case. Распространяется ли это на Farm v6, не подтверждено; см. его раздел.

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

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

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

Некоторые инструкции Raydium принимают оставшиеся аккаунты, список переменной длины, добавленный после фиксированных аккаунтов. Помощники CPI Anchor не проверяют типы оставшихся аккаунтов; передайте их через .with_remaining_accounts(...):
Порядок всегда имеет значение, так как программа-получатель перебирает оставшиеся аккаунты в порядке, в котором вы их передаёте. Два подтверждённых порядка:
  • CLMM SwapV2: tick arrays, упорядочены по направлению.
  • Farm v6: пары (reward_vault, user_reward_ata), но только со второго потока вознаграждения и далее; см. Farm v6 для того, что показывает декодирование реальной транзакции.

Применение паттерна: CLMM

SwapV2 следует общему паттерну выше с другим списком аккаунтов и требованием оставшихся аккаунтов для tick arrays. Модуль #[program] крейта назван raydium_clmm, что также является его путём Rust use.
Структура CPI аккаунтов названа SwapSingleV2, а не SwapV2. SwapV2 — это имя инструкции на цепи.
Вычислите список tick-array так же, как это делает 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_inBuyExactIn), и имена полей совпадают с именами аккаунтов 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.
Одна измеренная точка данных, а не бенчмарк. Обычное эмпирическое правило для оценки CPI свопа CPMM (примерно 1500 CU накладных расходов CPI + 150000 CU для самого свопа + 10000 CU для обновления наблюдения, ~161500 CU всего) переоценивает реальное использование на широкий край. Реальный CPI свопа (my_proxy_swap вызывает swap_base_input, SPL-token mints, свежесозданный двухтокенный пул) потребил ~47700 CU всего, прочитано из connection.getTransaction(...).meta.computeUnitsConsumed, примерно треть этой оценки. Рассматривайте это как одну точку данных из одной формы пула и одной конфигурации mint, а не спецификацию. Измерьте вашу собственную транзакцию вместо бюджетирования по любому числу.
CPI CLMM и LaunchLab стоят дороже (CLMM в частности проходит дополнительные tick arrays через 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 для этой программы.
Статус Anchor CPI Farm v6 не подтверждён, и доступные доказательства противоречат ему. Нет публичного IDL или исходного кода для Farm v6, что противоречит утверждению products/farm-staking/code-demos.mdx о крейте Anchor CPI (raydium_farm_v6) со структурой аккаунтов Deposit.
Если вам всё же нужен Rust CPI, например при составлении из другой программы на цепи, постройте Instruction вручную, так же как AMM v4: выведите реальный список аккаунтов и дискриминаторы инструкций независимо, например декодируя макеты TypeScript SDK (raydium-sdk-V2’s farm модуль), декодируя реальные транзакции напрямую (см. ниже), или дампируя и дизассемблируя развёрнутую программу. Для формы инструкции с нулевыми аргументами, согласованной с вызовом harvest или claim, реальный порядок аккаунтов — это фиксированный префикс (token_program, аккаунт состояния фермы, PDA authority хранилища, первое хранилище вознаграждения этого PDA, второй PDA, вызывающий и ATA вызывающего для первого mint вознаграждения), за которым следуют пары (reward_vault_i, user_reward_ata_i) в remaining_accounts для каждого потока вознаграждения после первого. Соглашение о парировании реально, но оно начинается только со второго потока вознаграждения: хранилище и ATA первого потока — это фиксированные аккаунты, а не смежные друг с другом, и вообще не являются частью remaining_accounts.

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

Локальная разработка требует, чтобы программы Raydium были доступны в вашем локальном валидаторе. Три варианта:
  1. anchor test с клонированием программы. Вытягивает развёрнутый байткод 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) между созданием пула и первым свопом достаточен. Это специфично для тестирования; человек, запускающий две отдельные ручные команды, обычно не заметит, так как ввод и запуск процесса уже едят более одной секунды.

Указатели

Источники: