Developing a Protocol Fee Distribution System
Uniswap V3 generates millions of dollars in fees daily. Before the fee switch was enabled, all this revenue went to LPs — UNI holders received nothing. When governance voted to redirect part of the fees to treasury, a system was needed to collect, accumulate and distribute. This is a typical task — as a protocol monetizes, you need a mechanism that fairly divides revenue among token holders/stakers.
Two Distribution Patterns: Push vs Pull
Push Distribution — Broadcasting to All
The contract accumulates fees and periodically calls distribute(), which iterates through stakers and transfers each their share. Simple to understand, complex to implement: unbounded loop — classic gas griefing vector. With 10,000 stakers, the transaction exceeds block gas limit.
Only acceptable for systems with explicit cap on participant count and batch processing (pagination). In most cases — wrong choice.
Pull Distribution — Users Claim Themselves
The contract maintains rewardPerTokenStored — accumulated reward per unit of stake since launch. On each deposit/withdraw/claim, it updates userRewardPerTokenPaid for the user. Reward = (rewardPerTokenStored - userRewardPerTokenPaid) * balance.
This math comes from Synthetix StakingRewards contract — one of the most copied patterns in DeFi. Key property: O(1) gas complexity for claim, independent of staker count.
function earned(address account) public view returns (uint256) {
return (
(balanceOf[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18
) + rewards[account];
}
We use this pattern as base for most fee distribution systems.
Fee Collection and Conversion
Protocols generate fees in different tokens — swap fees in traded tokens, lending fees in debt-tokens. Before distributing to stakers, you must convert to single target token (usually protocol token or USDC).
FeeCollector contract — aggregates fees from all source contracts. Periodically called by keeper (Chainlink Automation, Gelato) or any user.
Conversion via DEX — swap accumulated fees to target token via Uniswap V3. Important: converting large volume at once creates price impact and MEV opportunities. Solution: convert in small portions via TWAP-oriented swaps or use Cow Protocol for MEV-protected swaps.
Multi-token distribution — sometimes better not to convert, but distribute in original fee tokens. Curve distributes 3CRV LP tokens (stablecoin basket) instead of converting. More expensive to implement (multi-reward staking), but preserves value without slippage.
Multi-Channel Distribution
Rarely does entire fee go only to stakers. Typical scheme:
| Recipient | Share | Mechanism |
|---|---|---|
| Token stakers | 40-60% | Pull-distribution, rewardPerToken |
| Treasury | 20-30% | Direct transfer to multisig |
| Insurance fund | 10-20% | Accumulation for bad debt coverage |
| Burn | 5-10% | token.burn() |
Proportions set via governance-controlled parameters with timelock. FeeDistributor contract reads current proportions on each distribute() call.
veToken Model (Vote-Escrowed)
Curve introduced a model where you must lock CRV for term up to 4 years to receive fees. Longer lock — more veCRV, bigger fee share. This aligns incentives: long-term holders get more. Implementation is more complex than basic staking — need decay calculation for voting power, periodic checkpoints, gauge voting integration.
If veToken mechanics needed — separate scope on top of basic fee distribution.
Development Process and Timeline
Design (2-3 days). Define source contracts, target token, distribution proportions, keeper mechanism for periodic conversion.
Development (5-8 days). FeeCollector + FeeDistributor + Staking contract. Foundry tests: correctness of earned() calculation on totalSupply changes, correct deposit/withdraw handling in same block as distribute().
Integration tests. Fork test with real Uniswap V3 pool to verify fee conversion. Fuzz tests on distribution math — edge cases with very small or very large balances.
Basic system (pull-distribution, single reward token, periodic collect) — 1 week. With DEX conversion and multi-reward — 1.5-2 weeks. veToken mechanics — additional 2-3 weeks.







