Разработка Merkle Distributor для массовых выплат токенов

Разработка Merkle Distributor для массовых выплат Мы регулярно решаем задачу массовых выплат токенов — airdrop на 50 000+ адресов. Наивное решение — хранить `mapping(address => uint256)` on-chain и итерироваться по нему в скрипте деплоя. Деплой такого маппинга стоит ~50 SLOAD + 50 SSTORE на каждо

Направления блокчейн-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1450
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1308
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1003
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1269
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    717
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1008

Разработка 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: пошаговое руководство

Подробная инструкция по построению дерева
  1. Подготовьте данные: список получателей с индексами, адресами и суммами.
  2. Сформируйте листья: leaf = keccak256(abi.encodePacked(index, address, amount)) — двойное хэширование защищает от second preimage attack.
  3. Постройте дерево: используйте библиотеку @openzeppelin/merkle-tree (JS/TS) или rs_merkle (Rust). Каждый internal node = keccak256(abi.encodePacked(left, right)) с canonical ordering (меньший хэш слева).
  4. Получите корень: tree.root — единственное значение, которое хранится в контракте.
  5. Сгенерируйте 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, и мы поможем сэкономить тысячи долларов на газе. Получите консультацию прямо сейчас.