A sophisticated airdrop system built with Foundry that combines Merkle tree verification with EIP-712 signatures and account abstraction principles to enable gasless token distribution.
This protocol utilizes Merkle proofs to identify eligible addresses for an airdrop based on a whitelist, while implementing account abstraction concepts to allow users to claim tokens without paying gas fees. The system is designed with security and efficiency in mind, preventing double-spending and ensuring only authorized addresses can claim their allocated tokens.
The main contract that handles the distribution of tokens using:
- Merkle Tree Verification: Efficiently validates eligibility without storing the entire whitelist on-chain
- EIP-712 Signatures: Secure, typed message signing for claim authorization
- Double Hashing: Prevents second-preimage attacks on the Merkle tree
- Claim Tracking: Prevents double-spending through mapping-based verification
A simple ERC20 token used to reward users for their contributions to the Bagel ecosystem. These tokens are distributed via the Merkle airdrop to whitelisted addresses.
A Merkle tree (also known as a hash tree) is a data structure that allows efficient and secure verification of large datasets. In this airdrop system:
- Leaf Nodes: Each leaf represents a hash of
(address, amount)pair - Internal Nodes: Each internal node is the hash of its two children
- Root: The single hash at the top represents the entire dataset
- Gas Efficiency: Only the proof path needs to be stored on-chain, not the entire whitelist
- Scalability: Can handle millions of addresses with minimal on-chain storage
- Verification: O(log n) complexity for proof verification
- Security: Tampering with any leaf would change the root hash
The system includes scripts to generate Merkle proofs:
# Generate input data
forge script script/GenerateInput.s.sol
# Create Merkle tree and proofs
forge script script/MakeMerkle.s.sol- Leaf Creation:
keccak256(keccak256(abi.encode(account, amount))) - Proof Path: Array of sibling hashes from leaf to root
- Verification:
MerkleProof.verify(proof, root, leaf)
- Double Hashing: Prevents second-preimage attacks
- Collision Resistance: Uses SHA-256 for cryptographic security
- Proof Validation: Each claim requires a valid Merkle proof
The contract implements EIP-712 for secure, typed message signing:
struct AirdropClaim {
address account;
uint256 amount;
}- User Signs: Creates EIP-712 signature of their claim data
- Gas Payer Submits: Any address can submit the transaction and pay gas
- Contract Verifies: Validates both signature and Merkle proof
- Tokens Transferred: Directly to the claiming address
- No Gas Fees: Users don't need ETH to claim tokens
- Better UX: Seamless claiming experience
- Flexibility: Anyone can pay gas for others
- Security: Maintains user control through signatures
- EIP-712 Compliant: Uses OpenZeppelin's EIP712 implementation
- SafeERC20: Secure token transfers with proper error handling
- MerkleProof: OpenZeppelin's battle-tested Merkle verification
- ECDSA: Elliptic curve digital signature algorithm for verification
claim(): Main claiming function with signature and proof verificationgetMessageHash(): Generates EIP-712 message hash for signing_isValidSignature(): Verifies ECDSA signaturesgetMerkleRoot(): Returns the current Merkle root
// User signs the message off-chain
bytes32 messageHash = airdrop.getMessageHash(userAddress, amount);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, messageHash);
// Gas payer submits the claim
airdrop.claim(userAddress, amount, merkleProof, v, r, s);# Run all tests
forge test
# Run specific test
forge test --match-test testUsersCanClaim- Foundry (latest version)
- Solidity ^0.8.24
- OpenZeppelin Contracts
# Install dependencies
forge install
# Build contracts
forge build
# Deploy
forge script script/DeployMerkleAirdrop.s.solDeployMerkleAirdrop.s.sol: Deploys the airdrop systemMakeMerkle.s.sol: Generates Merkle trees and proofsInteract.s.sol: Example interaction with the contractGenerateInput.s.sol: Creates input data for Merkle generation
- Reentrancy Protection: CEI (Checks-Effects-Interactions) pattern
- Signature Verification: ECDSA recovery with proper validation
- Double-Claim Prevention: Mapping-based claim tracking
- Input Validation: Comprehensive parameter checking
- Second-Preimage Attacks: Double hashing of leaf nodes
- Signature Replay: EIP-712 typed data prevents cross-domain attacks
- Merkle Tree Manipulation: Cryptographic verification of proofs
- Unauthorized Claims: Both signature and proof verification required
- Merkle Tree Second-Preimage Attack
- EIP-712: Ethereum Typed Structured Data Hashing
- OpenZeppelin MerkleProof
- Foundry Book
MIT License - see LICENSE file for details.