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. Обе ветки master закрепляют =1.0.2, поэтому одна программа может вызывать CPI в CPMM и CLMM из одного крейта. Смешивание линий ломает сборку: например, raydium-cp-swap на master с raydium-clmm, закреплённым на rev до его обновления Anchor 1.0. Cargo пришлось бы связать две несовместимые копии трейтов Anchor в один бинарный файл. Если вы застряли на смешанной паре, разделите программу на две или откажитесь от типизированного крейта CPI для одной стороны и вручную закодируйте эту инструкцию (паттерн, показанный для AMM v4, работает для любой программы).
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 — это модули крейта 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 следует идентичной структуре с другим списком аккаунтов и требованием remaining-accounts. 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; если любой из них может быть Token-2022 mint, добавьте поле 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, неподтверждено; см. его раздел.

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

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

Remaining accounts

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

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

SwapV2 следует общему паттерну выше с другим списком аккаунтов и требованием remaining-accounts для tick arrays. Модуль #[program] крейта называется raydium_clmm, что также является его путём Rust use.
Структура CPI аккаунтов называется SwapSingleV2, а не SwapV2. SwapV2 — это имя инструкции на цепи.
Вычислите список tick-array так же, как это делает SDK, через quote против текущего состояния пула, а не угадывайте фиксированное количество; swap, который превышает переданные вами массивы, откатывается с ошибкой TickArrayNotFound (см. products/clmm/instructions для полной таблицы аккаунтов и списка ошибок). Передавайте их в направлении обхода цены: первый массив в направлении swap первым.

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

LaunchLab построен на Anchor и опубликован в IDL: raydium_launchpad/raydium_launchpad.json в публичном репозитории raydium-idl. Внутренний идентификатор метаданных этого IDL — raydium_launchpad, техническое имя базовой программы, а не альтернативное название продукта. В отличие от CPMM и CLMM, исходный код самой программы не является публичным (см. reference/program-addresses). Нет зависимости git = "..." для Cargo и нет исходного кода, чтобы подтвердить, какой был бы реальный путь Rust use в крейте. Генерируйте привязки из опубликованного IDL, используя макрос Anchor declare_program!. Сохраните JSON IDL как idls/raydium_launchpad.json в вашем крейте (Cargo ищет директорию idls/ относительно CARGO_MANIFEST_DIR), затем declare_program!(raydium_launchpad); генерирует структуры raydium_launchpad::cpi::accounts::<Ix> и функции cpi::<ix>() прямо из IDL, без исходного кода программы. Имя сгенерированной структуры accounts всегда — имя инструкции в PascalCase (buy_exact_in → BuyExactIn), а имена полей совпадают с именами accounts в IDL ровно, тот же список accounts, что уже используется в MyProxyBuy ниже. Форма CPI следует общему паттерну. Список accounts и аргументы ниже взяты из инструкции buy_exact_in в IDL на цепи, а не из products/launchlab/instructions.mdx:
После выпуска целевая программа — CPMM или AMM v4 в зависимости от pool_state.migrate_type, который согласно products/launchlab/accounts.mdx устанавливается во время Initialize. Ваш список accounts для 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: ручное построение Instruction

AMM v4 предшествует Anchor и не имеет CPI крейта, что делает его единственной программой в этом документе, которая не следует общему паттерну выше. Постройте Instruction вручную:
Полный список accounts см. в products/amm-v4/code-demos.

Farm v6

Используйте TS SDK, если это вариант для вашей интеграции. raydium.farm.deposit(...) (см. products/farm-staking/code-demos) проверена реальными демонстрациями и не зависит от того, существует ли крейт Rust Anchor для этой программы.
Farm v6 не предлагает путь Anchor CPI. На crates.io нет крейта raydium_farm_v6, нет публичного исходного репозитория и нет IDL на цепи — программа не имеет ни устаревшего аккаунта anchor:idl, ни записи в Program Metadata программе (см. sdk-api/anchor-idl). Рассматривайте её как программу без Anchor и постройте её инструкции вручную, как ниже.
Если вам всё же нужен Rust CPI, например при составлении из другой программы на цепи, постройте Instruction вручную, так же как AMM v4: выведите реальный список accounts и дискриминаторы инструкций независимо, например декодируя макеты TypeScript SDK (raydium-sdk-V2’s farm модуль), декодируя реальные транзакции напрямую (см. ниже) или дампируя и дизассемблируя развёрнутую программу. Для формы инструкции без аргументов, согласованной с вызовом harvest или claim, реальный порядок accounts — это фиксированный префикс (token_program, аккаунт состояния фермы, PDA органа-хранилища, первое хранилище вознаграждения этого PDA, второй PDA, вызывающий, и ATA вызывающего для первого mint вознаграждения), за которым следуют пары (reward_vault_i, user_reward_ata_i) в remaining_accounts для каждого потока вознаграждения после первого. Соглашение о парировании реально, но оно начинается только со второго потока вознаграждения: хранилище и ATA первого потока — это фиксированные accounts, не смежные друг с другом и вообще не являются частью remaining_accounts.

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

Для локальной разработки требуется, чтобы программы Raydium были доступны в вашем локальном валидаторе. Есть три варианта:
  1. anchor test с клонированием программы. Загружает развёрнутый на мейннете байт-код в локальный валидатор; см. раздел Клонирование программ в локальный валидатор ниже для конфигурации Anchor.toml и двух моментов, которые часто вызывают проблемы при тестировании создания пула.
  2. Devnet. Raydium развёртывает большинство программ на devnet, но с другими ID программ, чем на мейннете для каждой программы (CPMM, CLMM, AMM v4, Stable AMM и LaunchLab имеют отдельные адреса на devnet; см. таблицу Devnet в reference/program-addresses). Farm v3/v5/v6 не всегда публикуются на devnet; текущую информацию можно найти в live API (https://api-v3-devnet.raydium.io/main/info). Если вы используете встроенные константы DEVNET_PROGRAM_ID из raydium_clmm (или эквивалент для других крейтов), не предполагайте, что ID мейннета также работает на 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–2 сек) между созданием пула и первым своп достаточно. Это специфично для тестирования; человек, выполняющий две отдельные команды вручную, обычно не заметит этого, так как ввод текста и запуск процесса уже занимают более одной секунды.

Ссылки

Источники: