Skip to content

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 schedule is defined by three constants on TideHook, expressed in pips (Uniswap fee units where 1_000_000 pips = 100%):

TierConstantPipsFeeWhen (elapsed since launchTime)
1FEE_TIER_1250_00025%t < feeWindow1
2FEE_TIER_2100_00010%feeWindow1 ≤ t < feeWindow2
FinalFEE_FINAL50_0005%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 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.

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.

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.
PropertyValue
Fee typeUniswap V4 dynamic fee (DYNAMIC_FEE_FLAG), overridden per swap
Enforcement point_beforeSwap at the PoolManager
Tiers25%10%5% (pips 250_000 / 100_000 / 50_000)
Gated byfeeWindow1, feeWindow2 (immutable), measured from launchTime
Production windows300s / 480s (5 min / 8 min)
Changeable after deploy?No — no setter on tiers or windows
Protocol buyback (sender == hook)Fee-exempt (0)
Read it livecurrentFee() (returns pips)

For the full list of constants, events, and errors, see TideHook and Events & errors.