Skip to main content
Эта страница переведена с помощью ИИ. За эталон принимается английская версия.Открыть английскую версию →
Эта страница дополняет products/clmm/accounts (что такое аккаунты) и products/clmm/math (что такая математика). Она является авторитетным источником для аргументов и порядка аккаунтов; конкретные макеты байтов берутся из IDL.Обновление программы 2026-09 перестроило CLMM на Anchor 1.0.2 / Solana 3.1.10, добавило административную инструкцию CollectExcessLamports и изменило то, что CreateAmmConfig записывает в owner / fund_owner. Ни одна пользовательская инструкция не изменила свои аккаунты, аргументы или математику. См. запись журнала изменений от 2026-09-30.

Инвентарь инструкций

Нет инструкции инициализации массива тиков, и она не требуется. Массив тиков создаётся внутри OpenPosition* / IncreaseLiquidity* через TickArrayState::get_or_create_tick_array, оплачивается payer. Передайте (возможно, ещё неинициализированный, принадлежащий системе) PDA массива тиков как tick_array_lower / tick_array_upper и программа выделит его, если он отсутствует. OpenLimitOrder делает то же самое для своего единственного tick_array.
Большинство административных инструкций (CreateAmmConfig, UpdateAmmConfig, UpdatePoolStatus, CreateOperationAccount, UpdateOperationAccount, CloseProtocolPosition) защищены жёстко закодированным публичным ключом admin программы. CreatePermissionPda / ClosePermissionPda принимают либо публичный ключ admin либо выделенный ключ permission_pda_admin; CreateSupportMintAssociated / CloseSupportMintAssociated принимают либо публичный ключ admin либо выделенный ключ владельца support-mint; CollectExcessLamports принимает либо публичный ключ admin либо выделенный кошелёк для сбора lamports. Административные инструкции потока вознаграждения (TransferRewardOwner, CollectRemainingRewards) защищены финансистом вознаграждения, а не администратором программы. Суффикс V2 означает “поддерживает Token-2022 на хранилищах / NFT, требует слота расширения bitmap”. SDK выбирает V2 по умолчанию для новых пулов.

CreatePool

Аргументы
Аккаунты (сокращённо)
Слоты 10 и 11 — по mint, а не “SPL Token затем Token-2022”. Каждый ограничен mint::token_program на соответствующем mint, поэтому для пула, чей token_mint_0 — Token-2022 и token_mint_1 — классический SPL, вы должны передать Token-2022 в слот 10 и SPL Token в слот 11. CreateCustomizablePool и CreatePermissionedPool используют те же два слота.
Предусловия
  • token_mint_0 < token_mint_1 по порядку байтов.
  • amm_config.disable_create_pool == false.
  • Mints не отклоняются списком разрешений расширения Token-2022.
Постусловия
  • pool_state.sqrt_price_x64 = sqrt_price_x64, tick_current = floor(log_{1.0001}(price)).
  • pool_state.liquidity = 0 (нет позиций).
  • pool_state.fee_on = FromInput (устаревший по умолчанию).
  • pool_state.dynamic_fee_info обнулен (динамическая комиссия отключена).

CreateCustomizablePool

Рекомендуется для новых пулов. Тот же эффект, что и CreatePool, плюс режим сбора комиссии для каждого пула и опциональное включение динамической комиссии. Аргументы
Аккаунты — ровно 13 объявленных аккаунтов CreatePool. dynamic_fee_config — не объявленный аккаунт.
Когда enable_dynamic_fee = true, DynamicFeeConfig для снимка должен быть предоставлен как последний remaining_account — обработчик читает ctx.remaining_accounts.last(). Любые PDA обхода SupportMintAssociated должны поэтому идти перед ним, или программа сделает снимок неправильного аккаунта и не сможет десериализовать. Пропуск при установленном флаге приводит к AccountLack.
Предусловия — те же, что и CreatePool. Если enable_dynamic_fee = false, remaining_account не требуется и любые переданные сканируются только на предмет записей support-mint. Постусловия
  • pool_state.fee_on установлен на выбранный вариант CollectFeeOn.
  • Если динамическая комиссия была включена: pool_state.dynamic_fee_info инициализирован из предоставленного DynamicFeeConfig (пять параметров калибровки скопированы; поля состояния обнулены).
  • В противном случае: pool_state.dynamic_fee_info обнулен (= динамическая комиссия неактивна навсегда для этого пула).
