Перейти к основному содержанию

Documentation Index

Fetch the complete documentation index at: https://docs.raydium.io/llms.txt

Use this file to discover all available pages before exploring further.

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

Зачем нужны тики

Ликвидность в CLMM сосредоточена в диапазонах цен. Чтобы работать с диапазонами на цепи, цены квантуются в целые числа — тики, где каждый тик представляет постоянный множитель от предыдущего: price(i)=1.0001i\text{price}(i) = 1.0001^{\,i} Один тик соответствует движению цены на 0,01%, или примерно 1 базисный пункт. Соответствие выглядит так:
Индекс тика iМножитель цены
01.0000
1001.0100 (≈ +1.00%)
-1000.9900 (≈ −0.99%)
100002.7181 (≈ e)
MAX_TICK = 4436361.84e19
MIN_TICK = -4436365.42e-20
MIN_TICK и MAX_TICK выбраны так, чтобы sqrt_price_x64 уместился в u128 на обоих концах. Каждый пул требует, чтобы tick_lower >= MIN_TICK и tick_upper <= MAX_TICK. На практике веб-интерфейс ограничивает диапазон намного сильнее, чтобы предотвратить блокировку ликвидности в недостижимых тиках.

Интервал тиков

AmmConfig пула устанавливает интервал тиков — единственные тики, которые позиция может использовать в качестве граничных точек. Если tick_spacing = 60, допустимы только тики …, −120, −60, 0, 60, 120, …. Попытка открыть позицию с граничной точкой 31 откатывается с ошибкой InvalidTickIndex. Типичные публичные интервалы:
Уровень комиссииtrade_fee_rateИнтервал тиковМинимальный шаг цены на позицию
0.01%10010.01%
0.05%500100.10%
0.25%2500600.60%
1.00%100001201.21%
Чем больше интервал, тем меньше массивов тиков нужно инициализировать, тем дешевле открыть широкую позицию, но тем менее точна граница цены. Волатильные пары обычно находятся в уровнях с интервалом 120; стейблкоины — в уровнях с интервалом 1.

Массивы тиков

Пул не хранит состояние каждого тика в отдельных аккаунтах. Вместо этого TICK_ARRAY_SIZE смежных тиков (60 в текущей Raydium CLMM) упакованы в один TickArrayState. Первый тик массива — это его start_tick_index, и он охватывает ровно TICK_ARRAY_SIZE * tick_spacing целых единиц тика. Для tick_spacing = 60 и TICK_ARRAY_SIZE = 60:
  • Каждый массив тиков охватывает 60 × 60 = 3600 целых тиков.
  • start_tick_index — это кратное 3600: …, -7200, -3600, 0, 3600, 7200, ….
Граничная точка позиции t = 2040 при tick_spacing = 60 находится в массиве тиков с start_tick_index = 0. Граничная точка позиции t = 4200 находится в массиве с start_tick_index = 3600.

Когда создаётся массив

Массив тиков создаётся лениво: первая позиция, ссылающаяся на какой-либо тик внутри него, инициализирует массив и платит за хранение. Свопы не инициализируют массивы тиков — они пропускают неинициализированные массивы, используя битовую карту. Поток открытия позиции в SDK проверяет выбранный диапазон, вычисляет список массивов тиков, которые он затрагивает, и добавляет инструкции init_tick_array в ту же транзакцию что и OpenPosition, если какие-то из них отсутствуют.

Массивы тиков не закрываются

После инициализации массив тиков сохраняется на весь срок существования пула. Программа не предоставляет способ закрыть массив тиков, даже когда initialized_tick_count вернётся к нулю. Плата за хранение массивов тиков не возвращается; плата, уплаченная первой позицией, которая коснулась массива, остаётся заблокирована в этом аккаунте навсегда. Это намеренный компромисс: повторное использование существующего массива тиков бесплатно для каждой последующей позиции, поэтому активно торгуемый пул платит за хранение только один раз за слот (pool, start_tick_index), независимо от текучести.

Битовая карта

Поиск “следующего инициализированного тика слева/справа от текущего тика” должен быть быстрым — один свап может пересечь много тиков. Пул хранит 1-бит-на-массив-тиков битовую карту прямо в PoolState для диапазона ±1,024 массивов вокруг тика 0. За пределами этого диапазона (полнодиапазонные позиции, экзотические конфигурации) TickArrayBitmapExtension предоставляет переполнение. Свопу прогулять битовую карту: lowest_set_bit_above(tick_current_array_index) даёт следующий массив с инициализированным тиком на стороне, в которую свопу движется. Внутри этого массива похожее сканирование битов находит следующий инициализированный тик.

liquidity_gross и liquidity_net

Каждый инициализированный тик хранит две величины ликвидности:
  • liquidity_gross — сумма L над всеми позициями, ссылающимися на этот тик как граничную точку. Когда liquidity_gross достигает нуля, тик становится неинициализированным и может быть удалён из битовой карты.
  • liquidity_netзнаковое изменение уровня пула liquidity при пересечении этого тика в направлении вверх (слева направо в пространстве тиков). Если этот тик — нижняя граница позиции размером L, он добавляет +L; если это верхняя граница этой позиции, он добавляет −L.
