Разработка Merkle Distributor для массовых выплат
Мы регулярно решаем задачу массовых выплат токенов — airdrop на 50 000+ адресов. Наивное решение — хранить mapping(address => uint256) on-chain и итерироваться по нему в скрипте деплоя. Деплой такого маппинга стоит ~50 SLOAD + 50 SSTORE на каждого получателя, итого при 50K получателях — несколько ETH только на запись данных в storage. Merkle Tree решает это принципиально иначе: экономия газа на деплой достигает 95% по сравнению с прямым storage, а каждый claim обходится в фиксированную сумму. Сравните: при airdrop на 50 000 участников вы сохраняете тысячи долларов (при цене ETH $2000 экономия ~$5000) используя Merkle Tree.
Как работает Merkle Distributor?
В контракт деплоится только один bytes32 merkleRoot — корень дерева. Вся таблица выплат (адрес → сумма) остаётся off-chain. Получатель сам приходит за своими токенами с merkleProof — набором хэшей, доказывающих его включение в дерево.
On-chain верификация:
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);
require(IERC20(token).transfer(account, amount), "Transfer failed");
emit Claimed(index, account, amount);
}
isClaimed(index) проверяет один bit в mapping(uint256 => uint256) — packed bitmask. 50 000 получателей = ~1563 uint256 слотов вместо 50 000. Экономия на storage — в десятки раз.
Почему Merkle Distributor — стандарт для airdrop?
Сравним затраты на деплой для 50 000 получателей:
| Подход | Слотов storage | Стоимость газа (примерно) |
|---|---|---|
| Mapping напрямую | 50 000 | ~2.5 ETH |
| Merkle Distributor | ~1 563 | ~0.08 ETH |
Экономия более 95%. Кроме того, off-chain таблица легко обновляется без передеплоя контракта. Для ещё большей наглядности — сравнение затрат на единичный claim:
| Метод | Газ на claim | Примечание |
|---|---|---|
| Mapping + batch | ~300k+ | Зависит от числа получателей |
| Merkle Distributor | ~80k | Фиксированная стоимость |
При 50 000 claims экономия составит десятки ETH. Merkle Distributor лучше прямого storage в 32 раза по газу на деплой.
Как построить Merkle Tree: пошаговое руководство
Подробная инструкция по построению дерева
- Подготовьте данные: список получателей с индексами, адресами и суммами.
-
Сформируйте листья:
leaf = keccak256(abi.encodePacked(index, address, amount))— двойное хэширование защищает от second preimage attack. -
Постройте дерево: используйте библиотеку
@openzeppelin/merkle-tree(JS/TS) илиrs_merkle(Rust). Каждый internal node =keccak256(abi.encodePacked(left, right))с canonical ordering (меньший хэш слева). - Получите корень:
tree.root— единственное значение, которое хранится в контракте. - Сгенерируйте proofs: для каждого получателя с помощью
tree.getProof(...). Раздайте proofs через API или опубликуйте в IPFS вместе с полной таблицей.
Типичный скрипт:
import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
const values = recipients.map(([address, amount], index) => [
index, address, amount
]);
const tree = StandardMerkleTree.of(values, ["uint256", "address", "uint256"]);
console.log("Root:", tree.root);
// proof для конкретного получателя
const proof = tree.getProof([index, address, amount]);
Какие ошибки допускают при реализации Merkle Distributor?
Первая и самая распространённая ошибка — некорректная битовая маска для защиты от double spend. Если _setClaimed устанавливает бит неправильно, возможен повторный claim. Используйте проверенный паттерн из OpenZeppelin MerkleDistributor. Вторая проблема — коллизия листьев: если листья формируются без index (keccak256(abi.encodePacked(address, amount))), то два получателя с одинаковой суммой могут доказать claim друг на друга — хотя это даёт им чужой адрес, на практике коллизия небезопасна. Добавление index гарантирует уникальность. Третья ошибка — несоответствие кодирования: abi.encodePacked в off-chain и abi.encodePacked в Solidity должны совпадать. Uniswap и Optimism используют abi.encodePacked для листьев.
Расширенные паттерны
Многораундовый distributor. Новый merkleRoot каждую неделю/эпоху. Вместо деплоя нового контракта — обновляемый root через updateMerkleRoot(bytes32) с onlyOwner или governance. Claimed bitmask сбрасывается для новой эпохи или индексируется по эпохе: mapping(uint256 epoch => mapping(uint256 wordIndex => uint256 bitmask)).
Delegated claiming. Получатель подписывает разрешение на claim от своего имени — полезно для gasless UX через релеер или ERC-2771 meta-transactions. Паттерн: claimFor(address account, uint256 amount, bytes32[] calldata proof, bytes calldata signature).
Тестирование на Foundry
function test_ClaimValidProof() public {
// строим дерево в тесте
bytes32[] memory leaves = new bytes32[](3);
leaves[0] = keccak256(abi.encodePacked(uint256(0), alice, uint256(100e18)));
// merkle proof вычисляем вручную или через FFI к JS скрипту
distributor.claim(0, alice, 100e18, proof);
assertEq(token.balanceOf(alice), 100e18);
vm.expectRevert("Already claimed");
distributor.claim(0, alice, 100e18, proof); // double claim
}
Для генерации proofs в Foundry тестах: vm.ffi с вызовом TypeScript скрипта через @openzeppelin/merkle-tree. Или пишите чистую Solidity-реализацию в setUp() — медленнее, но без внешних зависимостей.
Что входит в нашу разработку
Мы на рынке более 5 лет, реализовали 20+ distributor-контрактов для airdrop, стейкинга и массовых выплат. В стоимость входит:
- Исходный код смарт-контракта на Solidity (протестирован на Foundry, включает fuzz-тесты).
- Off-chain скрипты: построение дерева, генерация proofs, деплой и верификация (TypeScript, ethers.js).
- Документация: описание архитектуры, инструкция по развертыванию, описание API.
- Доработка под ваш токен: ERC-20, ERC-721, ERC-1155 или кросс-чейн с помощью bridge.
- Поддержка после запуска: исправление багов, консультации по интеграции.
Сроки и стоимость
Базовый Merkle Distributor с single root: 2-3 дня включая off-chain скрипты. Многораундовый с governance и gasless claiming: 4-5 дней. Деплой и верификация контракта на mainnet — дополнительно несколько часов. Стоимость рассчитывается индивидуально после уточнения деталей. Свяжитесь с нами для оценки вашего проекта — проанализируем количество получателей, требования к эпохам и UX. Закажите разработку Merkle Distributor, и мы поможем сэкономить тысячи долларов на газе. Получите консультацию прямо сейчас.







