Inventory-risk protection for Uniswap v4 stable pools.
Oscillon is a dynamic-fee hook that monitors oracle prices on the token being sold, detects depeg conditions, and applies a base fee plus depeg surcharge so liquidity providers are compensated when traders drain below-peg stablecoins.
Designed for stable/stable pools such as USDC/USDT, USDC/DAI, and USDT/DAI.
Static swap fees (e.g. 3 bps flat) do not scale with depeg risk. When a stablecoin trades below $1, arbitrageurs can extract value from the pool while LPs absorb impaired inventory. Oscillon raises fees on drain swaps (selling the below-peg asset) while keeping normal swaps competitive at peg.
Swap (user sells tokenIn)
│
▼
┌───────────────────────────────────────┐
│ OscillonHook.beforeSwap │
│ 1. Identify input token (token0/1) │
│ 2. Read Chainlink + pool TWAP │
│ 3. Compute depegBps + isDrain │
│ 4. Select fee = base + surcharge │
│ 5. Emit DepegDetected │
└───────────────────────────────────────┘
│
▼
Uniswap v4 applies override fee
| Component | Role |
|---|---|
ChainlinkOracleAdapter |
Primary USD price per token |
OscillonTwapOracle |
30-minute pool TWAP fallback |
OscillonPriceEngine |
Blends sources; disagreement guard at 20 bps |
OscillonFeePolicy |
Hybrid piecewise + quadratic surcharge curve |
OscillonHook |
Orchestrates policy and returns dynamic fee |
Drain swap = user sells the token that is below $1 (pegBelow = true).
Restore swap = user sells the above-peg token → base fee only.
Oscillon uses base fee + depeg surcharge (not a replacement curve):
total fee = BASE_FEE (3 bps) + depeg surcharge (hybrid curve)
| Scenario | Fee (approx.) |
|---|---|
| At peg, any direction | 3 bps base |
| 6 bps drain (sell below-peg USDT) | 4 bps (3 + 1) |
| 20 bps drain | 9 bps (3 + 6) |
| Restore direction (sell above-peg) | 3 bps base |
| Max cap | 50 bps |
Constants (OscillonConstants.sol):
BASE_FEE_PIPS = 300(3 bps)SMALL_DEPEG_BPS = 3— dynamic path starts hereMAX_FEE_PIPS = 5000(50 bps)MAX_ORACLE_AGE = 25 hoursORACLE_DISAGREE_BPS = 20— Chainlink vs TWAP blend threshold
On each swap the hook emits:
event DepegDetected(
PoolId indexed poolId,
uint256 depegBps,
uint24 feeApplied,
uint256 swapSize,
bool isDrain,
bool usingFallback
);Historical simulations on USDC/USDT swap data compare Oscillon against a static 3 bps fee. Two periods are shown: a calm month (May 2026) and the USDC depeg crisis (March 2023).
18,500 chronological swaps with low volatility. Oscillon keeps fees near the 3 bps base while static and dynamic models earn similar total income ($65k). The benefit shows up in the 3–7 bps deviation bucket, where static LP capture drops to ~85% and Oscillon stays near 100%.
Chainlink-matched replay from March 10–16, 2023. As USDC depegged to ~1,200 bps, Oscillon scaled fees from 3 bps to the 50 bps cap while static stayed flat.
Top: oracle depeg magnitude. Middle: static 3 bps vs Oscillon hybrid. Bottom: remaining arb gap after fee (drain protection proxy).
Fee curves, LVR distribution, cumulative LP income, and LP capture % over the March 2023 stress window. Income spikes around swap 2,500–3,500 when the depeg hit. Oscillon hybrid ends 2× static ($450k vs ~$215k) and holds 25–50% LP capture in the 15–30+ bps ranges where static collapses below 10%.
Side-by-side fee models including hybrid, piecewise, quadratic (K=45), and additive (base + drain tax). Additive and paper-minimum benchmarks show the upper bound on LP protection.
Backtests are illustrative simulations on historical data. On-chain behavior depends on oracle freshness, pool liquidity, and swap direction. See Known Limitations.
src/
├── OscillonHook.sol # beforeSwap fee override + pool registry
├── oracle/
│ ├── OscillonPriceEngine.sol # Chainlink + TWAP cascade
│ └── adapters/
│ └── ChainlinkOracleAdapter.sol
├── libraries/
│ ├── OscillonFeePolicy.sol # Hybrid surcharge curve
│ ├── OscillonDepegMath.sol # Depeg bps + disagreement guard
│ └── OscillonTwapOracle.sol # In-pool TWAP ring buffer
└── constants/
└── OscillonConstants.sol
Multi-pool support: owner calls registerPool() per stable pair with Chainlink adapters for each token. No pool freeze — severe depeg caps fee at 50 bps; swaps continue.
forge build
forge test --match-contract OscillonHook -vv# Terminal 1
anvil --chain-id 31337
# Terminal 2
source .env # PRIVATE_KEY must have 0x prefix
forge script script/DeployOscillon.s.sol:DeployOscillon \
--rpc-url http://127.0.0.1:8545 \
--broadcastDeployment addresses are written to oscillon-ui/src/deployment.json.
forge script script/DeployOscillon.s.sol:DeployOscillon \
--rpc-url "$RPC_URL" \
--broadcastThe UI should show three linked values per swap:
- Hook price — effective USD price for the input token (Chainlink + TWAP logic)
- Depeg deviation — bps from $1 for that token
- Total fee — base (3 bps) + surcharge, from
DepegDetectedor swap simulation
Use getDeployment(chainId) from oscillon-ui/src/deployment.config.ts.
Fee quotes must be per swap direction — getPoolState() only reflects token0.
- Hook callbacks restricted to
PoolManagerviaBaseHook - Chainlink staleness:
block.timestamp > updatedAt + 25h→ TWAP fallback - Oracle disagreement > 20 bps → conservative price (closer to $1)
- Exact-output swaps disabled when
depegBps >= 3 - Rolling drain multiplier increases fee under sustained drain pressure
Liquidity add/remove is unrestricted (no hook on LP operations).
Surplus accounting (indicative only)
surplusAccrued and protocolAccrued track theoretical surplus from dynamic fees but are not connected to actual v4 fee settlement. collectProtocolFees requires proper fee skimming via donate() or afterSwapReturnDelta for production.
Oracle latency
TWAP window is 30 minutes. During fast depegs, TWAP can lag spot. Chainlink freshness uses a 25-hour maxAge.
K parameter
K_STANDARD = 45 used universally; thin-pool tier not yet wired to live liquidity reads.
POC scope
Static thresholds; single oracle per token; economic calibration from backtests, not live mainnet stress.
MIT



