このページは AI による自動翻訳です。すべての内容は英語版を正とします。英語版を表示 →
このページでは各アカウントのレイアウトと役割を説明します。シード値は正規のものであり、reference/program-addresses に一覧があります。CLMMプールはCPMMプールよりもアカウント数が多くなります。これはリクイディティがティック範囲にわたってスパースに格納されるためであり、そのスパース性の理解がこのページの中心的な内容です。
アカウント一覧
稼働中のCLMMプールは以下のアカウントファミリーで構成されます。2つのミントとそのボルトを除き、すべてCLMMプログラムが所有します。
| アカウント | 用途 | プールあたりの数 |
|---|
AmmConfig | 手数料ティア:取引手数料率、プロトコル分配、ファンド分配、デフォルトのティック間隔。このティアのすべてのプールで共有。 | 1(共有) |
PoolState | 現在の sqrt_price_x64、現在のティック、総リクイディティ、手数料グロース全体値、リワード情報、オブザベーションポインタ。 | 1 |
TickArrayState | TICK_ARRAY_SIZE 個の隣接ティックをまとめたブロック。必要に応じて初期化される。 | 0 ≤ N ≤ range |
TickArrayBitmapExtension | PoolState のインラインビットマップの範囲外にあるティック配列の存在を追跡するオーバーフロービットマップ。 | 0 または 1 |
PersonalPositionState | LP ポジション 1 つにつき 1 つ。範囲、リクイディティ、最終確認済みの手数料・リワードグロースを保持。Authority = NFT オーナー。 | ポジションごとに 1 |
| ポジション NFT ミント | 供給量 1 のミント。PersonalPositionState に関連付けられる。NFT を転送するとポジションが移転する。 | ポジションごとに 1 |
ObservationState | TWAP 用の価格オブザベーションのリングバッファ。 | 1 |
token_0_vault、token_1_vault | プールの残高を保持するトークンアカウント。プールオーソリティが所有。 | 2 |
DynamicFeeConfig | 動的手数料メカニズムの再利用可能なパラメータセット。create_customizable_pool で作成されたプールがオプトイン可能。管理者が管理。 | 共有(インデックスごと) |
LimitOrderState | 未約定の指値注文 1 つにつき 1 つ。オーナー、ティック、売買方向、合計金額、決済済み出力のスナップショットを記録。 | 注文ごとに 1 |
LimitOrderNonce | (wallet, nonce_index) ごとのカウンター。一意の注文 PDA を導出するために使用。 | (wallet, index) ごとに 1 |
PoolState
プールのライブ状態。スワップのたびにすべてのポジション変更のたびに参照されます。
// programs/amm/src/states/pool.rs
pub struct PoolState {
pub bump: [u8; 1],
pub amm_config: Pubkey, // fee tier binding
pub owner: Pubkey, // admin (multisig)
pub token_mint_0: Pubkey,
pub token_mint_1: Pubkey,
pub token_vault_0: Pubkey,
pub token_vault_1: Pubkey,
pub observation_key: Pubkey,
pub mint_decimals_0: u8,
pub mint_decimals_1: u8,
pub tick_spacing: u16, // inherited from amm_config at init
pub liquidity: u128, // total active (in-range) liquidity
pub sqrt_price_x64: u128, // Q64.64 of sqrt(price)
pub tick_current: i32, // current tick index
pub padding3: u16,
pub padding4: u16,
// Global fee growth per unit of liquidity, Q64.64.
pub fee_growth_global_0_x64: u128,
pub fee_growth_global_1_x64: u128,
// Accrued-but-not-swept protocol fees (per mint).
pub protocol_fees_token_0: u64,
pub protocol_fees_token_1: u64,
// Reserved padding for future upgrades.
pub padding5: [u128; 4],
// Status bitmask. Bits 0-5: open-position, decrease-liquidity,
// collect-fee, collect-reward, swap, limit-order. A set bit disables
// the corresponding operation.
pub status: u8,
// Fee-collection mode (CollectFeeOn).
// 0 = FromInput (deduct fee from the swap input — Uniswap-V3 default)
// 1 = Token0Only (always deduct fee from token0 vault)
// 2 = Token1Only (always deduct fee from token1 vault)
pub fee_on: u8,
pub padding: [u8; 6],
// Live reward streams (up to REWARD_NUM = 3).
pub reward_infos: [RewardInfo; 3],
// Inline bitmap tracking initialized tick-arrays in the primary range.
pub tick_array_bitmap: [u64; 16],
// Reserved padding for future upgrades.
pub padding6: [u64; 4],
pub fund_fees_token_0: u64,
pub fund_fees_token_1: u64,
pub open_time: u64, // currently disabled by the program
pub recent_epoch: u64,
// Per-pool dynamic-fee state. Zero-valued unless the pool was
// created with `enable_dynamic_fee = true` via create_customizable_pool.
pub dynamic_fee_info: DynamicFeeInfo,
// Reserved for future upgrades.
pub padding1: [u64; 14],
pub padding2: [u64; 32],
}
実際に参照するフィールド:
sqrt_price_x64 と tick_current はプールの価格状態です。スワップのたびに同時に更新されます。tick_current は log_{1.0001}(price) の切り捨て値です。
liquidity はアクティブなリクイディティ、つまり tick_current を含む範囲のすべてのポジションの L 値の合計です。スワップがティックを跨ぐたびに、またポジションが開かれ・閉じられ・サイズ変更されるたびに変化します。
fee_growth_global_{0,1}_x64 は、プール全体の履歴にわたってリクイディティ単位あたりに累積された手数料です。ポジションはこれを参照して自分の取り分を計算します。
tick_spacing は初期化時に AmmConfig から固定され、変更されません。どのティックインデックスがポジションのエンドポイントとして許可されるかを決定します。
tick_array_bitmap はスポット価格周辺のよく使われるティック範囲をカバーするインラインビットマップです。ポジションが遠くまで届くプールでは、オーバーフロー追跡は別の TickArrayBitmapExtension アカウントに格納されます。
fee_on はプール作成時に固定されます。0(FromInput)はクラシックな Uniswap-V3 の動作を再現します。1 と 2 はスワップ手数料を板の片側にルーティングします。トレードオフについては products/clmm/fees を参照してください。
dynamic_fee_info は動的手数料サーチャージのボラティリティ状態を保持します。有効化されている場合、スワップのたびに AmmConfig.trade_fee_rate に加えて dynamic_fee_component が再計算されます。レイアウトは以下の DynamicFeeInfo に記載しています。動的手数料を使用しないプールではこの構造体全体がゼロになります。
AmmConfig
pub struct AmmConfig {
pub bump: u8,
pub index: u16, // uses "amm_config"+u16 seed
pub owner: Pubkey, // admin
pub protocol_fee_rate: u32, // fraction of trade fee to protocol, denom 1e6
pub trade_fee_rate: u32, // trade fee in 1e6ths of volume
pub tick_spacing: u16, // default spacing for pools using this config
pub fund_fee_rate: u32, // fraction of trade fee to fund, denom 1e6
pub padding_u32: u32,
pub fund_owner: Pubkey,
pub padding: [u64; 3],
}
公開されている典型的な CLMM 手数料ティアのセット(GET https://api-v3.raydium.io/main/clmm-config で最新情報を確認してください):
| Index | trade_fee_rate | ティック間隔 | 主な用途 |
|---|
| 0 | 100(0.01%) | 1 | ステーブルペア、USDC/USDT |
| 1 | 500(0.05%) | 10 | 相関の高いブルーチップ |
| 2 | 2_500(0.25%) | 60 | 標準ペア |
| 3 | 10_000(1.00%) | 120 | ボラティリティの高いペアやロングテール |
protocol_fee_rate と fund_fee_rate は取引手数料に対する割合で、CPMM と同じ規則です。詳細は products/clmm/fees を参照してください。
TickArrayState
CLMMはティックごとに 1 つのレコードを格納しません。そうすると数十億のアカウントが必要になります。代わりに、TICK_ARRAY_SIZE 個の隣接する初期化済み・未初期化のティック(プログラムバージョンによって通常 60 または 88)を 1 つの TickArrayState にグループ化し、初めて使用されるときに遅延生成します。
pub const TICK_ARRAY_SIZE: usize = 60;
pub const TICK_ARRAY_SIZE_USIZE: usize = 60;
pub struct TickArrayState {
pub pool_id: Pubkey,
pub start_tick_index: i32, // lowest tick in this array
pub ticks: [TickState; TICK_ARRAY_SIZE], // 60 entries
pub initialized_tick_count: u8,
pub recent_epoch: u64,
pub padding: [u8; 107],
}
pub struct TickState {
pub tick: i32,
pub liquidity_net: i128, // ΔL when crossing this tick upward
pub liquidity_gross: u128, // total L referencing this tick
pub fee_growth_outside_0_x64: u128, // see math.mdx
pub fee_growth_outside_1_x64: u128,
pub reward_growths_outside_x64: [u128; 3],
// Limit-order bookkeeping. All zero for ticks that have never carried
// a limit order. See products/clmm/math for the matching algorithm.
pub order_phase: u64, // monotonic FIFO cohort id
pub orders_amount: u64, // unfilled tokens in current cohort
pub part_filled_orders_remaining: u64, // remaining tokens of partially-filled cohort
pub unfilled_ratio_x64: u128, // Q64.64; starts at 1.0 and shrinks as fills occur
pub padding: [u32; 3],
}
指値注文関連の 4 つのフィールドは、一度も指値注文に使われていないティックではすべてゼロです。ティックに注文が開かれると、プログラムはそれをコホートの連続として追跡します。
order_phase はコホート ID です。コホートが「すべて未約定」から「一部約定済み」に遷移するたびにインクリメントされます。
orders_amount は現在(最新)のコホートの入力トークン合計です。
part_filled_orders_remaining は進行中のスワップによって約定されつつある直前のコホートを追跡します。
unfilled_ratio_x64 はコホートに対して保持される Q64.64 の乗数です。スワップがコホートの 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) で求まります。
- ティック配列は遅延初期化されます。未初期化の配列に最初に触れるポジションまたはスワップが、レントを支払って配列を作成します。
- ティック配列はプログラムによってクローズされることはありません。一度割り当てられると、内部のすべてのティックが
liquidity_gross == 0 に戻った後もプールの存続期間中は維持されます。後続のポジションやスワップは既存のアカウントを追加レントなしで再利用できます。ティック配列のクリーンアップを行う ClosePosition 主導のパスは存在しません。
TickArrayBitmapExtension
PoolState.tick_array_bitmap(インライン)は「スポット付近」の範囲(±1,024 ティック配列)をカバーします。その範囲外(極端なティック値)に対して、プログラムは拡張アカウントを管理します。
pub struct TickArrayBitmapExtension {
pub pool_id: Pubkey,
pub positive_tick_array_bitmap: [[u64; 8]; 14],
pub negative_tick_array_bitmap: [[u64; 8]; 14],
}
ポジションの範囲が「通常」であれば、拡張アカウントを意識する必要はありません。フルレンジポジション(例:(MIN_TICK, MAX_TICK))には必要ですが、SDK が自動的に解決します。
ポジション
CLMMのポジションは、ミントを含む3 つのアカウントのバンドルです。
ポジション NFT ミント
供給量 1 の SPL Token ミント。ミントのアドレスは決定論的な PDA であり、オーナーのウォレット内のポジション NFT はその単一トークンを保持する ATA に過ぎません。NFT を転送することでポジションが移転します。プログラムは、状態に格納された Pubkey ではなく、NFT の ATA 残高の現在の保有者に対してオーソリゼーションを紐付けます。
PersonalPositionState
未決ポジション 1 つにつき 1 つ。NFT ミントをキーとします。
pub struct PersonalPositionState {
pub bump: [u8; 1],
pub nft_mint: Pubkey, // this position's NFT mint
pub pool_id: Pubkey,
pub tick_lower_index: i32,
pub tick_upper_index: i32,
pub liquidity: u128, // this position's L
// Fee-growth snapshots at last time the position was touched.
pub fee_growth_inside_0_last_x64: u128,
pub fee_growth_inside_1_last_x64: u128,
pub token_fees_owed_0: u64, // accrued since last collect
pub token_fees_owed_1: u64,
pub reward_infos: [PositionRewardInfo; 3],
pub recent_epoch: u64,
pub padding: [u64; 7],
}
pub struct PositionRewardInfo {
pub growth_inside_last_x64: u128,
pub reward_amount_owed: u64,
}
ProtocolPositionState(非推奨)
古い CLMM リリースでは、(pool, tick_lower, tick_upper) ごとの集計ブックキーピングを ProtocolPositionState PDA に格納していました。新しいリリースではこのアカウントを作成も参照もしません。 OpenPosition / IncreaseLiquidity / DecreaseLiquidity のアカウントリストには ABI 互換性のために UncheckedAccount として残っていますが、プログラムは書き込みを行いません。オンチェーンに残っている既存のアカウントは残骸です。管理者は CloseProtocolPosition を呼び出してレントを回収できます。範囲集計のブックキーピングは現在、TickArrayState 内の 2 つのエンドポイントティック(liquidity_gross、liquidity_net、ティックごとの fee_growth_outside_* / reward_growths_outside_x64)から直接導出されます。手数料グロースインサイドの計算式 fee_growth_inside = global − outside_lower − outside_upper は集計ポジションアカウントなしでも正しく機能します。
オブザベーション
pub const OBSERVATION_NUM: usize = 100;
pub struct Observation {
pub block_timestamp: u32,
pub tick_cumulative: i64, // Σ tick_current × Δt
pub padding: [u64; 4],
}
pub struct ObservationState {
pub initialized: bool,
pub recent_epoch: u64,
pub observation_index: u16,
pub pool_id: Pubkey,
pub observations: [Observation; OBSERVATION_NUM], // 100 entries
pub padding: [u64; 4],
}
CLMM のオブザベーションバッファは累積価格ではなく累積ティックを格納します。外部の利用者は (tick_cumulative[t1] − tick_cumulative[t0]) / (t1 − t0) から区間の幾何平均価格を計算し、price = 1.0001 ** tick で価格を求めます。詳細は algorithms/clmm-math を参照してください。
DynamicFeeConfig と DynamicFeeInfo
動的手数料パラメータは 2 箇所に存在します。再利用可能なテンプレートである DynamicFeeConfig は管理者が管理し、オプトインしたプール間で共有されます。プールごとのランタイム状態である DynamicFeeInfo は PoolState に埋め込まれ、スワップのたびに更新されます。
DynamicFeeConfig
// programs/amm/src/states/pool_fee.rs
pub struct DynamicFeeConfig {
pub index: u16, // identifier; PDA seed component
pub filter_period: u16, // seconds — within this window the volatility reference is held
pub decay_period: u16, // seconds — beyond this window the reference fully decays
pub reduction_factor: u16, // fixed-point in [1, 10_000); applied at decay
pub dynamic_fee_control: u32, // fixed-point in (0, 100_000); fee-rate gain
pub max_volatility_accumulator: u32, // ceiling on the volatility accumulator
pub padding: [u64; 8],
}
PDA シード:["dynamic_fee_config", index.to_be_bytes()]。create_dynamic_fee_config(管理者のみ)で作成し、update_dynamic_fee_config で変更します。enable_dynamic_fee = true で作成されたプールは、作成時にコンフィグの 5 つのキャリブレーションパラメータ(filter_period、decay_period、reduction_factor、dynamic_fee_control、max_volatility_accumulator)を自身の DynamicFeeInfo にスナップショットとして保存します。その後の DynamicFeeConfig への変更は既存のプールに遡及して影響しません。
DynamicFeeInfo(PoolState に埋め込み)
pub struct DynamicFeeInfo {
pub filter_period: u16,
pub decay_period: u16,
pub reduction_factor: u16,
pub dynamic_fee_control: u32,
pub max_volatility_accumulator: u32,
pub tick_spacing_index_reference: i32, // tick-spacing-units; reference for next swap
pub volatility_reference: u32, // running floor for the accumulator
pub volatility_accumulator: u32, // current cumulative volatility (capped)
pub last_update_timestamp: u64,
pub padding: [u8; 46],
}
下部 4 フィールドが状態、上部 5 フィールドが DynamicFeeConfig からコピーされたキャリブレーション値です。手数料の計算とデケイルールは products/clmm/math および products/clmm/fees に記載されています。
計算式で使用される定数:
| 定数 | 値 | 意味 |
|---|
VOLATILITY_ACCUMULATOR_SCALE | 10_000 | ボラティリティアキュムレータの粒度 |
REDUCTION_FACTOR_DENOMINATOR | 10_000 | reduction_factor の分母 |
DYNAMIC_FEE_CONTROL_DENOMINATOR | 100_000 | dynamic_fee_control の分母 |
MAX_FEE_RATE_NUMERATOR | 100_000 | 結果として得られる手数料率の上限(10%) |
LimitOrderState
未決の指値注文 1 つにつき 1 アカウント。
// programs/amm/src/states/limit_order.rs
pub struct LimitOrderState {
pub pool_id: Pubkey,
pub owner: Pubkey,
pub tick_index: i32,
pub zero_for_one: bool, // direction: true sells token0 for token1
pub order_phase: u64, // snapshot of TickState.order_phase at open time
pub total_amount: u64, // input-token amount placed
pub filled_amount: u64, // informational; computed precisely on settle
pub settle_base: u64, // unfilled remainder at last settle/decrease
pub settled_output: u64, // cumulative output-token paid to owner
pub open_time: u64,
pub unfilled_ratio_x64: u128, // Q64.64 snapshot of TickState.unfilled_ratio_x64 at open
pub padding: [u64; 4],
}
ライフサイクル:
- オープン — ユーザーが
open_limit_order を呼び出し、入力トークンの total_amount を預け入れます。注文は TickState のコホートに紐付けられます。
- (任意)増減 —
increase_limit_order は total_amount を追加し、decrease_limit_order は未約定トークン(およびその時点までの決済済み出力)を返還します。
- 決済 — コホートが全部または一部約定された場合、オーナーまたは運用キーパーが
settle_limit_order を呼び出してオーナーの ATA に出力トークンを送付します。
- クローズ —
unfilled_amount == 0 になった後、アカウントをクローズできます。レントは常に 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) ごとのカウンター。1 人のユーザーが PDA の衝突なしに複数の指値注文パイプラインを並行して実行できるようにします。
pub struct LimitOrderNonce {
pub user_wallet: Pubkey,
pub nonce_index: u8, // user-chosen, 0..255
pub order_nonce: u64, // monotonic, incremented every time a new order is opened
pub padding: [u64; 4],
}
PDA シード:[user_wallet.as_ref(), &[nonce_index]]。ほとんどのクライアントは nonce_index = 0 を使用し、order_nonce でカーディナリティを管理します。
キーアカウントの導出
import { PublicKey } from "@solana/web3.js";
const CLMM_PROGRAM_ID = new PublicKey(
"CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"
); // see reference/program-addresses
function i32ToBytes(n: number): Buffer {
const b = Buffer.alloc(4);
b.writeInt32BE(n);
return b;
}
export function deriveClmmAccounts(
ammConfig: PublicKey,
token0Mint: PublicKey, // must already be sorted
token1Mint: PublicKey,
) {
const [poolState] = PublicKey.findProgramAddressSync(
[Buffer.from("pool"), ammConfig.toBuffer(),
token0Mint.toBuffer(), token1Mint.toBuffer()],
CLMM_PROGRAM_ID,
);
const [observation] = PublicKey.findProgramAddressSync(
[Buffer.from("observation"), poolState.toBuffer()],
CLMM_PROGRAM_ID,
);
const [tickArrayBitmapExtension] = PublicKey.findProgramAddressSync(
[Buffer.from("pool_tick_array_bitmap_extension"), poolState.toBuffer()],
CLMM_PROGRAM_ID,
);
return { poolState, observation, tickArrayBitmapExtension };
}
export function deriveTickArray(
pool: PublicKey,
startTickIndex: number,
) {
const [tickArray] = PublicKey.findProgramAddressSync(
[Buffer.from("tick_array"), pool.toBuffer(), i32ToBytes(startTickIndex)],
CLMM_PROGRAM_ID,
);
return tickArray;
}
export function deriveDynamicFeeConfig(index: number) {
const idx = Buffer.alloc(2);
idx.writeUInt16BE(index);
const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("dynamic_fee_config"), idx],
CLMM_PROGRAM_ID,
);
return pda;
}
export function deriveLimitOrderNonce(
wallet: PublicKey,
nonceIndex: number,
) {
const [pda] = PublicKey.findProgramAddressSync(
[wallet.toBuffer(), Buffer.from([nonceIndex & 0xff])],
CLMM_PROGRAM_ID,
);
return pda;
}
export function deriveLimitOrder(
wallet: PublicKey,
nonceAccount: PublicKey,
orderNonce: bigint,
) {
const nonceBytes = Buffer.alloc(8);
nonceBytes.writeBigUInt64BE(orderNonce);
const [pda] = PublicKey.findProgramAddressSync(
[wallet.toBuffer(), nonceAccount.toBuffer(), nonceBytes],
CLMM_PROGRAM_ID,
);
return pda;
}
export function derivePersonalPosition(nftMint: PublicKey) {
const [personalPosition] = PublicKey.findProgramAddressSync(
[Buffer.from("position"), nftMint.toBuffer()],
CLMM_PROGRAM_ID,
);
return personalPosition;
}
シード文字列は必ずオンチェーンの IDL および reference/program-addresses と照合して確認してください。
ライフサイクル早見表
| イベント | 作成されるアカウント | 削除されるアカウント |
|---|
CreatePool | poolState、observation、token_0_vault、token_1_vault | — |
OpenPosition[WithToken22Nft] | NFT ミント + ATA、personalPosition、場合によって新規 tickArrayState、未作成の場合 tickArrayBitmapExtension | — |
IncreaseLiquidity | 場合によって新規 tickArrayState | — |
DecreaseLiquidity | — | ティックエントリーをクリアする場合あり(tickArrayState 自体はクローズされない) |
ClosePosition | — | NFT ミント、personalPosition |
SwapV2 | 場合によって新規 tickArrayState | — |
OpenLimitOrder | limitOrderState、必要に応じて limitOrderNonce(init-if-needed)、場合によって新規 tickArrayState | — |
IncreaseLimitOrder | — | — |
DecreaseLimitOrder | — | 注文が完全に消化された場合に limitOrderState をクローズ |
SettleLimitOrder | — | — |
CloseLimitOrder | — | limitOrderState(レント → owner) |
CreateDynamicFeeConfig | dynamicFeeConfig | — |
CreateCustomizablePool | poolState、observation、ボルト(CreatePool と同様)。enable_dynamic_fee = true の場合は dynamicFeeConfig をスナップショット。 | — |
CollectRewards | — | — |
UpdateRewardInfos | — | — |
CloseProtocolPosition(管理者) | — | 残骸 protocolPositionState(レント → 管理者) |
TickArrayState アカウントはプログラムによってクローズされることはありません。プールの存続期間中ずっとオンチェーンに残ります。一度初期化されたティック配列は、内部のすべてのティックが liquidity_gross == 0 に戻った後も存続します。既存のティック配列の再利用は無料で、追加レントが発生するのは一度も初期化されていない配列に最初に触れるポジションのみです。
参照先ガイド
ソース: