Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →
Когда CPI — правильный инструмент
Пользовательская программа имеет смысл, когда своп должен произойти атомарно с другими изменениями состояния на цепи, которые может сделать только ваша программа. Типичные случаи:- Программы эскроу / лимитных ордеров — пользователь депонирует монету в ваше эскроу, ваша программа отслеживает условие цены, и при его срабатывании программа атомарно выполняет своп через Raydium и зачисляет результат на счёт пользователя.
- Прокси-агрегаторы — одна инструкция, которая маршрутизирует своп через Raydium и один или несколько других DEX, со всеми переходами под единой проверкой проскальзывания, принадлежащей вашей программе.
- Автокомпаундирующие хранилища — депонируйте LP или ставку фермы в ваше хранилище, хранилище собирает награды по расписанию, переинвестирует ликвидность, выпускает токены доли.
- Стратегические хранилища — позиции LP с кредитным плечом, которые перебалансируются путём свопа через CLMM; ликвидаторы, которые закрывают позиции и свопят залог в одной транзакции.
- Платформы запуска токенов с пользовательским вестингом — ваша программа держит токены вестинга и выпускает их в пул Raydium по расписанию.
Паттерны композиции
Паттерн 1: Тонкий прокси
Ваша программа предоставляет одну инструкцию, которая проверяет некоторую политику (например, белый список пар монет, скидка комиссии для проверенных пользователей) и затем перенаправляет в Raydium.Паттерн 2: Эскроу
Ваша программа владеет PDA, который держит входную монету пользователя. При срабатывании PDA подписывает CPI в Raydium для свопа своего собственного баланса.CpiContext::new_with_signer. См. Семена подписантов PDA.
Паттерн 3: Составной многоскачковый своп
Ваша программа выполняет несколько CPI в одной инструкции, обеспечивая единую границу проскальзывания для всех них. Инструкции свопа Raydium имеют свой собственныйminimum_amount_out, но вы устанавливаете их на 0 (или очень свободный минимум) и обеспечиваете строгий финальный минимум сами после последнего скачка.
Паттерн 4: Хранилище / стратегия
Ваша программа держит LP-токены или ставку фермы в PDA. Хранитель (или пользователь) вызываетcompound(), который:
- Собирает награды с фермы.
- Свопит награды на токены пула (CPI в CPMM или CLMM).
- Депонирует результаты обратно в LP (ещё один CPI).
- Ставит новый LP (ещё один CPI).
Конструкция списка аккаунтов
СтруктураAccounts вызывающей программы отражает порядок аккаунтов программы Raydium, но большинство аккаунтов на стороне Raydium — это UncheckedAccount, потому что Raydium сам их проверяет. Вы добавляете ограничения только на аккаунты, которыми вы владеете:
UncheckedAccount на стороне Raydium — это не лень. Получатель проверяет свои собственные; двойная проверка у вызывающей стороны просто сжигает CU и рискует выйти из синхронизации, когда Raydium выпустит новое поле структуры.
Сам вызов CPI
Семена подписантов PDA
CPI успешен только если PDA, переданный какauthority, совпадает с выводом, который заявляет вызывающая сторона. Оба должны согласиться на:
- Последовательность байтов семени (здесь
[b"escrow", user.key().as_ref()]). - Bump.
- ID вызывающей программы (ваша программа, не Raydium).
authority в CPMM — это его собственный vault PDA: фиксированный аккаунт уровня программы, который CPMM выводит и подписывает сам и который ваша программа не контролирует и не подменяет. Аккаунт, с которым должны совпадать сиды вашего PDA, — это payer: проверка происходит внутри собственного хелпера CPMM transfer_from_user_to_pool_vault, который требует, чтобы аккаунт, переданный как payer, был владельцем input_token_account.
Частая ошибка: передача user как payer, когда escrow_input_ata принадлежит PDA эскроу. Программа SPL Token отклоняет с owner mismatch. Всегда делайте payer владельцем ATA — и подписывайте за него через new_with_signer, когда этот владелец является PDA.
Оставшиеся аккаунты
Несколько инструкций Raydium принимают список аккаунтов переменной длины, добавленный после фиксированных — оставшиеся аккаунты.- CLMM
SwapV2: 1–8 аккаунтовTickArrayStateдля массивов тиков, которые своп может пересечь, в направлении свопа. - Farm v6
Deposit/Harvest/Withdraw: пары(reward_vault, user_reward_ata), одна пара на активный слот награды. - Монеты Token-2022 с transfer-hook: программа transfer-hook плюс любые аккаунты, которые нужны хуку.
Бюджет вычислений для составных вызовов
CPI стоит ~1,500 CU для самого фрейма вызова; собственное использование CU вызываемой стороны складывается сверху. Приведённые ниже цифры для вызываемой стороны измерены на реальных транзакциях mainnet в высокообъёмных пулах 2026-09-09 и считаны из строки логаProgram <id> consumed N of M compute units для собственного вызова программы Raydium (то есть они включают её внутренние CPI к token-программе):
Добавьте сверху ~1,500 на каждый фрейм CPI и накладные расходы вашей собственной программы. Стоимость свопа в CLMM растёт с числом пересечённых тиков, поэтому считайте её цифру нижней границей. Минты Token-2022 добавляют стоимость обработки расширения при самой передаче; измеряйте её для своих минтов, а не применяйте единый множитель.
Всегда устанавливайте явный
ComputeBudgetProgram::set_compute_unit_limit:
Распространение ошибок
Программы Raydium возвращают ошибки Anchor со стабильными кодами ошибок. Ваша вызывающая программа видит их какErr(ProgramError::Custom(code)). Пробросьте по умолчанию:
ERROR_CODE_OFFSET: варианты #[error_code] нумеруются начиная с 6000, поэтому сравнение с «сырым» дискриминантом перечисления никогда не совпадёт. (Ни в anchor-lang, ни в raydium_cp_swap нет вспомогательной функции is_err — в более ранних редакциях этой страницы использовалась функция, которой не существует.)
Сопоставление кода ошибки со значением стабильно согласно политике IDL (sdk-api/anchor-idl); новые коды добавляются в конец, существующие коды никогда не меняют значение.
Полный рабочий пример: эскроу лимитного ордера
Поток:open_order— пользователь депонируетamount_inизinput_mintв PDA эскроу; записывает целевойmin_amount_outи срок действия.execute_order— кто-либо (хранитель) вызывает с текущими аккаунтами пула. Программа проверяет текущую котировку ≥min_amount_out, затем выполняет CPI своп Raydium и держит выход в эскроу.claim— пользователь снимает выходную монету из эскроу.
order подписывает CPI как payer, поскольку ему принадлежит входной ATA эскроу; поэтому ExecuteOrder также нужно поле pool_authority: UncheckedAccount<'info> для собственного vault PDA программы CPMM. Как проверка проскальзывания на стороне Raydium, так и собственная проверка дельты эскроу обеспечивают минимум — подстраховка.
Тестирование
Подтягивание программ Raydium в локальный валидатор для интеграционных тестов (изAnchor.toml):
anchor test получает их из mainnet при запуске. См. sdk-api/rust-cpi.
Подводные камни, специфичные для композиции
Реентерабельность
Solana не имеет истинной реентерабельности — CPI не может вызвать обратно в исходную программу в одном вызове. Но вы всё ещё можете построить себя в логическую реентерабельность: CPI, который читает ваше состояние, затем ваш код читает его снова, предполагая, что CPI его не изменил. Для Raydium CPI не трогают ваше состояние, поэтому это менее проблема, чем, например, в контекстах flash-loan. Но если вы составляете Raydium с протоколом кредитования, будьте осторожны.Дрейф изменяемости аккаунта
Если ваша программа передаёт аккаунт какmut, но Raydium ожидает его только для чтения (или наоборот), runtime отклоняет вызов с InvalidAccountData. Всегда проверяйте ожидаемую изменяемость инструкции Raydium в IDL; raydium_cp_swap::cpi::accounts::Swap выставляет изменяемость каждого аккаунта за вас, исходя из маркеров #[account(mut)] на собственной структуре Swap в CPMM — сгенерированные поля все имеют простой тип AccountInfo<'info>, так что флаги несёт производная реализация ToAccountMetas, а не типы полей.
Поле программы Token-2022
Входные и выходные монеты могут быть под разными программами токенов — одна SPL Token, одна Token-2022. CPI имеет отдельные поляinput_token_program и output_token_program по этой причине. Всегда проверяйте поле owner каждой монеты и маршрутизируйте правильную программу в каждый слот.
Версионированные транзакции
Составная транзакция, которая делает 2+ CPI Raydium плюс создание ATA, редко помещается в legacy (v0-без-LUT) транзакцию. Используйте V0 с таблицами поиска адресов; получите публичные LUT Raydium черезraydium.getRaydiumLutAddresses().
Указатели
sdk-api/rust-cpi— низкоуровневая механика CPI.integration-guides/priority-fee-tuning— определение размера бюджета вычислений.products/cpmm/code-demos,products/clmm/code-demos,products/farm-staking/code-demos— фрагменты CPI для каждого продукта.

