Liquidity & LP fees
The FeeLocker holds every launch position permanently while swap fees stay claimable by anyone, and split creator/protocol.
A launch mints its whole supply into one concentrated-liquidity position and hands that NFT to a FeeLocker. The locker has no function that transfers, approves, decreases, burns or sweeps a position it holds — not for the owner, not for anyone. The liquidity is there permanently. What stays live is the fee stream, and collecting it is open to anybody.
#The lock
Positions can only enter through onERC721Received, and the locker rejects everything that is not a launch:
- the caller must be this venue’s position manager (immutable at deploy) — foreign NFT collections bounce with
OnlyPositionManager; - the sender must be the launcher (immutable) —
OnlyLauncherotherwise; - the range must reach an extreme usable tick on at least one side —
RangeNotExtremeotherwise. A defensive backstop: the real invariant is that the launcher only ever mints one-sided-to-infinity curve positions.
The deposit carries abi.encode(creator, creatorBps), which becomes the position’s permanent configuration. creatorBps is fixed at that moment and cannot be changed by anyone, ever — not by the creator, not by the protocol.
#Collecting fees
collect(tokenId) and collectMany(tokenIds[]) are permissionless. Anyone can crank them — the creator, a holder, a keeper bot, a curious stranger — and the destination of the proceeds is fixed by the position’s config, not by who called. Cranking is a favour, not a privilege.
anyone ──▶ FeeLocker.collect(positionId)
│ pulls both fee amounts from the position manager
▼
V3FeeReceiver.onFeesCollected(...)
│
┌─────────────┴──────────────┐
▼ ▼
TOKEN side WETH side
│ │
token fee policy set? split by creatorBps (70/30)
│ │
yes│ no creator │ │ protocol
▼ ▼ ▼ ▼
full 70/30 split creator policy paid in kind
amount in kind set? yes -> policy
to the no -> creator recipient
policy#The split
| Side | With a fee policy | Without one |
|---|---|---|
| Token side | The full amount is handed to the token-side policy — burn, vest, or stake. | Split creator/protocol in kind by creatorBps. |
| WETH side | Split by creatorBps first; the creator’s 70% goes to the creator-side policy (buy-burn, stake, or a backing vault). The protocol’s 30% is always paid in kind. | Split by creatorBps; the creator’s 70% is paid straight to the position’s creator recipient. |
creatorBps is currently 7000 — a 70/30 split, 70% to the creator — and it is protocol-wide, not a per-creator deal. The protocol can change it for future launches but never below the compiled-in floor of 1000 (10%), and never for a token already launched.
#Nothing can brick collection
Two independent fallbacks make collect() effectively un-revertable, which matters because it is the shared path for every launched token:
- Push with a pull fallback. If paying a recipient fails — a blocklisted address, a paused token, a contract that reverts on receive — the amount is credited to
claimable[recipient][token]and can be pulled later withclaim(token). One bad recipient cannot block collection for anyone else. - Delegation is try/catch’d. The receiver approves the fee manager and calls it; if the policy reverts, the approval is cleared and that amount falls back to the plain in-kind split for this collection. A broken policy module cannot strand fees or halt the crank.
#Rotating where your fees land
FeeLocker.updateCreatorRecipient(tokenId, newCreator) changes where a position’s creator share is paid. It is gated by TokenOwnerRegistry.ownerOf(token) — the same key that controls metadata and the vesting recipient — so a single ownership transfer moves this power along with everything else. The share itself (creatorBps) is not touchable; only the destination is.
#Admin surface
| Contract | Owner powers | Cannot |
|---|---|---|
| FeeLocker | setFeeReceiver only — swap what happens to fees. Renounce to ossify. | Touch liquidity, move NFTs, change a creatorBps, or change a creator recipient. |
| V3FeeReceiver | None. The protocol recipient can only rotate itself. | Everything else — the contract has no owner. |
The setFeeReceiver seam is the one deliberate piece of mutability. It is what lets the protocol add a new fee behaviour (say, converting the token side to ETH) without ever relaunching the locker or moving a single position. A future policy is just a new contract implementing IV3FeeReceiver.
#Per-venue deployments
A locker binds its position manager immutably, so each venue has its own locker and its own receiver. Everything downstream is shared.
