Airdrop Tracking System Development
Airdrop tracking seems simple at first: watch contract events, record addresses, show status. In practice it's a complete data-pipeline system working with thousands of addresses across multiple chains simultaneously and must deliver current state in real-time. Without upfront architecture, the system will collapse under load on day one of distribution.
What a Tracking System Must Do
Minimum functionality set for serious airdrop:
- Eligibility tracking: who is eligible based on what criteria (snapshot, activity, holdings)
- Claim status: claimed / unclaimed / expired for each address
- Multi-chain support: simultaneous tracking of Ethereum, Arbitrum, Base, Polygon
- Merkle proof generation: generate and store proofs on-the-fly or pre-computed
- Real-time sync: update state without delay after on-chain events
- Analytics dashboard: how much claimed, distribution speed, top recipients
Architecture: from Chain to UI
On-chain Part: Distribution via Merkle Tree
Almost all modern airdrop contracts use Merkle proof scheme — this became standard after Uniswap v1 airdrop. Contract stores only one bytes32 merkleRoot, not all eligible addresses:
contract MerkleAirdrop {
bytes32 public immutable merkleRoot;
mapping(address => bool) public hasClaimed;
IERC20 public immutable token;
event Claimed(address indexed account, uint256 amount);
function claim(
address account,
uint256 amount,
bytes32[] calldata merkleProof
) external {
require(!hasClaimed[account], "Already claimed");
bytes32 leaf = keccak256(bytes.concat(
keccak256(abi.encode(account, amount))
));
require(
MerkleProof.verify(merkleProof, merkleRoot, leaf),
"Invalid proof"
);
hasClaimed[account] = true;
token.safeTransfer(account, amount);
emit Claimed(account, amount);
}
}
Double hashing of leaf (keccak256(keccak256(...))) — protection against second preimage attack. This is the pattern from OpenZeppelin MerkleProof library, don't invent your own.
Off-chain Part: Indexer and API
Tracking system relies on three components:
1. Blockchain Indexer
Listens to Claimed events via WebSocket RPC (Alchemy/Infura) or own node. For reliability — two independent providers with fallback. Data written to PostgreSQL:
claims(
id, chain_id, tx_hash, block_number,
address, amount, claimed_at, log_index
)
Uniqueness: (chain_id, tx_hash, log_index) — protection against duplicates on reorg.
2. Merkle Tree Builder
Takes snapshot — list of (address, amount) — and builds tree. For large airdrops (100k+ addresses) use @openzeppelin/merkle-tree (TypeScript) or merkle-distributor from Uniswap. Important: leaf encoding must exactly match contract — common source of errors.
3. REST/GraphQL API
Endpoints for frontend:
-
GET /eligibility/:address— eligible + amount + proof -
GET /status/:address— claimed or not, tx_hash -
GET /stats— aggregate statistics
Proofs either pre-computed and stored in Redis, or generated on-demand from tree in memory. For 1M addresses tree weighs ~64MB — quite feasible to keep in-memory.
Snapshot Mechanism
Snapshot — moment at which holdings or activity state is fixed. Two approaches:
| Approach | How It Works | Tools |
|---|---|---|
| Block snapshot | Take balances at specific block number | Alchemy getBalance, The Graph |
| Activity-based | Count transactions/volume over period | Dune Analytics, Flipside |
| NFT holders | Owners of specific NFT at snapshot time | Moralis, Alchemy NFT API |
For ERC-20 snapshot at specific block — use eth_call with blockNumber parameter or ERC20Snapshot extension from OpenZeppelin, which stores balance history directly in contract.
Handling Edge Cases
Re-org protection: transactions should only be considered finalized after N confirmations (12 for Ethereum mainnet, 64 for Polygon). Don't mark claim as complete until finality.
Expiration: if airdrop has deadline — contract should have expiry timestamp and reclaim() function to return unclaimed tokens. Tracker should show expired status.
Multi-wallet: some users try to claim via proxy contracts or different wallets. If Sybil filtering required — apply it at snapshot building stage, not in contract.
Scaling Under Load
On TGE day tracker receives peak load. Preparation:
- CDN caching proofs: they're immutable, safe to cache for 24h
- Read replicas PostgreSQL for analytics queries
- Rate limiting by IP and by address: protection from scrapers
- Pre-warming: build tree and write proofs to Redis before claim starts
Tracking system — not decoration, but critical infrastructure for airdrop. User unable to check status perceives this as project problem.







