Skip to content

Latest commit

 

History

History
76 lines (62 loc) · 3.35 KB

File metadata and controls

76 lines (62 loc) · 3.35 KB

@spry/fee

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 through PoolManager then SpryHook.beforeSwap and 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).

Tier vs dynamic fee

Spry splits the two concepts Uniswap conflates:

  • Tier (PoolTier): an immutable asset/risk bucket selected at pool creation via tickSpacing, 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%

What's here

  • tiers - the immutable tier table and tickSpacing <-> tier mapping.
  • params - TIER_PARAMS, the exact curve coefficients transcribed verbatim from SpryHook (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 - the SpryFee event labels.
  • reserves - virtualReservesFromState and computeSignedDelta, the (sqrtPriceX96, liquidity, swap) -> signed delta pipeline.
  • format - feePipsToPercent, formatFeePercent, etc. (accept subgraph strings, since GraphQL BigInt/BigDecimal arrive as strings).
  • sample - sampleFeeCurve(tier) for the fee-curve chart.

Fidelity to the contract

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 -> 1200

Test

cd packages/spry-fee && bunx vitest run