Position NFT
Description
The Position NFT represents a single liquidity position as a standalone, TEP-62/TEP-64 compatible NFT contract. Its address is deterministic — derived from {pool, index} — so the pool can always recompute and trust it. The NFT holds the position's liquidity, tick range and fee counters, and works hand in hand with the Pool: the pool seeds it on mint, and burns flow through it so it can verify ownership and tick consistency. The owner can transfer it, deposit part of it into their Account, or burn it — including via a plain wallet burn text comment, a front-end-free escape hatch (see BURN_WITH_TEXT).
Interface
getPoolAddress
(address) getPoolAddress ()
This function returns pool address that created this Position NFT
Return Values
| # | Type | Description |
|---|---|---|
| 0 | address | address in question |
getUserAddress
(address) getUserAddress ()
This function returns user address that owned created this Position NFT
Return Values
| # | Type | Description |
|---|---|---|
| 0 | address | address in question |
getPositionInfo
(int, int, int, int, int) getPositionInfo ()
This function returns data stored in Position NFT and is related to the position
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | liquidity that this position owns |
| 1 | int | lower tick of the position |
| 2 | int | upper tick of the position |
| 3 | int | fee growth of jetton0 in the given range at moment of the creation or latest collect of the NFT position |
| 4 | int | fee growth of jetton1 in the given range at moment of the creation or latest collect of the NFT position |
get_nft_data
(bool, int, address, address, cell) get_nft_data ()
This function returns data of this Position NFT that is related to NFT as TEP-62 It also attaches some values to transfer them to pool
Return Values
| # | Type | Description |
|---|---|---|
| 0 | bool | Is position active (in our case, if positionv3::liquidity != 0) |
| 1 | int | positionv3::index |
| 2 | address | positionv3::pool_address |
| 3 | address | Owner address (positionv3::user_address) |
| 4 | cell | Content of the NFT. The cell with a dict that has position data appended to the cell for the pool to parse |
Messages
POSITIONNFTV3_POSITION_INIT
Opcode : 0xd5ecca2a
Initial message that pools sends to the NFT after state_init Access Rights: Accepted only from the pool that owns this NFT (its address is fixed at creation). Sent right after deployment to fill in the position.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| user_address | Address(267) | NFT owner | |
| 0 | Cell(0) pos0 | Position | |
| 0 | liquidity | Uint(128) | Amount of the liquidity |
| 0 | tickLower | Int(24) | Lower tick of the NFT |
| 0 | tickUpper | Int(24) | Upper tick of the NFT |
| 0 | feeGrowthInside0LastX128 | Int(256),x128 | Fees collected before the position was opened or updated for jetton0 (in pool terms) |
| 0 | feeGrowthInside1LastX128 | Int(256),x128 | Fees collected before the position was opened or updated for jetton1 (in pool terms) |
| 0 | 0 | Maybe Cell(1) fees | newFees |
| 0->0 | feeGrowthInside0LastX128 | Int(256),x128 | Optional updated fee growth for jetton0 inside the range, per unit of liquidity, 128.128 fixed point (used by the indexer to track fees to collect) |
| 0->0 | feeGrowthInside1LastX128 | Int(256),x128 | Optional updated fee growth for jetton1 inside the range, per unit of liquidity, 128.128 fixed point (used by the indexer to track fees to collect) |
| 1 | Cell(0) Indexer | Fee counters From | |
| 1 | seqno | Uint(64),Indexer | Indexer-only. Pool sequence number at mint time |
| 1 | nftIndex | Uint(64),Indexer | Indexer-only. Index of this position NFT in the collection |
| 1 | jetton0Amount | Coins(124),Indexer | Indexer-only. Amount of jetton0 deposited into this position at mint |
| 1 | jetton1Amount | Coins(124),Indexer | Indexer-only. Amount of jetton1 deposited into this position at mint |
| 1 | tick | Int(24),Indexer | Indexer-only. Pool current tick at mint time |
| 1 | sqrtPriceX96 | Uint(160),PriceX96, Indexer | Current price |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POSITIONNFTV3_POSITION_INIT**POSITIONNFTV3_POSITION_INIT#d5ecca2a
query_id:uint64
user_address:MsgAddress
pos0:^Pos0Type
Indexer:^IndexerType
= ContractMessages;
_
feeGrowthInside0LastX128:int256
feeGrowthInside1LastX128:int256
= FeesType;
_
liquidity:uint128
tickLower:int24
tickUpper:int24
feeGrowthInside0LastX128:int256
feeGrowthInside1LastX128:int256
fees:(Maybe ^FeesType)
= Pos0Type;
_
seqno:uint64
nftIndex:uint64
jetton0Amount:(VarUInteger 16)
jetton1Amount:(VarUInteger 16)
tick:int24
sqrtPriceX96:uint160
= IndexerType;
POSITIONNFT_POSITION_BURN
Opcode : 0x46ca335a
Message from the pool that is part of burn process. This message carries new feeGrowthInside?Last values form the pool Access Rights: Accepted only from the pool that owns this NFT. It is the pool's reply within the burn flow, carrying the fee counters needed to settle the position.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| nft_owner | Address(267) | NFT owner to receive funds | |
| liquidity2Burn | Uint(128) | Amount of the liquidity to burn, 0 is a valid amount, in this case only collected fees would be returned | |
| tickLower | Int(24) | Lower tick of the NFT. NFT would check that it is the same as in position | |
| tickUpper | Int(24) | Upper tick of the NFT. NFT would check that it is the same as in position | |
| 0 | Cell(0) old_fee_cell | Fee counters From | |
| 0 | feeGrowthInside0LastX128 | Int(256),x128 | Current fee growth for jetton0 inside the position range supplied by the pool, per unit of liquidity, 128.128 fixed point. The NFT uses the delta versus its stored value to compute fees owed |
| 0 | feeGrowthInside1LastX128 | Int(256),x128 | Current fee growth for jetton1 inside the position range supplied by the pool, per unit of liquidity, 128.128 fixed point. The NFT uses the delta versus its stored value to compute fees owed |
| action | Cell(0),Maybe | Cell that describes what to do with burn result |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POSITIONNFT_POSITION_BURN**POSITIONNFT_POSITION_BURN#46ca335a
query_id:uint64
nft_owner:MsgAddress
liquidity2Burn:uint128
tickLower:int24
tickUpper:int24
old_fee_cell:^Old_fee_cellType
action:(Maybe ^Cell)
= ContractMessages;
_
feeGrowthInside0LastX128:int256
feeGrowthInside1LastX128:int256
= Old_fee_cellType;
BURN_WITH_TEXT
Opcode : 0x0
A plain wallet transfer with the text comment burn, sent by the position owner to burn the whole position manually. Meant for an ordinary, non-technical user: no front-end, no SDK and no knowledge of message layouts required. From any wallet (Tonkeeper, Tonhub, etc.) the owner just sends a small amount of GRAM to the address of their own position NFT and types burn in the comment field. The wallet encodes that comment in the standard TON way — 32 zero bits (op = 0) followed by the UTF-8 bytes of the text, no query_id — and the NFT recognises it.
The comment must be exactly burn: the contract compares the whole comment against the literal burn, so any extra text or argument is rejected (WRONG_COMMAND). There is no burn <amount> form — this path always burns the entire position. Accepted only from the current owner of the NFT (otherwise INVALID_CALLER); the NFT then forwards START_BURN to the pool for the position's full liquidity.
Why it exists: a deliberate self-custodial escape hatch. The owner can always reclaim their funds directly on-chain with nothing but a wallet — no front-end, SDK or indexer needed — even if the indexer is down or the web front-end is unavailable to them (e.g. geo-blocking). (At the TS level the same body is beginCell().storeUint(0, 32).storeBuffer(Buffer.from("burn")).endCell() — see sendBurnText in jest/burn.system.spec.ts — but a real user never needs this; it is just the wallet comment under the hood.)
Dangers — mind the gas. The NFT requires a minimum attached value (BURN_GAS_AMOUNT, ~0.1 GRAM in release builds) and rejects anything below it with INSUFFICIENT_GAS. That minimum only covers the first hop, though: a burn fans out into a multi-contract round-trip (NFT → pool → router → jetton wallets). If too little GRAM is attached, an intermediate step can run out of gas and the funds are NOT recovered automatically. Over-fund to be safe (the tests attach ~0.5 GRAM); unspent gas is returned. Always send the comment to the correct NFT address — a typo or a foreign/forged NFT just wastes the gas spent.
Access Rights:
Allowed only for the current NFT owner. Sending the text comment burn initiates a full-position burn toward the pool.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| text | String(32) | Text with command |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for BURN_WITH_TEXT**BURN_WITH_TEXT#0
text:Text
= ContractMessages;
POSITIONNFTV3_NFT_TRANSFER
Opcode : 0x5fcc3d14
Transfer LP NFT to another user. Please be warned that some UI elements could be unable to track it. However with SDK it still can be burned Access Rights: Allowed only for the current NFT owner. Transfers ownership of the position to a new address.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| new_owner | Address(267) | New NFT owner | |
| response_destination | Address(267) | Address to receive response | |
| custom_payload | Cell(0),Maybe | Custom information for NFT. Ignored by our implementation | |
| forward_amount | Coins(124) | Amount of coins to forward for processing | |
| forward_payload | Cell(0),Either | Payload for processing |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POSITIONNFTV3_NFT_TRANSFER**POSITIONNFTV3_NFT_TRANSFER#5fcc3d14
query_id:uint64
new_owner:MsgAddress
response_destination:MsgAddress
custom_payload:(Maybe ^Cell)
forward_amount:(VarUInteger 16)
forward_payload:(Either ^Cell Cell)
= ContractMessages;