Skip to main content
Trang này được dịch tự động bằng AI. Phiên bản tiếng Anh là bản chính thức.Xem bản tiếng Anh →

Tại sao ticks tồn tại

Thanh khoản của CLMM được tập trung vào các khoảng giá. Để làm cho các khoảng này khả thi trên chuỗi, giá được lượng tử hóa thành các số nguyên ticks, trong đó mỗi tick là một bội số hằng số của tick trước: price(i)=1.0001i\text{price}(i) = 1.0001^{\,i} Một tick tương ứng với mức giá di chuyển 0,01%, hoặc khoảng 1 điểm cơ sở. Ánh xạ như sau: MIN_TICKMAX_TICK được chọn sao cho sqrt_price_x64 vừa trong một u128 ở cả hai đầu. Mỗi pool đảm bảo rằng tick_lower >= MIN_TICKtick_upper <= MAX_TICK. Trong thực tế, giao diện web cắt giới hạn khoảng thành một khoảng hẹp hơn nhiều để ngăn người dùng khóa thanh khoản vào các ticks không thể tiếp cận.

Khoảng cách tick

AmmConfig của một pool xác định khoảng cách tick — các tick duy nhất mà một vị trí được phép sử dụng làm điểm cuối. Nếu tick_spacing = 60, chỉ các ticks …, −120, −60, 0, 60, 120, … là hợp lệ. Nếu cố gắng mở một vị trí với điểm cuối 31 sẽ hoàn tác với InvalidTickIndex. Các khoảng cách được công bố phổ biến: Khoảng cách càng thô, càng ít tick arrays cần khởi tạo, chi phí mở một vị trí rộng càng thấp, và ranh giới giá càng mờ. Các cặp biến động thường nằm trong các tầng khoảng cách 120; các stables nằm trong các tầng khoảng cách 1.

Tick arrays

Pool không lưu trữ trạng thái cho mỗi tick trong các tài khoản riêng biệt. Thay vào đó, TICK_ARRAY_SIZE ticks liền kề (60 trong Raydium CLMM hiện tại) được đóng gói vào một TickArrayState duy nhất. Tick đầu tiên của mảng là start_tick_index của nó, và nó bao phủ chính xác TICK_ARRAY_SIZE * tick_spacing đơn vị tick nguyên. Đối với tick_spacing = 60TICK_ARRAY_SIZE = 60:
  • Mỗi tick array bao trùm 60 × 60 = 3600 ticks nguyên.
  • start_tick_index là bội số của 3600: …, -7200, -3600, 0, 3600, 7200, ….
Một điểm cuối vị trí t = 2040tick_spacing = 60 nằm trong tick array có start_tick_index = 0. Một điểm cuối vị trí t = 4200 nằm trong mảng có start_tick_index = 3600.

Khi một mảng được tạo

Một tick array là lười biếng: vị trí đầu tiên tham chiếu bất kỳ tick nào bên trong nó sẽ khởi tạo mảng, trả tiền thuê. Các swaps không khởi tạo tick arrays — chúng bỏ qua các mảng chưa khởi tạo bằng cách sử dụng bitmap. Luồng open-position của SDK kiểm tra khoảng được chọn, tính toán danh sách các tick arrays nó chạm vào, và thêm các lệnh init_tick_array trong cùng một giao dịch với OpenPosition nếu có bất kỳ lệnh nào bị thiếu.

Tick arrays không được đóng

Sau khi một tick array đã được khởi tạo, nó tồn tại suốt vòng đời của pool. Chương trình không cung cấp đường dẫn để đóng một tick array, ngay cả sau khi initialized_tick_count quay trở lại không. Không có khôi phục tiền thuê cho tick arrays; tiền thuê được trả bởi vị trí đầu tiên chạm vào một mảng bị khóa vào tài khoản đó vĩnh viễn. Đây là một sự đánh đổi cố ý: tái sử dụng một tick array hiện có là miễn phí cho mỗi vị trí tiếp theo, vì vậy một pool được giao dịch nhiều chỉ trả chi phí thuê một lần cho mỗi slot (pool, start_tick_index) bất kể sự thay đổi.

Bitmap

Tìm “tick được khởi tạo tiếp theo ở bên trái/phải của tick hiện tại” phải nhanh — một swap có thể vượt qua nhiều ticks. Pool lưu trữ một bitmap 1-bit-per-tick-array nội tuyến trong PoolState cho khoảng ±1.024 mảng xung quanh tick 0. Ngoài khoảng đó (vị trí full-range, thiết lập kỳ lạ), TickArrayBitmapExtension cung cấp tràn. Một swap đi qua bitmap: lowest_set_bit_above(tick_current_array_index) cho mảng tiếp theo có một tick được khởi tạo ở phía swap đang vượt qua. Trong mảng đó, một quét bit tương tự xác định vị trí tick được khởi tạo tiếp theo.

