Skip to main content
Esta página foi traduzida automaticamente por IA. A versão em inglês é a fonte oficial.Ver versão em inglês →

Por que ticks existem

A liquidez do CLMM é concentrada em faixas de preço. Para tornar as faixas viáveis on-chain, os preços são quantizados em ticks inteiros, onde cada tick é um múltiplo constante do anterior: price(i)=1.0001i\text{price}(i) = 1.0001^{\,i} Um tick corresponde a um movimento de preço de 0,01%, ou ~1 basis point. O mapeamento é: MIN_TICK e MAX_TICK são escolhidos para que sqrt_price_x64 caiba em um u128 em ambas as extremidades. Cada pool garante que tick_lower >= MIN_TICK e tick_upper <= MAX_TICK. Na prática, a interface web limita o intervalo a algo muito mais estreito para evitar que os usuários travem liquidez em ticks inacessíveis.

Espaçamento de ticks

O AmmConfig de um pool define um espaçamento de ticks — os únicos ticks que uma posição pode usar como endpoints. Se tick_spacing = 60, apenas ticks …, −120, −60, 0, 60, 120, … são válidos. Uma tentativa de abrir uma posição com endpoint 31 reverte com InvalidTickIndex. Espaçamentos publicados comuns: Quanto mais grosseiro o espaçamento, menos tick arrays para inicializar, mais barato abrir uma posição ampla, e mais borrada a fronteira de preço. Pares voláteis normalmente vivem em camadas de espaçamento 120; stablecoins vivem em camadas de espaçamento 1.

Tick arrays

O pool não armazena estado por-tick em contas separadas. Em vez disso, TICK_ARRAY_SIZE ticks adjacentes (60 no CLMM atual do Raydium) são empacotados em um único TickArrayState. O primeiro tick do array é seu start_tick_index, e ele cobre exatamente TICK_ARRAY_SIZE * tick_spacing unidades de tick inteiro. Para tick_spacing = 60 e TICK_ARRAY_SIZE = 60:
  • Cada tick array abrange 60 × 60 = 3600 ticks inteiros.
  • start_tick_index é um múltiplo de 3600: …, -7200, -3600, 0, 3600, 7200, ….
Um endpoint de posição t = 2040 em tick_spacing = 60 vive no tick array com start_tick_index = 0. Um endpoint de posição t = 4200 vive no array com start_tick_index = 3600.

Quando um array é criado

Um tick array é lazy: a primeira posição que referencia qualquer tick dentro dele inicializa o array, pagando o aluguel. Swaps não inicializam tick arrays — eles pulam sobre arrays não inicializados usando o bitmap. O fluxo de abertura de posição do SDK inspeciona o intervalo escolhido, calcula a lista de tick arrays que ele toca, e adiciona instruções init_tick_array na mesma transação que OpenPosition se algum estiver faltando.

Tick arrays não são fechados

Uma vez que um tick array foi inicializado, ele persiste pela vida do pool. O programa não expõe um caminho para fechar um tick array, mesmo depois que initialized_tick_count retorna a zero. Não há recuperação de aluguel para tick arrays; o aluguel pago pela primeira posição que toca um array é travado naquela conta permanentemente. Esta é uma troca deliberada: reutilizar um tick array existente é gratuito para cada posição subsequente, então um pool muito negociado paga o custo de aluguel apenas uma vez por slot (pool, start_tick_index) independentemente da rotatividade.

O bitmap

Encontrar “o próximo tick inicializado à esquerda/direita do tick atual” tem que ser rápido — um swap pode cruzar muitos ticks. O pool armazena um bitmap de 1-bit-por-tick-array inline em PoolState para o intervalo ±1.024 arrays ao redor do tick 0. Fora desse intervalo (posições de intervalo completo, configurações exóticas), TickArrayBitmapExtension fornece o overflow. Um swap caminha pelo bitmap: lowest_set_bit_above(tick_current_array_index) fornece o próximo array com um tick inicializado no lado para o qual o swap está se movendo. Dentro desse array, um bit-scan similar localiza o próximo tick inicializado.

