Skip to main content
Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →
На этой странице описаны структура и роль каждого аккаунта. Канонические сиды перечислены в reference/program-addresses. CLMM-пул использует больше аккаунтов, чем CPMM-пул, поскольку ликвидность хранится разреженно по диапазону тиков — понимание этой разреженности составляет основу данной страницы.

Перечень аккаунтов

Активный CLMM-пул описывается следующими группами аккаунтов. Все они принадлежат программе CLMM, за исключением двух минтов и их хранилищ.

PoolState

Текущее состояние пула, считываемое при каждом swap и каждом изменении позиции.
Поля, с которыми вы будете работать непосредственно:
  • sqrt_price_x64 и tick_current — ценовое состояние пула. Обновляются вместе при каждом swap. tick_current — это нижняя целая часть log_{1.0001}(price).
  • liquidityактивная ликвидность: сумма значений L для всех позиций, чей диапазон содержит tick_current. Изменяется каждый раз, когда swap пересекает тик, а также при открытии, закрытии или изменении размера позиции.
  • fee_growth_global_{0,1}_x64 — накопленные комиссии на единицу ликвидности за всю историю пула. Позиции используют это значение для расчёта причитающейся им суммы.
  • tick_spacing привязывается к AmmConfig при инициализации и никогда не меняется. Определяет, какие индексы тиков допустимы в качестве граничных точек позиций.
  • tick_array_bitmapвстроенный bitmap, охватывающий часто используемый диапазон тиков вблизи спот-цены. Для позиций, выходящих за эти границы, отслеживание ведётся в отдельном аккаунте TickArrayBitmapExtension.
  • fee_on фиксируется при создании пула. Значение 0 (FromInput) воспроизводит поведение классического Uniswap V3. Значения 1 и 2 направляют комиссию swap на одну из сторон — подробнее о компромиссах см. products/clmm/fees.
  • dynamic_fee_info хранит состояние волатильности для надбавки динамической комиссии. При включении каждый swap пересчитывает dynamic_fee_component поверх AmmConfig.trade_fee_rate. Структура описана ниже в разделе DynamicFeeInfo; у пулов без динамической комиссии весь struct равен нулю.

AmmConfig

