Skip to main content

Two denominators, and they are not the same

The rates themselves are governance parameters per deployment and per pool: read them, never hardcode them.

The swap fee

It lives in the pool, as slot0.fee, and it is uniform across pool types:
On a dynamic pool, fee is what the last swap charged, not what the next one will. The plugin resolves the rate once per swap and the pool writes it into slot0 without an eventFeeChanged is emitted only when governance calls setFee. An indexer cannot follow a dynamic pool’s rate from logs: read feeInfo(), or ask the plugin’s currentFee() for what the next swap would pay.
When the default plugin’s dynamic-fee module is enabled, the rate moves between two governance-set bounds with measured volatility: at zero volatility it is baseFee, at or above sensitivity it is maxFee, and in between it interpolates linearly. A window shorter than 60 seconds of history charges baseFee. The whole curve — baseFee, maxFee, sensitivity, twapPeriod — is readable as feeCurve() on the plugin.

The fee coin

A pool takes its fee either in whatever coin the trade pays, or always in one named coin of the pair:
Paid is the upstream behaviour and every pool’s default. Under Token0 or Token1, a trade selling the named coin pays the fee on its input, and a trade buying it pays on its output — so fee growth and protocol fees for that pool accrue on one side only. A pool born on non-default terms emits FeeTokenChanged at creation, so an indexer never has to assume. The Swap event carries no fee field — it is byte-identical to V3’s. Derive what was paid, using feeInfo().fee as the rate f: Both are approximations of a per-step sum on concentrated pools, and every step rounds the fee up — so the exact figure is at or slightly above the rate, never below.

The protocol share

feeProtocol0 and feeProtocol1 are a share of the fee, not of the trade. A quarter of a 0.30% fee is 2500, and leaves traders paying 0.30%.
  • Which of the two applies is decided by the fee coin, not by the input coin: the fee accrues on one side, and that side’s share is the one charged.
  • The maximum is the whole fee. There is no half-the-fee ceiling — a governance call can route all of it away from liquidity providers, which is why the per-pool setter sits behind the slow governance key rather than a role.
  • A pool copies the factory default once, when it is created. Changing the default moves later pools only; an existing pool changes only through setFeeProtocol on that pool, which emits SetFeeProtocol.
SetFeeProtocol is the one event whose signature differs from Uniswap V3’s: four uint16 fields where V3 has four uint8. Every other pool event — Swap, Mint, Burn, Collect, Flash, Initialize — is byte-identical. See Events.
The LP side accrues into feeGrowthGlobal0X128 / feeGrowthGlobal1X128 and reaches a position the V3 way: poke with burn(lower, upper, 0), then collect. A step that crosses with no in-range liquidity credits no position — that fee simply stays in the pool. The protocol side accrues in protocolFees and leaves through:
The caller must hold the factory’s FEE_COLLECTOR role, or be the factory owner, and the recipient is an argument rather than a stored treasury. Draining a side pays one unit less so the storage slot is never cleared — the returned and emitted amounts already reflect that, so reconcile against CollectProtocol, not against your own subtraction.

Flash loans

The fee is the pool’s current rate, rounded up: fee = ceil(amount · f / 1,000,000) per token, with the dynamic plugin consulted the same way it is for a swap. Repayment is checked by balance delta, so a borrower may return more than principal plus fee — and the surplus is split exactly like the fee. Flash carries paid0 and paid1, which is the honest figure to index; the protocol’s share comes off what was actually paid, and the rest becomes fee growth for the LPs.

Limit orders

The module takes no fee of its own — placing, cancelling and claiming move principal only, and an order fills at its tick price. A resting order is a pool position owned by the plugin, so while the price sits on it, it earns ordinary swap fees. Those are not paid to the order’s owner: anything above the escrowed principal is protocol revenue, swept by the FEE_COLLECTOR role through sweepOrderFees, and auditable from outside with escrowedFor(fillTick, sellingToken0, spacing).

Managed vaults

A vault charges its operator share only on the swap fees its position earned and that actually arrived — never on principal, deposits, withdrawals, or the balance over time. It is taken when the vault rebalances or compounds, booked rather than pushed, and pulled later with claimFee(token, to).
  • Capped at 20% by the vault contract itself.
  • The share and its recipient belong to the vault’s own owner, not to protocol governance; governance sets only what new vaults are born with.
  • Read feeShare(), feeRecipient() and feesOwed(recipient, token); watch FeeChanged, FeeTaken and FeeClaimed.
  • A vault takes no cut of farming rewards — those are paid to holders in full.

Farming

Nothing takes a share of reward emissions. A claim pays what the programme owes, and a reward token that charges its own transfer fee simply funds less. What farming costs instead is fees: while a position is enrolled, the position manager keeps a governance-set share — farmedFeeShare, in basis points, up to the whole fee — of the swap fees that position earns.
  • Charged on collection, on a range move, and on leaving the farm, so no ordering avoids it.
  • Only fees credited while enrolled are ever in scope; principal never is.
  • A cut a token refuses is deferred, not reverted: it stays in the pool position and is fetched later by a permissionless call. Read what is outstanding with deferredFarmedFee(pool, tickLower, tickUpper, token).
  • Events: FarmedFeeTaken, FarmedFeeDeferred, FarmedFeeCollected, and FarmedFeeChanged when governance retunes it.

Where to read each rate

Two trust statements the contracts make plainly, worth carrying into any risk write-up: a plugin and governance together can set a fee just short of the whole trade, and the protocol share can be set to the entire fee. Both live behind governance keys, and both are readable at any moment from the getters above.