Saltar al contenido principal

Documentation Index

Fetch the complete documentation index at: https://docs.raydium.io/llms.txt

Use this file to discover all available pages before exploring further.

Esta página fue traducida automáticamente por IA. La versión en inglés es la fuente autorizada.Ver versión en inglés →

Por qué precio-raíz, no precio

Los CLMMs de la familia Uniswap-v3 representan el precio como su raíz cuadrada, almacenada en punto fijo Q64.64:
sqrt_price_x64 = floor(sqrt(price) · 2^64)
Tres razones:
  1. Matemática de liquidez lineal. La cantidad de token0 o token1 en un rango de precio resulta ser una función lineal de sqrt_price, no de price. Almacenar sqrt_price permite que el paso de swap evalúe esas fórmulas lineales sin calcular una raíz cuadrada.
  2. Control de desbordamiento. sqrt_price · L cabe en u256 para todos los parámetros razonables; price · L puede desbordar mucho antes.
  3. Matemática de ticks uniforme. Como los ticks se definen como 1.0001^i, sqrt(price) = 1.00005^i también es una potencia exacta de la escala 1.00005. Cada cruce de tick se traduce en una pequeña multiplicación en el espacio sqrt_price_x64.
El precio y el precio-raíz tienen una relación uno a uno; la conversión es price = (sqrt_price_x64 / 2^64)^2.

Malla de ticks

Los precios se discretizan en una cuadrícula:
price(tick_i) = 1.0001^i
tick_i es un i32. El rango activo es [MIN_TICK, MAX_TICK] = [−443636, 443636], lo que proporciona un rango de precio aproximadamente [2^−128, 2^128]. El tick_spacing de cada pool se establece según su nivel de comisión: espaciados más pequeños para pares ajustados (por ejemplo, el nivel de stablecoin 0.01% usa espaciado 1), espaciados más grandes para pares volátiles (el nivel 0.25% usa 60, el nivel 1% usa 120). Las posiciones deben tener tick_lower y tick_upper alineados a tick_spacing. Los ticks activos de un pool (aquellos con liquidez que comienzan o terminan allí) son los únicos ticks que el paso de swap considera.

Liquidez-a-cantidad

Para una posición con liquidez L y rango de precio [sqrt_lo, sqrt_hi] (todos valores sqrt_price):
Estado del poolCantidad de token0Cantidad de token1
Precio por encima del rango (sqrt_p ≥ sqrt_hi)0L · (sqrt_hi − sqrt_lo)
Precio dentro del rangoL · (sqrt_hi − sqrt_p) / (sqrt_p · sqrt_hi)L · (sqrt_p − sqrt_lo)
Precio por debajo del rango (sqrt_p ≤ sqrt_lo)L · (sqrt_hi − sqrt_lo) / (sqrt_lo · sqrt_hi)0
Derivación: diferencia el invariante CPMM localmente. Dentro de cualquier rango de tick único, la posición se comporta como un CPMM con reservas virtuales (x_v, y_v) elegidas de modo que el (sqrt_p, L) actual del pool sea consistente con L = sqrt(x_v · y_v). Integrando desde sqrt_p hasta el límite del rango se obtienen las cantidades anteriores. Fórmulas inversas (usadas al crear una posición para una amount0 o amount1 dada):
L_from_amount0(amount0, sqrt_lo, sqrt_hi, sqrt_p) =
    amount0 · sqrt_p · sqrt_hi / (sqrt_hi − sqrt_p)

L_from_amount1(amount1, sqrt_lo, sqrt_hi, sqrt_p) =
    amount1 / (sqrt_p − sqrt_lo)

// Para un depósito simétrico en una posición dentro del rango, tomar el mínimo.
L = min(L_from_amount0, L_from_amount1)

Paso de swap de un solo tick

Dentro de un rango de tick único el pool se comporta como un CPMM. Dado el sqrt_p actual y el sqrt_target:
Δamount0_step = L · (sqrt_target − sqrt_p) / (sqrt_p · sqrt_target)     // si se intercambia por token0
Δamount1_step = L · (sqrt_target − sqrt_p)                              // si se intercambia por token1

Paso de entrada exacta

Dado Δin_remaining:
// Candidato a nuevo sqrt_p si llenáramos hasta el límite del tick:
sqrt_after_full = sqrt_target
amount_to_full  = Δamount_in_to_reach(sqrt_p → sqrt_target)

if Δin_remaining ≥ amount_to_full:
    // consumir el resto del cubo
    sqrt_p'         = sqrt_target
    Δin_consumed    = amount_to_full
    Δout            = amount_out_at_boundary
else:
    // no cruzamos; resolver para el sqrt_p terminal
    sqrt_p'         = L · sqrt_p / (L + Δin_remaining · sqrt_p)      // para swaps 0→1
    Δin_consumed    = Δin_remaining
    Δout            = L · (sqrt_p − sqrt_p')                          // proporcional a Δsqrt
El swap 0→1 reduce sqrt_p (el precio baja a medida que vendemos token0). Un swap 1→0 lo aumenta. Las fórmulas son simétricas con sqrt_p y sqrt_target intercambiados.

Paso de salida exacta

Misma estructura, resolviendo para Δin en su lugar.

Bucle de swap multi-tick

Un swap itera sobre ticks hasta que la entrada se agota o se alcanza el límite de precio:
while Δin_remaining > 0 and sqrt_p != sqrt_price_limit:
    next_tick = find_next_initialized_tick(pool.tick_current, direction)
    sqrt_target = min(next_tick.sqrt_price, sqrt_price_limit)       // direccionalmente

    (Δin, Δout, sqrt_p') = single_step(sqrt_p, sqrt_target, L, Δin_remaining)

    Δin_remaining -= Δin
    accumulated_out += Δout

    if sqrt_p' == next_tick.sqrt_price:
        // cruzar el tick
        L += next_tick.liquidity_net * direction_sign
        flip_fee_growth_outside(next_tick)
        match_limit_orders_at_tick(next_tick, ...)        // ver products/clmm/math
        pool.tick_current = next_tick.tick_index
    sqrt_p = sqrt_p'
Cada single_step utiliza la L actual del pool. L cambia solo al cruzar un tick inicializado. La liquidez entre ticks es constante, que es lo que hace que la matemática del paso sea de forma cerrada. liquidity_net en un tick es la suma con signo de las liquideces de posición que comienzan en ese tick menos las que terminan allí. Cruzar hacia arriba suma liquidity_net; cruzar hacia abajo lo resta. Cuando el pool tiene órdenes de límite abiertas en un tick, el paso de cruce de tick también consume oportunamente parte de la entrada del swap para llenar esas órdenes (FIFO entre cohortes). El algoritmo de coincidencia y el recargo de comisión dinámica que puede aplicarse además del paso base se documentan en products/clmm/math; no cambian las fórmulas de forma cerrada de un solo paso anteriores.

Acumuladores de crecimiento de comisiones

CLMM rastrea comisiones por unidad de liquidez activa, por lado, globalmente y por tick:
fee_growth_global_0_x64     // Q64.64, monótono
fee_growth_global_1_x64
tick.fee_growth_outside_0_x64   // "comisiones acumuladas mientras este tick estaba fuera del rango activo"
tick.fee_growth_outside_1_x64
En cada single_step:
step_lp_fee = (Δin · fee_rate) · (1 − protocol_fraction − fund_fraction)
fee_growth_global += step_lp_fee · 2^64 / L     // solo para el lado de entrada
(El fee_growth_global del otro lado no se mueve en este paso, ya que ningún token en ese lado se pagó como entrada.) Al cruzar un tick, el programa invierte fee_growth_outside:
tick.fee_growth_outside_0_x64 = fee_growth_global_0_x64 − tick.fee_growth_outside_0_x64
tick.fee_growth_outside_1_x64 = fee_growth_global_1_x64 − tick.fee_growth_outside_1_x64
“Fuera” es relativo a tick_current. Cuando tick_current está por encima del tick, fuera significa “debajo”. Cuando tick_current está por debajo, fuera significa “arriba”. La inversión cambia la interpretación.

fee_growth_inside para una posición

Dada una posición [tick_lower, tick_upper] y el tick_current actual:
if tick_current >= tick_upper:
    inside = tick_lower.fee_growth_outside − tick_upper.fee_growth_outside
else if tick_current < tick_lower:
    inside = tick_upper.fee_growth_outside − tick_lower.fee_growth_outside
else:     // la posición está dentro del rango
    inside = fee_growth_global
           − tick_lower.fee_growth_outside
           − tick_upper.fee_growth_outside
Las comisiones no cobradas de una posición para el lado de token s son:
tokens_owed_s += L · (fee_growth_inside_s − fee_growth_inside_last_s) / 2^64
fee_growth_inside_last_s = fee_growth_inside_s
Esta actualización se ejecuta en cada interacción con la posición (IncreaseLiquidity, DecreaseLiquidity, CollectFees).

Ejemplo trabajado — cruzar un tick

Pool (simplificado):
  • sqrt_p_x64 = 2^64 · 1.0 = 2^64 (precio = 1.0)
  • L = 1_000_000
  • tick_current = 0
  • Siguiente tick inicializado abajo: tick = −60, sqrt_price = 1.0001^(−30) ≈ 0.99700, liquidity_net = −400_000 (este tick termina una posición, por lo que un cruce hacia abajo elimina 400k)
  • Tasa de comisión: 0.25%
Swap: Δin = 10_000 token0, dirección = 0→1. Paso 1 — hasta sqrt_target = 0.99700 · 2^64:
amount_in_to_target = L · (1/sqrt_target − 1/sqrt_p)
                    = 1_000_000 · (1/0.99700 − 1/1.0)
                    ≈ 1_000_000 · 0.003009
                    ≈ 3_009
3,009 < 10,000, así que llenamos este paso completamente:
Δin_step  = 3_009 / (1 − 0.0025)  = 3_017    // bruto de comisión
Δout_step = L · (sqrt_p − sqrt_target) ≈ 1_000_000 · 0.00299 ≈ 2_990
sqrt_p    = 0.99700 · 2^64
tick_current = −60
L         = 1_000_000 + (−400_000)  = 600_000         // cruzó el tick
fee_growth_outside en tick −60 se invierte
Δin_remaining = 10_000 − 3_017 = 6_983
Paso 2 — con nuevo L = 600_000: El siguiente tick inicializado (digamos tick = −120) está en sqrt = 0.99402. Recomputa amount_in_to_target:
amount_in_to_target = 600_000 · (1/0.99402 − 1/0.99700)
                    ≈ 600_000 · 0.003010
                    ≈ 1_806
Todavía menos que Δin_remaining. Cruza de nuevo. Continúa hasta que Δin_remaining llegue a cero. La secuencia completa de Δout se acumula en la salida de swap final.

Inicialización y guardas contra desbordamiento

  • MIN_SQRT_PRICE_X64 y MAX_SQRT_PRICE_X64 corresponden a tick = ±443636. Cualquier swap que empuje sqrt_p fuera de este rango revierte.
  • El parámetro sqrt_price_limit del usuario debe estar en el mismo intervalo; el programa lo verifica.
  • Los productos de L · Δsqrt se calculan en u256 y luego se desplazan de nuevo a u128 para evitar desbordamiento.

Diferencias vs Uniswap v3

  • Oráculo. El ObservationState de Raydium almacena un búfer de anillo (block_timestamp, tick_cumulative, seconds_per_liquidity_cumulative); formato de transferencia ligeramente diferente de Uniswap pero la misma matemática TWAP.
  • Token-2022. Raydium CLMM soporta acuñaciones de Token-2022; la variante de comisión de transferencia requiere ajustes adicionales de cantidad pre/post-swap. Ver algorithms/token-2022-transfer-fees.
  • Mapa de bits de tick. Raydium empaqueta el mapa de bits de tick inicializado en [u64; 16] por pool para find_next_initialized_tick rápido; Uniswap usa una asignación por palabra en cadena. La compensación es alquiler vs costo de búsqueda.
  • Espacios de recompensas. Raydium soporta 3 flujos de recompensas por pool con contadores reward_growth_global_x64 separados; la misma estructura que el acumulador de crecimiento de comisiones.

Enlaces

Fuentes:
  • Documento técnico de Uniswap v3 (derivación canónica de matemática de precio-raíz).
  • Código fuente del programa Raydium CLMM.