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

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка merkle-tree whitelist для NFT
Середній
~2-3 дні
Часті запитання

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

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    920
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1147
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    885

Розробка Merkle-Tree Whitelist для NFT

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

Як працює верифікація Merkle Proof

Побудова дерева

Листи дерева — це хеші keccak256 адрес (іноді з додатковими даними: keccak256(abi.encodePacked(address, maxMintAmount))). Дерево будується знизу вгору: хеші сусідніх листів комбінуються та хешуються. Корінь — один 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 хешів. Вартість верифікації газу зростає повільно.

Вразливість Double-Leaf та захист

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

Захист в OpenZeppelin MerkleProof: бібліотека перевіряє, що leaf != internal_node, тобто лист ніколи не приймається як проміжний вузол. Це вбудовано з OZ 4.7. Якщо використовуєте старішу версію або кастомну реалізацію — додайте явну перевірку.

Додатковий захист: хешувати лист двічі keccak256(keccak256(abi.encodePacked(addr))). Це робить практично неможливим збіг листа з внутрішнім вузлом.

Розширений whitelist: тиери та кількості

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

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

Тепер proof верифікує не лише адресу, але й максимальну кількість для цієї адреси. Одне дерево, один root, різні аллокації.

Захист від подвійного мінтингу

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..."] } — статичний словник адреса → proof. Генерується один раз при створенні дерева, публікується на CDN. Користувач робить GET запит з адресою, отримує proof. Швидко, дешево, без бекенду.

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

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

Оновлення Whitelist після розгортання

Якщо контракт дозволяє змінювати merkleRoot (через onlyOwner), whitelist можна оновити без повторного розгортання. Сценарій: основний whitelist + останні додавання. Побудуйте нове дерево з додаваннями, оновіть root через setMerkleRoot().

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

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

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

Інтеграція з фронтендом. wagmi для виклику mint функції, merkletreejs для верифікації proof'а на клієнтській стороні перед відправкою транзакції (оптимізація UX).

Оцінка часу

Базова реалізація Merkle whitelist — 2-3 дні. З тиерами, API для proof'ів та інтеграцією з фронтендом — до 5 днів.

Вартість розраховується після уточнення розміру whitelist та структури тиерів.