Tier table, cached on-chain tierParams, and a faithful JavaScript port of the
four-zone Spry fee curve.
Charts and client-side preview only. Never size a real trade from this package. Execution pricing always goes through the
V4Quoter, which routes throughPoolManagerthenSpryHook.beforeSwapand returns exactly what a swap would do at that block. This package exists to plot the curve, place the live marker, and reason about how high the fee can rise within a block window (the slippage / fee-headroom model in brief section 7).
Spry splits the two concepts Uniswap conflates:
- Tier (
PoolTier): an immutable asset/risk bucket selected at pool creation viatickSpacing, with a fixed base fee and cap fee. Never changes. Hardcoded here; do not query it. - Dynamic fee: the actual per-swap LP fee, which varies between base and cap per swap. This is Spry's headline metric. Surface it as current / average / range, never as a single fixed percent.
| Tier | tickSpacing | base | cap |
|---|---|---|---|
STABLE |
1 | 0.01% | 0.50% |
LIKE_ASSET |
10 | 0.05% | 1.00% |
BLUE_CHIP |
60 | 0.30% | 5.50% |
VOLATILE |
200 | 0.50% | 9.00% |
EXOTIC |
1000 | 1.00% | 9.90% |
tiers- the immutable tier table andtickSpacing<-> tier mapping.params-TIER_PARAMS, the exact curve coefficients transcribed verbatim fromSpryHook(safeFee/capFee, zone bounds, linear + exponential coefficients).curve- the four-zone curve:feeForDelta(delta, params)- point fee at a single delta (the curve you plot).marginalFee(cumBefore, cumAfter, params)- the integral-mode average fee a swap actually pays (GROWTH / UNWIND / FLIP).feeIntegral(y0, y1, params, right)- the path-independent cumulative cost.zoneOf,dispatchCase- theSpryFeeevent labels.
reserves-virtualReservesFromStateandcomputeSignedDelta, the(sqrtPriceX96, liquidity, swap)-> signeddeltapipeline.format-feePipsToPercent,formatFeePercent, etc. (accept subgraph strings, since GraphQLBigInt/BigDecimalarrive as strings).sample-sampleFeeCurve(tier)for the fee-curve chart.
The safe / alert / cap arithmetic uses BigInt and reproduces Solidity's
integer truncation exactly. The danger zone is exponential
(a * e^(b*delta/1000)); on-chain it uses PRB-Math SD59x18, here f64
Math.exp. They are allowed a 2-pip tolerance in principle, but in practice
the port is bit-exact: the differential test (contract-diff.test.ts)
diffs the JS output against the real on-chain Solidity over a grid generated by
tools/contract-diff and currently reports maxDiff 0 pips across 1995
feeForDelta points and 980 marginalFee pairs, plus exact tierParams for
all five tiers. When you need the guaranteed on-chain value regardless, read
tierParams (these are transcribed) or the Quoter.
import { PoolTier, feeForDelta, marginalFee, formatFeePercent, TIER_PARAMS } from '@spry/fee';
const p = TIER_PARAMS[PoolTier.BLUE_CHIP];
feeForDelta(0, p); // 3000 (base fee, in pips)
formatFeePercent(feeForDelta(0, p)); // "0.30%"
marginalFee(0, 1200, p); // integral-mode fee for a swap pushing cum 0 -> 1200cd packages/spry-fee && bunx vitest run