Skip to main content
Cette page est traduite automatiquement par IA. La version anglaise fait foi.Voir la version anglaise →

Pourquoi les ticks existent

La liquidité de CLMM est concentrée dans des plages de prix. Pour rendre les plages tractables on-chain, les prix sont quantifiés en ticks entiers, où chaque tick est un multiple constant du précédent : price(i)=1.0001i\text{price}(i) = 1.0001^{\,i} Un tick correspond à un mouvement de prix de 0,01 %, soit environ 1 point de base. Le mappage est : MIN_TICK et MAX_TICK sont choisis de sorte que sqrt_price_x64 tienne dans un u128 aux deux extrémités. Chaque pool impose que tick_lower >= MIN_TICK et tick_upper <= MAX_TICK. En pratique, l’interface web restreint la plage à quelque chose de beaucoup plus étroit pour éviter que les utilisateurs ne verrouillent la liquidité dans des ticks inaccessibles.

Espacement des ticks

L’AmmConfig d’un pool fixe un espacement des ticks — les seuls ticks qu’une position est autorisée à utiliser comme points d’extrémité. Si tick_spacing = 60, seuls les ticks …, −120, −60, 0, 60, 120, … sont valides. Une tentative d’ouverture d’une position avec le point d’extrémité 31 revient avec InvalidTickIndex. Espacements publiés courants : Plus l’espacement est grossier, moins il y a de tick arrays à initialiser, moins cher il est d’ouvrir une position large, et plus floue est la limite de prix. Les paires volatiles vivent généralement dans les niveaux d’espacement 120 ; les stables vivent dans les niveaux d’espacement 1.

Tick arrays

Le pool ne stocke pas l’état par tick dans des comptes séparés. Au lieu de cela, TICK_ARRAY_SIZE ticks adjacents (60 dans le CLMM Raydium actuel) sont compressés dans un seul TickArrayState. Le premier tick du tableau est son start_tick_index, et il couvre exactement TICK_ARRAY_SIZE * tick_spacing unités de tick entier. Pour tick_spacing = 60 et TICK_ARRAY_SIZE = 60 :
  • Chaque tick array s’étend sur 60 × 60 = 3600 ticks entiers.
  • start_tick_index est un multiple de 3600 : …, -7200, -3600, 0, 3600, 7200, ….
Un point d’extrémité de position t = 2040 à tick_spacing = 60 réside dans le tick array avec start_tick_index = 0. Un point d’extrémité de position t = 4200 réside dans le tableau avec start_tick_index = 3600.

Quand un tableau est créé

Un tick array est lazy : la première position qui référence un tick à l’intérieur initialise le tableau, en payant le loyer. Les swaps n’initialisent pas les tick arrays — ils les contournent en utilisant le bitmap. Le flux open-position du SDK inspecte la plage choisie, calcule la liste des tick arrays qu’il touche, et ajoute les instructions init_tick_array dans la même transaction que OpenPosition si l’une d’elles manque.

Les tick arrays ne sont pas fermés

Une fois qu’un tick array a été initialisé, il persiste pour la durée de vie du pool. Le programme n’expose pas de chemin pour fermer un tick array, même après que initialized_tick_count revienne à zéro. Il n’y a pas de récupération de loyer pour les tick arrays ; le loyer payé par la première position qui touche un tableau est verrouillé dans ce compte de manière permanente. C’est un compromis délibéré : réutiliser un tick array existant est gratuit pour chaque position ultérieure, donc un pool fortement échangé ne paie le coût du loyer qu’une seule fois par slot (pool, start_tick_index) indépendamment du renouvellement.

Le bitmap

Trouver « le prochain tick initialisé à gauche/droite du tick actuel » doit être rapide — un swap peut traverser de nombreux ticks. Le pool stocke un bitmap 1-bit-par-tick-array en ligne dans PoolState pour la plage ±1 024 tableaux autour du tick 0. En dehors de cette plage (positions full-range, configurations exotiques), TickArrayBitmapExtension fournit le débordement. Un swap parcourt le bitmap : lowest_set_bit_above(tick_current_array_index) donne le prochain tableau avec un tick initialisé du côté vers lequel le swap se dirige. Dans ce tableau, un balayage de bit similaire localise le prochain tick initialisé.

