- A public
Positionmapping entry holds the pool, tick range, liquidity, fee checkpoints, and amounts owed. - A
PositionNFTrecord authorizes the holder to operate that position.
token_id can follow its public range, liquidity, fee checkpoints, and owed balances, but the positions mapping does not contain the owner’s address.
Every position binds two private addresses at mint:
- The owner recipient receives the
PositionNFTand authorizes position operations. - The immutable withdrawal address receives every later
collectpayout record.
- Select a pool and range.
- Mint and preserve the returned ownership record.
- Monitor price, inventory, fees, and pool controls.
- Increase, decrease, or collect as needed.
- Remove all liquidity, collect all value, and burn the empty position.
Finish line
A position is fully closed only when:- public liquidity is zero
tokens_owed0andtokens_owed1are zero- the final collect records are stored
burnis accepted- the public position row is removed
- the final
PositionNFThas been consumed
Client state you must keep
Store the deployment ID, pool key, token ID, latestPositionNFT, transaction state, and returned token records together. Do not update record inventory until acceptance is known.
If you only need to display public positions, use the REST API. The workflow below is for applications that construct position transactions.
Before supplying liquidity
Confirm the deployment and pool before constructing a transaction. A pool key identifies a sorted token pair and fee tier, but mappings are scoped to a program deployment. The same logical pool key in an old and a new program does not make their state interchangeable. Check:- The intended program ID and deployment checksum
- Canonical token0 and token1 order
- Pool fee in fee pips, where 3000 means 0.30 percent
- Tick spacing bound to that fee tier
- Current tick and square-root price
- Token display decimals and wrapper relationships from the deployment registry
- Pool, token, pair, and global pause state
- The predecessor hints required for any new boundary tick
u256::U256 Q128.128 fee-growth values. A deployment using scalar fee-growth fields cannot be upgraded in place to this layout. A production cutover requires a fresh deployment. LP tools should bind records, mapping queries, and pool configuration to one program ID rather than assuming all shield_swap deployments share state.
Read Pools and keys, Native token amounts, and Choosing a range before building a mint.
Mint
mint consumes token records and creates:
- A public
token_id - A
PositionNFTrecord - Change records for unused input-record value
- An investigator-owned mint compliance record
- A public
Positionmapping entry
desired - used excess in tokens_owed. Change records contain only input-record value above the desired transfers.
Persist the returned PositionNFT ciphertext and its decrypted representation. The input records are spent. Reconstructing the public fields does not recreate a valid Aleo ownership record.
See Minting a position.
Monitor an open position
An open position can be:- In range and contributing active liquidity
- Below range and represented economically by token0
- Above range and represented economically by token1
- Frozen and removed from active liquidity
- At zero liquidity but still holding amounts owed
tokens_owed0 and tokens_owed1 fields are not live fee quotes. Pending fees remain encoded in the difference between current inside growth and the position’s last checkpoint until a settlement operation runs.
For an independent fee estimate, read the slot, both boundary ticks, and the position at a consistent chain height. Apply wrapping 256-bit subtraction. A normal signed subtraction is wrong after accumulator wrap.
See Fee entitlements and Q128.128 fee growth.
Increase
increase_liquidity consumes the current PositionNFT and token records. It settles fees earned by the old liquidity amount before adding new liquidity. This creates a clean epoch boundary:
PositionNFT and change records returned by the transaction.
The increase credits both settled fees and any desired - used funding excess to tokens_owed; it does not return that AMM-held excess in the change records.
See Increasing liquidity.
Decrease
decrease_liquidity reduces liquidity but does not transfer tokens to the LP. It settles all pending fees for the position’s full pre-decrease liquidity and credits both those fees and the withdrawn principal to tokens_owed.
A partial decrease leaves the position active with less liquidity. A full decrease leaves a zero-liquidity position that may still hold amounts owed. Boundary ticks whose liquidity_gross reaches zero are removed from the initialized-tick index.
Decrease remains an accounting operation. The LP receives tokens only through collect.
See Decreasing liquidity.
Collect
collect requests explicit raw amounts for token0 and token1. It:
- Derives current fee growth inside the range.
- Settles pending fees using current position liquidity.
- Adds settled fees to previously stored amounts owed.
- Checks that each requested amount is available.
- Returns token payout records.
- Stores the uncollected remainder and a new fee checkpoint.
PositionNFT. The records keep payout ownership confidential from public observers, but they do not hide fee growth, position state, or collection amounts.
Each settlement floors fractional entitlement to an integer native base unit. Calling collect frequently can discard more sub-unit dust than settling less often. Dust is not reassigned to another LP, and the contract has no generic sweep for unaccounted surplus.
See Collecting and burning.
Burn
burn removes an already empty position record. It succeeds only when:
PositionNFT and removes the public positions row. A correct closeout sequence is:
- Decrease all remaining liquidity.
- Collect all token0 and token1 owed.
- Confirm the public position has zero balances.
- Burn the position.
Record-handling rule
Every position operation consumes an ownership record. Increase, decrease, and collect return a replacement. Burn does not. Wallets and institutional custody systems should update their record inventory only after the transaction is accepted and should retain the prior ciphertext until acceptance is confirmed. Do not rely on filenames, local timestamps, or the stabletoken_id to determine which record is unspent. Use transaction state and record spent status.
Operating checklist
For each position:- Track program ID, pool key, token ID, and latest ownership record together.
- Confirm the private owner recipient and immutable withdrawal address before mint; changing the withdrawal destination requires closing and reminting the position.
- Read pause and freeze state before submitting a change.
- Quote and reconcile in exact native token base units. Apply decimals only for display.
- Treat
tokens_owedas settled amounts, not total pending fees. - Recompute insertion hints immediately before mint or reactivation.
- Account for exact-limit tick crossing when predicting post-swap active liquidity.
- Decide a fee-settlement cadence that balances custody operations with sub-unit rounding.
- Preserve separate decoders and history for scalar-fee-growth and Q128.128 deployments.