A smart contract and tooling for gas-efficient ERC20 token airdrops using Merkle proofs.
Fork Notice: This project is a fork of Uniswap's merkle-distributor with improvements to the deployment workflow and multi-chain support.
Merkle Distributors allow protocols to airdrop tokens to thousands of addresses while only storing a single 32-byte merkle root on-chain. Users claim their tokens by providing a merkle proof that verifies their allocation is part of the distribution.
- Gas Efficient: Only stores merkle root on-chain, not individual allocations
- Scalable: Supports distributions to unlimited addresses
- Secure: Cryptographic proofs prevent unauthorized claims
- Flexible: Optional claiming deadline with owner withdrawal
- Multi-Chain: Configured for 15+ EVM networks
npm install# Copy example files
cp .env.example .env
cp hardhat.config.ts.example hardhat.config.ts
# Edit .env with your deployer private key
# Edit hardhat.config.ts with your RPC URLs and API keysCreate a JSON file mapping addresses to token amounts (in wei):
{
"0x1234567890123456789012345678901234567890": "1000000000000000000",
"0xabcdefabcdefabcdefabcdefabcdefabcdefabcd": "2500000000000000000"
}npx ts-node scripts/generateMerkleRoot.ts -i allocations.json -o merkle-output.json --prettyThis outputs:
- merkleRoot: The root hash to deploy with the contract
- tokenTotal: Total tokens to transfer to the contract
- claims: Proof data for each address (share with frontend)
Edit scripts/deploy.ts with your configuration:
const CONFIG = {
TOKEN_ADDRESS: "0x...", // Your ERC20 token
MERKLE_ROOT: "0x...", // From step 4
END_TIME: 1735689600, // Unix timestamp (0 for no deadline)
USE_DEADLINE: true, // true = MerkleDistributorWithDeadline
};Then deploy:
npx hardhat run scripts/deploy.ts --network optimism# Transfer tokens to the deployed contract address
# Verify on block explorer
npx hardhat verify --network optimism <CONTRACT_ADDRESS> \
"<TOKEN_ADDRESS>" "<MERKLE_ROOT>" "<END_TIME>"Merkle-Distributor/
├── contracts/
│ ├── MerkleDistributor.sol # Basic distributor (no deadline)
│ ├── MerkleDistributorWithDeadline.sol # With deadline & withdrawal
│ └── interfaces/
│ └── IMerkleDistributor.sol # Interface definition
├── scripts/
│ ├── generateMerkleRoot.ts # Merkle tree generator
│ ├── deploy.ts # Deployment script
│ └── verify.ts # Verification script
├── src/
│ ├── merkle-tree.ts # Core merkle tree implementation
│ ├── balance-tree.ts # Balance-specific tree wrapper
│ └── parse-balance-map.ts # Input parser & proof generator
├── hardhat.config.ts.example # Hardhat configuration template
└── .env.example # Environment variables template
Basic distributor without time restrictions:
- Immutable token address and merkle root
- Users claim by providing index, amount, and merkle proof
- Efficient bitmap-based tracking (256 claims per storage slot)
Extended version with deadline support:
- All features of MerkleDistributor
- Configurable claim deadline
- Owner can withdraw unclaimed tokens after deadline
- Prevents withdrawal during active claim period
{
"0xAddress1": "1000000000000000000",
"0xAddress2": "2000000000000000000"
}[
{
"address": "0xAddress1",
"earnings": "1000000000000000000",
"reasons": "lp,user"
}
]The reasons field is optional and can include flags: socks, lp, user.
{
"merkleRoot": "0x...",
"tokenTotal": "0x...",
"claims": {
"0xAddress1": {
"index": 0,
"amount": "0x0de0b6b3a7640000",
"proof": ["0x...", "0x...", "0x..."]
}
}
}Users call the claim function with:
function claim(
uint256 index, // From claims[address].index
address account, // Claimant address
uint256 amount, // From claims[address].amount
bytes32[] proof // From claims[address].proof
)The example configuration includes:
- Ethereum, Optimism, Arbitrum, Base
- Polygon, BSC, Avalanche
- Fantom, Metis, Linea
- Mode, Horizen, Mantle, Fraxtal
- Never commit your
.envorhardhat.config.tswith real keys - Verify the merkle root matches your allocation file before deploying
- Double-check the token total matches your intended distribution
- Test on a testnet before mainnet deployment
- Transfer exact token amount to avoid excess funds in contract
- Tree Construction: Addresses and amounts are hashed into leaf nodes, then paired and hashed up to a single root
- Proof Generation: For each leaf, the sibling hashes at each level form the merkle proof
- On-Chain Verification: Contract reconstructs the root from the leaf + proof and compares to stored root
- Claim Tracking: Bitmap efficiently tracks claimed indices (1 bit per claim)
GPL-3.0-or-later (inherited from Uniswap)
- Original implementation by Uniswap
- OpenZeppelin for MerkleProof library and security patterns