本頁內容由 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
參數
前置條件
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 不是聲明帳戶。
前置條件 — 與 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的permissionPDA 存在(由管理員通過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。
數學 — 見 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 路徑凍結帳戶。OpenPositionV1 不凍結。
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
向已開放的頭寸新增流動性。
參數
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
從頭寸移除流動性。
參數
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 鑄幣無法關閉並保持供應量為零。
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 年後發佈):
- 動態費用附加費 — 如果
pool.dynamic_fee_info非零,程式使用自上次交換以來遍歷的 tick 距離更新波動性累積器(使用products/clmm/fees中的過濾/衰減規則)並在AmmConfig.trade_fee_rate之上新增dynamic_fee_component。總費用上限為 10%(MAX_FEE_RATE_NUMERATOR / 1_000_000)。 - 限價單匹配 — 當價格行走穿過持有開放限價單的 tick 時,程式首先在該 tick 處填充可用的限價單流動性(按
order_phase的 FIFO),然後沿著 LP 流動性曲線進行。已填充的金額更新tick.unfilled_ratio_x64和tick.part_filled_orders_remaining以供稍後結算;訂單本身保持未花費,直到其所有者呼叫SettleLimitOrder。 - 單邊費用路由 — 當
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 陣列時需要。否則傳遞任何內容。
帳戶列表變化(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 呼叫。
參數
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 仍然旋轉它們。
