Skip to main content
本頁內容由 AI 自動翻譯,所有內容以英文版本為準。查看英文版 →
本頁與 products/clmm/accounts(帳戶是什麼)和 products/clmm/math(數學是什麼)配套。它對於參數和帳戶順序是權威的;具體的位元組佈局來自 IDL。2026-09 程式升級在 Anchor 1.0.2 / Solana 3.1.10 上重建了 CLMM,新增了管理員指令 CollectExcessLamports,並改變了 CreateAmmConfig 寫入 owner / fund_owner 的內容。沒有用戶面向的指令改變了其帳戶、參數或數學。請參閱 2026-09-30 變更日誌條目。

指令清單

沒有初始化 tick 陣列指令,也不需要。 Tick 陣列在 OpenPosition* / IncreaseLiquidity* 內由 TickArrayState::get_or_create_tick_array 建立,由 payer 支付。將(可能仍未初始化、系統擁有的)tick 陣列 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 公鑰或專用的支援鑄幣所有者金鑰;CollectExcessLamports 接受 admin 公鑰或專用的收集 lamport 錢包。獎勵流管理員指令(TransferRewardOwner、CollectRemainingRewards)由獎勵資金提供者把守,而不是程式管理員。 V2 後綴表示「支援保管庫 / NFT 上的 Token-2022,需要位圖擴展槽」。SDK 預設為新池選擇 V2。

CreatePool

參數
帳戶(簡化)
槽 10 和 11 是按鑄幣,而不是「SPL Token 然後 Token-2022」。每個都受到匹配鑄幣上的 mint::token_program 約束,因此對於 token_mint_0 是 Token-2022 且 token_mint_1 是經典 SPL 的池,你必須在槽 10 中傳遞 Token-2022,在槽 11 中傳遞 SPL Token。CreateCustomizablePool 和 CreatePermissionedPool 使用相同的兩個槽。
前置條件
  • token_mint_0 < token_mint_1 按位元組順序。
  • amm_config.disable_create_pool == false。
  • 鑄幣不被 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 相同的效果加上按池費用收集模式和可選的動態費用選擇加入。 參數
帳戶 — 完全是 CreatePool 的 13 個聲明帳戶。dynamic_fee_config 不是聲明帳戶。
當 enable_dynamic_fee = true 時,要快照的 DynamicFeeConfig 必須作為最後一個 remaining_account 提供 — 處理程序讀取 ctx.remaining_accounts.last()。任何 SupportMintAssociated 繞過 PDA 必須因此在它之前,否則程式快照錯誤的帳戶並失敗反序列化。在設定標誌時省略它會失敗,出現 AccountLack。
前置條件 — 與 CreatePool 相同。如果 enable_dynamic_fee = false,不需要 remaining_account,傳遞的任何帳戶僅掃描支援鑄幣記錄。 後置條件
  • pool_state.fee_on 設定為選定的 CollectFeeOn 變體。
  • 如果動態費用已啟用:pool_state.dynamic_fee_info 從提供的 DynamicFeeConfig 初始化(複製五個校準參數;狀態欄位清零)。
  • 否則:pool_state.dynamic_fee_info 被清零(= 此池永遠動態費用不活躍)。
fee_on 和動態費用啟用位僅在池建立時設定。沒有就地升級 — 通過舊版 CreatePool 建立的池無法追溯獲得動態費用或單邊費用。新部署應預設為此指令。

CreatePermissionedPool

CreatePool 和 CreateCustomizablePool 都從 ["pool", amm_config, token_mint_0, token_mint_1] 衍生池 PDA,因此每個 (config, mint0, mint1) 三元組恰好有一個規範池地址 — 在相同種子處的第二個 init 失敗。CreatePermissionedPool 通過將客戶端提供的 seed_index: u16 折疊到池 PDA 種子中來解除該限制,允許同一交易對和費用層級的多個池 — 每個在其自己的地址。因為任意池地址是特權功能,支付者必須持有授權它的 Permission PDA。 關於池的其他一切與 CreateCustomizablePool 相同:它採用相同的 CreateCustomizableParams 並支援單邊費用和動態費用選擇加入。 參數
帳戶(簡化) — 與 CreateCustomizablePool 相同加上,在前面: pool_state PDA 衍生自 ["pool", amm_config, token_mint_0, token_mint_1, seed_index.to_le_bytes()]。 前置條件
  • seed_index != 0。seed_index 為 0 保留給舊版池並在此被拒絕;[0, 0] 種子分量是使舊版池地址崩潰為經典四種子形式的原因。
  • payer 的 permission PDA 存在(由管理員通過 CreatePermissionPda 建立)。
  • 與 CreatePool 相同的鑄幣/允許列表規則。
後置條件
  • 新的 pool_state 存在於 seed_index 衍生地址,pool_state.seed_index = seed_index。
  • 所有其他後狀態與 CreateCustomizablePool 相符(費用模式、可選動態費用)。
