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

# Positions and liquidity

> How liquidity is deposited into bins, and what a position actually is

## Positions are NFTs

A position is a bounded range of bins — at most 100, i.e. one bin array's width — owned by a
Token-2022 NFT. `open_position` mints the NFT directly into the caller's wallet; every
downstream action (deposit, withdraw, claim fee, close) is gated to **whoever currently holds
that NFT**, not necessarily the original opener. Selling or transferring the NFT transfers the
position along with it. Ownership is checked declaratively against the position mint's direct
owner — there's no delegate support.

```rust theme={null}
pub struct Position {
    pub pool: Pubkey,
    pub position_mint: Pubkey,
    pub lower_bin_id: i32,
    pub upper_bin_id: i32,
    pub fee_states: [FeeState; 100],   // one slot per bin in [lower_bin_id, upper_bin_id]
}
```

## Depositing by weight

A single instruction, `deposit_by_weight`, places a deposit across a position's bins. The
caller supplies:

* a total `(amount_x, amount_y)`,
* a **weight vector** — one `u16` weight per bin in the range,
* a `distribution_mode` (below),
* the client's believed `active_bin_id` (checked against the pool's real one, within a caller-set
  tolerance).

The program then allocates capital bin by bin, proportional to each bin's weight, adjusted for
that bin's price (a higher-priced bin needs fewer raw tokens to represent the same weight). The
partition is strict:

* bins **below** the active bin only ever receive **Y** (bid liquidity),
* bins **above** the active bin only ever receive **X** (ask liquidity),
* the **active bin** may receive both.

<Warning>
  **Liquidity shapes (Spot / Curve / Bid-Ask) are not a program or SDK concept.** The on-chain
  instruction and the SDK both only accept a raw `bin_weights: number[]` array — generating a
  flat, triangular, or U-shaped weight curve from a shape name is entirely client-side. Picon's
  own web app does this in a plain TypeScript helper (`generateBinWeights`), not inside
  `@picon-finance/dlmm-sdk`. If you want shape presets, you'll write that helper yourself — it's
  a few lines: `Spot` is `Array(n).fill(1)`; `Curve` peaks at the active bin and tapers outward;
  `Bid-Ask` is the inverse, troughing at the active bin.
</Warning>

## Distribution modes

The active bin is the only bin that can receive both tokens, and how its weight splits between
X and Y depends on `DistributionMode`:

| Mode          | Active bin behavior                                                                                                                                                                                                                                                                                                                              |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Balanced`    | Split by the bin's current composition ratio. Additionally, both sides are scaled down together to whichever token is more constrained relative to its own weight demand — so a `Balanced` deposit can pull in **less** than the full requested amount on one side. The undeposited remainder is simply never charged, not returned as an error. |
| `Unbalanced`  | Split by the bin's current composition ratio, same as `Balanced`, but each side is scaled independently — no cross-side capping.                                                                                                                                                                                                                 |
| `UnbalancedX` | All of the active bin's weight goes to X.                                                                                                                                                                                                                                                                                                        |
| `UnbalancedY` | All of the active bin's weight goes to Y.                                                                                                                                                                                                                                                                                                        |

If the active bin currently holds zero liquidity, the composition ratio defaults to 50/50 —
there's no existing composition to match yet.

<Note>
  A position entirely on one side of the active bin can only ever absorb the corresponding
  token — any amount typed for the other side is silently unused (the CPI transfer for that
  side is a no-op, not an error). You can derive this yourself from data the SDK already
  exposes: `pool.activeBinId < position.data.lowerBinId` means the position is entirely above
  the active bin (X only, so a Y input should be disabled); `pool.activeBinId >
      position.data.upperBinId` means the mirror case (Y only). Worth checking before letting a
  user type into the side that will silently be ignored.
</Note>

## Withdrawing and closing

`withdraw` removes a proportional share (`bps_to_remove`, out of 10,000) of a position's bins,
rounding down. It's **bps-proportional, not price-exposed** — there's no `min_amount_out`-style
slippage parameter, by design, since a withdrawal takes back a fixed fraction of whatever the
position currently holds rather than trading against price.

`close_position` burns the NFT and closes the `Position` account. It requires the position to
be fully empty first — zero shares in every bin, and zero unclaimed fees. A position that still
has anything in it (principal or fees) must be withdrawn and claimed before it can be closed.

## Fees accrue independently of principal

LP swap fees accrue directly into the bins a position holds shares in, tracked per-bin on the
position's own `FeeState` slots via a lazily-materialized, checkpoint-based accumulator (the
same pattern as a masterchef-style farm). `claim_fee` sweeps accrued fees for a caller-specified
sub-range of a position's bins, independent of withdrawing principal — you can claim fees
without touching your deposited liquidity at all.

## Fee state and bin-array lifecycle

A position's unclaimed `fee_owed` lives on the `Position` account itself, not on the `BinArray`
— so a bin array can, in principle, be deleted (`delete_bin_array`, admin-only, requires the
array to be fully empty of shares) while a position still has fees owed against one of its bins.
If that happens, `claim_fee` for that bin fails until the array is recreated
(`create_bin_array` is idempotent) — the already-earned fee amount is untouched by the deletion,
since it was already materialized onto the `Position` account before the array went away.
