On-chain revenue sharing is a mechanism where a protocol's generated fees or profits are programmatically distributed to its token holders. This creates a direct economic alignment between users and the protocol's success. Unlike traditional dividend models, these distributions occur automatically and transparently on the blockchain, typically triggered by specific events like fee collection or on a regular schedule (e.g., weekly, monthly). The most common implementation uses a governance or utility token, where holding the token grants a pro-rata claim to a share of the protocol's treasury or revenue stream.
How to Implement Token-Based Revenue Sharing for Members
How to Implement Token-Based Revenue Sharing for Members
A technical guide to building on-chain systems that automatically distribute protocol revenue to token holders using smart contracts.
The core smart contract architecture involves a few key components. First, a mechanism to collect and escrow revenue, often a dedicated vault or treasury contract that receives native tokens (like ETH) or stablecoins from protocol fees. Second, a distribution logic that calculates each eligible holder's share, usually based on their proportional token balance at a specific snapshot block. Third, a claim function that allows users to withdraw their allocated share, which is more gas-efficient than automatic pushes for large holder bases. Popular standards like ERC-20 or ERC-1155 are used for the membership token.
Here is a simplified Solidity example of a basic revenue-sharing contract. The contract tracks total revenue collected and allows users to claim based on their token balance proportion.
solidity// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract BasicRevenueShare { IERC20 public membershipToken; IERC20 public revenueToken; // e.g., USDC uint256 public totalRevenueDeposited; mapping(address => uint256) public lastClaimedAt; constructor(address _membershipToken, address _revenueToken) { membershipToken = IERC20(_membershipToken); revenueToken = IERC20(_revenueToken); } function depositRevenue(uint256 amount) external { revenueToken.transferFrom(msg.sender, address(this), amount); totalRevenueDeposited += amount; } function claimShare() external { uint256 userBalance = membershipToken.balanceOf(msg.sender); uint256 totalSupply = membershipToken.totalSupply(); uint256 share = (userBalance * totalRevenueDeposited) / totalSupply; require(share > 0, "No share available"); totalRevenueDeposited -= share; revenueToken.transfer(msg.sender, share); lastClaimedAt[msg.sender] = block.timestamp; } }
This basic model has limitations, like not accounting for token transfers between snapshots, which more advanced implementations solve.
For production systems, consider critical design decisions. Snapshotting (using a merkle tree or a snapshot token) prevents users from buying tokens just before a distribution. Gas optimization is crucial; consider distributing in stablecoins instead of volatile native tokens and using merkle claims or layer-2 solutions. Security is paramount: the revenue vault should have strict access controls, and the math must be protected against rounding errors and reentrancy attacks. Audited templates from projects like Sablier (for streaming) or OpenZeppelin payment splits can provide a secure foundation.
Real-world implementations vary. Some protocols, like SushiSwap, distribute a portion of trading fees to xSUSHI stakers. Others, like Lido, share staking rewards with stETH holders. The model can be extended with features like vesting schedules for team allocations, multi-token revenue support (distributing ETH, USDC, and protocol tokens), or tiered systems where different token tiers receive different revenue percentages. The choice depends on the tokenomics and desired user incentives.
To implement this, start by defining the revenue source and distribution frequency. Use established libraries for safe math and token handling. Thoroughly test distribution logic with edge cases, and always get a professional audit before mainnet deployment. This creates a transparent, trust-minimized system that rewards long-term community members and aligns incentives for sustainable protocol growth.
Prerequisites and Setup
This guide outlines the technical prerequisites and initial setup required to implement a secure, on-chain token-based revenue sharing system for a membership protocol.
Before writing any code, you must define the core economic parameters of your revenue-sharing model. This includes determining the revenue token (a new ERC-20 or an existing governance token), the revenue source (protocol fees, NFT royalties, external payments), and the distribution logic. Key decisions are whether distribution is pro-rata based on token holdings, uses a merkle distributor for gas efficiency, or employs a vesting schedule to align long-term incentives. Clearly document these rules, as they will dictate your smart contract architecture.
Your development environment needs a local blockchain for testing, such as Hardhat or Foundry. Install the necessary dependencies: a Node.js environment, the Hardhat/Foundry toolkit, and essential libraries like OpenZeppelin Contracts for secure, audited base implementations (e.g., ERC20, Ownable, SafeERC20). You will also need a wallet with testnet ETH for deployments. For interacting with existing DeFi protocols as a revenue source, consider integrating with Chainlink Data Feeds for price oracles and the Multicall pattern for efficient on-chain data aggregation.
The foundation of the system is a set of interoperable smart contracts. You will typically need a Revenue Collector contract to receive and hold funds (in ETH or ERC-20 tokens), a Distributor contract containing the core logic for calculating and executing payouts, and the Membership Token contract itself. Using OpenZeppelin's ERC20Snapshot extension can be crucial, as it allows you to record token holder balances at specific block numbers, ensuring fair distribution even if tokens are traded between snapshot and payout.
Security is paramount when handling user funds. Implement access controls using OpenZeppelin's Ownable or AccessControl to restrict critical functions like triggering a distribution or withdrawing funds to a designated admin. For the distributor, incorporate checks-effects-interactions patterns and use ReentrancyGuard to prevent reentrancy attacks. Always write comprehensive tests covering edge cases: zero balances, large holder distributions, contract pausing, and failed external transfers. Tools like Slither or Mythril can be used for static analysis.
Finally, plan for the user interface and real-world operation. Your front-end will need to connect to the distributor contract to allow users to claim their share, displaying pending rewards and transaction history. For maintenance, establish off-chain scripts (using ethers.js or viem) to fetch on-chain revenue events, calculate distributions, and optionally generate merkle proofs. Decide on an upgrade path; using a proxy pattern (e.g., Transparent or UUPS) allows you to fix bugs or adjust parameters post-deployment, but adds significant complexity.
Implementing Token-Based Revenue Sharing
A guide to designing and deploying a secure, on-chain system for distributing protocol revenue to token holders.
Token-based revenue sharing is a core mechanism for aligning incentives in decentralized protocols. It allows a project to programmatically distribute a portion of its generated fees or profits directly to holders of its native governance or utility token. This transforms token holders from passive speculators into active stakeholders with a direct financial interest in the protocol's success. Common revenue sources include trading fees from a DEX, interest from a lending protocol, or minting fees from an NFT marketplace.
The system architecture requires several key smart contract components. A Revenue Accumulator contract receives and holds the protocol's earnings, often in a stablecoin like USDC or the network's native gas token. A Distribution Logic contract contains the rules for allocation, such as snapshots of token holder balances and the calculation of pro-rata shares. Finally, a Claim Mechanism allows users to withdraw their entitled share. Security is paramount; these contracts must be immutable or governed by a robust, time-locked multisig to prevent fund diversion.
A critical design choice is between push and pull distribution models. In a push system, the contract automatically sends tokens to all eligible holders in a single transaction, which can be prohibitively expensive in gas fees. The pull model, used by protocols like SushiSwap's xSUSHI, is more gas-efficient: it credits rewards to a global pool and lets users initiate a claim transaction to withdraw their accrued share. This shifts the gas cost to the claimant and prevents failed transactions from funds being sent to inactive wallets.
Implementing a basic pull-based distributor involves tracking a cumulative reward per token. The formula rewards = (balanceOf(user) * cumulativeRewardPerToken) - userRewardDebt calculates a user's claimable amount. Here's a simplified Solidity snippet for the core state and function:
solidityuint256 public cumulativeRewardPerToken; mapping(address => uint256) public userRewardDebt; function updateRewards(uint256 newReward) internal { cumulativeRewardPerToken += (newReward * 1e18) / totalSupply; } function claim() external { uint256 pending = (balanceOf(msg.sender) * cumulativeRewardPerToken) / 1e18 - userRewardDebt[msg.sender]; userRewardDebt[msg.sender] = (balanceOf(msg.sender) * cumulativeRewardPerToken) / 1e18; if(pending > 0) { safeTransferRewardToken(msg.sender, pending); } }
For production systems, you must integrate secure access controls, add a timelock for governance changes, and consider gas optimization techniques like merkle tree distributions for large holder sets. Always conduct thorough audits, as seen with established revenue-sharing contracts from Compound (COMP) or Aave (stkAAVE). The final architecture should be transparent, gas-efficient, and resilient, ensuring token holders can trust the automated and fair distribution of protocol-generated value.
Distribution Mechanism Comparison
A comparison of common on-chain mechanisms for distributing revenue shares to token holders.
| Feature / Metric | Manual Claim (Pull) | Automatic Transfer (Push) | Vesting Contract |
|---|---|---|---|
Gas Cost for Admin | $5-15 per distribution | $50-200 per distribution | $100-300 initial setup |
Gas Cost for Member | $10-30 per claim | None | None |
Distribution Automation | |||
Claim Flexibility | |||
Tax Reporting Complexity | High (per claim) | Medium (per tx) | Low (scheduled) |
Typical Use Case | Small DAOs, infrequent payouts | High-frequency revenue (e.g., daily) | Team/Investor allocations |
Smart Contract Risk | Low (simple function) | Medium (auto-exec logic) | High (time-locked logic) |
Member Onboarding | Requires action | Fully passive | Fully passive |
Implementing Snapshot and Eligibility
A technical guide to using on-chain snapshots and eligibility checks for automated, transparent revenue sharing with token holders.
Token-based revenue sharing automates the distribution of protocol fees or profits directly to eligible holders. The core mechanism involves two distinct phases: first, taking a snapshot of token ownership at a specific block height to create a definitive list of participants, and second, applying eligibility rules to determine each participant's share. This process ensures fairness and transparency, as the rules and the resulting distribution are verifiable on-chain. Common use cases include distributing governance token rewards, sharing DEX trading fees, or allocating profits from NFT project royalties.
The first step is capturing the snapshot. For ERC-20 or ERC-721 tokens, you query the contract's state at a predetermined block number. Using a tool like The Graph to index historical data or a node provider's archive node via eth_call is essential, as you cannot query past states directly from the current chain head. The snapshot records each holder's address and their balance at that moment. It's critical to pin the block hash in your smart contract or off-chain script to prevent manipulation via chain reorganizations. For large token sets, consider using a Merkle tree to efficiently store and verify holdings.
Next, define and enforce eligibility criteria. Common rules include a minimum token balance (e.g., 100 tokens), a vesting period where tokens must have been held since before the snapshot, or exclusion of certain addresses like the project treasury or known exchange contracts. These checks are often performed off-chain in the distribution script for gas efficiency, with the results (like a Merkle root of eligible addresses and their shares) stored on-chain. The share is typically calculated pro-rata based on the snapshot balance, but can also use tiered systems or include multipliers for long-term holders.
Here is a simplified example of an off-chain script snippet that calculates pro-rata shares from a snapshot, using ethers.js and assuming data is fetched from a subgraph:
javascript// Pseudocode for share calculation const snapshotBlock = 18900000; const totalRevenue = ethers.utils.parseEther('100'); // 100 ETH to distribute const holders = await subgraphQuery(snapshotBlock); // Fetch balances const totalSupplyAtSnapshot = holders.reduce((sum, h) => sum + h.balance, 0); const distribution = holders.map(holder => ({ address: holder.id, share: totalRevenue.mul(holder.balance).div(totalSupplyAtSnapshot) }));
This list can then be used to generate a Merkle tree for a gas-efficient claim contract or to execute a batch distribution.
For on-chain enforcement, deploy a claim contract. A common pattern is a Merkle distributor, where the contract stores a Merkle root of the eligible addresses and their allocated amounts. Users submit a Merkle proof to claim their share, which the contract verifies against the stored root. This is gas-efficient as only claiming users pay gas. Always include a deadline for claims and a function for the admin to recover unclaimed funds after the period ends. Audit this contract thoroughly, as errors in Merkle proof verification can lead to loss of funds.
Key considerations for a production system include handling airdrops to contract addresses (which may not be able to claim), accounting for token transfers after the snapshot (the snapshot is immutable), and managing gas costs for distribution. For large holder sets, direct batch transfers via a multisend contract may be prohibitively expensive. Always publish the snapshot data, eligibility rules, and Merkle root on IPFS or a similar decentralized storage solution to ensure full transparency and allow community verification of the distribution fairness.
Building a Merkle Distributor Contract
A Merkle distributor is a gas-efficient smart contract pattern for distributing tokens or NFTs to a large list of addresses. This guide explains how to implement one for token-based revenue sharing.
A Merkle distributor solves a critical scaling problem in on-chain airdrops and revenue sharing. Instead of storing all recipient addresses and amounts in the contract storage—which is prohibitively expensive—it stores only a single cryptographic hash, the Merkle root. This root is generated from a Merkle tree, where each leaf is a hash of a recipient's address and their allocated amount. To claim, a user provides a Merkle proof, a small set of hashes that the contract uses to verify their leaf's inclusion in the tree without needing the full list.
To build the distributor, you first need to generate the Merkle tree off-chain. Using a library like @openzeppelin/merkle-tree in JavaScript, you create leaves from an array of [address, amount] pairs. The library hashes and sorts these to produce the final root. This root and the total token amount are the only data you need to initialize your contract. The complete claim list can be hosted on IPFS or a server, allowing users to fetch their specific proof.
The core contract inherits from OpenZeppelin's MerkleProof library. Its key function is claim(address recipient, uint256 amount, bytes32[] calldata proof). Inside, it hashes the recipient and amount to recreate the leaf, then calls MerkleProof.verify(proof, merkleRoot, leaf). If valid, it marks the claim as processed in a mapping and transfers the tokens using IERC20(token).transfer(recipient, amount). This design ensures each address can only claim once and only their designated share.
For revenue sharing, this pattern is ideal. A DAO's treasury can periodically snapshot member contributions or token holdings, calculate rewards, and update the Merkle root in the distributor contract. Members claim at their convenience, paying their own gas. Key security considerations include using a pull-over-push model to avoid failed transfers, ensuring the token contract is trusted, and carefully verifying the off-chain generation script to prevent root manipulation.
Pro-Rata Share Calculation Logic
A technical guide to implementing on-chain revenue distribution using token-weighted pro-rata logic, covering key formulas, Solidity patterns, and security considerations.
Pro-rata share calculation is a core mechanism for distributing rewards, fees, or governance power proportionally to a user's stake in a pool. The fundamental formula is: userShare = (userTokens / totalTokens) * distributionAmount. In a token-based revenue sharing model, this logic is executed on-chain via a smart contract whenever revenue (e.g., protocol fees) is collected. The contract must track the total supply of the membership or stake token and each holder's balance at the time of distribution. A critical design choice is whether to use a snapshot of balances or real-time balances, each with different gas and fairness implications.
A basic Solidity implementation involves a state variable for the distributable asset (often the native chain token or an ERC-20) and a function to trigger the payout. Here is a simplified example using the pull-over-push pattern for gas efficiency:
solidityfunction claimShare() external { uint256 total = totalTokenSupply; uint256 myBalance = balanceOf(msg.sender); uint256 myShare = (distributableRevenue * myBalance) / total; require(myShare > 0, "No share available"); distributableRevenue -= myShare; payable(msg.sender).transfer(myShare); }
This pattern lets users claim their share on-demand, saving gas for the protocol and preventing failed transactions to inactive addresses, which is a risk with automatic "push" distributions.
For accurate and fair distributions, especially in systems where tokens are transferable, you must decide on a snapshot mechanism. Without one, users could buy tokens right before a distribution and sell immediately after (a form of "sniping"). To prevent this, consider using a snapshot of token balances from a specific block number. Alternatively, implement a checkpoint system like those used in vote-escrow models (e.g., Curve Finance), where a user's share is based on their time-weighted average balance over an epoch. The ERC20Snapshot extension from OpenZeppelin provides a foundational tool for this.
When distributing ERC-20 tokens as revenue, security practices are paramount. Always ensure the contract has granted sufficient allowance to the distributor. Use the Checks-Effects-Interactions pattern to prevent reentrancy: update the internal state tracking the remaining distributable revenue before making the external call to transfer tokens. For arithmetic, favor multiplication before division to minimize early rounding errors, and use a library like SafeMath (for Solidity <0.8) or rely on the built-in overflow checks in Solidity 0.8+. Failing to account for decimal precision of the revenue and stake tokens is a common source of calculation errors.
Advanced implementations may integrate with deflationary or rebasing tokens. If your stake token's total supply can change (e.g., through burns or elastic supply), your snapshot logic must account for it. Furthermore, for automated, periodic distributions, you can combine the pro-rata logic with a keeper network (like Chainlink Automation) to trigger a distribution round. The key is to make the calculation function permissionless or role-restricted and to emit clear events (e.g., RevenueDistributed(uint256 amount, uint256 timestamp)) for transparency and off-chain tracking.
Finally, always test your logic comprehensively. Use forked mainnet simulations with tools like Foundry or Hardhat to verify calculations against real-world token balances and decimal variations. The goal is a system that is transparent, gas-efficient, and resilient to manipulation, ensuring members receive their correct economic share without trust in a central operator. For production inspiration, review the distributor contracts of established protocols like Compound (COMP distribution) or Lido (stETH reward referrals).
How to Implement Token-Based Revenue Sharing for Members
A technical guide to building a gas-efficient smart contract system for distributing revenue across multiple token holders.
Token-based revenue sharing is a core mechanism for decentralized autonomous organizations (DAOs), staking protocols, and community projects. It allows a protocol to collect fees or profits and distribute them proportionally to members based on their holdings of a specific governance or utility token. The primary challenge is designing a system that is gas-efficient and can handle an unbounded number of token types without prohibitive costs. A naive approach of iterating over all token holders for each distribution is computationally impossible on-chain.
The standard solution is to use an accrual or pull-based payment model. Instead of actively sending tokens to each member (a push payment), you credit an internal accounting ledger with each member's share. Members then call a claim function to withdraw their accumulated rewards. This shifts the gas cost of the transaction from the protocol (which would pay for many transfers) to the individual user (who pays for one). The core data structure is a mapping, such as mapping(address => uint256) public rewards;, which tracks unclaimed amounts per user.
To handle revenue in multiple ERC-20 tokens, you must maintain separate accounting ledgers for each currency. A robust implementation uses a nested mapping: mapping(IERC20 => mapping(address => uint256)) public rewards;. When the protocol receives USDC fees, it calls an internal _creditRewards function that calculates each eligible member's share based on their token balance at that block and updates their entry in the rewards[usdc][member] ledger. The contract must safely hold the treasury of all reward tokens.
Calculating shares fairly requires a snapshot of token balances. For simple, non-transferable tokens, you can read balances directly from the token contract. For transferable tokens, you must use a snapshot mechanism to prevent manipulation, such as the ERC20Snapshot extension from OpenZeppelin or a checkpointing system like that used in ERC20Votes. The distribution logic typically uses the formula: userShare = (totalRevenue * userBalanceAtSnapshot) / totalSupplyAtSnapshot. This calculation is performed off-chain or in a gas-optimized manner during the funding transaction.
Critical gas optimization techniques include using SSTORE opcodes sparingly. Instead of updating a user's reward balance for every revenue event, you can store a cumulative rewardsPerToken value and a userDebt value. A user's claimable amount is calculated as (rewardsPerToken - userDebt) * userBalance. This pattern, inspired by MasterChef-style staking contracts, requires only two state updates per user (on stake/deposit and claim) rather than one per distribution event. Always use checks-effects-interactions and guard against reentrancy when transferring tokens.
For production deployment, consider integrating with existing standards like EIP-2981 for royalty payments or building atop modular primitives from libraries like Solady or OpenZeppelin. Thoroughly test distribution logic with forked mainnet simulations to ensure accuracy under high gas prices and with many users. The final system should allow members to claim multiple token rewards in a single transaction to further optimize their gas costs.
Resources and Further Reading
These resources cover the core smart contract patterns, token standards, and production protocols used to implement token-based revenue sharing for members. Each link focuses on practical implementation details rather than high-level theory.
Frequently Asked Questions
Common technical questions and solutions for implementing automated, on-chain revenue distribution for token holders or community members.
The core architectural choice for revenue distribution is between push and pull models.
Push (Active Distribution): The contract logic actively sends funds to recipients. This is often done via a loop in a function like distribute().
solidityfor (uint i = 0; i < recipients.length; i++) { (bool success, ) = recipients[i].call{value: amounts[i]}(""); require(success, "Transfer failed"); }
Pros: Automatic for recipients. Cons: High gas costs, risk of running out of gas with many recipients, and failed transfers can block the entire process.
Pull (Claimable Balances): The contract tracks each user's share in a mapping (e.g., mapping(address => uint256) public claimable;). Users call a claim() function to withdraw their accrued balance.
solidityfunction claim() external { uint256 amount = claimable[msg.sender]; require(amount > 0, "Nothing to claim"); claimable[msg.sender] = 0; (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed"); }
Pros: Gas-efficient, user-initiated, avoids failed transfer issues. Cons: Requires user action. The pull model is generally preferred for its security and scalability.
How to Implement Token-Based Revenue Sharing for Members
A secure token-based revenue sharing contract requires careful design to prevent exploits and ensure fair, verifiable payouts. This guide covers critical security patterns and audit checklists.
The primary security risk in revenue sharing is the reentrancy attack, where a malicious contract can call back into your payout function before its state is updated. For ERC-20 token distributions, use the Checks-Effects-Interactions pattern: validate inputs, update internal state (like claimed balances), and then perform the external token transfer. For native ETH payouts, consider using Pull-over-Push, where users withdraw funds themselves, mitigating denial-of-service risks. Always use OpenZeppelin's ReentrancyGuard for functions handling external calls.
Accurate and tamper-proof accounting is non-negotiable. Your contract must correctly calculate each member's share, often based on their token balance at a specific snapshot block. Use a merkle tree for gas-efficient verification of off-chain calculations, as seen in protocols like Uniswap's merkle distributor. For on-chain calculations, ensure the totalShares denominator cannot be manipulated and that rounding errors don't lead to locked funds. Implement a withdrawal pattern so users initiate claims, preventing forced sends and reducing gas costs for the distributor.
Access control and privilege escalation are common audit findings. Clearly define admin roles (e.g., using OpenZeppelin's Ownable or AccessControl) for critical functions like setting the merkle root, pausing distributions, or recovering stuck tokens. However, these powers must be limited; an admin should never be able to arbitrarily mint shares or alter a user's rightful claim. Consider implementing timelocks for sensitive admin actions and a multi-signature wallet for the treasury or admin keys to decentralize trust.
Thoroughly test your implementation. Write unit tests (using Foundry or Hardhat) covering edge cases: zero balances, duplicate claims, front-running protection, and contract interactions from other smart contracts (not just EOAs). Perform fuzz testing to input random data and invariant testing to ensure key properties (e.g., "total claimable never exceeds contract balance") always hold. Use tools like Slither or Mythril for static analysis before engaging a professional audit firm like Trail of Bits or ConsenSys Diligence for a final review.
Conclusion and Next Steps
This guide has outlined the core architecture for building a token-gated revenue sharing system. The next steps involve deployment, testing, and exploring advanced features.
You should now have a functional understanding of the key components: a revenue vault contract to collect funds, a membership token (ERC-20 or ERC-721) to gate access, and a distributor contract to execute pro-rata payouts. The critical security pattern is using onlyOwner functions for fund management and a claim() function for members, preventing unauthorized withdrawals. Always conduct a final audit of the logic, especially the calculation in the _calculateShare function, to ensure mathematical accuracy and prevent rounding errors that could lock funds.
Before a mainnet deployment, rigorously test the system. Use a forked mainnet environment with tools like Hardhat or Foundry to simulate real transactions. Write comprehensive tests that cover: minting tokens to mock members, sending various amounts of ETH or ERC-20 tokens to the vault, executing distributions, and verifying each member's claimable balance. Test edge cases like zero-balance members, large member counts, and minimal payout amounts. Consider integrating with a front-end library like wagmi or ethers.js to build a simple dApp interface for users to connect their wallets and claim rewards.
For production, evaluate gas optimization. Batch operations or using a merkle distributor pattern can significantly reduce costs for large member sets. Instead of storing individual claims on-chain, you calculate a merkle root of all entitlements off-chain. Members then submit a merkle proof to claim, which is far more gas-efficient. Explore integrating with Chainlink Automation or Gelato to trigger periodic, automated distributions without manual intervention. Finally, document the contract addresses, ABI, and a clear user guide for your community. The complete code and further resources are available in the Chainscore Labs GitHub repository.