liquidity_gross e liquidity_net

Todo tick inicializado armazena dois valores de liquidez:
  • liquidity_gross — a soma de L sobre todas as posições que referenciam este tick como endpoint. Quando liquidity_gross atinge zero, o tick se torna não inicializado e pode ser removido do bitmap.
  • liquidity_net — a mudança assinada para a liquidity de nível de pool quando o preço cruza este tick se movendo para cima (esquerda-para-direita no espaço de ticks). Se este tick é o limite inferior de uma posição com tamanho L, ele contribui +L; se é o limite superior dessa posição, ele contribui −L.
Exemplo trabalhado: duas posições no mesmo pool.
  • Posição A: tick_lower = -120, tick_upper = 0, liquidez L_A = 100.
  • Posição B: tick_lower = -60, tick_upper = 60, liquidez L_B = 50.
Estado tick-por-tick: liquidity de nível de pool para diferentes valores de tick_current:
  • tick_current = -180: liquidity = 0 (antes de qualquer posição)
  • tick_current = -90: liquidity = 100 (dentro de A apenas)
  • tick_current = -30: liquidity = 150 (dentro de A e B)
  • tick_current = 30: liquidity = 50 (dentro de B apenas)
  • tick_current = 90: liquidity = 0 (após ambas)
A cada cruzamento de tick durante um swap, o programa adiciona liquidity_net (possivelmente negativo) a PoolState.liquidity. Este é o mecanismo exato do Uniswap-v3.

Posições como NFTs

Uma posição CLMM do Raydium é um NFT. Abrir uma posição cria um novo mint com suprimento 1 na carteira do chamador, e a autoridade do mint é o programa CLMM. O programa vincula a propriedade da posição a quem quer que tenha um saldo em um ATA daquele mint no momento do CPI. Consequências:
  • Posições são normalmente transferíveis. Uma carteira pode vender ou fazer airdrop de uma posição transferindo o NFT. O novo detentor pode então chamar CollectRewards, IncreaseLiquidity, etc. A exceção é uma posição congelada sob o caminho de emissor restrito abaixo.
  • Posições são endereçáveis fora do CLMM. Marketplaces e carteiras exibem posições como outros NFTs. O SDK define um name/symbol razoável nos metadados do mint.
  • O PDA de uma posição é derivado do mint do NFT. Você pode encontrar o PersonalPositionState sem saber quem o detém atualmente.

Posições de emissor restrito

Todo mint de NFT de posição criado após a atualização de 2026-08 registra seu pool_state do CLMM como autoridade de congelamento. Isto não significa que toda nova posição está congelada. Para pools ordinários e toda posição não correspondente, a conta de token do NFT permanece descongelada e transferível. O PDA do pool não pode assinar fora do programa CLMM, e o CLMM não expõe nenhuma instrução de congelamento de propósito geral. O congelamento requer ambas estas condições:
  1. A posição é aberta através de OpenPositionV2 ou OpenPositionWithToken22Nft.
  2. Pelo menos um mint de vault do pool carrega uma autoridade de congelamento da lista de emissor restrito codificada do programa.
Apenas quando ambas as condições se mantêm o CLMM congela a conta de NFT de posição recém-criada imediatamente após a cunhagem. OpenPosition V1 não aplica este filtro. Veja reference/program-addresses para a lista atual. Uma posição congelada:
  • Não pode transferir seu NFT para outra conta de token.
  • Não pode mudar o proprietário da conta de token do NFT.
  • Ainda pode aumentar ou diminuir liquidez e coletar taxas ou recompensas quando o proprietário registrado assina.
  • Ainda pode fechar. ClosePosition usa o PDA do pool para descongelar a conta do NFT, depois queima o NFT e fecha as contas de posição na mesma instrução.
Posições existentes não são migradas ou congeladas retroativamente. Mints de NFT de posição criados antes da atualização mantêm sua configuração anterior de autoridade de congelamento.
Um cliente fechando uma posição congelada deve anexar o pool_state da posição como a primeira conta restante para ClosePosition. A lista de contas IDL declarada não é alterada, então clientes mais antigos podem abrir uma posição congelada com sucesso mas depois falhar ao fechá-la com AccountLack. Atualize o construtor de fechamento antes de suportar esses pools.

Posições Token-2022

O CLMM pode cunhar um NFT de posição sob Token SPL clássico através de OpenPositionV2, ou sob Token-2022 através de OpenPositionWithToken22Nft. Ambos os caminhos V2 inspecionam os mints de vault do pool e aplicam a mesma regra de congelamento de emissor restrito. OpenPosition V1 é o caminho clássico de token legado e não pode servir um pool com mints de vault Token-2022. A compatibilidade de carteira e marketplace difere; a interface do Raydium rastreia ambos os programas de NFT.

Regras de intervalo permitido

No momento de OpenPosition o programa garante:
  1. tick_lower < tick_upper.
  2. tick_lower % tick_spacing == 0 e tick_upper % tick_spacing == 0.
  3. MIN_TICK <= tick_lower e tick_upper <= MAX_TICK.
  4. O chamador forneceu os tick arrays contendo tick_lower e tick_upper — já inicializados ou via um init_tick_array na mesma transação.
  5. A conta de extensão de bitmap, se esta posição se estende para o intervalo de extensão.
Se qualquer verificação falhar a instrução reverte com InvalidTickIndex, NotApproved, ou InsufficientLiquidity dependendo de qual restrição. Veja reference/error-codes.

”In-range” vs “out-of-range”

Uma posição está in-range quando tick_lower <= tick_current < tick_upper. Apenas posições in-range contribuem para PoolState.liquidity e portanto apenas elas ganham taxas de swap. Uma posição out-of-range:
  • Detém 100% de um token (aquele para o qual seu intervalo caminhou). Especificamente, se tick_current < tick_lower, a posição detém apenas token1 (já foi “vendida” pelo preço se movendo para longe); se tick_current >= tick_upper, detém apenas token0.
  • Não ganha taxas de swap.
  • Continua a acumular recompensas se os fluxos de recompensa do pool emitem para liquidez out-of-range — mas o comportamento padrão do Raydium é “emitir apenas para in-range”, correspondendo à convenção do Uniswap v3. Veja products/clmm/fees.
LPs gerenciando posições CLMM gastam a maior parte de sua atenção mantendo posições in-range conforme o preço se move.

Armadilhas comuns de integração

  • Endpoints fora do espaçamento. Código que calcula um tick a partir de um preço alvo deve se ajustar a um múltiplo de tick_spacing antes de passá-lo para OpenPosition. Os auxiliares do SDK (TickUtils.getTickWithPriceAndTickspacing) fazem isso; matemática caseira frequentemente não.
  • Tick arrays faltando. Abrir uma posição ampla pode exigir inicializar vários tick arrays; esquecer de passá-los como contas graváveis reverte. O openPositionFromBase do SDK retorna a lista para você.
  • Tick obsoleto após um swap. tick_current pode cruzar muitos ticks em um swap. Se sua UX mostra um “tick atual” de uma chamada RPC e depois abre uma posição em uma posterior, a posição relativa vs o preço ao vivo pode estar desligada por dezenas de ticks. Re-busque logo antes de assinar.
  • NFTs de posição com metadados extras. Se você construir uma carteira que reconheça posições do Raydium, use o PDA de posição / dados do programa e não um campo de metadados codificado. Novos mints de posição usam o PDA do pool como autoridade de mint e congelamento na criação; a autoridade de mint é removida após o único NFT ser cunhado.
  • Assumindo que toda posição é transferível. Leia o estado isFrozen da conta de token do NFT antes de mostrar transferência, marketplace, escrow, ou ações de Burn & Earn.

Próximos passos

  • Math — o passo de swap e derivação de crescimento de taxa que limites de ticks participam.
  • Accounts — os layouts TickArrayState e PositionState.
  • Fees and rewards — como in-range-ness controla acúmulo de taxa.
  • algorithms/clmm-math — a derivação compartilhada das fórmulas de liquidez concentrada.
Fontes: