Bins
Every bin has a unique signed integer identifier, bin_id: i32. Bin 0 is the par bin (price
1.0); positive ids are above par, negative below. A bin’s price is fixed and deterministic —
no bin’s price can ever be anything other than what its id implies:
bin_step, in basis points, is fixed per pool at creation and sets how far apart adjacent bins
are priced. A finer bin_step (e.g. 1 bp) covers a huge price range at high density; a coarser
one (e.g. 400 bp) covers a wider range per bin, more coarsely. 22 bin steps are supported,
from 1 bp to 400 bp. Prices are pre-computed once when a bin array is created and stored per
bin, so a swap never has to exponentiate on the hot path.
Bin arrays
Bins are grouped into fixed-size bin arrays of 100 bins each — the unit of account
allocation. Bin arrays are created permissionlessly, on demand (create_bin_array), and a
swap or deposit only ever loads the arrays it actually touches. There is no bitmap scanning
the full range up front — this is a deliberate sparse design (see
Swaps for the traversal consequences).
div_euclid, not plain integer division, is required — truncating division misclassifies
negative ids (bin_id = -1 lands in array -1 under div_euclid, array 0 under /).
The active bin
At any moment, exactly one bin is the pool’s active bin — the bin currently holding both
tokens, at the pool’s current market price. Every bin below the active bin holds only Y (it’s a
bid); every bin above holds only X (it’s an ask). This partition isn’t a separate
invariant the program maintains — it falls directly out of how deposits are
distributed and how swaps drain
bins: a swap can leave a fully-drained bin single-sided and still
active, and the next deposit or swap prices correctly against whatever that bin’s actual
post-drain composition is.
There’s no oracle and no curve formula to invert. The active bin’s price is the market
price, full stop — reading it is one field access (Pool.activeBinId), not a computation.
Fee tiers
Independent of bin_step, a pool also fixes a base fee rate at creation, from a table of 23
supported tiers, 0.01% to 10.00%. Fee rate is stored in native units where
FEE_PRECISION = 1_000_000_000 represents 100% — one native unit is 0.00001 basis points,
far finer than the 23 supported tiers themselves need, leaving headroom below basis-point
resolution for tiers finer than the current table. See Fees for how the
static rate combines with the dynamic surcharge.
Computing a bin’s price off-chain
The SDK exposes the same Q64.64 math the program uses, so a client can price a bin — e.g. to
pick lowerBinId/upperBinId for a new position — without an RPC round trip:
undefined means someBinId is outside the representable range for that bin_step — every
supported bin_step has a fixed, compile-time-verified min_bin_id/max_bin_id range, sized
so base^bin_id stays representable.