Skip to content

Execution

Uniswap v4 Dynamic LP Fees: Stored Fee vs Override

A hypothetical 10,000-token swap separates the stored LP rate, a one-swap override and the directional protocol allocation, then reconciles output and fee accounting.

Ivory ceramic ring and inner crescent held by a lime wedge on a black circular plinth.
Illustration for execution; examples in this article are educational.

Uniswap v4 dynamic LP fees let a pool's hook change the liquidity-provider rate. The hook can update the stored fee or return a permitted override for one swap. An override alone does not replace the stored fee. To explain a swap's cost, identify the LP rate actually selected, combine it with the protocol rate for that direction, and reconcile the resulting token amounts. Separate hook charges and network costs require their own accounting.

Consider a hypothetical pool storing a 0.30% LP fee while applying 1.00% to one swap. Reading the stored value afterward can still show 0.30%. Follow the call to distinguish its selected rate from the default.

Read the rate separately from the dynamic marker

The mechanics below follow Uniswap v4-core's tagged v4.0.0 source, pinned to commit e50237c. Check the actual deployment before applying these rules to a fork.

Uniswap's dynamic-fee documentation, checked on 28 October 2026, distinguishes updates through PoolManager from per-swap hook overrides. Dynamic capability is fixed when the pool is created. The protocol supplies the mechanism; the particular hook supplies its pricing rule. A dynamic pool does not automatically follow a standard volatility formula.

Keep these records apart:

RecordMeaningWhat it cannot establish alone
Pool key's fee fieldStatic rate or dynamic markerCurrent numerical LP rate for a dynamic pool
Stored LP feeDefault numerical rate in pool stateWhether a swap selected an override
Hook's flagged fee returnCandidate rate for this swapA permanent change to the stored rate
Directional protocol feeSeparate core protocol rateHook charges, gas or LP net return

In the pinned LP fee library, numerical rates use millionths: 3,000 means 0.30%, and 10,000 means 1.00%. One unit is 0.0001 percentage points, or one hundredth of a basis point.

The pool key value 0x800000 is the dynamic marker. It is not an exceptionally expensive numerical fee. The same library initializes a dynamic pool's stored LP rate to zero; a hook that wants another starting rate must arrange an update. A zero initial stored rate also says nothing about separate protocol or hook charges.

A stored update and an override have different lifetimes

PoolManager's dynamic-fee setter accepts an update only for a dynamic-fee pool and only from its configured hook address. A trader cannot call it directly to choose a cheaper rate. Who can make that hook request an update depends on the hook's own code and controls.

For a per-swap override, the hook needs its beforeSwap callback enabled. The pinned hook dispatch code reads the returned fee only for dynamic pools. The numerical rate must carry the separate 0x400000 override flag. Returning an unflagged 10,000 does not select a 1.00% override; the stored fee remains the fallback.

A flagged zero can deliberately select a zero LP rate. A flagged rate above 1,000,000, or 100%, reverts during validation. The permission to return a balance delta is separate from permission to run beforeSwap; changing the LP rate does not inherently require a custom balance charge. The core skips that callback when the hook is the direct caller of PoolManager's swap function. If a separate router calls PoolManager, the equality check uses that router's address.

Consider this hypothetical sequence, using ordinary external swap calls and a hook that returns no custom deltas:

EventLP rate used by the swapStored LP rate afterward
Hook sets the default to 3,000No swap3,000
Swap receives a flagged 10,000 override10,0003,000
Next swap receives no override3,0003,000
Hook explicitly updates the default to 1,500No swap1,500
Next swap receives no override1,5001,500

The pool swap implementation selects either the validated override or the stored rate. The override is not a write to that stored field. A hook can also call the setter during its execution, so inspect both actions when reconstructing a real transaction.

Reconcile two hypothetical swaps from identical starting state

Suppose a constructed pool exchanges token A, its currency0, for token B, its currency1. Both tokens have six decimals. Compare two separate exact-input scenarios, each starting at 1 B per A with raw liquidity of 1,000,000,000,000. Liquidity spans ticks −600 to 600, with tick spacing 1.

Each scenario supplies exactly 10,000 A, including core swap fees. Assume the entire input is consumed, no initialized liquidity boundary is crossed, the price limit is not reached, transfers behave normally, and hook deltas are zero. Exclude router charges and gas. These are hypothetical inputs, not a live pool quote or fee policy.

Set the directional protocol rate to 1,000 millionths, or 0.10%. The pinned protocol fee library caps that rate at 1,000 for this implementation and keeps separate rates for the two swap directions. No current activation or rate is assumed here.

The fee composition follows the protocol charge first and the LP rate on the remaining input. Let p be the directional protocol rate and l the selected LP rate, both in millionths. Add p and l, then subtract floor(p × l / 1,000,000). This produces the integer combined rate; simply adding the percentages overstates it.