liquidity_grossliquidity_net

Mỗi tick được khởi tạo lưu trữ hai giá trị thanh khoản:
  • liquidity_gross — tổng của L trên tất cả các vị trí tham chiếu tick này làm điểm cuối. Khi liquidity_gross đạt không, tick trở thành chưa khởi tạo và có thể bị xóa khỏi bitmap.
  • liquidity_net — thay đổi có dấu đối với liquidity cấp pool khi giá vượt qua tick này di chuyển hướng lên (trái sang phải trong không gian tick). Nếu tick này là giới hạn dưới của một vị trí có kích thước L, nó đóng góp +L; nếu nó là giới hạn trên của vị trí đó, nó đóng góp −L.
Ví dụ minh họa: hai vị trí trên cùng một pool.
  • Vị trí A: tick_lower = -120, tick_upper = 0, thanh khoản L_A = 100.
  • Vị trí B: tick_lower = -60, tick_upper = 60, thanh khoản L_B = 50.
Trạng thái từng tick: liquidity cấp pool cho các giá trị tick_current khác nhau:
  • tick_current = -180: liquidity = 0 (trước bất kỳ vị trí nào)
  • tick_current = -90: liquidity = 100 (chỉ bên trong A)
  • tick_current = -30: liquidity = 150 (bên trong A và B)
  • tick_current = 30: liquidity = 50 (chỉ bên trong B)
  • tick_current = 90: liquidity = 0 (vượt qua cả hai)
Trên mỗi lần vượt qua tick trong một swap, chương trình thêm liquidity_net (có thể âm) vào PoolState.liquidity. Đây là cơ chế chính xác của Uniswap-v3.

Vị trí dưới dạng NFTs

Một vị trí Raydium CLMM là một NFT. Mở một vị trí sẽ mint một mint hoàn toàn mới với cung cấp 1 vào ví của người gọi, và quyền của mint là chương trình CLMM. Chương trình liên kết quyền sở hữu vị trí với bất kỳ ai nắm giữ số dư trong một ATA của mint đó tại thời điểm CPI. Hậu quả:
  • Vị trí thường có thể chuyển nhượng được. Một ví có thể bán hoặc airdrop một vị trí bằng cách chuyển NFT. Chủ sở hữu mới sau đó có thể gọi CollectRewards, IncreaseLiquidity, v.v. Ngoại lệ là một vị trí bị đóng băng theo đường dẫn restricted-issuer bên dưới.
  • Vị trí có thể được địa chỉ hóa bên ngoài CLMM. Các sàn giao dịch và ví hiển thị vị trí giống như các NFT khác. SDK đặt name/symbol hợp lý trên siêu dữ liệu mint.
  • PDA của một vị trí được lấy từ NFT mint. Bạn có thể tìm PersonalPositionState mà không cần biết ai hiện đang nắm giữ nó.

Vị trí restricted-issuer

Mỗi mint NFT vị trí được tạo sau bản nâng cấp 2026-08 ghi lại pool_state CLMM của nó làm quyền freeze. Điều này không có nghĩa là mỗi vị trí mới đều bị đóng băng. Đối với các pool thông thường và mỗi vị trí không khớp, tài khoản token NFT vẫn không bị đóng băng và có thể chuyển nhượng được. PDA của pool không thể ký bên ngoài chương trình CLMM, và CLMM không cung cấp lệnh freeze mục đích chung. Đóng băng yêu cầu cả hai điều kiện này:
  1. Vị trí được mở thông qua OpenPositionV2 hoặc OpenPositionWithToken22Nft.
  2. Ít nhất một mint vault của pool mang quyền freeze từ danh sách restricted-issuer được mã hóa cứng của chương trình.
Chỉ khi cả hai điều kiện được giữ thì CLMM mới đóng băng tài khoản token NFT vị trí mới được tạo ngay sau khi mint. OpenPosition V1 không áp dụng bộ lọc này. Xem reference/program-addresses để biết danh sách hiện tại. Một vị trí bị đóng băng:
  • Không thể chuyển NFT của nó sang tài khoản token khác.
  • Không thể thay đổi chủ sở hữu của tài khoản token NFT.
  • Vẫn có thể tăng hoặc giảm thanh khoản và thu thập phí hoặc phần thưởng khi chủ sở hữu được ghi lại ký.
  • Vẫn có thể đóng. ClosePosition sử dụng pool PDA để rã đông tài khoản NFT, sau đó đốt NFT và đóng các tài khoản vị trí trong cùng một lệnh.
Các vị trí hiện có không được di chuyển hoặc đóng băng hồi tưởng. Các mint NFT vị trí được tạo trước bản nâng cấp giữ lại cài đặt quyền freeze trước đó của chúng.
Một client đóng một vị trí bị đóng băng phải thêm pool_state của vị trí làm tài khoản còn lại đầu tiên vào ClosePosition. Danh sách tài khoản IDL được khai báo không thay đổi, vì vậy các client cũ hơn có thể mở một vị trí bị đóng băng thành công nhưng sau đó không thể đóng nó với AccountLack. Cập nhật trình tạo close trước khi hỗ trợ các pool này.

