Blog

Fees you can only see by trying to take them

Uniswap V3 has no view for a position’s pending fees. The profile page shows them anyway, by simulating collectFees against the current block. On the difference between “zero” and “already paid”, and why the split is snapshotted.

August 26, 2026 5 min readfeescontracts

A creator wants to know what they have earned. On the old bonding-curve pool that was easy: fees were pushed on every trade, and a pendingFees mapping held the rare deferred case. On a locked Uniswap V3 position it is harder, because V3 does not expose a clean answer.

The problem

A position’s tokensOwed0/1 are only updated when the position is poked. The fees that have accrued since then are implied by the difference between the pool’s global fee growth and the position’s last-recorded fee growth, and reconstructing that off-chain means reimplementing the pool’s tick maths. The one thing that is guaranteed correct is whatever collect would actually return right now.

So we ask by pretending to claim

usePonsCreatorFees simulates collectFees(token) on the locker with eth_call from the creator’s address. The simulation runs the real collect, the real split, and returns the real amounts — against the same block the UI is reading — without sending anything. When the creator clicks claim, the identical call is sent for real.

If nothing has accrued, the locker reverts NoFeesToCollect. That revert is the zero case, and the hook treats it as zero rather than as an error.

Zero means “not yet”, not “already paid”
This is the opposite of the old design, where zero pending usually meant the fee had already been pushed to you on trade. These contracts never push. A zero here is simply a position that has not earned since the last claim.

Why the split is snapshotted

When a position is locked, the locker copies its current protocolFeeShare into tokenProtocolFeeShares[token], and collectFees reads from there. The owner can change the global share — capped at 50 by a constant — but that only affects tokens locked afterwards. A fork test launches at 30%, sets 50%, and asserts the first token still splits at 30%.

The reason is trust arithmetic. A creator deciding whether to launch here is agreeing to a 70/30 split. If the protocol could later change that to 50/50 for tokens already trading, the agreement would be worth nothing. So the contract makes it impossible rather than merely promised.

Fees arrive as what they were paid in

A buy pays its 1% in WETH; a sell pays in the token. Claiming delivers both assets, unconverted. The profile page shows them as two lines, not as one USD figure, because converting the token leg at spot would be pretending it could be sold at spot.

Redirects

A deployer can route their share to another wallet with setFeeRedirect. The locker keeps a reverse index so the profile page can also show tokens a wallet was assigned, not just ones it deployed. Multiple factories — ours and both of pons’s — are queried and merged, because a creator can hold launches on more than one.