liquidity_gross et liquidity_net

Chaque tick initialisé stocke deux valeurs de liquidité :
  • liquidity_gross — la somme de L sur toutes les positions qui référencent ce tick comme point d’extrémité. Quand liquidity_gross atteint zéro, le tick devient non initialisé et peut être supprimé du bitmap.
  • liquidity_net — le changement signé à la liquidité au niveau du pool quand le prix traverse ce tick en se déplaçant vers le haut (de gauche à droite dans l’espace des ticks). Si ce tick est la limite inférieure d’une position de taille L, il contribue +L ; s’il est la limite supérieure de cette position, il contribue −L.
Exemple travaillé : deux positions sur le même pool.
  • Position A : tick_lower = -120, tick_upper = 0, liquidité L_A = 100.
  • Position B : tick_lower = -60, tick_upper = 60, liquidité L_B = 50.
État tick par tick : Liquidité au niveau du pool pour différentes valeurs de tick_current :
  • tick_current = -180 : liquidity = 0 (avant toute position)
  • tick_current = -90 : liquidity = 100 (à l’intérieur de A uniquement)
  • tick_current = -30 : liquidity = 150 (à l’intérieur de A et B)
  • tick_current = 30 : liquidity = 50 (à l’intérieur de B uniquement)
  • tick_current = 90 : liquidity = 0 (après les deux)
À chaque traversée de tick lors d’un swap, le programme ajoute liquidity_net (possiblement négatif) à PoolState.liquidity. C’est le mécanisme exact d’Uniswap-v3.

Positions en tant que NFT

Une position CLMM Raydium est un NFT. L’ouverture d’une position crée un nouveau mint avec un approvisionnement de 1 dans le portefeuille de l’appelant, et l’autorité du mint est le programme CLMM. Le programme lie la propriété de la position à celui qui détient un solde dans un ATA de ce mint au moment du CPI. Conséquences :
  • Les positions sont normalement transférables. Un portefeuille peut vendre ou airdrop une position en transférant le NFT. Le nouveau détenteur peut alors appeler CollectRewards, IncreaseLiquidity, etc. L’exception est une position gelée selon le chemin de l’émetteur restreint ci-dessous.
  • Les positions sont adressables en dehors de CLMM. Les places de marché et les portefeuilles affichent les positions comme d’autres NFT. Le SDK définit un name/symbol raisonnable sur les métadonnées du mint.
  • Le PDA d’une position est dérivé du mint NFT. Vous pouvez trouver le PersonalPositionState sans savoir qui le détient actuellement.

Positions d’émetteur restreint

Chaque mint NFT de position créé après la mise à niveau 2026-08 enregistre son pool_state CLMM comme autorité de gel. Cela ne signifie pas que chaque nouvelle position est gelée. Pour les pools ordinaires et chaque position non correspondante, le compte de token NFT reste dégelé et transférable. Le PDA du pool ne peut pas signer en dehors du programme CLMM, et CLMM n’expose aucune instruction de gel à usage général. Le gel nécessite ces deux conditions :
  1. La position est ouverte via OpenPositionV2 ou OpenPositionWithToken22Nft.
  2. Au moins un mint de coffre du pool porte une autorité de gel de la liste d’émetteurs restreints codée en dur du programme.
Seulement quand les deux conditions sont remplies, CLMM gèle le compte NFT de position nouvellement créé immédiatement après le minage. OpenPosition V1 n’applique pas ce filtre. Voir reference/program-addresses pour la liste actuelle. Une position gelée :
  • Ne peut pas transférer son NFT à un autre compte de token.
  • Ne peut pas changer le propriétaire du compte de token NFT.
  • Peut toujours augmenter ou diminuer la liquidité et collecter les frais ou récompenses quand le propriétaire enregistré signe.
  • Peut toujours se fermer. ClosePosition utilise le PDA du pool pour dégeler le compte NFT, puis brûle le NFT et ferme les comptes de position dans la même instruction.
Les positions existantes ne sont pas migrées ou gelées rétroactivement. Les mints NFT de position créés avant la mise à niveau conservent leur paramètre d’autorité de gel précédent.
Un client fermant une position gelée doit ajouter le pool_state de la position comme premier compte restant à ClosePosition. La liste de comptes IDL déclarée est inchangée, donc les clients plus anciens peuvent ouvrir une position gelée avec succès mais échouer plus tard à la fermer avec AccountLack. Mettez à jour le générateur de fermeture avant de supporter ces pools.

