메인 콘텐츠로 건너뛰기

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.

이 페이지는 AI 자동 번역입니다. 모든 내용은 영문판을 기준으로 합니다.영문판 보기 →

틱이 존재하는 이유

CLMM의 유동성은 가격 범위에 집중되어 있습니다. 범위를 체인에서 다루기 쉽게 만들기 위해 가격은 정수 으로 양자화되며, 각 틱은 이전 틱의 상수배입니다: price(i)=1.0001i\text{price}(i) = 1.0001^{\,i} 틱은 0.01% 가격 변동, 즉 약 1 베이시스 포인트에 해당합니다. 매핑은 다음과 같습니다:
틱 인덱스 i가격 승수
01.0000
1001.0100 (≈ +1.00%)
-1000.9900 (≈ −0.99%)
100002.7181 (≈ e)
MAX_TICK = 4436361.84e19
MIN_TICK = -4436365.42e-20
MIN_TICKMAX_TICK은 양쪽 끝에서 sqrt_price_x64u128에 맞도록 선택됩니다. 모든 풀은 tick_lower >= MIN_TICKtick_upper <= MAX_TICK을 강제합니다. 실제로 웹 UI는 사용자가 도달 불가능한 틱에 유동성을 잠그는 것을 방지하기 위해 범위를 훨씬 더 좁게 제한합니다.

틱 간격

풀의 AmmConfig틱 간격을 정합니다. 이는 포지션이 끝점으로 사용할 수 있는 유일한 틱입니다. tick_spacing = 60이면 …, −120, −60, 0, 60, 120, … 틱만 유효합니다. 끝점 31로 포지션을 열려는 시도는 InvalidTickIndex로 되돌려집니다. 일반적인 공개 간격:
수수료 등급trade_fee_rate틱 간격틱 포지션당 가장 거친 가격 스텝
0.01%10010.01%
0.05%500100.10%
0.25%2500600.60%
1.00%100001201.21%
간격이 거칠수록 초기화해야 할 틱 배열이 적고, 넓은 포지션을 열 비용이 저렴하며, 가격 경계가 불명확합니다. 변동성이 높은 쌍은 보통 120-간격 등급에 있고, 스테이블코인은 1-간격 등급에 있습니다.

틱 배열

풀은 틱 단위 상태를 별도 계정에 저장하지 않습니다. 대신 TICK_ARRAY_SIZE 인접 틱(현재 Raydium CLMM에서는 60)이 단일 TickArrayState으로 압축됩니다. 배열의 첫 틱은 start_tick_index이고, 정확히 TICK_ARRAY_SIZE * tick_spacing 정수-틱 단위를 다룹니다. tick_spacing = 60TICK_ARRAY_SIZE = 60의 경우:
  • 각 틱 배열은 60 × 60 = 3600 정수 틱을 포함합니다.
  • start_tick_index는 3600의 배수입니다: …, -7200, -3600, 0, 3600, 7200, ….
tick_spacing = 60에서 포지션 끝점 t = 2040start_tick_index = 0인 틱 배열에 있습니다. 포지션 끝점 t = 4200start_tick_index = 3600인 배열에 있습니다.

배열이 생성될 때

틱 배열은 지연 로딩됩니다: 내부의 틱을 참조하는 첫 번째 포지션이 배열을 초기화하고 렌트를 지불합니다. 스왑은 틱 배열을 초기화하지 않습니다. 비트맵을 사용하여 초기화되지 않은 배열을 건너뜁니다. SDK의 포지션 열기 플로우는 선택한 범위를 검사하고 건드리는 틱 배열 목록을 계산하며 누락된 배열이 있으면 OpenPosition과 같은 트랜잭션에 init_tick_array 명령어를 추가합니다.

틱 배열은 닫혀지지 않습니다

틱 배열이 초기화되면 풀의 수명 동안 유지됩니다. 프로그램은 initialized_tick_count가 0으로 돌아온 후에도 틱 배열을 닫을 방법을 제공하지 않습니다. 틱 배열에 대한 렌트 복구가 없습니다. 배열에 접근한 첫 번째 포지션이 지불한 렌트는 그 계정에 영구적으로 잠깁니다. 이는 의도적 트레이드오프입니다: 기존 틱 배열을 재사용하는 것은 모든 후속 포지션에 무료이므로, 거래량이 많은 풀은 변동이 많아도 (pool, start_tick_index) 슬롯당 한 번만 렌트 비용을 지불합니다.

비트맵

“현재 틱의 좌측/우측 다음 초기화된 틱 찾기”는 빨라야 합니다. 스왑이 많은 틱을 지날 수 있기 때문입니다. 풀은 틱 0 주변 ±1,024 배열 범위에 대해 PoolState에 인라인 1비트-틱-배열 비트맵을 저장합니다. 그 범위 밖(전체 범위 포지션, 특이한 설정)에서는 TickArrayBitmapExtension이 오버플로우를 제공합니다. 스왑은 비트맵을 따라갑니다: lowest_set_bit_above(tick_current_array_index)는 스왑이 향하는 방향의 초기화된 틱이 있는 다음 배열을 제공합니다. 그 배열 내에서 유사한 비트 스캔이 다음 초기화된 틱을 찾습니다.

liquidity_grossliquidity_net

모든 초기화된 틱은 두 개의 유동성 값을 저장합니다:
  • liquidity_gross — 이 틱을 끝점으로 참조하는 모든 포지션의 L 합. liquidity_gross가 0에 도달하면 틱은 초기화되지 않고 비트맵에서 제거될 수 있습니다.
  • liquidity_net — 가격이 이 틱을 상향 이동(틱 공간에서 좌측에서 우측)할 때 풀 수준 liquidity로의 부호있는 변화. 이 틱이 크기 L인 포지션의 하한이면 +L에 기여하고, 상한이면 −L에 기여합니다.
작동 예시: 같은 풀의 두 포지션.
  • 포지션 A: tick_lower = -120, tick_upper = 0, 유동성 L_A = 100.
  • 포지션 B: tick_lower = -60, tick_upper = 60, 유동성 L_B = 50.
틱별 상태:
적용 대상liquidity_grossliquidity_net
-120A 하한100+100
-60B 하한50+50
0A 상한100−100
60B 상한50−50
다양한 tick_current 값에 대한 풀 수준 liquidity:
  • tick_current = -180: liquidity = 0 (어떤 포지션 전)
  • tick_current = -90: liquidity = 100 (A만 내부)
  • tick_current = -30: liquidity = 150 (A와 B 모두 내부)
  • tick_current = 30: liquidity = 50 (B만 내부)
  • tick_current = 90: liquidity = 0 (둘 다 초과)
스왑 중 모든 틱 크로스에서 프로그램은 liquidity_net(음수일 수 있음)을 PoolState.liquidity에 추가합니다. 이는 정확한 Uniswap-v3 메커니즘입니다.

NFT로서의 포지션

Raydium CLMM 포지션은 NFT입니다. 포지션을 열면 공급량 1인 완전히 새로운 민트를 호출자의 지갑에 민팅하고, 민트의 권한은 CLMM 프로그램입니다. 프로그램은 포지션 소유권을 CPI 시점에서 그 민트의 ATA에 잔액을 보유한 모든 사람에게 키합니다. 결과:
  • 포지션은 양도 가능합니다. 지갑은 NFT를 이전하여 포지션을 판매하거나 에어드롭할 수 있습니다. 새 보유자는 CollectRewards, IncreaseLiquidity 등을 호출할 수 있습니다.
  • 포지션은 CLMM 외부에서 주소 지정 가능합니다. 마켓플레이스와 지갑은 다른 NFT처럼 포지션을 표시합니다. SDK는 민트 메타데이터에 합리적인 name/symbol을 설정합니다.
  • 포지션의 PDA는 NFT 민트에서 파생됩니다. 현재 보유자를 모르고도 PersonalPositionState를 찾을 수 있습니다.

Token-2022 포지션

