Разработка merkle-tree whitelist для NFT

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка merkle-tree whitelist для NFT
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка merkle-tree whitelist для NFT

Коллекция на 10 000 токенов с 3 000 whitelist-адресов. Хранить whitelist в on-chain mapping — это 3 000 SSTORE операций при заполнении, около 0.5-0.8 ETH газа только на setup. Merkle Tree решает эту задачу за одну транзакцию: root хэш в контракте, proof для каждого адреса off-chain. Газ на верификацию одного адреса при минтинге — порядка 3-5k gas вместо полного SLOAD по mapping.

Как работает Merkle Proof верификация

Построение дерева

Листья дерева — keccak256 хэши адресов (иногда с дополнительными данными: keccak256(abi.encodePacked(address, maxMintAmount))). Дерево строится снизу вверх: хэши соседних листьев объединяются и хэшируются. Корень (root) — один bytes32, хранящийся в контракте.

import { MerkleTree } from 'merkletreejs'
import { keccak256, encodePacked } from 'viem'

const leaves = whitelist.map(addr => 
  keccak256(encodePacked(['address'], [addr]))
)
const tree = new MerkleTree(leaves, keccak256, { sortPairs: true })
const root = tree.getHexRoot() // → bytes32 для контракта

sortPairs: true — критичный параметр. Он обеспечивает детерминированное построение дерева независимо от порядка листьев. Без него один и тот же whitelist даёт разные root-ы при разном порядке адресов.

Верификация в контракте

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

bytes32 public merkleRoot;

function mint(uint256 amount, bytes32[] calldata proof) external payable {
    bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
    require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
    // ... mint logic
}

MerkleProof.verify() из OpenZeppelin — стандартная реализация с O(log n) сложностью. Для 3 000 адресов — proof из 12 хэшей (log2(3000) ≈ 12). Для 100 000 адресов — 17 хэшей. Gas cost верификации растёт медленно.

Double-leaf уязвимость и защита

Классическая проблема Merkle Tree в смарт-контрактах: если leaf не уникален — один proof может верифицировать несколько листьев. Атака: создать адрес, keccak256 которого совпадает с concat двух листьев следующего уровня.

Защита в OpenZeppelin MerkleProof: библиотека проверяет что leaf != internal_node, то есть leaf никогда не принимается как промежуточный узел. Это встроено в реализацию начиная с OZ 4.7. Если используешь старую версию или собственную реализацию — добавь явную проверку.

Дополнительная защита: хэшировать лист дважды keccak256(keccak256(abi.encodePacked(addr))). Это делает совпадение leaf с internal node практически невозможным.

Расширенный whitelist: тиеры и количества

Простой whitelist — только проверка «адрес есть / нет». Для многоуровневых whitelist (tier 1: 2 NFT, tier 2: 1 NFT) — включаем данные в leaf:

bytes32 leaf = keccak256(abi.encodePacked(msg.sender, maxAmount));
require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
require(amount <= maxAmount, "Exceeds allocation");

Теперь proof верифицирует не только адрес, но и максимальное количество для этого адреса. Одно дерево, один root, разные аллокации.

Защита от double-mint

Merkle Proof только верифицирует право минтить — не предотвращает повторный минт. Нужно отдельно трекать использованные аллокации:

mapping(address => uint256) public mintedAmount;

function mint(uint256 amount, uint256 maxAmount, bytes32[] calldata proof) external {
    require(mintedAmount[msg.sender] + amount <= maxAmount, "Exceeds allocation");
    mintedAmount[msg.sender] += amount;
    // ...
}

mintedAmount занимает SSTORE только при первом минте пользователя — это O(unique_minters) storage, а не O(whitelist_size).

Off-chain дистрибуция proof-ов

Три подхода к тому, как пользователь получает свой proof:

JSON файл в IPFS/CDN. { "0xABC...": ["0x...", "0x..."] } — статический словарь address → proof. Генерируется один раз при создании дерева, публикуется в CDN. Пользователь делает GET запрос с адресом, получает proof. Быстро, дёшево, без бэкенда.

Backend API. GET /api/whitelist/proof?address=0xABC. Позволяет добавлять адреса без пересборки всего JSON. Но требует или пересчёта дерева при добавлениях, или хранения всех листьев в БД для динамического построения proof. При изменении whitelist после публикации root — нужно обновить root в контракте (если контракт позволяет).

On-chain events. Если добавление в whitelist происходит через смарт-контракт (например, через Premint или поставку NFT proof-of-hold) — события можно индексировать через The Graph и строить дерево динамически.

Обновление whitelist после деплоя

Если контракт позволяет смену merkleRoot (через onlyOwner), то whitelist можно обновить без передеплоя. Сценарий: основной whitelist + last-minute добавления. Строим новое дерево с дополнениями, обновляем root через setMerkleRoot().

Важно: после смены root старые proof-ы перестают работать. Пользователи с закешированными proof-ами получат revert. Нужна коммуникация об обновлении.

Процесс разработки

Разработка (2-3 дня). Генератор дерева (Node.js скрипт), контракт с верификацией, API или статический JSON для proof-ов. Foundry тесты: проверяем корректные proof-ы проходят, некорректные — нет, double-mint заблокирован.

Интеграция с frontend. wagmi для вызова mint функции, merkletreejs для клиентской верификации proof-а перед отправкой транзакции (UX оптимизация).

Ориентиры по срокам

Базовая Merkle whitelist реализация — 2-3 дня. С тиерами, API для proof-ов и frontend интеграцией — до 5 дней.

Стоимость рассчитывается после уточнения размера whitelist и структуры тиеров.