Skip to main content
Diese Seite wurde mit KI automatisch übersetzt. Maßgeblich ist stets die englische Version.Englische Version ansehen →
Diese Seite beschreibt das Layout und die Rolle jedes Accounts. Die Seeds sind kanonisch und unter reference/program-addresses aufgeführt. Ein CLMM-Pool benötigt mehr Accounts als ein CPMM-Pool, da Liquidität dünn verteilt über den Tick-Bereich gespeichert wird — das Verstehen dieser Verteilung ist der Kerninhalt dieser Seite.

Account-Übersicht

Ein aktiver CLMM-Pool wird durch folgende Account-Familien beschrieben. Alle gehören dem CLMM-Programm, mit Ausnahme der beiden Mints und ihrer Vaults.

PoolState

Der Live-Zustand des Pools, der bei jedem Swap und jeder Positionsänderung gelesen wird.
Felder, mit denen Sie in der Praxis arbeiten werden:
  • sqrt_price_x64 und tick_current stellen den Preisstatus des Pools dar. Sie werden bei jedem Swap gemeinsam aktualisiert. tick_current ist der ganzzahlige Anteil von log_{1.0001}(price).
  • liquidity ist die aktive Liquidität — die Summe der L-Werte aller Positionen, deren Bereich tick_current enthält. Sie ändert sich, sobald ein Swap einen Tick kreuzt oder eine Position geöffnet, geschlossen oder angepasst wird.
  • fee_growth_global_{0,1}_x64 sind die kumulierten Gebühren pro Liquiditätseinheit über die gesamte Pool-Geschichte. Positionen lesen diesen Wert, um ihren ausstehenden Anteil zu berechnen.
  • tick_spacing wird bei der Initialisierung an den AmmConfig gebunden und ändert sich nie. Es legt fest, welche Tick-Indizes als Positionsendpunkte zulässig sind.
  • tick_array_bitmap ist eine inline Bitmap, die den üblicherweise genutzten Tick-Bereich um den Spotpreis abdeckt. Für Pools, deren Positionen weiter außerhalb liegen, lebt die Overflow-Nachverfolgung im separaten TickArrayBitmapExtension-Account.
  • fee_on wird bei der Pool-Erstellung festgelegt. 0 (FromInput) reproduziert das klassische Uniswap-V3-Verhalten. 1 und 2 leiten die Swap-Gebühr an eine einzelne Seite des Buches weiter — siehe products/clmm/fees für die jeweiligen Abwägungen.
  • dynamic_fee_info enthält den Volatilitätsstatus für den dynamischen Gebührenzuschlag. Wenn aktiviert, berechnet jeder Swap einen dynamic_fee_component zusätzlich zu AmmConfig.trade_fee_rate. Das Layout ist unter DynamicFeeInfo weiter unten dokumentiert; Pools ohne dynamische Gebühr lassen die gesamte Struktur auf null.

AmmConfig