최신 CLMM 풀은 클래식 SPL Token 대신 Token-2022에서 포지션을 민팅할 수 있습니다. 프로그램은 두 개의 병렬 열기 명령어를 노출합니다. OpenPositionOpenPositionWithToken22Nft는 NFT 민트를 소유한 토큰 프로그램을 제외하고 동일한 의미를 가집니다. 지갑 및 마켓플레이스 호환성은 다릅니다. Raydium의 UI는 둘 다 추적합니다.

허용된 범위 규칙

OpenPosition 시점에서 프로그램은 다음을 강제합니다:
  1. tick_lower < tick_upper.
  2. tick_lower % tick_spacing == 0tick_upper % tick_spacing == 0.
  3. MIN_TICK <= tick_lowertick_upper <= MAX_TICK.
  4. 호출자가 tick_lowertick_upper를 포함하는 틱 배열을 제공했습니다. 이미 초기화되어 있거나 같은 트랜잭션의 init_tick_array를 통해.
  5. 이 포지션이 확장 범위로 확장되면 비트맵 확장 계정.
어떤 검사가 실패하면 명령어는 어떤 제약인지에 따라 InvalidTickIndex, NotApproved 또는 InsufficientLiquidity로 되돌려집니다. reference/error-codes를 참조하세요.

”범위 내” vs “범위 외”

포지션은 tick_lower <= tick_current < tick_upper일 때 범위 내입니다. 범위 내 포지션만 PoolState.liquidity에 기여하므로 스왑 수수료를 얻습니다. 범위 외 포지션:
  • 범위가 지나간 한 가지 토큰의 100%를 보유합니다. 구체적으로, tick_current < tick_lower이면 포지션은 토큰1만 보유합니다(가격이 멀어지면서 이미 “판매”되었음). tick_current >= tick_upper이면 토큰0만 보유합니다.
  • 스왑 수수료를 얻지 않습니다.
  • 풀의 보상 스트림이 범위 외 유동성에 방출하면 계속해서 보상을 적립합니다. 하지만 Raydium의 기본 동작은 “범위 내에만 방출”로, Uniswap v3 규칙과 일치합니다. products/clmm/fees를 참조하세요.
CLMM 포지션을 관리하는 LP는 가격이 움직이면서 포지션을 범위 내로 유지하는 데 대부분의 관심을 집중합니다.

일반적인 통합 함정

  • 간격을 벗어난 끝점. 목표 가격에서 틱을 계산하는 코드는 OpenPosition에 전달하기 전에 tick_spacing의 배수로 스냅해야 합니다. SDK 헬퍼(TickUtils.getTickWithPriceAndTickspacing)는 이를 수행합니다. 직접 작성한 수학은 종종 그렇지 않습니다.
  • 누락된 틱 배열. 넓은 포지션을 열려면 여러 틱 배열을 초기화해야 할 수 있습니다. 쓰기 가능한 계정으로 전달하는 것을 잊으면 되돌려집니다. SDK의 openPositionFromBase는 목록을 반환합니다.
  • 스왑 후 오래된 틱. tick_current는 한 번의 스왑에서 많은 틱을 지날 수 있습니다. UX가 한 RPC 호출에서 “현재 틱”을 표시하고 나중에 포지션을 열면, 라이브 가격에 대한 상대 포지션이 수십 틱 떨어져 있을 수 있습니다. 서명하기 직전에 다시 가져오세요.
  • 추가 메타데이터가 있는 포지션 NFT. Raydium 포지션을 인식하는 지갑을 구축하면 하드코딩된 메타데이터 필드가 아닌 민트 권한(= CLMM 프로그램의 PDA)으로 감지하세요.

다음으로 이동

  • 수학 — 틱 경계가 참여하는 스왑 단계 통과 및 수수료 성장 도출.
  • 계정TickArrayStatePositionState 레이아웃.
  • 수수료와 보상 — 범위 내 여부가 수수료 적립을 제어하는 방법.
  • algorithms/clmm-math — 집중 유동성 공식의 공유 도출.
출처: