Skip to main content
A Shield Swap position has two connected forms:
  • A public Position mapping entry holds the pool, tick range, liquidity, fee checkpoints, and amounts owed.
  • A PositionNFT record authorizes the holder to operate that position.
The economic state is observable. Ownership remains confidential through the Aleo record model. Anyone who knows a 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 PositionNFT and authorizes position operations.
  • The immutable withdrawal address receives every later collect payout record.
These addresses may differ from each other and from the mint signer. Collect cannot override the withdrawal address. A custody system must therefore confirm both destinations before minting and preserve the withdrawal field in every replacement ownership record. The lifecycle has five stages:
  1. Select a pool and range.
  2. Mint and preserve the returned ownership record.
  3. Monitor price, inventory, fees, and pool controls.
  4. Increase, decrease, or collect as needed.
  5. 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_owed0 and tokens_owed1 are zero
  • the final collect records are stored
  • burn is accepted
  • the public position row is removed
  • the final PositionNFT has been consumed

Client state you must keep

Store the deployment ID, pool key, token ID, latest PositionNFT, 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
The current pool, tick, and position schemas use 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 PositionNFT record
  • Change records for unused input-record value
  • An investigator-owned mint compliance record
  • A public Position mapping entry
The request includes the pool, lower and upper ticks, desired and minimum token amounts, and insertion hints. All amounts use native token base units. Mint also binds a private position owner, immutable withdrawal address, and freezelist non-inclusion proofs. The contract snapshots fee growth inside the selected range when the position is created. That checkpoint is the start of the position’s first liquidity epoch. It prevents the new position from claiming fees generated before it joined. The token calls move the full desired amounts into AMM custody. If the calculated liquidity uses less, the new public position starts with the 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
Fee growth advances only for active liquidity. A zero-for-one swap pays fees in token0. A one-for-zero swap pays fees in token1. The public 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:
If a boundary was removed after gross liquidity reached zero, the caller must provide valid insertion hints so it can be initialized again. Save the replacement 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:
  1. Derives current fee growth inside the range.
  2. Settles pending fees using current position liquidity.
  3. Adds settled fees to previously stored amounts owed.
  4. Checks that each requested amount is available.
  5. Returns token payout records.
  6. Stores the uncollected remainder and a new fee checkpoint.
The requested amounts are public. Payout goes to the private immutable withdrawal address in the 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:
The operation consumes the PositionNFT and removes the public positions row. A correct closeout sequence is:
  1. Decrease all remaining liquidity.
  2. Collect all token0 and token1 owed.
  3. Confirm the public position has zero balances.
  4. 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 stable token_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_owed as 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.