Ein typischer veröffentlichter Satz von CLMM-Fee-Tiers (bitte gegen GET https://api-v3.raydium.io/main/clmm-config verifizieren): protocol_fee_rate und fund_fee_rate sind Anteile der Handelsgebühr — gleiche Konvention wie bei CPMM. Siehe products/clmm/fees.

TickArrayState

CLMM speichert keinen einzelnen Eintrag pro Tick — das wären Milliarden von Accounts. Stattdessen fasst es TICK_ARRAY_SIZE benachbarte, initialisierte oder nicht initialisierte Ticks (typischerweise 60 oder 88, je nach Programmversion) in einem TickArrayState zusammen, der bei erster Verwendung lazily angelegt wird.
Die vier Limit-Order-Felder sind bei jedem Tick, der noch nie für eine Limit-Order verwendet wurde, auf null gesetzt. Sobald Orders auf einem Tick eröffnet werden, verwaltet das Programm sie als Folge von Kohorten:
  • order_phase ist die Kohorten-ID. Sie wird jedes Mal erhöht, wenn eine Kohorte von „vollständig ungefüllt” auf „teilweise gefüllt” übergeht.
  • orders_amount ist der Input-Token-Gesamtbetrag der aktuellen (neuesten) Kohorte.
  • part_filled_orders_remaining verfolgt die vorherige Kohorte, die gerade durch laufende Swaps gefüllt wird.
  • unfilled_ratio_x64 ist ein Q64.64-Multiplikator der Kohorte: Wenn ein Swap X % der Kohorte füllt, wird das Verhältnis mit (1 − X) multipliziert. Jede offene Order speichert ihren eigenen (order_phase, unfilled_ratio_x64)-Snapshot zum Eröffnungszeitpunkt, sodass die Abrechnungsrechnung auf einen Snapshot-Vergleich reduziert wird.
Regeln:
  • Ein Positionsendpunkt-Tick t muss t % tick_spacing == 0 erfüllen. Das Programm lehnt Positionen mit ungültigem Spacing ab.
  • Das Array eines Ticks befindet sich bei floor(t / (TICK_ARRAY_SIZE * tick_spacing)) * (TICK_ARRAY_SIZE * tick_spacing).
  • Ein Tick-Array wird lazily initialisiert: Die erste Position oder der erste Swap, der ein nicht initialisiertes Array berührt, legt es an und übernimmt die Miete.
  • Ein Tick-Array wird vom Programm nie geschlossen. Einmal angelegt, bleibt er für die Lebensdauer des Pools bestehen, auch wenn alle darin enthaltenen Ticks wieder liquidity_gross == 0 erreichen. Folgende Positionen und Swaps nutzen den bestehenden Account ohne zusätzliche Miete. Es gibt keinen ClosePosition-gesteuerten Bereinigungspfad für Tick-Arrays.

TickArrayBitmapExtension

PoolState.tick_array_bitmap (inline) deckt den „nahe am Spotpreis”-Bereich ab — ±1.024 Tick-Arrays. Außerhalb dieses Bereichs (für extreme Tick-Werte) verwaltet das Programm einen Extension-Account:
Wenn der Bereich Ihrer Position „normal” ist, müssen Sie sich nie um den Extension-Account kümmern. Full-Range-Positionen (z. B. (MIN_TICK, MAX_TICK)) benötigen ihn; das SDK löst ihn für Sie auf.

Positionen

Eine CLMM-Position ist ein Bündel aus drei Accounts plus einem Mint:

Position-NFT-Mint

Ein SPL-Token-Mint mit Supply 1. Die Adresse des Mints ist ein deterministischer PDA; das Position-NFT im Wallet des Inhabers ist lediglich ein ATA, das dieses einzelne Token hält. Die Übertragung des NFTs ist der Mechanismus, über den eine Position den Besitzer wechselt — das Programm knüpft die Autorisierung an den aktuellen Inhaber des ATA-Guthabens des NFTs, nicht an einen in State gespeicherten Pubkey.

PersonalPositionState

Einer pro offener Position. Abgeleitet vom NFT-Mint.

ProtocolPositionState (veraltet)

Ältere CLMM-Versionen speicherten aggregierte Buchführung pro (pool, tick_lower, tick_upper) in einem ProtocolPositionState-PDA. Neuere Versionen erstellen oder lesen diesen Account nicht mehr. Der Slot erscheint in den Account-Listen von OpenPosition / IncreaseLiquidity / DecreaseLiquidity noch als UncheckedAccount für ABI-Kompatibilität, aber das Programm schreibt nicht mehr darin. Bestehende Accounts on-chain sind Relikte; der Admin kann CloseProtocolPosition aufrufen, um die Miete zurückzugewinnen.Die aggregierte Bereichsbuchführung wird jetzt direkt aus den beiden Endpunkt-Ticks (liquidity_gross, liquidity_net sowie die Tick-spezifischen fee_growth_outside_* / reward_growths_outside_x64) im TickArrayState abgeleitet. Die Fee-Growth-Inside-Formel fee_growth_inside = global − outside_lower − outside_upper funktioniert weiterhin ohne aggregierten Positions-Account.

Observation

Der Observations-Puffer von CLMM speichert einen kumulierten Tick, keinen kumulierten Preis. Externe Verbraucher berechnen den geometrischen Mittelpreis über ein Intervall aus (tick_cumulative[t1] − tick_cumulative[t0]) / (t1 − t0) und anschließend price = 1.0001 ** tick. Siehe algorithms/clmm-math.

DynamicFeeConfig und DynamicFeeInfo

Dynamische Gebührenparameter befinden sich an zwei Stellen. Die wiederverwendbare Vorlage — DynamicFeeConfig — wird vom Admin verwaltet und von Pools geteilt, die sich anmelden. Der Pool-spezifische Laufzeitzustand — DynamicFeeInfo — ist in PoolState eingebettet und wird bei jedem Swap aktualisiert.

DynamicFeeConfig

PDA-Seed: ["dynamic_fee_config", index.to_be_bytes()]. Wird über create_dynamic_fee_config (Admin-gesperrt) erstellt und über update_dynamic_fee_config geändert. Ein Pool, der mit enable_dynamic_fee = true erstellt wird, übernimmt die fünf Kalibrierungsparameter (filter_period, decay_period, reduction_factor, dynamic_fee_control, max_volatility_accumulator) bei der Erstellung in seine eigene DynamicFeeInfo; spätere Änderungen am DynamicFeeConfig wirken sich nicht rückwirkend auf bestehende Pools aus.

DynamicFeeInfo (eingebettet in PoolState)

Die unteren vier Felder sind Zustandsfelder; die oberen fünf sind Kalibrierungsparameter, die aus DynamicFeeConfig kopiert wurden. Die Gebührenberechnung und die Decay-Regeln sind unter products/clmm/math und products/clmm/fees dokumentiert. Von der Formel verwendete Konstanten:

LimitOrderState

Einer pro offener Limit-Order.
Lebenszyklus:
  1. Eröffnen — Der Nutzer ruft open_limit_order auf, hinterlegt total_amount des Input-Tokens; die Order wird an eine TickState-Kohorte gebunden.
  2. (optional) Erhöhen / Verringernincrease_limit_order erhöht total_amount; decrease_limit_order gibt ungefüllte Token zurück (und jeden bis dahin abgerechneten Output).
  3. Abrechnen — Wenn die Kohorte vollständig oder teilweise gefüllt ist, ruft der Inhaber oder der operationale Keeper settle_limit_order auf, um Output-Token an das ATA des Inhabers zu übertragen.
  4. Schließen — Sobald unfilled_amount == 0, kann der Account geschlossen werden. Die Miete geht immer an owner zurück.
PDA-Seed: [owner.as_ref(), limit_order_nonce.key().as_ref(), limit_order_nonce.order_nonce.to_be_bytes().as_ref()]. Der Order-PDA ist daher eindeutig pro (owner, nonce_index, order_nonce).

LimitOrderNonce

Pro-(wallet, nonce_index)-Zähler, der es einem einzelnen Nutzer ermöglicht, mehrere parallele Pipelines von Limit-Orders zu betreiben, ohne PDA-Kollisionen zu verursachen.
PDA-Seed: [user_wallet.as_ref(), &[nonce_index]]. Die meisten Clients verwenden nonce_index = 0 und lassen order_nonce die Kardinalität tragen.

Ableitung der wichtigsten Accounts

Die genauen Seed-Strings sollten stets gegen das On-Chain-IDL und reference/program-addresses gegengeprüft werden.

Kurzreferenz Lebenszyklus

TickArrayState-Accounts werden vom Programm nie geschlossen — sie bleiben für die gesamte Lebensdauer des Pools bestehen. Einmal initialisiert, verbleibt ein Tick-Array on-chain, auch wenn jeder darin enthaltene Tick wieder liquidity_gross == 0 erreicht. Die Wiederverwendung eines bestehenden Tick-Arrays ist kostenlos; nur die erste Position, die ein noch nie initialisiertes Array berührt, übernimmt dessen Miete.

Was wo nachzulesen ist

Quellen: