Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

README.md

6.44 Kb · 228 lines

Position

NFT-based liquidity position management for concentrated liquidity.

Overview

Each liquidity position is a unique GRC721 NFT. Stored state includes the pool key, price range, liquidity, fee-growth checkpoints, tokens owed, burned marker, and operator. Current token balances are derived from the current pool price, range, and liquidity; they are not permanently stored balances.

The pool accounting key encodes only the lower/upper tick pair and is scoped by pool. NFTs with the same range in one pool share the pool-level accounting entry.

Configuration

  • Withdrawal Fee: 1% by default on fee-bearing swap-fee collection
  • Max Position Size: No separate position-level cap; pool tick limits apply
  • Transfers: Unstaked NFTs follow GRC721 owner/approval/operator rules; staked NFTs are locked to staker-mediated transfers

Core Functions

Mint

Creates new position NFT with initial liquidity.

  • Validates tick range alignment
  • Calculates optimal token ratio
  • Returns actual amounts used

IncreaseLiquidity

Adds liquidity to an existing position.

  • Maintains the existing price range
  • Uses the current-price token ratio
  • Can clear a burned marker when the position is used again

DecreaseLiquidity

Removes liquidity while keeping the NFT.

  • One atomic public operation: internally collects swap fees, burns liquidity, then collects principal through the pool's fee-free Collect path
  • Returns fee amounts net of the withdrawal fee and collected principal
  • Amount-minimum checks apply to the principal actually collected

CollectFee

Claims accumulated swap fees without removing liquidity.

  • No liquidity removal required
  • Returns net collected amounts plus the raw pre-withdrawal-fee amounts
  • The configured withdrawal fee applies only to this fee-bearing path

Reposition

Updates an existing position's price range.

  • Requires the position to be clear first (zero liquidity and tokens owed)
  • Reuses the same position ID and NFT
  • Adds new liquidity to the updated range and clears the burned marker

Technical Details

Tick Alignment

Ticks must align with pool's tick spacing:

0.01% fee: every 1 tick
0.05% fee: every 10 ticks
0.3% fee: every 60 ticks
1% fee: every 200 ticks

Optimal Range Width

Stable Pairs (USDC/USDT):

  • Narrow: ±0.05% (max efficiency)
  • Medium: ±0.1% (balanced)
  • Wide: ±0.5% (safety)

Correlated Pairs (WETH/stETH):

  • Narrow: ±0.5%
  • Medium: ±1%
  • Wide: ±2%

Volatile Pairs (WETH/USDC):

  • Narrow: ±5%
  • Medium: ±10%
  • Wide: ±25%

Capital Efficiency

Concentration factor vs infinite range:

Range ±0.1%  → 2000x efficient
Range ±1%    → 200x efficient
Range ±10%   → 20x efficient
Range ±50%   → 4x efficient

Token Calculations

For liquidity L and square-root prices sqrtLower, sqrtCurrent, and sqrtUpper:

Below range (current < lower, token0 only):

amount0 = L * (sqrtUpper - sqrtLower) / (sqrtUpper * sqrtLower)
amount1 = 0

In range (lower <= current < upper, both tokens):

amount0 = L * (sqrtUpper - sqrtCurrent) / (sqrtUpper * sqrtCurrent)
amount1 = L * (sqrtCurrent - sqrtLower)

Above range (current >= upper, token1 only):

amount0 = 0
amount1 = L * (sqrtUpper - sqrtLower)

Approval and Transfer Requirements

Mint, IncreaseLiquidity, and Reposition pull token0 and token1 from the caller inside the pool realm, so the approved spender is the pool realm address, not the position realm.

  • Approve the pool realm for both token contracts before calling a liquidity-adding function.
  • Approving the position realm alone is not sufficient; the position realm never holds or pulls the pair tokens itself.
  • Approve at least amount0Desired / amount1Desired. Any desired amount the pool does not consume stays with the caller.
  • DecreaseLiquidity and CollectFee pay out to the caller and require no approval.
1// Approve the pool realm for both pair tokens before minting
2poolAddress := access.MustGetAddress(prabc.ROLE_POOL.String())
3weth.Approve(cross(cur), poolAddress, 1000000)
4usdc.Approve(cross(cur), poolAddress, 2000000000)

Usage

These snippets call the public domain proxy from a realm function with a current cur token. Import the proxy package and qualify its function names in integrating code.

 1// Mint a new position through the domain proxy
 2tokenId, liquidity, amount0, amount1 := Mint(
 3    cross(cur),
 4    "gno.land/r/gnoland/wugnot.wugnot", // token0
 5    "gno.land/r/gnoswap/gns.GNS",   // token1
 6    3000,                      // fee
 7    -887220,                   // tickLower
 8    887220,                    // tickUpper
 9    "1000000",                 // amount0Desired
10    "2000000000",              // amount1Desired
11    "950000",                  // amount0Min
12    "1900000000",              // amount1Min
13    deadline,
14    recipient,                 // mintTo
15    "",                        // referrer
16)
17
18// Add liquidity
19positionId, liquidity, amount0, amount1, poolPath := IncreaseLiquidity(
20    cross(cur),
21    tokenId,
22    "500000",
23    "1000000000",
24    "475000",
25    "950000000",
26    deadline,
27)
28
29// Collect swap fees
30positionId, collected0, collected1, poolPath, rawAmount0, rawAmount1 := CollectFee(
31    cross(cur),
32    tokenId,
33)
34
35// Reposition to a new range (requires a clear position)
36positionId, liquidity, tickLower, tickUpper, amount0, amount1 := Reposition(
37    cross(cur),
38    tokenId,
39    -443610,                   // new tickLower
40    443610,                    // new tickUpper
41    "1000000",                 // amount0Desired
42    "2000000000",              // amount1Desired
43    "950000",                  // amount0Min
44    "1900000000",              // amount1Min
45    deadline,
46)

Lifecycle

A full decrease that leaves zero liquidity and zero tokens owed sets the burned marker but does not destroy the NFT. IncreaseLiquidity and Reposition clear the marker when the position is used again; the marker does not by itself block an increase.

Security

  • Tick range validation prevents invalid positions
  • Slippage protection applies to liquidity-changing operations; fee collection has no amount-minimum parameter
  • Deadlines prevent stale liquidity-changing transactions
  • Unstaked NFTs follow standard GRC721 transfer authorization; staked NFTs can move only through staker-mediated flows
  • Liquidity changes and repositioning require the owner; fee collection also permits the position's approved operator where applicable