fee_on и бит включения динамической комиссии устанавливаются только при создании пула. Нет встроенного обновления — пулы, созданные через устаревший CreatePool, не могут ретроактивно получить динамическую комиссию или одностороннюю комиссию. Новые развёртывания должны по умолчанию использовать эту инструкцию.

CreatePermissionedPool

Как CreatePool, так и CreateCustomizablePool выводят PDA пула из ["pool", amm_config, token_mint_0, token_mint_1], поэтому существует ровно один канонический адрес пула на тройку (config, mint0, mint1) — второй init с теми же семенами не удаётся. CreatePermissionedPool снимает это ограничение, встраивая предоставленный клиентом seed_index: u16 в семена PDA пула, позволяя несколько пулов для одной пары и уровня комиссии — каждый по своему адресу. Поскольку произвольный адрес пула — это привилегированная возможность, плательщик должен иметь PDA Permission, который его авторизует. Всё остальное о пуле идентично CreateCustomizablePool: он принимает те же CreateCustomizableParams и поддерживает одностороннюю комиссию и опциональное включение динамической комиссии. Аргументы
Аккаунты (сокращённо) — то же, что и CreateCustomizablePool, плюс в начале: PDA pool_state выводится из ["pool", amm_config, token_mint_0, token_mint_1, seed_index.to_le_bytes()]. Предусловия
  • seed_index != 0. seed_index из 0 зарезервирован для устаревших пулов и отклоняется здесь; компонент семени [0, 0] — это то, что заставляет адрес устаревшего пула свернуться в классическую четырёхсеменную форму.
  • PDA permission для payer существует (создана администратором через CreatePermissionPda).
  • Те же правила mint / allow-list, что и CreatePool.
Постусловия
  • Новый pool_state существует по адресу, выведенному из seed_index, с pool_state.seed_index = seed_index.
  • Все остальные постусловия совпадают с CreateCustomizablePool (режим комиссии, опциональная динамическая комиссия).
Эта инструкция не расширяет общий доступ к созданию пула — бесправное создание продолжается через CreatePool / CreateCustomizablePool, которые остаются одним пулом на пару. CreatePermissionedPool существует для конкретного случая, когда внесённый в белый список оператор нуждается в нескольких пулах для одной пары (например, различающихся начальными ценами или когортами запуска) и имеет PDA Permission, предоставленный администратором.

OpenPositionV2 / OpenPositionWithToken22Nft

Создать новую позицию внутри существующего пула. Аргументы
Аккаунты — два варианта имеют разные списки аккаунтов. OpenPositionV2 объявляет 22; OpenPositionWithToken22Nft объявляет 20. OpenPositionV2, по порядку: OpenPositionWithToken22Nft — это тот же список с удалёнными metadata_account (5) и metadata_program (19) — он записывает метаданные позиции через расширение метаданных Token-2022 на mint NFT вместо этого — давая 20 аккаунтов. Его position_nft_mint — это голый Signer и position_nft_account — UncheckedAccount.
tick_array_bitmap_extension не является объявленным аккаунтом ни на одном варианте. Когда диапазон позиции выходит за пределы встроенного tick_array_bitmap пула (±512 массивов тиков), добавьте PDA TickArrayBitmapExtension с семенами ["pool_tick_array_bitmap_extension", pool_state] как remaining_accounts[0].
Математика — см. products/clmm/math. Учитывая base_flag, программа разрешает либо liquidity, либо (amount_0_max, amount_1_max) в фактическое L и фактические потреблённые суммы токенов. Предусловия
  • tick_lower < tick_upper, оба кратны pool.tick_spacing, в пределах [MIN_TICK, MAX_TICK].
  • Два PDA массива тиков переданы. Они не должны уже существовать — get_or_create_tick_array выделяет отсутствующий за счёт payer внутри этой инструкции. Нет отдельной инструкции инициализации массива тиков.
  • Пользователь имеет по крайней мере amount_0_max и amount_1_max в исходных ATA.
Постусловия
  • personal_position существует, liquidity установлена, fee_growth_inside_last снята.
  • Записи массива тиков в tick_lower и tick_upper обновлены (liquidity_gross += L, liquidity_net ± L, снимки роста комиссии поддерживаются).
  • pool_state.liquidity += L, если позиция в диапазоне (tick_lower ≤ tick_current < tick_upper).
  • Mint NFT позиции записывает pool_state как полномочие заморозки. Полномочие mint удаляется после выпуска единственного NFT. Запись полномочия заморозки не изменяет состояние аккаунта токена NFT.
  • Аккаунт токена NFT остаётся разморозенным, если инструкция — это OpenPositionV2 или OpenPositionWithToken22Nft и полномочие заморозки любого mint хранилища совпадает со списком ограниченных издателей CLMM. Только этот совпадающий путь V2 замораживает аккаунт. OpenPosition V1 не замораживает.
Распространённые ошибки — TickInvalidOrder (tick_lower >= tick_upper), TickAndSpacingNotMatch (конечная точка не кратна tick_spacing), InvalidTickIndex (вне [MIN_TICK, MAX_TICK]), MissingTickArrayBitmapExtensionAccount (диапазон вне встроенного bitmap и расширение не добавлено), NotApproved (pool_state.status блокирует открытие), ZeroAmountSpecified.
Замораживание позиции не добавляет объявленные аккаунты инструкции или аргументы. Клиенты могут открывать эти позиции с существующими макетами V2. Поведение выбирается в цепи из vault_0_mint и vault_1_mint.

IncreaseLiquidityV2

Добавить ликвидность к уже открытой позиции. Аргументы
Аккаунты — 15, и не выводимые из OpenPosition: нет rent, нет system_program, нет associated_token_program и нет аккаунта метаданных. Добавьте PDA TickArrayBitmapExtension как remaining_accounts[0], когда диапазон позиции находится вне встроенного bitmap. Эффект
  • Передаёт amount_0_actual / amount_1_actual из пользователя → хранилища.
  • Увеличивает personal_position.liquidity и pool_state.liquidity (если в диапазоне), и соответственно liquidity_gross / liquidity_net конечного тика.
  • Собирает комиссии и вознаграждения, причитающиеся с последнего касания, и кредитует их на token_fees_owed_{0,1} / reward_amount_owed. Они выплачиваются только на DecreaseLiquidity / DecreaseLiquidityV2, а не на увеличение — нет отдельной инструкции сбора.

DecreaseLiquidityV2

Удалить ликвидность из позиции. Аргументы
Аккаунты — 16, и не той же формы или порядка, что и IncreaseLiquidityV2: personal_position и pool_state поменяны местами, хранилища идут перед массивами тиков, аккаунты на стороне пользователя названы recipient_token_account_*, и есть дополнительный memo_program. Оставшиеся аккаунты — три на активное собираемое вознаграждение, в порядке reward_token_vault(W), recipient_token_account(W), reward_vault_mint. Добавьте PDA TickArrayBitmapExtension в начало, когда диапазон позиции находится вне встроенного bitmap.
Это также единственный способ собрать комиссии и вознаграждения. Чтобы собрать без изменения позиции, вызовите с liquidity = 0, amount_0_min = 0, amount_1_min = 0.
Эффект
  • Вычисляет (amount_0, amount_1) для удалённого L с учётом текущего sqrt_price_x64.
  • Урегулирует комиссии/вознаграждения, накопленные с последнего касания, то же, что и IncreaseLiquidity.
  • Передаёт amount_0 + fees_owed_0 и amount_1 + fees_owed_1 из хранилищ пользователю.
  • Уменьшает счётчики ликвидности; если новое personal_position.liquidity == 0, позиция имеет право на ClosePosition.
Проскальзывание — amount_0_min и amount_1_min — это минимумы, которые пользователь принимает за вычетом комиссий передачи Token-2022 на выходной стороне.

ClosePosition

Сжечь NFT позиции и закрыть PersonalPositionState. Объявленные аккаунты Оставшиеся аккаунты
  • Разморозенный NFT: ничего не требуется; дополнительный аккаунт пула безвреден, потому что обработчик его не читает.
  • Замороженный NFT: добавьте personal_position.pool_id как первый оставшийся аккаунт. Программа загружает его как PoolState и использует его семена PDA для подписания разморозки.
Предусловия
  • personal_position.liquidity == 0.
  • tokens_fees_owed_{0,1} == 0.
  • Все счётчики вознаграждения reward_amount_owed == 0.
(То есть сначала соберите всё и уменьшите до нуля.) Эффект
  • Если аккаунт токена NFT заморожен, проверяет, что первый оставшийся аккаунт равен personal_position.pool_id, затем размораживает его с помощью PDA пула.
  • Сжигает NFT.
  • Закрывает аккаунт токена NFT и personal_position, возвращая аренду nft_owner. Если NFT позиции использует Token-2022, он также закрывает mint NFT; классические mint SPL Token не могут быть закрыты и остаются с нулевым предложением.
Разморозка, сжигание и закрытие атомарны. NFT не может стать передаваемым между этими шагами. Условный разрыв клиента — объявленный макет IDL не изменился, поэтому устаревшие клиенты продолжают закрывать существующие и разморозенные позиции. Устаревший построитель, который пропускает оставшийся аккаунт пула, не удаётся с AccountLack при закрытии замороженной позиции. Передача пула для каждого закрытия — это самая простая совместимая стратегия.

SwapV2

Пройти по кривой ликвидности; точный вход или точный выход в зависимости от is_base_input. Аргументы
Аккаунты (сокращённо) Вызывающие передают ранжированный список массивов тиков, охватывающих ожидаемый проход своп; программа использует столько, сколько ей нужно. SDK вычисляет этот список через PoolUtils.computeAmountOutFormat или конечную точку котировки API. Предусловия
  • pool_state.status позволяет своп.
  • now >= open_time.
  • sqrt_price_limit_x64 находится на правильной стороне sqrt_price_x64 для направления.
Распространённые ошибки — TooLittleOutputReceived (точный вход проскальзывание), TooMuchInputPaid (точный выход проскальзывание), SqrtPriceLimitOverflow, NotEnoughTickArrayAccount, InvalidFirstTickArrayAccount, MissingTickArrayBitmapExtensionAccount, LiquidityInsufficient, NotApproved (бит своп установлен на pool_state.status). CLMM не имеет варианта ExceededSlippage — это имя CPMM — и нет TickArrayNotFound. Что SwapV2 делает внутри, что вызывающие должны знать (после выпуска 2025):
  1. Надбавка динамической комиссии — если pool.dynamic_fee_info ненулевой, программа обновляет накопитель волатильности, используя расстояние тика, пройденное с последнего своп (с правилами фильтра/затухания из products/clmm/fees), и добавляет dynamic_fee_component поверх AmmConfig.trade_fee_rate. Общая комиссия ограничена 10% (MAX_FEE_RATE_NUMERATOR / 1_000_000).
  2. Сопоставление лимитных ордеров — когда проход цены пересекает тик, содержащий открытые лимитные ордеры, программа сначала заполняет доступную ликвидность лимитного ордера на этом тике (FIFO по order_phase), затем продолжает вдоль кривой ликвидности LP. Заполненные суммы обновляют tick.unfilled_ratio_x64 и tick.part_filled_orders_remaining для последующего урегулирования; сами ордеры остаются неиспользованными до тех пор, пока их владелец не вызовет SettleLimitOrder.
  3. Маршрутизация комиссии в одну сторону — когда pool.fee_on = Token0Only или Token1Only, шаг своп всё ещё вычисляет тот же вход-выход торговли; комиссия затем маршрутизируется на настроенную сторону. Для направлений, где настроенная сторона комиссии — это выход, комиссия вычитается из выхода своп (пользователь получает out − fee); для направлений, где это вход, поведение совпадает с FromInput. См. is_fee_on_input(zero_for_one) и is_fee_on_token0(zero_for_one) на PoolState.
Swap (V1) реализует ту же динамическую комиссию, маршрутизацию комиссии в одну сторону и сопоставление лимитных ордеров, что и SwapV2; единственная функция, которой ему не хватает, — это поддержка Token-2022 — оба хранилища должны быть классическим SPL Token. Пулы с любым mint Token-2022 должны быть обменены через SwapV2. Агрегатор и SDK уже предпочитают V2 для каждого участка CLMM, поэтому вызывающим не нужно ветвиться по типу mint.

OpenLimitOrder

Разместить ордер на продажу на конкретном тике. Ордер сидит в когорте FIFO для каждого тика и заполняется при пересечении цены. Аргументы
Аккаунты (сокращённо) Оставшиеся аккаунты — [0] tick_array_bitmap_extension, требуется только при инициализации массива тиков, чей начальный индекс выходит за пределы встроенного bitmap пула. Передайте ничего в противном случае.
Нет аккаунта rent: структура заканчивается на system_program, 13 объявленных аккаунтов. Случайный rent в позиции 14 приземляется ровно там, где читается опциональное расширение bitmap, поэтому ордер кажется работающим до первого раза, когда массив тиков вне встроенного bitmap должен быть создан — и затем не удаётся.
Изменение списка аккаунтов (выпуск 2026-07). OpenLimitOrder теперь также принимает аккаунты выходной стороны — output_token_account, output_vault и output_vault_mint — в дополнение к входной стороне. Они используются только для валидации: программа отклоняет ордер, если входной или выходной аккаунт токена владельца заморожен. Это гарантирует, что заполнение может быть фактически урегулировано на выходной ATA владельца, что важно для allow-list / по умолчанию замороженных mint Token-2022 (например, разрешённые токены), где аккаунт может быть ещё не разморожен. Клиенты, построенные на основе старого одностороннего списка аккаунтов, должны добавить три выходных аккаунта.
Предусловия
  • Ни input_token_account, ни output_token_account не заморожены (иначе NotApproved).
  • pool_state.status позволяет как своп (бит 4), так и операции лимитного ордера (бит 5) (иначе NotApproved).
  • tick_index % pool.tick_spacing == 0 и в пределах [MIN_TICK, MAX_TICK].
  • tick_index находится на правильной стороне pool.tick_current для выбранного направления (продажа token0 → тик должен быть выше текущего, и наоборот). Продажа на уже пересечённом тике будет сопоставлена немедленно и отклонена.
Постусловия
  • limit_order существует, снимая tick.order_phase и tick.unfilled_ratio_x64 во время открытия.
  • tick.orders_amount += amount (в текущей когорте).
  • limit_order_nonce.order_nonce += 1.
  • Испущено OpenLimitOrderEvent.
Распространённые ошибки — NotApproved (входной или выходной аккаунт токена заморожен, или пул имеет отключённый своп / лимитный ордер), ZeroAmountSpecified (amount == 0 после комиссии передачи входной стороны), InvalidLimitOrderAmount (сумма произведёт выход ниже 1 базовой единицы на этом тике, или переполнение u64), InvalidTickIndex (вне [MIN_TICK, MAX_TICK], или на неправильной стороне tick_current для выбранного направления), TickAndSpacingNotMatch (tick_index % pool.tick_spacing != 0), OrderPhaseSaturated.

IncreaseLimitOrder

Добавить к существующему открытому ордеру. Может быть вызвано только владельцем ордера. Аргументы
Аккаунты — 8, только входная сторона. Он удаляет аккаунт nonce, все три аккаунта выходной стороны и system_program. Предусловия
  • limit_order.owner == signer.
  • Ордер всё ещё находится в той же когорте (tick.order_phase == limit_order.order_phase). Если когорта уже начала заполняться, ордер частично урегулирован — вызывающему следует сначала вызвать DecreaseLimitOrder или SettleLimitOrder для продвижения вперёд.
Эффект
  • Передаёт amount из ATA владельца в input_vault.
  • limit_order.total_amount += amount; tick.orders_amount += amount.

DecreaseLimitOrder

Уменьшить или полностью отменить открытый ордер. Выплачивает неисполненный остаток владельцу, плюс любой выход, уже урегулированный прошлыми частичными заполнениями. Аргументы
Аккаунты — как входная, так и выходная стороны токена: Эффект
  • Пересчитывает заполненную сумму ордера из unfilled_ratio_x64 когорты с момента открытия.
  • Отправляет заполненный выход на output_token_account.
  • Отправляет amount неисполненного входа обратно на input_token_account.
  • Обновляет limit_order соответственно. Если новый неисполненный остаток равен нулю, программа закрывает аккаунт и возвращает аренду owner.

SettleLimitOrder

Отправить заполненные выходные токены владельцу без изменения неисполненного остатка ордера. Полезно, когда хранители auto_withdraw хотят капельно выплачивать долгосрочные частичные заполнения. Вызывающий — либо владелец ордера, либо limit_order_admin программы (автономный оперативный горячий кошелёк, который запускает автоматизированный цикл хранителя). Хранитель не имеет других полномочий — он не может перемещать пользовательские средства вне отправки заполненного выхода на ATA владельца ордера. Аккаунты Эффект
  • Вычисляет кумулятивный выход, причитающийся, используя (limit_order.unfilled_ratio_x64, tick.unfilled_ratio_x64).
  • Передаёт дельту на output_token_account.
  • Обновляет limit_order.settled_output.
  • Не закрывает ордер; он всё ещё открыт против любого оставшегося входа.

CloseLimitOrder

Закрыть полностью исполненный аккаунт ордера. Аренда всегда возвращается limit_order.owner независимо от того, кто подписывает. Вызывающий — либо owner, либо limit_order_admin. Предусловия
  • Ордер имеет нулевой неисполненный остаток (либо amount == total_amount был заполнен и урегулирован, либо владелец ранее уменьшил ордер до нуля и забыл закрыть).
Эффект
  • Закрывает limit_order; аренда отправляется limit_order.owner.

CreateDynamicFeeConfig (администратор)

Создать переиспользуемый набор параметров под индексом u16. Аргументы
Аккаунты Распространённые ошибки — InvalidDynamicFeeConfigParams, если decay_period <= filter_period или любое поле с нулевым значением выходит за пределы.

UpdateDynamicFeeConfig (администратор)

Изменить существующий DynamicFeeConfig. Пулы, которые уже сделали снимок конфигурации во время создания, не обновляются ретроактивно; только вновь созданные пулы, которые ссылаются на эту конфигурацию, подберут новые значения. Аргументы — те же пять полей калибровки, что и CreateDynamicFeeConfig (filter_period, decay_period, reduction_factor, dynamic_fee_control, max_volatility_accumulator); index зафиксирован при создании и не переходит здесь.

CollectProtocolFee / CollectFundFee

Очистить накопленные комиссии протокола/фонда из хранилищ пула получателю, обнулив соответствующие поля PoolState.protocol_fees_* / fund_fees_*. Это не макет CPMM — CLMM не имеет аккаунта authority, и поля получателя названы recipient_token_account_{0,1} вместо recipient_token_{0,1}_account. Аргументы — amount_0_requested: u64, amount_1_requested: u64.