Пример расчёта: две позиции в одном пуле.
  • Позиция A: tick_lower = -120, tick_upper = 0, ликвидность L_A = 100.
  • Позиция B: tick_lower = -60, tick_upper = 60, ликвидность L_B = 50.
Состояние по тикам:
ТикЗатронутliquidity_grossliquidity_net
-120A lower100+100
-60B lower50+50
0A upper100−100
60B upper50−50
Уровень пула liquidity при разных значениях tick_current:
  • tick_current = -180: liquidity = 0 (до любой позиции)
  • tick_current = -90: liquidity = 100 (только внутри A)
  • tick_current = -30: liquidity = 150 (внутри A и B)
  • tick_current = 30: liquidity = 50 (только внутри B)
  • tick_current = 90: liquidity = 0 (после обеих)
При каждом пересечении тика во время свопа программа добавляет liquidity_net (возможно, отрицательное значение) к PoolState.liquidity. Это точно механизм Uniswap v3.

Позиции как NFT

Позиция Raydium CLMM — это NFT. Открытие позиции создаёт новый минт с предложением 1 в кошельке вызывающего, и авторитет минта — программа CLMM. Программа привязывает владение позицией к тому, кто держит баланс в ATA этого минта во время CPI. Следствия:
  • Позиции передаваются. Кошелёк может продать или раздать позицию, передав NFT. Новый владелец может вызвать CollectRewards, IncreaseLiquidity и т. д.
  • Позиции доступны вне CLMM. Маркетплейсы и кошельки отображают позиции как другие NFT. SDK устанавливает разумные name/symbol в метаданные минта.
  • PDA позиции выводится из минта NFT. Вы можете найти PersonalPositionState без знания текущего владельца.

Позиции Token-2022

Новые пулы CLMM могут создавать позиции в Token-2022 вместо классического SPL Token. Программа предоставляет две параллельные инструкции открытия — OpenPosition и OpenPositionWithToken22Nft — с идентичной семантикой, кроме того, какой программа токена владеет минтом NFT. Совместимость кошельков и маркетплейсов различается; интерфейс Raydium отслеживает оба варианта.

Правила допустимого диапазона

При OpenPosition программа проверяет:
  1. tick_lower < tick_upper.
  2. tick_lower % tick_spacing == 0 и tick_upper % tick_spacing == 0.
  3. MIN_TICK <= tick_lower и tick_upper <= MAX_TICK.
  4. Вызывающий предоставил массивы тиков, содержащие tick_lower и tick_upper — либо уже инициализированные, либо через init_tick_array в той же транзакции.
  5. Аккаунт расширения битовой карты, если эта позиция выходит в диапазон расширения.
Если любая проверка не пройдена, инструкция откатывается с InvalidTickIndex, NotApproved или InsufficientLiquidity в зависимости от нарушенного ограничения. См. reference/error-codes.

”В диапазоне” против “вне диапазона”

Позиция находится в диапазоне, когда tick_lower <= tick_current < tick_upper. Только позиции в диапазоне способствуют PoolState.liquidity и, следовательно, только они получают комиссии за свопы. Позиция вне диапазона:
  • Держит 100% одного токена (того, мимо которого цена уже прошла). Конкретно, если tick_current < tick_lower, позиция держит только token1 (она уже была “продана” движением цены); если tick_current >= tick_upper, она держит только token0.
  • Не получает комиссии за свопы.
  • Продолжает получать вознаграждения, если потоки вознаграждения пула выделяют средства на ликвидность вне диапазона — но поведение Raydium по умолчанию “выделять только в диапазон”, соответствуя соглашению Uniswap v3. См. products/clmm/fees.
LP, управляющие позициями CLMM, тратят большую часть внимания на поддержание позиций в диапазоне при движении цены.

Типичные ошибки интеграции

  • Граничные точки не кратные интервалу. Код, который вычисляет тик из целевой цены, должен округлить его к кратному tick_spacing перед передачей в OpenPosition. Вспомогательные функции SDK (TickUtils.getTickWithPriceAndTickspacing) это делают; самодельная математика часто не делает.
  • Отсутствующие массивы тиков. Открытие широкой позиции может потребовать инициализации нескольких массивов тиков; забывчивость передать их как записываемые аккаунты откатывает инструкцию. SDK-метод openPositionFromBase возвращает список для вас.
  • Устаревший тик после свопа. tick_current может пересечь много тиков в одном свопе. Если ваш интерфейс показывает “текущий тик” из одного вызова RPC, а потом открывает позицию в более позднем, относительное положение относительно активной цены может отличаться на десятки тиков. Переполучите данные перед подписанием.
  • NFT позиций с дополнительными метаданными. Если вы создаёте кошелёк, распознающий позиции Raydium, определяйте их по авторитету минта (= PDA программы CLMM), а не по жёстко закодированному полю метаданных.

Что дальше

  • Math — пошаговое объяснение свопа и вывод прироста комиссии, в котором участвуют граница тиков.
  • Accounts — макеты TickArrayState и PositionState.
  • Fees and rewards — как нахождение в диапазоне контролирует накопление комиссий.
  • algorithms/clmm-math — общий вывод формул концентрированной ликвидности.
Источники: