Разработка генеративной 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 для compositing PNG-слоёв. sharp в 3-5 раз быстрее canvas-based решений — 10k изображений генерируются за 5-15 минут. Для анимированных коллекций (GIF/APNG) — ffmpeg через child process.
Metadata и стандарты
Каждый токен требует 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's implementation) вместо стандартного 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 только в период mint, снимается после.
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 с прозрачностью, таблица рарити, матрица несовместимостей.
Генератор и metadata (2-4 дня). Node.js генератор, batch генерация коллекции, JSON metadata, загрузка на 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, рарити трекер.
Деплой. Testnet (Sepolia) → mainnet. Верификация контракта на Etherscan.
Полный цикл от готовых ассетов до деплоя на mainnet — 1.5-2 недели. Стоимость зависит от механик минта и требований к фронтенду.







