Розробка ERC-721 NFT: аудит, газ-оптимізація, деплой
Ми проєктуємо та розробляємо ERC-721 контракти, які не ламаються. Типова історія: проєкт деплоїть колекцію, через місяць виявляє, що роялті не виплачується на OpenSea (бо не реалізовано EIP-2981), metadata вірно завантажується з централізованого сервера (який упав), а mint через _safeMint замість _mint дозволяє повторний reentrancy через onERC721Received в кастомних контрактах-отримувачах. Підсумок — втрата довіри спільноти, судові позови, переробка контракту заново. З нашої практики: в одному проєкті відсутність EIP-2981 призвела до втрати 15% роялті на вторинних продажах — близько 120 ETH (~$300k). Правильний ERC-721 — це не просто відповідність інтерфейсу, це розуміння, як маркетплейси, гаманці та агрегатори взаємодіють з контрактом. Пропонуємо розробку під ключ з аудитом та підтримкою — зв'яжіться для оцінки проєкту.
Базова імплементація через OpenZeppelin
Відправна точка — ERC721 з OpenZeppelin 5.x. Не пишемо стандарт з нуля: OZ пройшов десятки аудитів, будь-яка кастомна реалізація додає ризик без очевидної вигоди. Розширюємо через наслідування:
contract MyNFT is ERC721, ERC721Enumerable, ERC721URIStorage, ERC2981, Ownable { uint256 private _nextTokenId; uint256 public constant MAX_SUPPLY = 10000; constructor(address initialOwner) ERC721("My Collection", "MYC") Ownable(initialOwner) {} } ERC721Enumerable потрібен, якщо контракт має повертати список токенів власника (tokenOfOwnerByIndex). Додає ~20K gas до кожного transfer через додаткові storage операції. Якщо маркетплейс не вимагає — краще обійтися без нього та читати дані через The Graph.
ERC721URIStorage дозволяє зберігати окремий URI для кожного токена. Альтернатива — baseURI + tokenId патерн, де всі токени використовують єдиний базовий шлях. Другий варіант дешевший у gas при mint.
EIP-2981: роялті на рівні контракту
_setDefaultRoyalty(royaltyReceiver, royaltyBps); // bps: 500 = 5% OpenSea та більшість сучасних маркетплейсів читають royaltyInfo() з EIP-2981. Старі маркетплейси використовували off-chain конфіг через Operator Filter Registry — цей підхід застарів. Реалізація EIP-2981 — мінімальний стандарт для будь-якої сучасної колекції.
Важливо: royaltyBps не забезпечується on-chain примусово — це інформаційний стандарт. Маркетплейс може його ігнорувати. Для примусового роялті потрібні кастомні transfer hooks (EIP-2981 + transfer restrictions через operator whitelist).
Metadata і зберігання
URI токена повертає JSON з полями name, description, image, attributes. Де зберігати — порівняння методів:
| Метод | Вартість | Децентралізація | Довговічність | Краще для |
|---|---|---|---|---|
| IPFS | Низька | Так (з pinning) | Потребує pinning | Більшість колекцій |
| Arweave | Середня (разова) | Так | Постійна | Довгострокові проєкти |
| On-chain | Висока | Повна | Постійна | Generative art (<1000 токенів) |
| Централізований сервер | Низька | Ні | Залежить від оператора | Pre-reveal фаза |
Централізований сервер — тільки для pre-reveal фази. Після reveal URI має переключитися на IPFS. Реалізуємо через флаг revealed та два baseURI.
Як оптимізувати mint і знизити газ?
Стандартний _safeMint дорожчий за _mint через перевірку IERC721Receiver на адресах-контрактах. Якщо mint призначений тільки для EOA — використовуємо _mint. Якщо потрібна підтримка контрактних гаманців (multisig) — _safeMint з явним reentrancy guard.
Для batch mint використовуємо ERC-721A (Azuki) замість стандартного OZ ERC-721. ERC-721A зберігає дані власника тільки при першому mint в batch, наступні токени дедукуються — економія до 70% gas при mint 10+ токенів. Для колекції з 10 000 токенів це економить близько 50 ETH (~$100k) на комісіях. Trade-off: transfer першого токена в batch трохи дорожчий через lazy initialization.
Порівняння методів mint:
| Метод | Gas на 1 mint | Gas на 10 mint | Захист від reentrancy |
|---|---|---|---|
_mint |
~60k | ~600k | Немає |
_safeMint |
~80k | ~800k | Частковий |
| ERC-721A | ~60k | ~150k | Немає (потрібен guard) |
Які ще вразливості потрібно закрити?
Reentrancy через onERC721Received
Якщо контракт-отримувач реалізує IERC721Receiver, він може викликати повторно ваш контракт під час mint. Рішення: використовувати ReentrancyGuard з OpenZeppelin або _mint і перевіряти balance після transfer.
Unchecked low-level calls
Уникайте прямих call без перевірки поверненого значення. У сучасному Solidity компілятор вимагає явного зазначення success.
Процес роботи
- Аналітика (0.5-1 день). Визначаємо supply, mint механіку (public/whitelist/Merkle), роялті, чи потрібні Enumerable та URIStorage, цільовий чейн.
- Проєктування та розробка (1-2 дні). Пишемо контракт на Solidity 0.8.x з тестами в Foundry. Збираємо скрипт деплою та верифікації.
- Аудит та газ-оптимізація (0.5 дня). Перевіряємо код статичним аналізатором Slither, фаззинг Echidna. Оптимізуємо gas: прибираємо надлишкові storage змінні, використовуємо unchecked блоки.
- Деплой на testnet (0.5 дня). Sepolia, перевірка взаємодії з маркетплейсами.
- Mainnet деплой і підтримка (1 день). Верифікація на Etherscan, передача прав, моніторинг. Пост-деплой підтримка 30 днів.
Що входить в роботу
- Повний вихідний код контракту з коментарями.
- Документація по функціях, подіях та модифікаторах.
- Скрипти деплою та верифікації на Etherscan.
- Інструкція по налаштуванню metadata (IPFS/Arweave).
- Навчання команди: перевірка mint, розкриття reveal.
- Технічна підтримка на 30 днів після деплою.
Переваги роботи з нами
Наша команда має 7+ років досвіду в блокчейн-розробці, виконано понад 50 проєктів по смарт-контрактах. Ми не просто копіюємо шаблон OpenZeppelin — ми адаптуємо кожну колекцію під конкретні вимоги маркетплейсів, gas-ліміти та модель розповсюдження. Отримайте консультацію — надішліть опис вашого проєкту, і ми оцінимо терміни та вартість.
// Приклад контракту з whitelist та reveal contract AdvancedNFT is ERC721, ERC2981, Ownable { using MerkleProof for bytes32[]; bytes32 public whitelistRoot; string public baseURI; string public placeholderURI; bool public revealed; uint256 public mintPrice = 0.08 ether; function whitelistMint(bytes32[] calldata proof) external payable { require(MerkleProof.verify(proof, whitelistRoot, keccak256(abi.encodePacked(msg.sender)))); _safeMint(msg.sender, _nextTokenId++); } function reveal(string memory _newBaseURI) external onlyOwner { revealed = true; baseURI = _newBaseURI; } } Орієнтовні терміни: базовий ERC-721 з роялті та IPFS metadata — 2-3 дні; з whitelist, reveal та mint сайтом — 5-7 днів. Вартість розраховується індивідуально. Замовте розробку ERC-721 контракту з аудитом та підтримкою.







