Розробка генеративної NFT-колекції
10 000 унікальних CryptoPunks, 8 888 Azuki, 8 000 Milady — усі ці колекції побудовані на одному принципі: алгоритмічна комбінація шарів трейтів з заданими вірогідностями породжує унікальні зображення. Технічна сторона розробки складається з двох рівноважних частин: генератора зображень та смарт-контракту мінтингу.
Генерація зображень: від шарів до токену
Структура трейтів та рарити
Колекція розбивається на шари (background, body, clothing, eyes, mouth, accessories). Кожен шар містить варіанти з заданими вагами. Наприклад, для background:
"background": [
{ "name": "Золотий", "weight": 2 },
{ "name": "Синій", "weight": 25 },
{ "name": "Сірий", "weight": 73 }
]
Генератор випадково вибирає варіант пропорційно вагам й комбінує PNG-шари. Результат: 2% колекції отримує золотий фон, 73% — сірий.
Ключова проблема: при наївній реалізації рарити порушується через конфліктуючі трейти (наприклад, скелет-персонаж не може носити звичайний одяг). Реалізуємо conditional traits: матриця сумісності шарів, яка виключає невалідні комбінації. При великій кількості обмежень алгоритм може зациклитися — потрібен backtracking з max-attempts.
Інструмент: власний генератор на Node.js з використанням sharp для PNG compositing. sharp у 3–5 разів швидше canvas-based рішень — 10k зображень генеруються за 5–15 хвилин. Для анімованих колекцій (GIF/APNG) — ffmpeg через child process.
Метаданні та стандарти
Кожен токен вимагає JSON метаданих формату OpenSea metadata standard:
{
"name": "Collection #1234",
"description": "...",
"image": "ipfs://Qm.../1234.png",
"attributes": [
{ "trait_type": "Background", "value": "Золотий" },
{ "trait_type": "Eyes", "value": "Лазерні" }
]
}
Поле image має вказувати на IPFS або Arweave. Централізований сервер — смерть колекції при закритті. Завантажуємо через Pinata або NFT.Storage, отримуємо CID, формуємо baseURI вигляду ipfs://QmXxx/.
Смарт-контракт: ERC-721 та паттерни мінтингу
Базова структура на OpenZeppelin
contract MyCollection is ERC721A, Ownable, ReentrancyGuard {
uint256 public constant MAX_SUPPLY = 10000;
uint256 public constant MAX_PER_WALLET = 5;
string private _baseTokenURI;
mapping(address => uint256) public mintedPerWallet;
}
Використовуємо ERC-721A (реалізація Azuki) замість стандартного ERC-721: batch mint 5 токенів у ERC-721A потребує ~50k газу проти ~250k у класичній. Різниця помітна при 10k колекції на Ethereum mainnet.
Механіки мінтингу
Public mint — відкритий для всіх, часто з обмеженням per wallet. Захист: require(mintedPerWallet[msg.sender] + quantity <= MAX_PER_WALLET). Проблема з контрактами: msg.sender — контракт, обходить ліміт. Додаємо require(msg.sender == tx.origin) — але це ломає Safe/AA wallets. Компромісс: перевірка msg.sender == tx.origin тільки в період мінту, знімається після.
Whitelist mint — Merkle tree proof. Список адрес → корінь Merkle tree → root зберігається в контракті. Користувач надає proof (масив хешей), контракт верифікує через MerkleProof.verify() з OpenZeppelin. Proof генерується off-chain через merkletreejs, публікується на фронтенді.
Dutch auction mint — ціна починається високою й падає кожні N хвилин до мінімуму. Рахуємо поточну ціну через startPrice - (elapsedTime / step) * priceDecrement. Користувач платить поточну ціну, надлишок ETH повертається в тій же транзакції.
Reveal механіка
Премійні колекції не розкривають трейти до закінчення продажу (anti-snipe). До reveal: tokenURI() повертає один спільний placeholder. Після reveal: owner викликає setBaseURI(ipfsCID) й усі токени миттєво показують фінальні зображення.
Більш честна механіка: Chainlink VRF для випадкового seed. Контракт запитує random через requestRandomWords(), отримує відповідь в fulfillRandomWords(), записує seed. Усі tokenId випадково перемішуються відносно seed — неможливо угадати трейти навіть знаючи порядок мінту.
Royalties та маркетплейси
ERC-2981 — стандарт on-chain royalties. Маркетплейси, що підтримують стандарт (Blur при включеній опції, OpenSea, Rarible) автоматично читають royaltyInfo(tokenId, salePrice) й утримують відсоток. Додається через ERC2981 mixin з OpenZeppelin.
Для принудового застосування роялті: OperatorFilterRegistry (підхід Blur/OpenSea) блокує трансферки через контракти маркетплейсів, які не дотримуються royalties. Але це спірна механіка — обмежує ліквідність. Рішення: змінний флаг royaltiesEnforced, який owner може вимкнути.
Процес розробки
Підготовка ассетів (залежить від художника). Шари у PNG з прозорістю, таблиця рарити, матриця несумісностей.
Генератор та метаданні (2–4 дні). Node.js генератор, batch генерація колекції, JSON метаданні, upload на IPFS через Pinata API.
Смарт-контракт (3–5 днів). ERC-721A базис, механіки мінтингу (public + whitelist + dutch auction в залежності від вимог), тесты у Foundry: supply limits, per-wallet limits, Merkle proof, refund при dutch auction.
Frontend мінт-сайт (3–5 днів). React + wagmi + viem, підключення MetaMask/WalletConnect, wallet connect, rarity трекер.
Деплой. Testnet (Sepolia) → mainnet. Верифікація контракту на Etherscan.
Повний цикл від готових ассетів до деплою на mainnet — 1.5–2 тижні. Вартість залежить від механік мінтингу та вимог до фронтенду.







