A gas-efficient ERC20 token built on OpenZeppelin with three core features: a hard supply cap, owner-controlled minting, and a pull-based emission mechanism that rewards holders proportionally over time.
Designed for Arbitrum. Tested with Hardhat. Clean code, no bloat.
There are two ways to distribute emission rewards to token holders:
Push model: The owner calls a function that iterates over every holder and mints their share. Simple to understand, but the gas cost scales linearly with the number of holders. At 1,000 holders you are burning serious gas. At 10,000 you are hitting block limits.
Pull model (this contract): Each holder calls claim() to collect their own rewards. Gas cost is constant regardless of how many holders exist. The tradeoff is that holders must actively claim, but in practice this is standard behavior in DeFi and users expect it.
This contract uses the pull model. Rewards accrue per block based on the holder's current balance. A checkpoint system tracks each address's last claim block to calculate what they are owed.
The MAX_SUPPLY is immutable. Set once at deployment, cannot be changed. The mint() function and the claim() function both check against this cap before minting. Even if a holder has accrued massive pending rewards, the contract will only mint up to the remaining supply and no further.
rewards = (holderBalance * emissionPerBlock * blocksSinceLastClaim) / 1e18
The division by 1e18 normalizes the rate so that emissionPerBlock represents tokens emitted per block per whole token held. If you hold 1,000 tokens and the rate is 0.01, you earn 10 tokens per block.
contracts/EmissionToken.sol
Key functions:
mint(address to, uint256 amount)— Owner only. Respects supply cap.claim()— Any holder. Mints accrued rewards to caller.pendingRewards(address holder)— View. Returns unclaimed rewards.setEmissionRate(uint256 newRate)— Owner only. Updates emission rate.toggleEmissions()— Owner only. Turns emissions on or off.
test/EmissionToken.test.js
Coverage:
- Deployment state validation
- Owner minting and cap enforcement
- Non-owner mint rejection
- Zero address and zero amount reverts
- Cumulative minting up to and beyond cap
- Emission toggling
- Reward accrual over blocks
- Claim checkpoint reset
- Supply cap respected during emission claims
- Emission rate updates with event verification
- Transfer checkpoint initialization for new holders
- Access control on all owner functions
Run tests:
npm install
npm test# Copy env template and add your keys
cp .env.example .env
# Deploy to Arbitrum Sepolia (testnet)
npm run deploy:sepolia
# Deploy to Arbitrum One (mainnet)
npm run deploy:mainnet- Solidity 0.8.20
- OpenZeppelin Contracts 5.0.1
- Hardhat 2.22.0
- Arbitrum One / Arbitrum Sepolia
MIT