此指令不會擴大一般池建立存取 — 無權限建立繼續通過 CreatePool / CreateCustomizablePool,每對仍為一個池。CreatePermissionedPool 存在於特定情況,其中白名單操作者需要同一交易對的多個池(例如不同的初始價格或啟動隊列)並持有由管理員授予的 Permission PDA。

OpenPositionV2 / OpenPositionWithToken22Nft

在現有池內建立新頭寸。 參數
帳戶 — 兩個變體有不同的帳戶列表。OpenPositionV2 聲明 22;OpenPositionWithToken22Nft 聲明 20。 OpenPositionV2,按順序: OpenPositionWithToken22Nft 是相同的列表,移除了 metadata_account(5)和 metadata_program(19) — 它通過 NFT 鑄幣上的 Token-2022 元資料擴展寫入頭寸的元資料 — 給出 20 個帳戶。其 position_nft_mint 是裸 Signer,position_nft_account 是 UncheckedAccount。
tick_array_bitmap_extension 不是任一變體上的聲明帳戶。 當頭寸的範圍超出池的內聯 tick_array_bitmap(±512 tick 陣列)時,在種子 ["pool_tick_array_bitmap_extension", pool_state] 處附加 TickArrayBitmapExtension PDA 作為 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] 內。
  • 傳遞兩個 tick 陣列 PDA。它們不需要已經存在 — get_or_create_tick_array 在此指令內以 payer 的費用分配缺失的。沒有單獨的初始化 tick 陣列指令。
  • 使用者在來源 ATA 中至少有 amount_0_max 和 amount_1_max。
後置條件
  • personal_position 存在,liquidity 設定,fee_growth_inside_last 快照。
  • tick_lower 和 tick_upper 處的 Tick 陣列條目已更新(liquidity_gross += L,liquidity_net ± L,費用增長快照維護)。
  • 如果頭寸在範圍內(tick_lower ≤ tick_current < tick_upper),pool_state.liquidity += L。
  • 頭寸 NFT 鑄幣記錄 pool_state 作為凍結權限。鑄幣權限在單個 NFT 被鑄造後被移除。記錄凍結權限不改變 NFT 代幣帳戶的狀態。
  • NFT 代幣帳戶保持未凍結,除非指令是 OpenPositionV2 或 OpenPositionWithToken22Nft 且任一保管庫鑄幣的凍結權限與 CLMM 的受限發行人列表相符。只有該匹配的 V2 路徑凍結帳戶。OpenPosition V1 不凍結。
常見錯誤 — TickInvalidOrder(tick_lower >= tick_upper)、TickAndSpacingNotMatch(端點不是 tick_spacing 的倍數)、InvalidTickIndex(超出 [MIN_TICK, MAX_TICK])、MissingTickArrayBitmapExtensionAccount(範圍超出內聯位圖且未附加擴展)、NotApproved(pool_state.status 阻止開放)、ZeroAmountSpecified。
頭寸凍結不新增聲明指令帳戶或參數。客戶端可以使用現有的 V2 佈局開放這些頭寸。行為在鏈上從 vault_0_mint 和 vault_1_mint 選定。

IncreaseLiquidityV2

向已開放的頭寸新增流動性。 參數
帳戶 — 15 個,且不可從 OpenPosition 衍生:沒有 rent、沒有 system_program、沒有 associated_token_program 和沒有元資料帳戶。 當頭寸的範圍超出內聯位圖時,在 remaining_accounts[0] 處前置 TickArrayBitmapExtension PDA。 效果
  • 轉移 amount_0_actual / amount_1_actual 從使用者 → 保管庫。
  • 遞增 personal_position.liquidity 和 pool_state.liquidity(如果在範圍內),以及端點 tick 的 liquidity_gross / liquidity_net 相應地。
  • 收集自上次觸及以來應付的費用和獎勵並將其記入 token_fees_owed_{0,1} / reward_amount_owed。這些僅在 DecreaseLiquidity / DecreaseLiquidityV2 上支付,不在增加時 — 沒有獨立的收集指令。

DecreaseLiquidityV2

從頭寸移除流動性。 參數
帳戶 — 16 個,且不是 IncreaseLiquidityV2 的相同形狀或順序:personal_position 和 pool_state 被交換,保管庫在 tick 陣列之前,使用者端帳戶被命名為 recipient_token_account_*,還有額外的 memo_program。 剩餘帳戶 — 每個被收集的活躍獎勵三個,按順序 reward_token_vault(W)、recipient_token_account(W)、reward_vault_mint。當頭寸的範圍超出內聯位圖時,前置 TickArrayBitmapExtension PDA。
這也是唯一收集費用和獎勵的方式。要在不改變頭寸的情況下收集,使用 liquidity = 0、amount_0_min = 0、amount_1_min = 0 呼叫它。
效果
  • 給定當前 sqrt_price_x64,計算移除的 L 的 (amount_0, amount_1)。
  • 結算自上次觸及以來累積的費用/獎勵,與 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,它也關閉 NFT 鑄幣;經典 SPL Token 鑄幣無法關閉並保持供應量為零。
解凍、燒毀和關閉是原子的。NFT 無法在這些步驟之間變為可轉移。 條件客戶端中斷 — 聲明的 IDL 佈局未改變,因此舊版客戶端繼續關閉現有和未凍結的頭寸。省略池剩餘帳戶的舊版構建器在關閉凍結頭寸時失敗,出現 AccountLack。為每次關閉傳遞池是最簡單的相容策略。

SwapV2

沿著流動性曲線行走;精確輸入或精確輸出取決於 is_base_input。 參數
帳戶(簡化) 呼叫者傳遞涵蓋預期交換行走的排名 tick 陣列列表;程式使用它需要的盡可能多。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 非零,程式使用自上次交換以來遍歷的 tick 距離更新波動性累積器(使用 products/clmm/fees 中的過濾/衰減規則)並在 AmmConfig.trade_fee_rate 之上新增 dynamic_fee_component。總費用上限為 10%(MAX_FEE_RATE_NUMERATOR / 1_000_000)。
  2. 限價單匹配 — 當價格行走穿過持有開放限價單的 tick 時,程式首先在該 tick 處填充可用的限價單流動性(按 order_phase 的 FIFO),然後沿著 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。任何 Token-2022 鑄幣的池必須通過 SwapV2 交換。聚合器和 SDK 已經為每個 CLMM 腿優先選擇 V2,因此呼叫者不必根據鑄幣類型分支。

OpenLimitOrder

在特定 tick 處下達賣單。訂單位於按 tick 的 FIFO 隊列中,並在價格穿過時填充。 參數
帳戶(簡化) 剩餘帳戶 — [0] tick_array_bitmap_extension,僅當初始化其開始索引超出池內聯位圖的 tick 陣列時需要。否則傳遞任何內容。
沒有 rent 帳戶:結構在 system_program 處結束,13 個聲明帳戶。在位置 14 處的流浪 rent 恰好落在讀取可選位圖擴展的位置,因此訂單看起來有效,直到第一次必須建立超出內聯位圖的 tick 陣列 — 然後失敗。
帳戶列表變化(2026-07 發佈)。 OpenLimitOrder 現在也採用輸出端帳戶 — output_token_account、output_vault 和 output_vault_mint — 除了輸入端。它們僅用於驗證:如果所有者的輸入或輸出代幣帳戶被凍結,程式拒絕訂單。這保證填充實際上可以結算到所有者的輸出 ATA,這對於允許列表/預設凍結 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 → tick 必須在當前之上,反之亦然)。在已穿過的 tick 處賣出會立即被匹配並被拒絕。
後置條件
  • 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(金額會在該 tick 處產生低於 1 個基本單位的輸出,或溢出 u64)、InvalidTickIndex(超出 [MIN_TICK, MAX_TICK],或在 tick_current 的錯誤一側用於所選方向)、TickAndSpacingNotMatch(tick_index % pool.tick_spacing != 0)、OrderPhaseSaturated。

IncreaseLimitOrder

新增到現有開放訂單。僅可由訂單的 owner 呼叫。 參數
帳戶 — 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 保管人想要滴灌支付長期運行的部分填充時很有用。 呼叫者 — 訂單的 owner,或程式的 limit_order_admin(運行自動保管人迴圈的離線操作熱錢包)。保管人沒有其他權限 — 它無法在將已填充輸出推送到訂單的 owner 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 索引下建立可重複使用的參數集。 參數
帳戶 常見錯誤 — 如果 decay_period <= filter_period 或任何 0 值欄位超出邊界,則 InvalidDynamicFeeConfigParams。

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。
在 2026-09 中改變:新配置不再從簽署者獲取其費用所有者。 create_amm_config 現在將程式的硬編碼 protocol_fee_owner::ID 寫入 owner 和 fund_fee_owner::ID 寫入 fund_owner。之前,它複製管理員簽署者的金鑰到兩者。在主網上,這些已經是存儲在所有 21 個現有配置上的相同金鑰。地址在 reference/program-addresses 中。上面接受的簽署者未改變。管理員仍可從任何配置收集,且現有 AmmConfig 帳戶不被重寫。從帳戶讀取 owner / fund_owner 而不是假設任一值。UpdateAmmConfig 參數 3 和 4 仍然旋轉它們。

CollectExcessLamports

管理員掃描 CLMM 控制的帳戶上超過租金豁免最低限額的 lamport。它在 2026-09 升級中新增,以回收 SIMD-0437 租金減少 在每個步驟之前建立的帳戶上留下的過度資金。 只有超額移動。代幣餘額、帳戶資料、所有者、池狀態和價格曲線保持不變。已在其最低限額的帳戶保持原樣,因此指令可以在每次推出步驟後安全地重新執行。 參數:無。 **