Airdrops, dividend distributions, and some votes need everyone's balance frozen at a single moment, so people cannot game eligibility by moving tokens afterward. Historically OpenZeppelin offered ERC20Snapshot; modern guidance has shifted to checkpoint-based history with a clock (ERC-6372). Both let you read a balance 'as of' a past point.
1// Classic ERC20Snapshot interface:2function snapshot() external returns (uint256 id); // restricted3function balanceOfAt(address account, uint256 snapshotId)4external view returns (uint256);5function totalSupplyAt(uint256 snapshotId)6external view returns (uint256);
The implementation is lazy and gas-aware: taking a snapshot just records a new id. A balance is only stored for that id on the FIRST transfer an account makes after the snapshot (capturing its pre-transfer value). Reads via balanceOfAt then return that frozen number. OpenZeppelin now recommends the checkpoint approach (like ERC20Votes) paired with an ERC-6372 clock that can tick by block number or timestamp.
Gas note: the first transfer by each holder after a snapshot pays an extra SSTORE to record the historical balance. Avoid triggering snapshots inside hot loops, and prefer off-chain Merkle airdrops (publish a root, let users claim) when you only need eligibility, not on-chain historical reads.
1// Distribute an airdrop proportional to balances at a snapshot2const snapId = await token.read.getCurrentSnapshotId();3const bal = await token.read.balanceOfAt([user, snapId]);4const supply = await token.read.totalSupplyAt([snapId]);5// share = rewardPool * bal / supply
Connect your wallet to mark this lesson as complete
Earn 25 EFFORT tokens