Разработка системы airdrop-трекинга
Airdrop-трекинг — задача, которая на первый взгляд кажется простой: смотри ивенты контракта, записывай адреса, показывай статус. На практике это полноценная data-pipeline система, которая работает с тысячами адресов, несколькими чейнами одновременно и должна выдавать актуальное состояние в real-time. Если архитектуру не продумать заранее, система ляжет под нагрузкой в первый же день распределения.
Что трекинговая система должна уметь
Минимальный набор функциональности для серьёзного airdrop:
- Eligibility tracking: кто имеет право на получение, на основании каких критериев (snapshot, активность, holdings)
- Claim status: claimed / unclaimed / expired для каждого адреса
- Multi-chain support: одновременное отслеживание Ethereum, Arbitrum, Base, Polygon
- Merkle proof generation: генерация и хранение proof-ов на лету или pre-computed
- Real-time sync: обновление состояния без задержки после on-chain событий
- Analytics dashboard: сколько claimed, скорость распределения, топ получателей
Архитектура: from chain to UI
On-chain часть: дистрибуция через Merkle tree
Почти все современные airdrop-контракты строятся на Merkle proof схеме — это стандарт после Uniswap v1 airdrop. Контракт хранит только один bytes32 merkleRoot, а не все eligible адреса:
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);
}
}
Двойное хеширование leaf (keccak256(keccak256(...))) — защита от second preimage attack. Это паттерн из OpenZeppelin MerkleProof library, не изобретайте своё.
Off-chain часть: indexer и API
Трекинговая система держится на трёх компонентах:
1. Blockchain indexer
Слушает Claimed события через WebSocket RPC (Alchemy/Infura) или собственную ноду. Для надёжности — два независимых провайдера с fallback. Данные пишутся в PostgreSQL:
claims(
id, chain_id, tx_hash, block_number,
address, amount, claimed_at, log_index
)
Уникальность: (chain_id, tx_hash, log_index) — защита от дубликатов при reorg.
2. Merkle tree builder
Принимает на вход snapshot — список (address, amount) — и строит дерево. Для больших airdrop (100k+ адресов) используйте @openzeppelin/merkle-tree (TypeScript) или merkle-distributor от Uniswap. Важно: leaf encoding должен точно совпадать с контрактом — это частый источник ошибок.
3. REST/GraphQL API
Эндпоинты для фронтенда:
-
GET /eligibility/:address— eligible + amount + proof -
GET /status/:address— claimed или нет, tx_hash -
GET /stats— aggregate статистика
Proof-ы либо pre-computed и хранятся в Redis, либо генерируются on-demand из дерева в памяти. Для 1M адресов дерево весит ~64MB — вполне реально держать in-memory.
Snapshot механизм
Snapshot — момент, на который фиксируется состояние holdings или активности. Два подхода:
| Подход | Как работает | Инструменты |
|---|---|---|
| Block snapshot | Берём балансы на конкретном block number | Alchemy getBalance, The Graph |
| Activity-based | Считаем транзакции/объём за период | Dune Analytics, Flipside |
| NFT holders | Владельцы конкретного NFT на момент snapshot | Moralis, Alchemy NFT API |
Для ERC-20 snapshot на конкретный блок — используйте eth_call с blockNumber параметром или ERC20Snapshot extension от OpenZeppelin, который хранит историю балансов прямо в контракте.
Обработка edge cases
Re-org protection: транзакции нужно считать finalized только после N подтверждений (12 для Ethereum mainnet, 64 для Polygon). Не помечайте claim как выполненный до finality.
Экспирация: если airdrop имеет срок — контракт должен иметь expiry timestamp и функцию reclaim() для возврата неполученных токенов. Трекер должен показывать expired статус.
Multi-wallet: некоторые пользователи пытаются клеймить через proxy-контракты или разные кошельки. Если требуется Sybil-фильтрация — её нужно применить на этапе построения snapshot, а не в контракте.
Масштабирование под нагрузку
В день TGE трекер получает пиковую нагрузку. Подготовка:
- CDN кешировение proof-ов: они immutable, кешировать на 24h безопасно
- Read replicas PostgreSQL для аналитических запросов
- Rate limiting по IP и по адресу: защита от scrapers
- Pre-warming: построить дерево и записать proof-ы в Redis до старта клейминга
Система трекинга — не украшение, а критическая инфраструктура airdrop. Пользователь, который не может проверить свой статус, воспринимает это как проблему с самим проектом.







