chapters· Recipients
12 · LP fees management

The recipients

One address per side, the exact remainder, and every pattern a recipient can implement.

Each side of the fee split names one recipient for its remainder — and because the remainder is computed by exact subtraction, "the rest" means the rest to the wei. A recipient is just an address, which makes it the program's most composable slot: anything that can receive tokens can be a fee policy.

Three recipients in a pool's life — don't conflate them

slotset byreceives
pot recipientthe pot admin (initPot / setRecipient)the main the pot buys and absorbs — pumps and shields. address(0) = burn
mainRecipientthe program operator (setProgramConfig)the main-side fee remainder after compound + burn
secondaryRecipientthe program operator (setProgramConfig)the secondary-side fee remainder after compound + buyback

The pot recipient is a buyback delivery target; the two program recipients are fee payees. They can be the same address or three different ones — the roles that control them are independent by design.

The remainder is exact — and the dust is yours

per side, every harvest
recipient leg = gross ⌊compound⌋ ⌊buyback or burn⌋ // subtraction, not a third multiplication: the share legs round DOWN, // so every wei of division dust lands with the recipient — never orphaned.

This is also why the validation insists on a live recipient whenever the shares sum below 100%: below-100% means a remainder can exist, and the design has no "nowhere" for value to go. At exactly 100% the remainder is structurally zero and the recipient slot may be empty. The full derivation is in The split math.

How the money actually arrives

harvest books the leg — state final before any external call
bounded-gas push to the recipient
success → Paid · refusal → the exact amount books as owed
owed folds into the next push, or the recipient pulls with claim(asset)· full gas, any time

A recipient therefore needs nothing special to work — an EOA, a Safe, a contract with a plain receive() all just get paid. And a recipient that is heavy or hostile hurts only itself: its legs accumulate in the owed ledger until it claims. The plumbing is detailed in Payouts & the owed ledger.

What a recipient can be — the pattern library

patternhow
treasurythe plain shape: a Safe or governance treasury receives the remainder as protocol revenue
staking rewardspoint the remainder at a rewards distributor — LP fee flow becomes staking yield with zero keepers
splitter / vestingany payment-splitter or vesting contract works unmodified — the hook just sends; policy lives in the recipient
cross-pool routinga tiny adapter whose receive()/sweep calls donate on ANOTHER pool's pot — one pool's fees become another pool's buyback pressure
buyback-and-makean adapter that adds the remainder back as liquidity elsewhere, or market-buys a different asset — the recipient slot is where custom strategy composes
design your recipient for the pull path too

A recipient contract should be able to call claim(asset) (or at least receive from a bounded-gas push). If your recipient's receive() does heavy work, it will be booked as owed every time — fine, but then something must eventually claim. The cheap pattern: accept plainly, do the work in a separate poke.

Changing and freezing

Recipients move with a setProgramConfig call by the operator — instantly, affecting future harvests only. And like every program rule, they can be made permanent: an operator that surrenders (setProgramOperator(poolId, address(0))) freezes the recipients along with the shares, forever — the strongest revenue-share promise a project can make on-chain. See Roles & surrender.

FAQ

How many recipients does a program have?+

One per side: a main-side recipient and a secondary-side recipient. Each receives the exact remainder of its side after the configured shares.

Can a recipient be a contract?+

Yes — staking pools, splitters, vesting vaults, treasuries. Pushes carry a bounded gas stipend; a contract that needs more gas can always claim() its owed balance itself.

What if the recipient refuses the transfer?+

The amount books to the owed ledger under (recipient, asset) and the harvest completes. The recipient — and only the recipient — can pull it any time with claim().

Can the recipients be changed later?+

The operator edits them with setProgramConfig at any time — unless the operator was surrendered, which freezes recipients along with the rest of the config.

What does recipient = 0x0 mean on a fee side?+

A side that can carry value (shares below 100%) must have a live recipient — that's validated at write-time. You zero a recipient only by making the shares consume the whole side.