Розробка whitelist/allowlist для мінтінгу NFT
Типова ситуація: колекція на 10,000 NFT, whitelist-мінт для 3,000 адрес за 12 годин до публічного. Якщо зберігати whitelist on-chain як mapping(address => bool) — деплой контракту із записом 3,000 адрес обходиться приблизно в 15-20M gas тільки на SSTORE. При ціні газу 20 gwei це близько $4 000. Наша команда з 7+ років досвіду в блокчейн-розробці та 50+ виконаними проєктами NFT-мінтів пропонує ефективні рішення. Ми розробляємо whitelist/allowlist системи під ключ з використанням Merkle tree, що знижує витрати до 50k gas — економія до $4 000. Оцінимо ваш проєкт за 1 день — отримайте консультацію.
Як працює Merkle proof і де помиляються
Merkle tree будується off-chain: кожен адрес хешується через keccak256(abi.encodePacked(address)), з листів будується дерево попарним хешуванням. Результат — 32-байтний merkleRoot. Цей root деплоїться в контракт. При мінті користувач передає proof[] — масив хешів-братів по шляху від його листа до кореня. Контракт верифікує через MerkleProof.verify() з OpenZeppelin.
Поширена помилка — подвійний мінт. Якщо в контракті немає mapping(address => bool) public hasMinted (або mapping(address => uint256) public mintedCount), користувач із whitelist може мінтити необмежену кількість разів. Proof залишається валідним. Контракт не знає, що адреса вже мінтила.
Друга серйозна проблема — leaf encoding. OpenZeppelin MerkleProof очікує, що лист — це keccak256(keccak256(data)) (double hash) для захисту від preimage attacks, якщо вузли дерева збігаються з листами. Якщо генерувати дерево через merkletreejs з одинарним hash, а контракт використовує _leaf = keccak256(abi.encodePacked(account)) без double hash — може спрацювати collision attack за певних конфігурацій. Ми використовуємо keccak256(bytes.concat(keccak256(abi.encode(addr)))) або стандартну зв'язку @openzeppelin/merkle-tree JS бібліотека + MerkleProof.sol.
Який спосіб обрати для вашого проєкту?
Існує три основні підходи для реалізації whitelist. Порівняємо їх характеристики:
| Критерій | Merkle proof | ECDSA підпис | On-chain mapping |
|---|---|---|---|
| Вартість деплою (газ) | 50k (фіксовано) | 50k + базова вартість | 20k * N адрес |
| Гнучкість оновлення | Потребує зміни root | Динамічна (будь-які зміни) | Тільки owner-функції |
| Централізація | Немає | Залежить від signer key | Немає |
| Безпека | Висока (доведена) | Середня (ключовий ризик) | Висока |
| Ідеальний розмір списку | 100 — 1 млн+ | Будь-який, з невідомими змінами | < 100 адрес |
Merkle proof — стандарт ринку, дешевший за on-chain mapping у 40 разів для 10 000 адрес. ECDSA застосовується в gaming mint та динамічних кампаніях. On-chain mapping — тільки для дуже маленьких фіксованих списків.
Порівняння схем для multi-tier та фаз
Для проєктів з різними рівнями доступу (OG, core, community) або часовими фазами (pre-sale, allowlist, public) потрібна додаткова логіка. Розглянемо типові конфігурації:
| Схема | Кількість root | Управління | Gas на мінт |
|---|---|---|---|
| Один root + multi-tier через encoding | 1 | Просте, але фіксовані квоти | ~60k-70k |
| Окремі root на фазу | 3-5 | Owner setPhase(), різні ціни | ~50k + зміна root |
| Гібрид: root + ECDSA для динаміки | 1 підпис | Backend підписує дозволи | ~70k + ризик signer |
Вибір схеми залежить від вимог: якщо квоти змінюються до мінту — краще ECDSA, якщо фіксовані — Merkle proof з multi-tier encoding.
Як генерувати Merkle tree на практиці?
- Зберіть всі адреси та квоти в масив.
- Використовуйте бібліотеку
@openzeppelin/merkle-tree:
const { StandardMerkleTree } = require('@openzeppelin/merkle-tree'); const values = [ ['0x...', 1], ['0x...', 3], ]; const tree = StandardMerkleTree.of(values, ['address', 'uint256']); const root = tree.root; - Збережіть root в контракт.
- Для кожного користувача генеруйте proof через
tree.getProof([address, maxMint])і передавайте при мінті.
Чому Merkle proof — стандарт ринку?
Незалежно від розміру списку, деплой коштує однаково. Proof передається користувачем (frontend генерує автоматично), контракт лише перевіряє. Ми гарантуємо відсутність reentrancy та коректну верифікацію proof. Наші контракти проходять аудит з використанням Slither та Mythril.
Додаткові механіки
Multi-tier whitelist — різні квоти для різних рівнів. Merkle дерево містить листя keccak256(abi.encode(address, maxMintAmount)). Користувач передає proof + свій maxMintAmount, контракт верифікує обидва параметри разом.
Temporal phases — WL → Allowlist → Public. Контракт тримає enum SalePhase { PAUSED, WHITELIST, ALLOWLIST, PUBLIC }. Різні root'и для різних фаз, різні ціни. owner змінює фазу через setPhase().
Batch mint з WL — користувач може мінтити N NFT за одну транзакцію, якщо його квота дозволяє. mintedCount[msg.sender] += amount замість bool прапорця.
Що входить в розробку
- Смарт-контракт з Merkle verify, hasMinted mapping та управлінням фазами
- Повний набір тестів у Foundry (edge cases: повторний мінт, невалідний proof, вичерпана квота)
- Frontend інтеграція (генерація proof через
@openzeppelin/merkle-tree, wagmi hookuseMint()) - Деплой через Foundry script з автоматичною верифікацією Etherscan
- Документація та підтримка після запуску
Орієнтири за термінами
Whitelist контракт з Merkle proof — 2-3 дні включаючи тести та frontend інтеграцію. Вартість розраховується індивідуально. Зв'яжіться з нами для обговорення вашого проєкту — ми допоможемо обрати оптимальну схему та реалізуємо її з гарантією безпеки.







