Skip to content

0xFantomMenace/Merkle-Distributor

Repository files navigation

Merkle Distributor

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.

Overview

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.

Key Features

  • 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

Quick Start

1. Install Dependencies

npm install

2. Configure Environment

# 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 keys

3. Create Airdrop Allocation File

Create a JSON file mapping addresses to token amounts (in wei):

{
  "0x1234567890123456789012345678901234567890": "1000000000000000000",
  "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd": "2500000000000000000"
}

4. Generate Merkle Root

npx ts-node scripts/generateMerkleRoot.ts -i allocations.json -o merkle-output.json --pretty

This 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)

5. Deploy Contract

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

6. Fund & Verify

# Transfer tokens to the deployed contract address

# Verify on block explorer
npx hardhat verify --network optimism <CONTRACT_ADDRESS> \
  "<TOKEN_ADDRESS>" "<MERKLE_ROOT>" "<END_TIME>"

Project Structure

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

Smart Contracts

MerkleDistributor

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)

MerkleDistributorWithDeadline

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

Input Formats

Simple Format

{
  "0xAddress1": "1000000000000000000",
  "0xAddress2": "2000000000000000000"
}

Detailed Format

[
  {
    "address": "0xAddress1",
    "earnings": "1000000000000000000",
    "reasons": "lp,user"
  }
]

The reasons field is optional and can include flags: socks, lp, user.

Output Format

{
  "merkleRoot": "0x...",
  "tokenTotal": "0x...",
  "claims": {
    "0xAddress1": {
      "index": 0,
      "amount": "0x0de0b6b3a7640000",
      "proof": ["0x...", "0x...", "0x..."]
    }
  }
}

Claiming Tokens

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
)

Supported Networks

The example configuration includes:

  • Ethereum, Optimism, Arbitrum, Base
  • Polygon, BSC, Avalanche
  • Fantom, Metis, Linea
  • Mode, Horizen, Mantle, Fraxtal

Security Considerations

  1. Never commit your .env or hardhat.config.ts with real keys
  2. Verify the merkle root matches your allocation file before deploying
  3. Double-check the token total matches your intended distribution
  4. Test on a testnet before mainnet deployment
  5. Transfer exact token amount to avoid excess funds in contract

How It Works

  1. Tree Construction: Addresses and amounts are hashed into leaf nodes, then paired and hashed up to a single root
  2. Proof Generation: For each leaf, the sibling hashes at each level form the merkle proof
  3. On-Chain Verification: Contract reconstructs the root from the leaf + proof and compares to stored root
  4. Claim Tracking: Bitmap efficiently tracks claimed indices (1 bit per claim)

License

GPL-3.0-or-later (inherited from Uniswap)

Credits

  • Original implementation by Uniswap
  • OpenZeppelin for MerkleProof library and security patterns

About

Merkle distributor smart contract and deployment scripts.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published