> ## Documentation Index
> Fetch the complete documentation index at: https://shield.fi/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Liquidity provider workflow

> Build the full position lifecycle from pool selection through final burn.

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](../rest/overview). 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](../concepts/pools-and-keys), [Native token amounts](../concepts/token-amount-normalization), and [Choosing a range](./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](./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](./fee-entitlements) and [Q128.128 fee growth](../math/q128-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:

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
old fee entitlement settles at old liquidity
checkpoint advances
new liquidity becomes active
```

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](./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](./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](./collecting-and-burning).

## Burn

`burn` removes an already empty position record. It succeeds only when:

```text theme={"languages":{"custom":["/languages/leo.tmLanguage.json"]}}
liquidity == 0
tokens_owed0 == 0
tokens_owed1 == 0
position is not frozen
```

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.