For the stored LP rate of 3,000, the combined rate is 3,997. For the override of 10,000, it is 10,990. In these two cases the multiplication divides exactly, so the rate calculation introduces no rounding difference.

Amount or rateStored-rate scenarioOverride scenario
Selected LP rate0.30%1.00%
Combined core rate0.3997%1.0990%
Gross A input10,000.00000010,000.000000
A entering the trading curve9,960.0300009,890.100000
Total core fee in A39.970000109.900000
Protocol allocation in A10.00000010.000000
LP allocation in A29.97000099.900000
B output after integer rounding9,861.8061159,793.243839
Stored LP rate after swap0.30%0.30%

The input check is exact in each column; the output is measured separately in B. In the first, 9,960.03 A enters the curve, 10 A goes to protocol accounting and 29.97 A goes to LP fee accounting. Those amounts sum to 10,000 A. The second column sums to the same input with a larger LP allocation.

Protocol fees are set through a separate controller-authorized path. An LP fee override does not itself change that directional protocol rate.

Verify the output as well as the fee split

At this illustration's starting price, the liquidity parameter implies virtual balances of 1,000,000 A and 1,000,000 B. These virtual balances parameterize the curve; actual deposited balances and pool TVL require separate measurements. Within the unchanged liquidity range, input a produces continuous output of 1,000,000 × a / (1,000,000 + a) B.

Putting 9,960.03 A into that expression gives approximately 9,861.806115238 B. Putting 9,890.10 A in gives approximately 9,793.243839107 B. This checks the scale of both outputs independently of the fee ledger. The concentrated-liquidity execution guide explains why crossing a liquidity boundary would require another segment of the calculation.

For the table, the exact-input calculation follows the pinned SwapMath branch: remove the combined fee from available input, compute the ending square-root price and obtain B output. The square-root price library rounds that price upward for this direction and rounds output downward. Integer recomputation gives the six-decimal quantities shown above.

The override reduces B output by 68.562276 B, while increasing the LP allocation by 69.93 A. Those are different token units, and the curved exchange rate also affects the comparison. Treating the extra fee as an equal-sized output deduction would miss the swap's price movement.

When a real swap spans several steps, preserve the contract's integer operations and protocol allocation at each step. A single multiplication against a dashboard's rounded rate can miss the resulting units.

A valid rate can still prevent the requested swap. At a combined rate of 100%, the pinned pool code rejects exact-output swaps, because no input remains to obtain output. Check this boundary explicitly when a hook can select extreme rates.

Read the selected rate in the swap event

A successful core Swap event reports the combined swap rate selected by the PoolManager event path. Calling that number the LP-only rate would mislabel it. The event is emitted before the afterSwap hook, so it does not by itself settle every possible hook-adjusted cost.

An end-of-block state read can follow several earlier swaps and fee decisions. In the hypothetical override scenario, a stored reading of 3,000 and an event rate of 10,990 are consistent: one is the default LP rate and the other combines the selected override with the protocol rate.

Asset settlement adds another distinction. The native ETH versus WETH guide explains which asset is delivered and how native value is handled; that identity does not choose the LP rate. Likewise, a flash swap or flash loan can impose separate repayment and fee rules. Do not put a lender's premium into the LP field merely because the operations share a transaction.

The older Uniswap v2 kLast calculation measures reserve-product growth for protocol LP-token minting. That checkpoint is a different accounting mechanism from selecting a v4 swap rate.

For LPs, this table's allocation is fee income from the stipulated swap. It does not establish a position's return or justify projecting unchanged volume at the higher rate. The pool fee APR versus net return reconciliation accounts for inventory, costs and the holding comparison.

Build a record that explains one completed swap

Use this worksheet when a quote, stored rate and receipt disagree:

SaveCheck
Chain, PoolManager, full pool key and implementationSame pool and code throughout
Block hash and transaction positionState corresponds to immediately before the relevant call
Stored LP rate and hook returnDistinguish fallback, flagged override and explicit stored updates
Swap direction and protocol rateDecode the applicable direction
Sender, hook data and hook logicReproduce the actual fee-selection branch
Price, liquidity, ticks and limitReplay each consumed segment and its rounding
Core event and final token deltasSeparate combined core fees from custom hook or router charges

A simulation using another sender or different hook data may exercise a different pricing branch. A later execution can also encounter changed state. Save the exact simulated call and its block, then compare the completed transaction against it.

Start the reconciliation with three numbers: the stored LP rate, the selected LP rate and the combined core rate. Finish by explaining the actual input allocation and output. A fee label is useful only when that record shows which rate the swap used and where the tokens went.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.