Positions Token-2022

CLMM peut créer un NFT de position sous le Token SPL classique via OpenPositionV2, ou sous Token-2022 via OpenPositionWithToken22Nft. Les deux chemins V2 inspectent les mints de coffre du pool et appliquent la même règle de gel d’émetteur restreint. OpenPosition V1 est le chemin classique-token hérité et ne peut pas servir un pool avec des mints de coffre Token-2022. La compatibilité des portefeuilles et des places de marché diffère ; l’interface utilisateur de Raydium suit les deux programmes NFT.

Règles de plage autorisée

Au moment de OpenPosition, le programme impose :
  1. tick_lower < tick_upper.
  2. tick_lower % tick_spacing == 0 et tick_upper % tick_spacing == 0.
  3. MIN_TICK <= tick_lower et tick_upper <= MAX_TICK.
  4. L’appelant a fourni les tick arrays contenant tick_lower et tick_upper — soit déjà initialisés, soit via un init_tick_array dans la même transaction.
  5. Le compte d’extension bitmap, si cette position s’étend dans la plage d’extension.
Si l’une des vérifications échoue, l’instruction revient avec InvalidTickIndex, NotApproved, ou InsufficientLiquidity selon la contrainte. Voir reference/error-codes.

« In-range » vs « out-of-range »

Une position est in-range quand tick_lower <= tick_current < tick_upper. Seules les positions in-range contribuent à PoolState.liquidity et donc seules elles gagnent des frais de swap. Une position out-of-range :
  • Détient 100 % d’un token (celui dont sa plage a dépassé). Spécifiquement, si tick_current < tick_lower, la position ne détient que le token1 (elle a déjà été « vendue » par le prix s’éloignant) ; si tick_current >= tick_upper, elle ne détient que le token0.
  • Ne gagne pas les frais de swap.
  • Continue à accumuler les récompenses si les flux de récompenses du pool émettent vers la liquidité out-of-range — mais le comportement par défaut de Raydium est « émettre uniquement vers in-range », correspondant à la convention Uniswap v3. Voir products/clmm/fees.
Les LP gérant les positions CLMM consacrent la plupart de leur attention à maintenir les positions in-range à mesure que le prix se déplace.

Pièges d’intégration courants

  • Points d’extrémité hors espacement. Le code qui calcule un tick à partir d’un prix cible doit s’aligner sur un multiple de tick_spacing avant de le passer à OpenPosition. Les assistants du SDK (TickUtils.getTickWithPriceAndTickspacing) le font ; les mathématiques maison souvent ne le font pas.
  • Tick arrays manquants. L’ouverture d’une position large peut nécessiter l’initialisation de plusieurs tick arrays ; oublier de les passer en tant que comptes inscriptibles revient. Le openPositionFromBase du SDK vous retourne la liste.
  • Tick obsolète après un swap. tick_current peut traverser de nombreux ticks en un seul swap. Si votre UX affiche un « tick actuel » d’un appel RPC et ouvre ensuite une position dans un appel ultérieur, la position relative par rapport au prix en direct peut être décalée de dizaines de ticks. Récupérez à nouveau juste avant de signer.
  • NFT de position avec métadonnées supplémentaires. Si vous construisez un portefeuille qui reconnaît les positions Raydium, utilisez le PDA de position / données du programme et non un champ de métadonnées codé en dur. Les nouveaux mints de position utilisent le PDA du pool comme autorité de mint et de gel à la création ; l’autorité de mint est supprimée après le minage du seul NFT.
  • Supposer que chaque position est transférable. Lisez l’état isFrozen du compte de token NFT avant d’afficher les actions de transfert, place de marché, séquestre, ou Burn & Earn.

Où aller ensuite

  • Math — le passage du swap et la dérivation de la croissance des frais auxquels les limites des ticks participent.
  • Comptes — les dispositions TickArrayState et PositionState.
  • Frais et récompenses — comment l’in-range-ness contrôle l’accumulation des frais.
  • algorithms/clmm-math — la dérivation partagée des formules de liquidité concentrée.
Sources :