Fees
Three numbers matter: the hook tax, the LP fee, and the composite a trader pays. The composite is not the sum of the other two.
The fee is not additive
Uniswap v4 applies the LP fee to the amount remaining after the hook's delta. So with a
hook tax h and an LP fee f, a trader pays:
h + f − h·f
With a 4% hook tax and a 1% LP fee, a trader pays 4.96%. The two are stored and displayed separately, and the composite is calculated rather than added up.
totalTax = holders + stakers + lpFee is false. Any interface, spreadsheet or integration that adds them is overstating what a trader pays and will disagree with the chain.
The protocol's 1% is carved out, not added on
One hundred basis points of every taxed swap goes to the protocol treasury. It is subtracted from the tax the pool already charges, not levied on top, so no number a trader sees changes. Two consequences look like bugs and are not:
- A pool charging no tax pays the protocol nothing. There is nothing to carve.
- A pool charging less than 1% pays its whole tax to the protocol and nothing to its own holders and stakers.
The carve is clamped to the tax actually charged, so it cannot exceed it. It is set at the protocol level rather than per pool, which puts it outside the guarantee that a creator’s configuration is immutable. Changing it changes every pool at once.
Who gets paid
- Liquidity providers are paid by Uniswap, natively, through V4's dynamic LP fee. No claim step, no accounting of ours.
- Holders and stakers are paid from the hook's share, which settles into a fee vault as ERC-6909 claims rather than a token transfer per swap. The vault redeems in batch.
- The protocol takes its carve as a real transfer on the swap path, so the treasury can be a plain address.
Units
Basis points in storage and on every screen. V4's boundary uses pips, where 1% is
10,000, and the conversion happens exactly once at the hook return.
Pips never reach a user interface. Returning basis points where pips are expected
under-charges liquidity providers by a factor of one hundred.
The charged fee, authoritatively
Read it from the PoolManager's Swap event. A beforeSwap override never
writes the stored fee, so getSlot0().lpFee reads 0 on our pools and is
not a usable source.