Claiming fees
One function, on both factories, and anybody may call it. What the caller cannot do is change where the money goes — there is no recipient argument, so there is nothing to misroute.
function collectFees(address token) external;// emitted by both factories, same signature, same topic0event FeesCollected( address indexed token, address indexed creator, uint256 creatorNative, // creatorWeth on the V3 factory — same slot uint256 platformNative, uint256 creatorToken);Permissionless on purpose
creatorFeeBps. A creator who loses interest does not strand their own fees, and nobody who calls it gains anything by doing so.Three regimes, and the hook decides which
Where fees come from — and therefore what tokens arrive — depends on whether the pool has a quote-side hook, not on the Uniswap version. Those are different questions and conflating them is how the launch panel once promised Base something it does not do.
| Regime | Fee comes from | Creator receives |
|---|---|---|
| V3 | the liquidity position, both sides | creatorFeeBps of the quote side, and 100% of the coin side |
| V4 with hook | 1% of the quote side, taken outside the pool | creatorFeeBps of that — quote asset only, no coin side at all |
| V4 hookless | the liquidity position, both sides | creatorFeeBps of the quote side, and 100% of the coin side |
The hook runs on Arc and Robinhood Mainnet. Everywhere else the third row applies, which is why a flat “90% in the quote asset” is true on one chain and not on another. The live split per network is on Fees and creator rewards.
A coin may send the creator's side somewhere else entirely
collectFees pays creatorOf[token], and on a coin whose creator chose holder fee sharing that address is a vault contract, not a wallet — holders then claim from it individually. The call is unchanged either way, which is the point: nothing in the fee path branches on it.A creator's own trade tax is separate and is paid to
taxPayeeOf[token], which stays the human even on a sharing coin. So one collectFees can pay three different addresses. Reading creatorOf alone and calling it “the creator” will misattribute both. See Trade tax and holder rewards.Reading what is owed, before claiming it
V3 has collect with a static call and you get a number. V4 has nothing equivalent: there is no view that answers “how much does this position owe”. You compute it.
Fees are a per-pool accumulator minus the snapshot taken when the position last moved, multiplied by liquidity. The subtraction must wrap:
// Solidity computes this unchecked, so it wraps past 2**256.// A plain subtraction in JS or Python goes negative and reports// a huge NEGATIVE fee on a position that is perfectly healthy.owed = ((growthNow - growthAtSnapshot) & ((1n << 256n) - 1n)) * liquidity >> 128n;feesOwedFromGrowth in @muupfun/shared does this, with a test that fails if the mask is removed. Reuse it rather than reimplementing the wrap.
A wrong position key reports no fees, not an error
keccak(positionManager, tickLower, tickUpper, bytes32(tokenId)). Get any part of it wrong and StateView returns a position with zero liquidity rather than reverting — so the integration reports “nothing owed” on a position earning steadily, and there is nothing in the response that looks wrong. Check the liquidity you read against the PositionManager’s own figure before trusting any number derived from it.When a claim can fail for reasons that are not ours
- A blocklisted wallet. On a chain whose issuer can freeze an address, a transfer to or from one reverts — and
collectFeespays a creator wallet, so the whole call reverts rather than skipping that leg. Nothing in our contract is broken when that happens. - A position that has never traded.
collectFeeson a coin with no accrued fees succeeds and pays nothing. It is not an error, and repeated calls are harmless. - Calling from inside another V4 lock. The factory does not guard for it, deliberately — the selector that would have given a nicer error is not in the deployed PoolManager. The call still fails, just with V4’s error instead of ours.
Fees are claimable, not automatic