Vị trí Token-2022

CLMM có thể mint một NFT vị trí theo Token SPL cổ điển thông qua OpenPositionV2, hoặc theo Token-2022 thông qua OpenPositionWithToken22Nft. Cả hai đường dẫn V2 kiểm tra các mint vault của pool và áp dụng quy tắc freeze restricted-issuer tương tự. OpenPosition V1 là đường dẫn token cổ điển kế thừa và không thể phục vụ một pool có các mint vault Token-2022. Khả năng tương thích ví và sàn giao dịch khác nhau; giao diện người dùng của Raydium theo dõi cả hai chương trình NFT.

Quy tắc khoảng cho phép

Tại thời điểm OpenPosition, chương trình thực thi:
  1. tick_lower < tick_upper.
  2. tick_lower % tick_spacing == 0tick_upper % tick_spacing == 0.
  3. MIN_TICK <= tick_lowertick_upper <= MAX_TICK.
  4. Người gọi đã cung cấp các tick arrays chứa tick_lowertick_upper — hoặc đã được khởi tạo hoặc thông qua init_tick_array trong cùng một giao dịch.
  5. Tài khoản mở rộng bitmap, nếu vị trí này mở rộng vào phạm vi mở rộng.
Nếu bất kỳ kiểm tra nào không thành công, lệnh sẽ hoàn tác với InvalidTickIndex, NotApproved, hoặc InsufficientLiquidity tùy thuộc vào ràng buộc nào. Xem reference/error-codes.

”In-range” so với “out-of-range”

Một vị trí in range khi tick_lower <= tick_current < tick_upper. Chỉ các vị trí in-range mới đóng góp vào PoolState.liquidity và do đó chỉ chúng mới kiếm được phí swap. Một vị trí out-of-range:
  • Nắm giữ 100% của một token (token mà khoảng của nó đã vượt qua). Cụ thể, nếu tick_current < tick_lower, vị trí chỉ nắm giữ token1 (nó đã được “bán” bởi giá di chuyển đi); nếu tick_current >= tick_upper, nó chỉ nắm giữ token0.
  • Không kiếm được phí swap.
  • Tiếp tục tích lũy phần thưởng nếu các luồng phần thưởng của pool phát hành cho thanh khoản out-of-range — nhưng hành vi mặc định của Raydium là “chỉ phát hành cho in-range”, phù hợp với quy ước Uniswap v3. Xem products/clmm/fees.
Các LP quản lý vị trí CLMM dành hầu hết sự chú ý của họ để giữ các vị trí in range khi giá di chuyển.

Những cạm bẫy tích hợp phổ biến

  • Điểm cuối không cách đều. Mã tính toán một tick từ giá mục tiêu phải snap thành bội số của tick_spacing trước khi chuyển nó tới OpenPosition. Các trình trợ giúp SDK (TickUtils.getTickWithPriceAndTickspacing) làm điều này; toán học tự tạo thường không.
  • Tick arrays bị thiếu. Mở một vị trí rộng có thể yêu cầu khởi tạo một số tick arrays; quên chuyển chúng làm tài khoản có thể ghi sẽ hoàn tác. SDK’s openPositionFromBase trả về danh sách cho bạn.
  • Tick cũ sau một swap. tick_current có thể vượt qua nhiều ticks trong một swap. Nếu UX của bạn hiển thị một “current tick” từ một lệnh gọi RPC và sau đó mở một vị trí trong một lệnh gọi sau, vị trí tương đối so với giá trực tiếp có thể sai lệch hàng chục ticks. Tìm nạp lại ngay trước khi ký.
  • NFT vị trí có siêu dữ liệu bổ sung. Nếu bạn xây dựng một ví nhận ra các vị trí Raydium, hãy sử dụng vị trí PDA / dữ liệu chương trình và không phải một trường siêu dữ liệu được mã hóa cứng. Các mint vị trí mới sử dụng pool PDA làm quyền mint và freeze tại thời điểm tạo; quyền mint bị xóa sau khi mint NFT duy nhất.
  • Giả định mỗi vị trí đều có thể chuyển nhượng được. Đọc trạng thái isFrozen của tài khoản token NFT trước khi hiển thị các hành động chuyển, sàn giao dịch, escrow, hoặc Burn & Earn.

Tiếp theo

  • Math — bước swap và dẫn xuất tăng trưởng phí mà ranh giới tick tham gia.
  • Accounts — bố cục TickArrayStatePositionState.
  • Phí và phần thưởng — cách in-range-ness gating accrual phí.
  • algorithms/clmm-math — dẫn xuất chung của các công thức thanh khoản tập trung.
Nguồn: