Skip to main content
Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →

Установка

Каждый пример на этой странице соответствует файлу из raydium-sdk-V2-demo/src/clmm. Ссылка на GitHub указана рядом с каждым разделом. Инициализация повторяет config.ts.template из демо-репозитория (исходный код). Для любой нетривиальной интеграции рекомендуется disableFeatureCheck: true:

Создание пула CLMM

Источник: src/clmm/createPool.ts
SDK:
  • Сортирует mint1 и mint2 по порядку байтов перед вычислением адреса.
  • Вычисляет sqrt_price_x64 = floor(sqrt(initialPrice × 10^(dB−dA)) × 2^64).
  • Создаёт аккаунты observation и tick_array_bitmap_extension.
  • Оплачивает комиссию за создание пула, заданную в ammConfig.

Открытие позиции в выбранном диапазоне

Источник: src/clmm/createPosition.ts
SDK сам определяет, какие tick-массивы затрагивает диапазон, и передаёт их как аккаунты. Отдельная инструкция инициализации не нужна: отдельной инструкции init-tick-array не существует, а OpenPosition* при необходимости создаёт недостающий tick-массив самостоятельно, за счёт плательщика.

Увеличение ликвидности существующей позиции

Источник: src/clmm/increaseLiquidity.ts

Уменьшение ликвидности (с одновременным сбором комиссий)

Источник: src/clmm/decreaseLiquidity.ts и src/clmm/closePosition.ts
Чтобы собрать только комиссии и награды, вызовите decreaseLiquidity с liquidity = new BN(0). Побочный эффект инструкции — расчёт token_fees_owed_{0,1} и reward_amount_owed и перевод этих средств владельцу. Другого способа собрать их нет. Чтобы полностью закрыть позицию после обнуления ликвидности и комиссий, передайте ownerInfo: { closePosition: true } в последний вызов decreaseLiquidity. SDK добавит ClosePosition и сожжёт NFT.
Для позиций с ограниченным эмитентом нужен совместимый построитель закрытия. У таких позиций NFT-аккаунт токена заморожен. ClosePosition должен добавить ID пула первым остаточным аккаунтом (remaining account), чтобы CLMM мог разморозить аккаунт перед сжиганием. В ветке исходного кода программы нет изменений в SDK. Перед тем как включать создание позиций с ограниченным эмитентом, убедитесь, что ваша версия SDK явно поддерживает путь закрытия для замороженных позиций.
Для прямого Anchor-клиента оставьте объявленные аккаунты без изменений и добавьте пул в конец:
poolId можно передавать при каждом закрытии. CLMM читает его только тогда, когда positionNftAccount заморожен, поэтому один клиентский путь работает и со старыми, и с новыми позициями.

Сбор наград

Источник: src/clmm/harvestAllRewards.ts
harvestAllRewards обходит все позиции во всех переданных пулах, группирует вызовы DecreaseLiquidity с нулевой ликвидностью, которые рассчитывают комиссии и награды (а также UpdateRewardInfos, если нужно), и при необходимости распределяет их по нескольким транзакциям.

Swap

Источник: src/clmm/swap.ts Этот пример повторяет src/clmm/swap.ts: симуляция выполняется через swapInternal, а не через PoolUtils.computeAmountOutFormat.
Симуляция обходит карту тиков офчейн, используя ту же логику, что и on-chain программа, и возвращает выходную сумму (amountCalculated) и точный список аккаунтов, которые затронет своп (accounts). Всегда передавайте remainingAccounts, которые возвращает симуляция: если их меньше, своп откатится посередине обхода с ошибкой NotEnoughTickArrayAccount; лишние аккаунты просто расходуют вычислительный бюджет.
PoolUtils.computeAmountOutFormat по-прежнему существует, но для его вызова нужен ComputeClmmPoolInfo (это computePoolInfo из getPoolInfoFromRpc, а не объект пула из API), а также два обязательных аргумента — tickarrayBitmapExtension и blockTimestamp. Кроме того, метода raydium.clmm.fetchTickArrays не существует: fetchTickArrays — это самостоятельная функция, а вспомогательные функции уровня модуля — это PoolUtils.fetchMultiplePoolTickArrays и tickData / tickArrays, которые возвращает getPoolInfoFromRpc.

Создание кастомизируемого пула CLMM

createCustomizablePool — это точка входа, которая на этапе создания пула открывает переключатели динамической комиссии и комиссии с одной стороны. Она принимает те же параметры, что и createPool, плюс два дополнительных:
Параметров enableDynamicFee, dynamicFeeConfigId и startTime не существует. Динамическая комиссия включается передачей dynamicFeeConfig — если его не передать, вы получите пул со статической комиссией, и никакой ошибки не будет. Обратите также внимание, что в SDK члены перечисления называются TokenOnlyA / TokenOnlyB, тогда как в on-chain перечислении на Rust они называются Token0Only / Token1Only; числовые значения совпадают (FromInput = 0).
createPool по-прежнему работает для пути с комиссией по умолчанию и без динамической комиссии. Используйте createCustomizablePool, когда нужен любой из этих параметров. Список аккаунтов on-chain см. в разделе products/clmm/instructions.

Лимитные ордера

Лимитный ордер размещает средства пользователя на одном тике и исполняется по принципу FIFO, когда своп пересекает этот тик. Результат отправляется на ATA владельца в момент расчёта; владельцу не нужно быть онлайн, чтобы ордер исполнился.

Открытие лимитного ордера

Конфигурационного аккаунта лимитных ордеров для каждого пула и вспомогательной функции getClmmLimitOrderConfigs() не существует: ордера адресуются по тику.
SDK выводит PDA LimitOrderState из (owner, nonce PDA, order nonce), увеличивает LimitOrderNonce для данного кошелька и добавляет ордер в очередь FIFO на этом тике.

Увеличение и уменьшение открытого ордера

decreaseLimitOrder может убрать средства только из неисполненной части ордера; исполненная часть заблокирована до расчёта. Обе инструкции откатываются с ошибкой InvalidOrderPhase, если ордер уже полностью исполнен.

Расчёт исполненного ордера

settleLimitOrder сверяет unfilled_ratio_x64 ордера с трекером очереди, вычисляет исполненный результат и переводит его на ATA владельца. Владелец может вызвать эту инструкцию сам; limit_order_admin (офчейн-оператор-кипер) также может вызвать её от имени владельца — результат всё равно уйдёт владельцу. Чтобы закрыть полностью рассчитанные ордера и вернуть ренту, используйте closeLimitOrder (один ордер) или closeAllLimitOrder (пакетно). Для расчёта сразу многих ордеров settleAllLimitOrder упаковывает столько вызовов SettleLimitOrder, сколько помещается в одну v0-транзакцию.

Список активных ордеров кошелька (офчейн)

Эндпоинт активных ордеров возвращает в одном ответе и неисполненные, и частично исполненные ордера (фазы различаются полями totalAmount / filledAmount / pendingSettle). Для истории закрытых ордеров используйте /limit-order/history/order/list-by-user?wallet=… (по кошельку, с постраничной навигацией через nextPageId); для полного журнала событий конкретного ордера используйте /limit-order/history/event/list-by-pda?pda=….

Скелет CPI на Rust

Порядок remaining-аккаунтов для SwapV2:
Если свопу расширение никогда не требуется, его можно опустить; в противном случае оно идёт первым в списке remaining-аккаунтов.

Типичные ошибки

  • Неправильно выровненные границы тика → TickAndSpacingNotMatch. Всегда приводите значения через TickUtil.getPriceAndTick (единственное число, TickUtil).
  • Недостаточно tick-массивов в SwapV2 → NotEnoughTickArrayAccount. Берите список из swapInternal(...).accounts.
  • Позиция на всём диапазоне без расширения bitmap → PDA расширения должен быть доступен для записи; SDK делает это автоматически.
  • Путаница между sqrt_price_x64 и price → ошибка в два раза здесь особенно болезненна. Если сомневаетесь, пусть SDK вычислит значение из читаемой цены.
  • Слишком частый сбор наград → каждый сбор — это DecreaseLiquidity с нулевой ликвидностью, и он стоит одну транзакцию. Объединяйте сборы через harvestAllRewards для нескольких позиций и помните, что его execute требует { sequentially: true }.
  • Самостоятельное закрытие NFT-аккаунтов → ClosePosition сжигает NFT и закрывает его ATA. Он также закрывает mint NFT в Token-2022; mint в классическом SPL Token остаётся с нулевым предложением, так как эта программа не умеет закрывать mint’ы. Не закрывайте поддерживаемые аккаунты отдельно, иначе инструкция завершится с ошибкой.
  • Открытие лимитного ордера на тике, не кратном шагу → TickAndSpacingNotMatch. Всегда квантуйте через экспортируемый хелпер getOrderTick.
  • Вызов decreaseLimitOrder для полностью исполненного ордера → InvalidOrderPhase. Вместо этого используйте settleLimitOrder, а затем closeLimitOrder.
  • Ожидание флага enableDynamicFee → такого флага нет. Если не передать dynamicFeeConfig, создаётся пул со статической комиссией, без предупреждений и ошибок. Если нужны динамические комиссии, передайте PublicKey аккаунта конфигурации, выбрав его в /main/clmm-dynamic-config.

Что дальше

Источники: