A gas-optimized, infinitely scalable staking contract using the proven MasterChef pattern for native token (ETH/BNB/MATIC) reward distribution.
- Features
- What is MasterChef Staking?
- How Is This Different from SushiSwap's MasterChef?
- Use Cases
- Integration Example
- Reward Flow Diagram
- Who Uses MasterChef?
- How This Contract Works
- Installation
- Configuration
- Usage
- Contract Interface
- Frontend Integration
- Security Features
- Gas Comparison
- Constructor Parameters
- Constants
- Supported Networks
- Testing
- License
- Contributing
- Acknowledgments
- Infinite Scalability - O(1) operations, no loops, works with millions of users
- Gas Optimized - 80-90% gas reduction compared to snapshot-based approaches
- Real-time Rewards - Users see earnings update immediately
- Anti-Frontrunning - Configurable eligibility delay prevents flash loan attacks
- MEV Protection - Block-based delay between user actions
- EIP-2612 Support - Gasless staking via permit signatures
- Battle-tested Pattern - Based on the MasterChef algorithm used by billions in DeFi TVL
The MasterChef pattern is a revolutionary reward distribution algorithm first introduced by SushiSwap in 2020. It solved a critical problem in DeFi: how to fairly distribute rewards to thousands (or millions) of stakers without running out of gas.
Traditional staking contracts often use one of these approaches:
-
Snapshot-based: Take periodic snapshots of all stakers and distribute rewards based on those snapshots. This requires O(n) operations where n is the number of users.
-
Epoch-based: Accumulate rewards in epochs and require users to claim from each epoch. This requires O(m) operations where m is the number of epochs.
-
Push-based: Iterate through all users and push rewards to them. This fails completely at scale.
All these approaches have a fatal flaw: gas costs increase linearly with users or time, eventually making the contract unusable.
MasterChef uses a mathematical trick called an accumulator that achieves O(1) complexity for all operations:
accRewardPerShare = Total rewards ever distributed / Total tokens ever staked
Instead of tracking what each user is owed, we track a single global number that represents "rewards per token." When a user stakes or unstakes, we record their "reward debt" - what they would have earned if they had been staking since the beginning.
User's Pending Rewards = (User's Stake × accRewardPerShare) - User's Reward Debt
This elegant formula means:
- Adding rewards: One storage write, regardless of user count
- Staking/Unstaking: Two storage writes per user
- Claiming: One storage read + one write
- Checking rewards: One storage read (view function, free)
The name comes from SushiSwap's original contract that distributed SUSHI tokens to liquidity providers. The "chef" metaphor represents the contract as a chef distributing "rewards" (food) to "stakers" (diners) fairly based on how much they contributed.
This is a common question. While both contracts use the same mathematical pattern, they serve completely different purposes:
| Aspect | SushiSwap MasterChef | This Contract |
|---|---|---|
| Reward Type | Mints new tokens (inflationary) | Distributes native tokens (ETH/BNB/MATIC) |
| Purpose | DEX liquidity mining | Revenue sharing with token holders |
| Complexity | Multi-pool, migrator, dev tax, bonus periods | Single-purpose, simple, ready to deploy |
- You're building a DEX and need to mint reward tokens
- You need multiple pools with different allocation weights
- You want inflationary tokenomics with emission schedules
- You want to share revenue (fees, profits) with your token stakers
- You have an existing token and want to reward holders with ETH/BNB/MATIC
- You want a simple, single-pool staking solution
This contract is designed for revenue sharing - distributing native tokens (ETH/BNB/MATIC) to stakers of your project's token. Here are practical examples:
Your platform collects fees in native tokens and distributes them to token stakers.
┌─────────────────────────────────────────────────────────────┐
│ YOUR PLATFORM │
│ │
│ User pays 0.1 ETH fee ──► Platform takes fee │
│ │ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ Staking Contract │ │
│ │ (this contract) │ │
│ └────────────────────┘ │
│ │ │
│ ▼ │
│ Distributed to all $TOKEN stakers │
└─────────────────────────────────────────────────────────────┘
Example flow:
- Deploy your ERC20 token ($TOKEN)
- Deploy this staking contract with your token address
- Users stake $TOKEN to earn ETH/BNB rewards
- Your platform sends fees to the staking contract
- Rewards are automatically distributed proportionally
// In your platform contract, send fees to staking contract
function collectFee() external payable {
uint256 platformFee = msg.value;
// Send to staking contract - rewards are auto-distributed
(bool success, ) = stakingContract.call{value: platformFee}("");
require(success, "Transfer failed");
}An NFT marketplace shares trading fees with governance token holders.
NFT Sale (1 ETH)
│
▼
┌─────────────────┐
│ 2.5% Fee │ = 0.025 ETH
└─────────────────┘
│
▼
┌─────────────────┐
│ Staking Contract│ ──► Distributed to $MARKET token stakers
└─────────────────┘
A blockchain game distributes in-game purchase revenue to token stakers.
// Weekly distribution from game treasury
async function distributeWeeklyRewards() {
const weeklyRevenue = await getWeeklyRevenue(); // e.g., 10 BNB
await stakingContract.addRewards({ value: weeklyRevenue });
console.log(`Distributed ${weeklyRevenue} BNB to stakers`);
}A lending protocol shares interest fees with governance token stakers.
A DAO distributes treasury earnings to token holders who stake.
Here's a complete example of integrating the staking contract with your platform:
// Your existing ERC20 token
contract MyToken is ERC20 {
constructor() ERC20("My Token", "MTK") {
_mint(msg.sender, 1000000 * 10**18);
}
}STAKING_TOKEN_ADDRESS=0xYourTokenAddress ELIGIBILITY_DELAY=259200 npx hardhat run scripts/deploy.js --network mainnet// Your platform contract
contract MyPlatform {
address public stakingContract;
constructor(address _stakingContract) {
stakingContract = _stakingContract;
}
// Example: User performs action, pays fee
function doSomething() external payable {
require(msg.value >= 0.01 ether, "Min fee required");
// Send fee to staking contract
(bool success, ) = stakingContract.call{value: msg.value}("");
require(success, "Fee transfer failed");
}
}// Frontend integration
const { ethers } = require("ethers");
// User stakes tokens
await myToken.approve(stakingContract.address, stakeAmount);
await stakingContract.stake(stakeAmount);
// Check pending rewards
const pending = await stakingContract.pendingRewards(userAddress);
console.log(`Pending: ${ethers.formatEther(pending)} ETH`);
// Claim rewards
await stakingContract.claimRewards(); YOUR PLATFORM
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
Trading Fees Service Fees Other Revenue
│ │ │
└────────────────┼────────────────┘
│
▼
┌───────────────────────┐
│ stakingContract │
│ .addRewards() │
│ or │
│ receive() payable │
└───────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Staker │ │ Staker │ │ Staker │
│ 1000 │ │ 2000 │ │ 3000 │
│ tokens │ │ tokens │ │ tokens │
└─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
16.6% of 33.3% of 50% of
rewards rewards rewards
The MasterChef pattern has become the gold standard for reward distribution in DeFi. Here are notable protocols that use it or variations of it:
| Protocol | TVL | Description |
|---|---|---|
| SushiSwap | $200M+ | The original MasterChef creator, used for SUSHI farming |
| PancakeSwap | $1.5B+ | BSC's largest DEX, uses MasterChef for CAKE distribution |
| Uniswap V3 | $3B+ | Uses similar accumulator math for fee distribution |
| Convex Finance | $2B+ | CVX and CRV reward distribution |
| Aura Finance | $500M+ | AURA rewards for Balancer LPs |
| Yearn Finance | $300M+ | Vault reward calculations |
| TraderJoe | $100M+ | Avalanche DEX with JOE farming |
| SpookySwap | $50M+ | Fantom DEX using BOO rewards |
| Quickswap | $100M+ | Polygon DEX with QUICK farming |
| Trader Joe V2 | $200M+ | Uses MasterChef for liquidity incentives |
- Battle-tested: The pattern has secured billions of dollars since 2020
- Gas efficient: Users pay the same gas whether there are 10 or 10 million stakers
- Fair distribution: Mathematical precision ensures no user is shortchanged
- Real-time: No waiting for epochs or snapshots
- Simple integration: Easy to integrate with any ERC20 token
Users stake ERC20 tokens to earn native token (ETH/BNB/MATIC) rewards. When rewards are sent to the contract, they are distributed proportionally to all stakers based on their stake size.
Instead of taking snapshots or iterating through users, the contract uses mathematical accumulators:
// When rewards arrive - single operation distributes to ALL stakers
accRewardPerShare += (newRewards * PRECISION) / totalStaked;
// User rewards calculated on-demand - O(1) complexity
pendingRewards = (userStake * accRewardPerShare) - userRewardDebt;This elegant approach ensures:
- Constant gas costs regardless of user count
- Perfect proportional distribution
- No need for epochs or snapshots
Timeline:
─────────────────────────────────────────────────────────────────
Day 1: Alice stakes 1000 tokens
accRewardPerShare = 0
Alice.rewardDebt = 0
Day 3: 10 ETH rewards arrive (Alice is only staker)
accRewardPerShare = 10 ETH / 1000 tokens = 0.01 ETH/token
Alice pending = (1000 × 0.01) - 0 = 10 ETH ✓
Day 5: Bob stakes 1000 tokens
Bob.rewardDebt = 1000 × 0.01 = 10 ETH (what he "missed")
Bob pending = (1000 × 0.01) - 10 = 0 ETH ✓ (just joined)
Day 7: 20 ETH rewards arrive (2000 total staked)
accRewardPerShare = 0.01 + (20/2000) = 0.02 ETH/token
Alice pending = (1000 × 0.02) - 0 = 20 ETH ✓
Bob pending = (1000 × 0.02) - 10 = 10 ETH ✓
Result: Alice got 10 + 10 = 20 ETH, Bob got 10 ETH. Fair! ✓
─────────────────────────────────────────────────────────────────
# Clone the repository
git clone https://github.com/samsatoshis/MasterChef-Staking-Contract.git
cd MasterChef-Staking-Contract
# Install dependencies
npm install
# Copy environment file
cp .env.example .envCreate a .env file based on the network you want to deploy to:
# Wallet
PRIVATE_KEY=your_private_key_here
# Staking token address
STAKING_TOKEN_ADDRESS=0x...
# Eligibility delay (optional, default: 3 days = 259200 seconds)
ELIGIBILITY_DELAY=259200
# Ethereum RPC URLs
SEPOLIA_URL=https://rpc.sepolia.org
MAINNET_URL=https://eth.llamarpc.com
# Contract verification
ETHERSCAN_API_KEY=your_etherscan_api_keyDeploy commands:
# Testnet (Sepolia)
npx hardhat run scripts/deploy.js --network sepolia
# Mainnet
npx hardhat run scripts/deploy.js --network mainnet# Wallet
PRIVATE_KEY=your_private_key_here
# Staking token address (BEP-20)
STAKING_TOKEN_ADDRESS=0x...
# Eligibility delay (optional)
ELIGIBILITY_DELAY=259200
# BSC RPC URLs
BSC_TESTNET_URL=https://data-seed-prebsc-1-s1.binance.org:8545/
BSC_MAINNET_URL=https://bsc-dataseed1.binance.org/
# Contract verification
BSCSCAN_API_KEY=your_bscscan_api_keyDeploy commands:
# Testnet
npx hardhat run scripts/deploy.js --network bscTestnet
# Mainnet
npx hardhat run scripts/deploy.js --network bscMainnet# Wallet
PRIVATE_KEY=your_private_key_here
# Staking token address
STAKING_TOKEN_ADDRESS=0x...
# Eligibility delay (optional)
ELIGIBILITY_DELAY=259200
# Base RPC URLs
BASE_SEPOLIA_URL=https://sepolia.base.org
BASE_URL=https://mainnet.base.org
# Contract verification
BASESCAN_API_KEY=your_basescan_api_keyDeploy commands:
# Testnet (Base Sepolia)
npx hardhat run scripts/deploy.js --network baseSepolia
# Mainnet
npx hardhat run scripts/deploy.js --network base# Wallet
PRIVATE_KEY=your_private_key_here
# Staking token address
STAKING_TOKEN_ADDRESS=0x...
# Eligibility delay (optional)
ELIGIBILITY_DELAY=259200
# Polygon RPC URLs
POLYGON_AMOY_URL=https://rpc-amoy.polygon.technology
POLYGON_URL=https://polygon-rpc.com
# Contract verification
POLYGONSCAN_API_KEY=your_polygonscan_api_keyDeploy commands:
# Testnet (Amoy)
npx hardhat run scripts/deploy.js --network polygonAmoy
# Mainnet
npx hardhat run scripts/deploy.js --network polygon# Wallet
PRIVATE_KEY=your_private_key_here
# Staking token address
STAKING_TOKEN_ADDRESS=0x...
# Eligibility delay (optional)
ELIGIBILITY_DELAY=259200
# Arbitrum RPC URLs
ARBITRUM_SEPOLIA_URL=https://sepolia-rollup.arbitrum.io/rpc
ARBITRUM_URL=https://arb1.arbitrum.io/rpc
# Contract verification
ARBISCAN_API_KEY=your_arbiscan_api_keyDeploy commands:
# Testnet (Arbitrum Sepolia)
npx hardhat run scripts/deploy.js --network arbitrumSepolia
# Mainnet
npx hardhat run scripts/deploy.js --network arbitrum| Parameter | Description | Default |
|---|---|---|
PRIVATE_KEY |
Your wallet private key (without 0x prefix) | Required |
STAKING_TOKEN_ADDRESS |
ERC20/BEP20 token address to stake | Required |
ELIGIBILITY_DELAY |
Seconds before stakers can claim rewards | 259200 (3 days) |
Common Eligibility Delay Values:
0- No delay (immediate rewards)3600- 1 hour86400- 1 day259200- 3 days (recommended for anti-frontrunning)604800- 7 days
npm run compilenpm test# Start local node
npx hardhat node
# Deploy to local network (in another terminal)
npx hardhat run scripts/deploy.js --network localhost# Testnet (Sepolia)
npx hardhat run scripts/deploy.js --network sepolia
# Mainnet
npx hardhat run scripts/deploy.js --network mainnet# Testnet
npx hardhat run scripts/deploy.js --network bscTestnet
# Mainnet
npx hardhat run scripts/deploy.js --network bscMainnet# Testnet (Base Sepolia)
npx hardhat run scripts/deploy.js --network baseSepolia
# Mainnet
npx hardhat run scripts/deploy.js --network base# Testnet (Amoy)
npx hardhat run scripts/deploy.js --network polygonAmoy
# Mainnet
npx hardhat run scripts/deploy.js --network polygon# Testnet (Arbitrum Sepolia)
npx hardhat run scripts/deploy.js --network arbitrumSepolia
# Mainnet
npx hardhat run scripts/deploy.js --network arbitrumAfter deployment, verify your contract on the block explorer. Replace the placeholders with your actual values.
# Sepolia
npx hardhat verify --network sepolia <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"
# Mainnet
npx hardhat verify --network mainnet <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"# Testnet
npx hardhat verify --network bscTestnet <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"
# Mainnet
npx hardhat verify --network bscMainnet <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"# Base Sepolia
npx hardhat verify --network baseSepolia <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"
# Mainnet
npx hardhat verify --network base <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"# Amoy
npx hardhat verify --network polygonAmoy <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"
# Mainnet
npx hardhat verify --network polygon <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"# Arbitrum Sepolia
npx hardhat verify --network arbitrumSepolia <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"
# Mainnet
npx hardhat verify --network arbitrum <CONTRACT_ADDRESS> "<TOKEN_ADDRESS>" "<ELIGIBILITY_DELAY>"# Example: Verify on BSC Mainnet with 3-day eligibility delay
npx hardhat verify --network bscMainnet 0x1234...abcd "0xYourTokenAddress" "259200"// Stake tokens
function stake(uint256 amount) external;
// Stake with gasless approval (EIP-2612)
function permitAndStake(uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
// Unstake tokens (auto-claims rewards if eligible)
function unstake(uint256 amount) external;
// Claim pending rewards
function claimRewards() external;// Get pending rewards for a user
function pendingRewards(address user) external view returns (uint256);
// Get comprehensive user info
function getUserInfo(address user) external view returns (
uint256 stakedAmount,
uint256 pendingRewardsAmount,
uint256 stakeTime,
bool isEligible,
uint256 timeToEligibility
);
// Get global statistics
function getGlobalStats() external view returns (
uint256 totalStakedTokens,
uint256 totalRewardsAvailable,
uint256 rewardPerShare,
uint256 lastDistribution
);
// Calculate APY based on recent rewards
function calculateAPY(uint256 recentRewards, uint256 periodDays) external view returns (uint256);Rewards can be sent to the contract in two ways:
// 1. Direct transfer (triggers receive())
await owner.sendTransaction({ to: stakingContract, value: rewardAmount });
// 2. Using addRewards function
await stakingContract.addRewards({ value: rewardAmount });const { ethers } = require("ethers");
// Connect to contract
const stakingContract = new ethers.Contract(address, abi, signer);
// Stake tokens
await stakingToken.approve(stakingContract.address, amount);
await stakingContract.stake(amount);
// Get user info
const info = await stakingContract.getUserInfo(userAddress);
console.log(`Staked: ${ethers.formatEther(info.stakedAmount)} tokens`);
console.log(`Pending: ${ethers.formatEther(info.pendingRewardsAmount)} ETH`);
console.log(`Eligible: ${info.isEligible}`);
// Claim rewards
await stakingContract.claimRewards();
// Unstake
await stakingContract.unstake(amount);- ReentrancyGuard - All state-changing functions protected
- Anti-Frontrunning - Configurable eligibility delay (default 3 days)
- MEV Protection - Minimum 1 block delay between user actions
- High Precision - 1e30 precision factor prevents rounding attacks
- Overflow Protection - Solidity 0.8.22 built-in overflow checks
- Access Control - Owner-only emergency functions
| Operation | Snapshot-based | MasterChef (this contract) | Improvement |
|---|---|---|---|
| Stake | O(n) | O(1) | 90% reduction |
| Unstake | O(n) | O(1) | 90% reduction |
| Claim | O(m) epochs | O(1) | 95% reduction |
| Distribute | O(n) users | O(1) | 99% reduction |
| Parameter | Type | Description |
|---|---|---|
_stakingToken |
address | ERC20 token address that users stake |
_eligibilityDelay |
uint256 | Seconds before stakers can claim rewards (anti-frontrunning) |
| Name | Value | Description |
|---|---|---|
PRECISION |
1e30 | Precision factor for calculations |
MIN_STAKE_AMOUNT |
1e6 | Minimum stake (prevents dust attacks) |
MIN_BLOCK_DELAY |
1 | Blocks between user actions (MEV protection) |
Pre-configured networks in hardhat.config.js:
- Ethereum: Mainnet, Sepolia
- BSC: Mainnet, Testnet
- Polygon: Mainnet, Amoy
- Arbitrum: One, Sepolia
- Base: Mainnet, Sepolia
# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Run specific test file
npx hardhat test test/MasterChefStaking.test.jsMIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
This contract is inspired by the MasterChef pattern pioneered by SushiSwap, which has been battle-tested with billions of dollars in TVL across DeFi.