Типичный набор уровней комиссии CLMM (сверяйтесь с GET https://api-v3.raydium.io/main/clmm-config): protocol_fee_rate и fund_fee_rate — доли от торговой комиссии; соглашение аналогично CPMM. Подробнее: products/clmm/fees.

TickArrayState

CLMM не хранит по одной записи на каждый тик — это потребовало бы миллиардов аккаунтов. Вместо этого программа объединяет TICK_ARRAY_SIZE смежных тиков (обычно 60 или 88 в зависимости от версии программы) в TickArrayState, который создаётся лениво при первом обращении.
Четыре поля для лимитных ордеров равны нулю для любого тика, который ни разу не использовался в лимитных ордерах. Когда ордера открываются на тике, программа отслеживает их как последовательность когорт:
  • order_phase — идентификатор когорты. Увеличивается каждый раз, когда когорта переходит из состояния «полностью не исполнена» в «частично исполнена».
  • orders_amount — суммарный объём входящего токена текущей (новейшей) когорты.
  • part_filled_orders_remaining — остаток предыдущей когорты, которая в данный момент исполняется текущими swap-ами.
  • unfilled_ratio_x64 — мультипликатор Q64.64 когорты: когда swap заполняет X% когорты, коэффициент умножается на (1 − X). Каждый открытый ордер хранит собственный снимок (order_phase, unfilled_ratio_x64) на момент открытия, поэтому расчёт расчёта сводится к сравнению снимков.
Правила:
  • Граничный тик позиции t должен удовлетворять условию t % tick_spacing == 0. Программа отклоняет позиции с несовпадающим шагом.
  • Массив тика определяется как floor(t / (TICK_ARRAY_SIZE * tick_spacing)) * (TICK_ARRAY_SIZE * tick_spacing).
  • Tick-массив инициализируется лениво: первая позиция или swap, обращающиеся к неинициализированному массиву, создают его и оплачивают rent.
  • Tick-массив никогда не закрывается программой. Однажды выделенный, он существует на протяжении всего жизненного цикла пула, даже если все тики внутри вернулись к liquidity_gross == 0. Последующие позиции и swap-ы переиспользуют существующий аккаунт без дополнительной оплаты rent. Пути очистки tick-массивов через ClosePosition не существует.

TickArrayBitmapExtension

PoolState.tick_array_bitmap (встроенный) охватывает диапазон «вблизи спота» — ±1 024 tick-массива. За пределами этого диапазона (для экстремальных значений тиков) программа ведёт отдельный аккаунт расширения:
При «нормальном» диапазоне позиции об аккаунте расширения думать не нужно. Позиции на полный диапазон (например, (MIN_TICK, MAX_TICK)) требуют его; SDK разрешает это автоматически.

Позиции

Позиция CLMM — это связка из трёх аккаунтов и одного минта.

Position NFT mint

Минт SPL Token с supply = 1. Адрес минта — детерминированный PDA; Position NFT в кошельке владельца — это просто ATA, хранящий этот единственный токен. Передача NFT — это и есть смена владельца позиции: программа привязывает авторизацию к текущему держателю баланса ATA NFT, а не к Pubkey, хранящемуся в состоянии.

PersonalPositionState

По одному на каждую открытую позицию. Ключ — адрес NFT-минта.

ProtocolPositionState (устарело)

В ранних версиях CLMM агрегированные данные по диапазону (pool, tick_lower, tick_upper) хранились в PDA ProtocolPositionState. Новые версии этот аккаунт не создают и не читают. В списке аккаунтов инструкций OpenPosition / IncreaseLiquidity / DecreaseLiquidity слот по-прежнему присутствует как UncheckedAccount для совместимости ABI, но программа в него не записывает. Существующие аккаунты на-чейне являются устаревшими; администратор может вызвать CloseProtocolPosition для возврата rent.Агрегированные данные диапазона теперь вычисляются напрямую из двух граничных тиков (liquidity_gross, liquidity_net и fee_growth_outside_* / reward_growths_outside_x64 по каждому тику) в TickArrayState. Формула прироста комиссии внутри диапазона — fee_growth_inside = global − outside_lower − outside_upper — работает без отдельного аккаунта агрегированной позиции.

Observation

Буфер наблюдений CLMM хранит накопленный тик, а не накопленную цену. Внешние потребители вычисляют среднегеометрическую цену за интервал из (tick_cumulative[t1] − tick_cumulative[t0]) / (t1 − t0), а затем применяют price = 1.0001 ** tick. Подробнее: algorithms/clmm-math.

DynamicFeeConfig и DynamicFeeInfo

Параметры динамической комиссии хранятся в двух местах. Переиспользуемый шаблон — DynamicFeeConfig — управляется администратором и является общим для подключившихся пулов. Рантайм-состояние конкретного пула — DynamicFeeInfo — встроено в PoolState и обновляется при каждом swap.

DynamicFeeConfig

PDA-сид: ["dynamic_fee_config", index.to_be_bytes()]. Создаётся через create_dynamic_fee_config (только для администратора), изменяется через update_dynamic_fee_config. Пул, созданный с enable_dynamic_fee = true, копирует пять калибровочных параметров конфига (filter_period, decay_period, reduction_factor, dynamic_fee_control, max_volatility_accumulator) в собственный DynamicFeeInfo на момент создания; последующие изменения DynamicFeeConfig на уже созданные пулы не влияют.

DynamicFeeInfo (встроено в PoolState)

Четыре нижних поля — это состояние; пять верхних — калибровочные параметры, скопированные из DynamicFeeConfig. Математика комиссий и правила затухания описаны в products/clmm/math и products/clmm/fees. Константы, используемые в формуле:

LimitOrderState

По одному аккаунту на каждый открытый лимитный ордер.
Жизненный цикл:
  1. Открытие — пользователь вызывает open_limit_order, вносит total_amount входящего токена; ордер привязывается к когорте TickState.
  2. (необязательно) Увеличение / Уменьшениеincrease_limit_order добавляет к total_amount; decrease_limit_order возвращает неисполненные токены (и любой накопленный вывод на этот момент).
  3. Расчёт — когда когорта исполнена полностью или частично, владелец или оперативный keeper вызывает settle_limit_order, чтобы перечислить выходные токены в ATA владельца.
  4. Закрытие — когда unfilled_amount == 0, аккаунт можно закрыть. Rent всегда возвращается owner.
PDA-сид: [owner.as_ref(), limit_order_nonce.key().as_ref(), limit_order_nonce.order_nonce.to_be_bytes().as_ref()]. Таким образом, PDA ордера уникален для каждой тройки (owner, nonce_index, order_nonce).

LimitOrderNonce

Счётчик для пары (wallet, nonce_index), позволяющий одному пользователю вести несколько параллельных цепочек лимитных ордеров без коллизий PDA.
PDA-сид: [user_wallet.as_ref(), &[nonce_index]]. Большинство клиентов используют nonce_index = 0, а счётчик ордеров несёт order_nonce.

Вычисление ключевых аккаунтов

Точные строки сидов всегда следует сверять с on-chain IDL и reference/program-addresses.

Краткий справочник по жизненному циклу

Аккаунты TickArrayState программой никогда не закрываются — они существуют на протяжении всего жизненного цикла пула. Однажды инициализированный tick-массив остаётся на-чейне, даже если все тики внутри вернулись к liquidity_gross == 0. Повторное использование существующего tick-массива бесплатно; rent платит только первая позиция, обращающаяся к ранее неинициализированному массиву.

Где искать дополнительную информацию

Источники: