Dynamic-fee Uniswap V4 hooks that protect liquidity providers from arbitrage-driven impermanent loss.
Spry is a Uniswap V4 hook: a small contract that V4's singleton PoolManager consults on every swap to set the fee. Instead of one flat fee, a Spry pool charges a fee that scales with how much each trade moves the price, and with how much the whole block has already moved it.
Ordinary trades pay a low base rate. Large, arbitrage- and MEV-sized swaps pay much more, and that excess flows back to liquidity providers through V4's standard fee channel. The result: LPs are compensated for the impermanent loss those swaps would otherwise inflict.
Constant-product AMMs leak value to arbitrageurs every time the price moves. That's impermanent loss. A flat fee (e.g. 0.30%) compensates badly: it's too high on tiny trades and far too low on the large rebalances that actually cause IL, so small traders end up subsidizing the cost created by big ones.
Spry replaces the flat fee with a curve that prices each swap by its own contribution to IL, so the takers causing the loss are the ones paying for it.
-
Five fee tiers, selected by a pool's
tickSpacing, matched to asset volatility:Tier tickSpacingExample pairs Base fee STABLE 1 USDC/USDT, stETH/ETH 0.01% LIKE-ASSET 10 wstETH/ETH 0.05% BLUE-CHIP 60 ETH/USDC, WBTC/ETH 0.30% VOLATILE 200 ETH/SHIB 0.50% EXOTIC 1000 low-cap pairs 1.00% -
A four-zone curve per tier. A flat safe zone, a linear ramp, an exponential ramp, then a cap (up to 9.9%), so the fee rises smoothly as a swap pushes the pool further from fair price.
-
Block-windowed MEV protection. Each pool keeps a running cumulative of the block's net price shift; a swap is priced over the path it traverses, not in isolation.
-
Path-independence. The fee is the integral of the curve over that path, so splitting one big swap into many small ones within a block costs at least as much, closing the multicall/sandwich-splitting loophole.
-
LP-friendly unwinds. Swaps that push the pool back toward neutral pay only the base rate.
The full derivation lives in the whitepaper: PDF (figure-driven) or Markdown.
spry-interface is a Spry-native fork of the Uniswap interface, trimmed to Uniswap V4 and wired to run on Unichain Sepolia & Base Sepolia. It is a complete LP and trading surface for Spry pools, with no extra contracts to learn:
- Provide liquidity, end to end. Open a position in an existing Spry pool or create a brand-new pool, then add, remove, and collect fees, all on Uniswap V4 through the canonical
PositionManagerand Permit2. - Your positions, live. Balances, uncollected fees, and status are read straight from the chain and the subgraph, each shown with its dynamic-fee tier and the pool's "alert" and "danger" zone counts.
- Pick a tier, not a number. The create flow presents the five Spry tiers with their fee bands and a log-scale fee curve, instead of a single fixed-fee dropdown.
- Swap against Spry pools, with quoting and approvals handled for the testnet.
- Find any token by symbol or address, resolved from the subgraph and on-chain ERC-20 metadata.
Run it locally:
git clone https://github.com/SpryFinance/spry-interface
cd spry-interface
bun install
bun web dev # serves the app at http://localhost:3000You provide liquidity through Uniswap's canonical V4 PositionManager: no custom Spry LP contract, no new token to hold. Your position is a standard ERC-721, with per-position fee accounting handled by V4. The dynamic fee simply means a larger share of arbitrage flow accrues to you.
In practice you never touch the contracts directly: the Spry app handles the whole lifecycle. Connect a wallet, pick a pair and tier, and add, collect, or withdraw liquidity, with each pool's dynamic fee and risk zones shown inline.
Spry pools are ordinary V4 pools. Any V4-aware router or aggregator can trade against them, and the hook prices each swap automatically. Spry also ships a thin swap-only router with native-ETH, multi-hop, and Permit2 support, and the Spry app exposes a Swap tab over Unichain Sepolia & Base Sepolia.
The protocol lives in spry-contracts (Foundry, Solidity ^0.8.26).
git clone https://github.com/SpryFinance/spry-contracts
cd spry-contracts
forge install # v4-core, v4-periphery, OpenZeppelin, PRB-Math, Permit2
forge build
forge test # 264 tests across unit / integration / scenarios / fuzz / forkWiring a pool to Spry: deploy SpryHook at a mined CREATE2 address (low 14 bits = BEFORE_SWAP_FLAG), then initialize a pool with fee = DYNAMIC_FEE_FLAG and tickSpacing set to your chosen tier. Liquidity goes through PositionManager; swaps through any V4 router. See script/DeploySpry.s.sol.
Core surface: SpryHook (dispatch + per-pool cumulative state), SpryRouter (swap-only), and the SmartFeeLib / SpryFeeTypes / VirtualReserves libraries.
The web client is spry-interface (see The Spry app to run it), and on-chain data is served by spry-subgraph.
On-chain activity is indexed by spry-subgraph, a fork of Uniswap's v4-subgraph that captures Spry pools, per-swap dynamic fees, tier stats, and the hook's SpryFee telemetry (signed cumulative · zone · dispatch case). Query pools, swaps, and fee distributions over GraphQL. The app reads your positions, live tier stats, and per-pool fee history from it.
⚠️ Pre-production. The contracts are extensively tested (264 passing tests, ~100% library coverage, stateful invariant fuzzing) but not yet externally audited. The app and subgraph run on Unichain Sepolia & Base Sepolia only, and there is no mainnet deployment. Do not use with material funds until an independent audit is complete.
- 📄 Whitepaper: PDF (print-ready, with figures) · Markdown (renders on GitHub)
- 🖥️ Interface:
spry-interface - 💻 Contracts:
spry-contracts - 📊 Subgraph:
spry-subgraph