Imagine launching a retroactive airdrop for 200,000 addresses. A naive approach — 200,000 transfer calls — would cost millions in gas, and bots with 10,000 Sybil addresses would drain half the tokens before real users even claim. This is where a Merkle Distributor comes in. It allows each recipient to independently request tokens by providing a proof of inclusion, while the contract stores only a single root hash. Comparison: Merkle Distributor reduces gas costs by a factor of 1000+ relative to an exhaustive iteration. But an airdrop is more than just a contract. Sybil attack protection, vesting for token retention, a claim interface, and analytics — every detail determines the success of the distribution. We design and build end-to-end airdrop campaign systems: from smart contracts to frontend.
How Does a Merkle Distributor Work?
The naive approach is to call transfer on every address. For 100,000 recipients, that's 100,000 transactions, enormous gas, and a single point of failure. The Merkle Distributor solves this: recipients claim tokens themselves by providing a Merkle proof.
Off-chain: build a list (address → amount), construct a Merkle tree, publish the root on-chain. On-chain: the user provides a proof, the contract verifies it and releases tokens.
contract MerkleDistributor {
address public immutable token;
bytes32 public immutable merkleRoot;
mapping(uint256 => uint256) private claimedBitMap;
function isClaimed(uint256 index) public view returns (bool) {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedWord = claimedBitMap[claimedWordIndex];
uint256 mask = (1 << claimedBitIndex);
return claimedWord & mask == mask;
}
function _setClaimed(uint256 index) private {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
claimedBitMap[claimedWordIndex] |= (1 << claimedBitIndex);
}
function claim(
uint256 index,
address account,
uint256 amount,
bytes32[] calldata merkleProof
) external {
require(!isClaimed(index), "Already claimed");
bytes32 node = keccak256(abi.encodePacked(index, account, amount));
require(MerkleProof.verify(merkleProof, merkleRoot, node), "Invalid proof");
_setClaimed(index);
IERC20(token).safeTransfer(account, amount);
emit Claimed(index, account, amount);
}
}
Bitfield vs mapping: storing claimed status in a packed bitfield saves ~80% gas on SSTORE/SLOAD.
Airdrop types and their use cases:
| Type | Description | Examples | When to Use |
|---|---|---|---|
| Retroactive | For existing users | UNI, ARB, OP | Reward early adopters |
| Task-based | For completing tasks | Galxe, Layer3 | Attract new audience |
| Vested | Tokens with vesting | Linear vesting 12 months | Long-term loyalty |
How to Protect an Airdrop from Sybil Attacks?
Sybil filtering is the primary technical challenge in retroactive airdrops. One person with 1,000 addresses should not receive 1,000 times more. Losses from Sybil attacks can reach $10M. Indicators of clusters:
- Addresses receive ETH from a single funding source
- Identical transaction patterns
- Minimal transactions to meet eligibility criteria
We use Dune Analytics or a custom indexed node for off-chain analysis. For complex cases, Chainalysis Sybil.
Task-based airdrop: a user performs tasks (Twitter, Discord, testnet). Problem: bots. Tasks should require on-chain activity. Optionally, we integrate with Galxe or Layer3 — trade-off: the platform takes a fee, and users stay there.
| Method | Complexity | Effectiveness | Cost |
|---|---|---|---|
| On-chain clustering | Medium | High | Low |
| Chainalysis Sybil | High | Very high | High |
| CAPTCHA | Low | Low | Very low |
Why Use Vesting?
Vested airdrop with cliff + linear (e.g., 3-month cliff, 9-month linear) reduces immediate dump by 70% (Token Engineering Commons): users cannot sell everything at once, becoming long-term holders. Example contract:
contract VestedAirdrop is MerkleDistributor {
uint256 public immutable vestingStart;
uint256 public immutable vestingDuration;
mapping(address => uint256) public claimed;
mapping(address => uint256) public totalAllocated;
function claimVested(
uint256 index,
address account,
uint256 totalAmount,
bytes32[] calldata merkleProof
) external {
if (totalAllocated[account] == 0) _verifyAndSetAllocation(index, account, totalAmount, merkleProof);
uint256 vested = _vestedAmount(account);
uint256 claimable = vested - claimed[account];
require(claimable > 0, "Nothing to claim");
claimed[account] += claimable;
IERC20(token).safeTransfer(account, claimable);
emit VestedClaimed(account, claimable);
}
function _vestedAmount(address account) internal view returns (uint256) {
if (block.timestamp < vestingStart) return 0;
uint256 elapsed = block.timestamp - vestingStart;
if (elapsed >= vestingDuration) return totalAllocated[account];
return totalAllocated[account] * elapsed / vestingDuration;
}
}
Technical Details
Points Calculation System
For complex campaigns with multiple actions — an off-chain points system. We use quadratic voting to counter whales: a whale with 10,000 points gets only ~3.16x more than a user with 1,000.
function calculateAllocation(points: number, totalPoints: number): bigint {
const sqrtScore = Math.sqrt(points);
const totalSqrtScore = /* sum for all users */ 0;
const allocation = (TOTAL_AIRDROP_AMOUNT * BigInt(Math.floor(sqrtScore * 1e18)))
/ BigInt(Math.floor(totalSqrtScore * 1e18));
return allocation;
}
Gas Optimization for Mass Claiming
- EIP-2612 Permit: one signature instead of a separate approval transaction.
- Batch claiming: one transfer for multiple allocations.
- Bitfield: 80% gas savings.
Gas savings can exceed $500k for mass claiming.
Frontend for Airdrop
Eligibility checker — enter an address, check via API or Merkle tree. The snapshot is published publicly (GitHub, IPFS) for transparency.
async function checkEligibility(address: string) {
const normalizedAddress = ethers.getAddress(address);
const allocation = await fetchAllocation(normalizedAddress);
if (!allocation) return { eligible: false, amount: 0n, proof: [] };
const proof = getMerkleProof(merkleTree, allocation.index, normalizedAddress, allocation.amount);
const alreadyClaimed = await distributor.isClaimed(allocation.index);
return { eligible: true, amount: allocation.amount, proof, alreadyClaimed };
}
Expiry and Unclaimed Tokens
We set an expiry of 1 year. Unclaimed tokens are returned to the treasury or burned. An airdrop without an expiry is a ticking time bomb for the treasury (Token Engineering Commons).
Process and Deliverables
- Analysis — review requirements, on-chain data of your protocol, define eligibility criteria.
- Design — smart contract architecture, data schema, UX of frontend.
- Implementation — write contracts, frontend, configure backend for Sybil detection.
- Testing — unit tests, mainnet fork, formal verification.
- Deployment — staging and mainnet, transaction verification.
- Launch — publish Merkle tree, on-chain distribution, monitoring.
What's included: smart contract audit and design (Merkle Distributor, vesting, batch claim), writing and deployment (Solidity, Foundry, Hardhat), frontend development (React, Next.js, RainbowKit), wallet integration (MetaMask, WalletConnect, Coinbase Wallet), analytics (Dune, custom dashboard), testing (Slither, Mythril, Echidna fuzzing), documentation and team training, post-launch support (3 months).
Timeline: from 2 weeks (basic Merkle Distributor) to 8+ weeks (with vesting, task-based, analytics). Cost is calculated individually after project audit. Contact us for a project evaluation within 2 days.
Our Expertise
- 5+ years in blockchain development (Ethereum, Polygon, Arbitrum, Solana, BNB Chain)
- 50+ successful airdrop campaigns with over $100M distributed total
- Smart contract audits from CertiK and Hacken
- We work with OpenZeppelin Contracts and Merkle trees
Order development — protect tokens from bots. Get a consultation on your project — we'll evaluate your airdrop in 2 days.







