Skip to main content

Fee rate representation

Pool.fee_rate is stored in native units, where FEE_PRECISION = 1_000_000_000 represents 100% — finer resolution than plain basis points, leaving headroom for very low fee tiers. It’s validated at pool creation against a fixed table of 23 supported tiers, 0.01% up to 10.00%. total_fee_rate = min(fee_rate + dynamic_fee_rate, MAX_FEE_RATE)MAX_FEE_RATE is a hard 10% ceiling on the combined rate, so a highly volatile pool can never charge more than a static-fee pool is bound by. Every downstream fee calculation — the swap fee, the composition fee — reads through this one combined rate; neither knows or cares that it has two components. Pool.protocol_share is a separate rate, in ordinary basis points, capped at 25%. It’s the protocol’s cut of the swap fee specifically — it scales with volatility exactly as the LP share does, since both are computed from the same combined rate. It does not apply to the composition fee (below), which exists purely to compensate existing LPs.

Dynamic fee

A volatility-based surcharge on top of the static base rate, in the Trader Joe/Meteora lineage (a decaying reference bin plus a squared-volatility fee curve — not Raydium/Orca-style tick-crossing counters). It exists so a pool’s price can move sharply — a real trade, not routed through many small swaps to avoid it — without LPs being underpriced for the risk that represents, and decays back toward zero once the pool goes quiet. Configuration, seeded per bin_step at pool creation and admin-overridable per pool: On every swap, before pricing anything, the pool decays its own state:
On every bin actually crossed during traversal, the accumulator grows:
Because this runs per bin inside the same swap’s fold, a single large swap crossing many bins prices its own later bins at a higher rate than its earlier ones — the fee responds within one swap, not just between swaps. From accumulator to rate:
Rounds up (LP-favorable). Scaling by bin_step means the same accumulator value implies a larger price move — and thus a larger fee — on a coarser pool.
Any quoting logic that doesn’t replicate this exact decay-then-accumulate state machine, bin by bin, will systematically misprice a multi-bin swap. This is precisely what @picon-finance/dlmm-sdk’s bundled quote engine does internally — see SDK → Pools and swaps.

Composition fee

Charged only on the active bin, only when a deposit’s ratio differs from the bin’s current composition. The intuition: a share is a claim locked to the bin’s composition, not to the depositor’s deposit ratio, so an off-ratio deposit is effectively an implicit swap against existing LPs — and that implicit swap gets charged the same way a real swap would. The amount that “crosses” is measured by simulating a deposit-then-immediate-redeem against the bin, not by comparing to a static target ratio — this correctly accounts for the deposit’s own effect on the bin’s composition (a large off-ratio deposit shifts the ratio itself, so less crosses than a naive pre-deposit-ratio estimate would predict). The fee is carved directly out of the deposit before shares are minted, and compounds into reserves before the depositor’s own shares exist — so a deposit-then-immediate-withdraw round trip cannot extract value from existing LPs; the depositor can only ever lose the fee, never recapture it. This fee is not protocol-split — 100% of it goes to existing LPs. Balanced deposits and deposits into an empty bin cross nothing (0 fee); a deposit entirely to one side of the active bin’s existing composition is bounded by the opposite reserve — it can never cross more value than actually exists on the other side.

Swap fee split and accrual

For each bin a swap touches: fee = gross_amount_in − net_amount_in for that bin. Split:
protocol_fee rounds down (favoring LPs, consistent with every other rounding decision in the system). The protocol slice accumulates pool-wide (Pool.protocol_fee_x/y); the LP slice folds into that specific bin’s per-share accumulator — a masterchef-style pattern where each position’s FeeState lazily materializes its share of newly-accrued fee on its next deposit/withdraw/claim, computed from the delta against its own last-seen checkpoint.

Claiming

  • claim_fee (any position owner, permissionless for their own position): materializes and sweeps fee_owed for every non-empty bin in the requested range.
  • claim_protocol_fee (admin-only): transfers the pool’s full accumulated protocol fee to admin-controlled accounts and resets the accumulator to zero.