Degressive launch fee
TIDE’s pool fee is not fixed. It starts high and decays with time since launch — 25% for the opening window, 10% for the next, then 5% forever. This is a native Uniswap V4 dynamic fee, recomputed on every swap, hard-coded and immutable: there is no setter, not even the owner can change it.
The point is a fair launch. The high opening fee doesn’t block snipers who pile in during the first seconds — it taxes them, and that value flows to holders (the fee accrues to the single protocol-owned position, i.e. to everyone holding a Tide-LP NFT). See Hold = LP (DN404) for how holding earns a share of those fees.
The three tiers
Section titled “The three tiers”The schedule is defined by three constants on TideHook, expressed in pips (Uniswap fee units where 1_000_000 pips = 100%):
| Tier | Constant | Pips | Fee | When (elapsed since launchTime) |
|---|---|---|---|---|
| 1 | FEE_TIER_1 | 250_000 | 25% | t < feeWindow1 |
| 2 | FEE_TIER_2 | 100_000 | 10% | feeWindow1 ≤ t < feeWindow2 |
| Final | FEE_FINAL | 50_000 | 5% | t ≥ feeWindow2 (forever) |
uint24 public constant FEE_TIER_1 = 250_000; // 25%uint24 public constant FEE_TIER_2 = 100_000; // 10%uint24 public constant FEE_FINAL = 50_000; // 5%t is block.timestamp - launchTime, where launchTime is set once, to block.timestamp, when the owner calls seed() (see How it works).
The two window boundaries
Section titled “The two window boundaries”The tier durations are the only configurable part, and they are constructor immutables — fixed forever at deploy time, with no setter:
uint32 public immutable feeWindow1;uint32 public immutable feeWindow2;The constructor enforces feeWindow1 <= feeWindow2 (otherwise it reverts InvalidDuration). Because the durations are part of the contract’s init code, they cannot be altered after deployment.
currentFee() — read the fee that applies right now
Section titled “currentFee() — read the fee that applies right now”currentFee() is a public view that returns the fee, in pips, that a swap would pay at the current block:
function currentFee() public view returns (uint24) { if (launchTime == 0) return FEE_FINAL; // not launched yet; quote the steady-state fee uint256 elapsed = block.timestamp - launchTime; if (elapsed < feeWindow1) return FEE_TIER_1; if (elapsed < feeWindow2) return FEE_TIER_2; return FEE_FINAL;}Before launch (launchTime == 0) it quotes the steady-state FEE_FINAL (5%). The dapp reads currentFee() live to drive the fee gauge and a countdown to the next tier, and uses it in the swap widget’s output estimate. See The dapp.
How it’s enforced: beforeSwap at the PoolManager
Section titled “How it’s enforced: beforeSwap at the PoolManager”The fee is applied inside the hook’s _beforeSwap, which runs on every swap against the pool, at the Uniswap V4 PoolManager level:
uint24 fee = (sender == address(this) ? uint24(0) : currentFee()) | LPFeeLibrary.OVERRIDE_FEE_FLAG;return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee);The pool’s PoolKey.fee field is set to LPFeeLibrary.DYNAMIC_FEE_FLAG, which is what permits the hook to override the fee per swap. _beforeSwap returns currentFee() OR’d with LPFeeLibrary.OVERRIDE_FEE_FLAG, telling the PoolManager to use that exact fee for this swap.
Two properties follow directly from enforcing the fee here:
- Router-agnostic. Because the fee is decided by the hook at swap time, every swap through the canonical pool pays it — regardless of which router, aggregator, or UI initiated the trade (Uniswap UI, 1inch, 0x, an MEV bot, the in-app widget). There is no path through the canonical pool that escapes the fee.
- Trustless. The tiers are hard-coded constants and the windows are immutable. There is no setter for the schedule — not even the contract owner can raise, lower, pause, or redirect it. The fee is a property of the code, not a privilege.
The protocol buyback is fee-exempt
Section titled “The protocol buyback is fee-exempt”When the protocol itself swaps ETH→TIDE — during a claim buyback or a Treasury buyback-burn — that swap is the hook calling swap from its own unlock callback, so sender == address(this). In that case _beforeSwap charges uint24(0):
sender == address(this) ? uint24(0) : currentFee()This is intentional. A claim converts a holder’s ETH fees into TIDE by buying in-pool; charging the launch fee on that buy would tax holders’ own rewards back into the pool. Only external traders pay the degressive schedule.
Why degressive (anti-sniper)
Section titled “Why degressive (anti-sniper)”A launch is the moment a sniper has the most edge: they buy in the first block at the lowest price and dump. A high opening fee turns that edge into a cost. The mechanism:
- It taxes front-running rather than blocking it — TIDE makes no attempt to gatekeep who can buy, which would require a custody-touching allowlist and is incompatible with the no-rug trust model.
- The tax doesn’t disappear — it accrues to the position, i.e. to existing and patient holders, so early speculation directly funds the people who stay.
- It decays automatically to a normal
5%steady state, so the long-run product is just a low-fee pool; the high fee exists only for the brief window when sniping is most profitable.
At a glance
Section titled “At a glance”| Property | Value |
|---|---|
| Fee type | Uniswap V4 dynamic fee (DYNAMIC_FEE_FLAG), overridden per swap |
| Enforcement point | _beforeSwap at the PoolManager |
| Tiers | 25% → 10% → 5% (pips 250_000 / 100_000 / 50_000) |
| Gated by | feeWindow1, feeWindow2 (immutable), measured from launchTime |
| Production windows | 300s / 480s (5 min / 8 min) |
| Changeable after deploy? | No — no setter on tiers or windows |
Protocol buyback (sender == hook) | Fee-exempt (0) |
| Read it live | currentFee() (returns pips) |
For the full list of constants, events, and errors, see TideHook and Events & errors.