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
| slot | set by | receives |
|---|---|---|
| pot recipient | the pot admin (initPot / setRecipient) | the main the pot buys and absorbs — pumps and shields. address(0) = burn |
| mainRecipient | the program operator (setProgramConfig) | the main-side fee remainder after compound + burn |
| secondaryRecipient | the 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
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
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
| pattern | how |
|---|---|
| treasury | the plain shape: a Safe or governance treasury receives the remainder as protocol revenue |
| staking rewards | point the remainder at a rewards distributor — LP fee flow becomes staking yield with zero keepers |
| splitter / vesting | any payment-splitter or vesting contract works unmodified — the hook just sends; policy lives in the recipient |
| cross-pool routing | a tiny adapter whose receive()/sweep calls donate on ANOTHER pool's pot — one pool's fees become another pool's buyback pressure |
| buyback-and-make | an adapter that adds the remainder back as liquidity elsewhere, or market-buys a different asset — the recipient slot is where custom strategy composes |
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.