Принудительные роялти в NFT: от ERC-2981 до кастомного whitelist
Вы запустили коллекцию из 10 000 NFT, вложили миллионы в арт и маркетинг, а через месяц видите — ваши роялти не выплачиваются. Раньше маркетплейсы делали это автоматически, но потом Blur ввёл нулевые комиссии, и часть платформ перестала чтить EIP-2981. Создатели потеряли миллионы. Выбор между принудительным ончейн-принуждением и добровольными выплатами стал продуктовым, а не техническим. Мы реализуем оба подхода, добавляем кастомную логику и гарантируем, что роялти дойдут до вас. При объёме вторичных торгов в 100 ETH роялти в 7,5% принесут 7,5 ETH — но только если они защищены принудительно. Получите консультацию — напишите нам, чтобы начать с бесплатного аудита.
Как обеспечить принудительные выплаты роялти?
ERC-2981: базовый, но необязательный
ERC-2981 — сигнальный стандарт. Контракт объявляет royaltyInfo(tokenId, salePrice), маркетплейс читает и (опционально) выплачивает. Blur может игнорировать. OpenSea чтит. Magic Eden — depends.
Реализация через OpenZeppelin занимает 10 строк:
import "@openzeppelin/contracts/token/common/ERC2981.sol";
contract MyCollection is ERC721, ERC2981 {
constructor(address royaltyReceiver) ERC721("Collection", "COL") {
_setDefaultRoyalty(royaltyReceiver, 750); // 7.5%
}
function supportsInterface(bytes4 interfaceId)
public view override(ERC721, ERC2981) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
Без supportsInterface override маркетплейс не увидит ERC-2981 поддержку при ERC-165 проверке. Это типичная ошибка, которую мы встречали в 10+ аудитах.
Operator Filter: принудительное взыскание
Если роялти важны коммерчески — нужен operator filter. Идея: контракт проверяет каждый transferFrom и safeTransferFrom, разрешает transfer только через апрувнутые маркетплейсы, которые честно выплачивают роялти. Operator Filter работает в 2-3 раза надёжнее чистого ERC-2981 по гарантии выплат.
OpenSea предложил OperatorFilterRegistry.
import {DefaultOperatorFilterer} from "operator-filter-registry/src/DefaultOperatorFilterer.sol";
contract MyCollection is ERC721, ERC2981, DefaultOperatorFilterer {
function transferFrom(address from, address to, uint256 tokenId)
public override onlyAllowedOperator(from) {
super.transferFrom(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId)
public override onlyAllowedOperatorApproval(from) {
super.safeTransferFrom(from, to, tokenId);
}
}
onlyAllowedOperator проверяет адрес оператора в реестре. Blur был изначально заблокирован, потом добавлен после переговоров.
Компромисс: operator filter защищает роялти, но ограничивает ликвидность — пользователи не могут торговать на неодобренных платформах. Для некоторых коллекций это неприемлемо.
Почему ERC-2981 недостаточен для защиты роялти?
ERC-2981 без фильтра — это честное слово. Operator filter даёт ончейн-гарантию. Если ваш проект рассчитан на долгосрочные продажи и планирует зарабатывать на роялти, фильтр окупается. Для арт-коллекций с высокой вторичной активностью мы рекомендуем именно его. Наш опыт: более 20 проектов использовали фильтр и увеличили доход от роялти на 30-50% по сравнению с чистыми ERC-2981.
Как настроить собственный whitelist маркетплейсов?
Независимость от OpenSea реестра — через кастомную логику. Подход: разрешаем transfer только если он инициирован через whitelist контрактов (маркетплейсы, которые явно интегрировали наш роялти механизм), или если это wallet-to-wallet transfer (не через маркетплейс).
mapping(address => bool) public approvedMarketplaces;
function _beforeTokenTransfer(address from, address to, uint256 tokenId)
internal override {
// Разрешаем прямые трансферы (не через маркетплейс)
if (from == tx.origin || to == tx.origin) return;
// Проверяем, что маркетплейс одобрен
require(approvedMarketplaces[msg.sender], "Marketplace not approved");
}
Это менее гибко, но независимо от внешних реестров. При изменении рыночной ситуации вы сами добавляете или удаляете платформы без ожидания обновления реестра OpenSea.
Splitter для команд
Если роялти делятся между несколькими адресами, ставим receiver в ERC-2981 на PaymentSplitter:
address[] memory payees = [founder, artist, treasury];
uint256[] memory shares = [50, 30, 20];
PaymentSplitter splitter = new PaymentSplitter(payees, shares);
_setDefaultRoyalty(address(splitter), 500); // 5% роялти на сплиттер
Каждый получатель вызывает splitter.release(token) чтобы забрать накопленные средства. Pull pattern — нет риска reentrancy при автоматической рассылке.
Сравнение подходов к роялти
| Подход |
Принуждение |
Зависимость |
Сложность |
Ликвидность |
| ERC-2981 |
Нет (опционально) |
от маркетплейса |
Низкая |
Высокая |
| Operator Filter |
Да |
от реестра OpenSea |
Средняя |
Ограничена |
| Кастомный whitelist |
Да |
от вашего контракта |
Высокая |
Умеренная |
Типичные ошибки и их последствия
| Ошибка |
Последствие |
Как избежать |
Забытый supportsInterface |
Маркетплейс не видит ERC-2981 |
Всегда override |
| Роялти на нулевой адрес |
Выплаты уходят в никуда |
Проверять address(0) |
| Слишком высокий % |
Падение торгового объёма |
5-7.5% оптимально |
| Отсутствие функции обновления receiver |
Нельзя сменить кошелёк |
Добавить updateDefaultRoyalty |
Для обновляемого receiver добавляем updateDefaultRoyalty() с onlyOwner:
function updateDefaultRoyalty(address receiver, uint96 feeNumerator)
external onlyOwner {
_setDefaultRoyalty(receiver, feeNumerator);
}
Пошаговая инструкция по внедрению Operator Filter
- Установите пакет
operator-filter-registry через npm или Foundry.
- Унаследуйте контракт от
DefaultOperatorFilterer.
- Добавьте модификаторы
onlyAllowedOperator и onlyAllowedOperatorApproval в функции перевода.
- Протестируйте на тестовой сети Rinkeby или Goerli.
- Разверните на mainnet и проверьте, что трансферы через одобренные площадки проходят.
Что входит в разработку NFT с роялти под ключ
- Смарт-контракт с ERC-2981 или operator filter
- Кастомная enforcement логика (по необходимости)
- PaymentSplitter для распределения роялти
- Скрипты для минтинга и взаимодействия
- Документация по развёртыванию и обновлению
- Поддержка в течение 30 дней после деплоя
Ориентиры по срокам
NFT контракт с ERC-2981 роялти и PaymentSplitter — 2-3 дня. С operator filter и кастомной enforcement логикой — 3-4 дня. Оценим ваш проект в течение дня. Готовы обсудить детали? Свяжитесь с нами — начнём с бесплатного аудита вашего текущего контракта.
Почему разработка NFT маркетплейсов требует комплексного подхода?
Мы видим, что на первый взгляд NFT-контракт выглядит просто: ERC-721, mint(), IPFS для метаданных, всё. На практике именно в этой «простоте» прячется большинство проблем — от ботов, скупающих весь mint в первый блок, до сломанных royalties на вторичном рынке. Типовой запрос: «Сделайте коллекцию как у других за неделю», а через месяц выясняется, что газ вырос втрое из-за неоптимизированного for-цикла, а OpenSea не видит метаданные после reveal. Мы знаем каждый из этих граблей и строим процессы так, чтобы их избежать.
За 5 лет работы с блокчейнами мы реализовали 40+ NFT-проектов, включая маркетплейсы с динамическими атрибутами и cross-chain мостами. Накопили библиотеку проверенных шаблонов — часть из них разберём ниже.
Какой стандарт выбрать: ERC-721 или ERC-1155?
ERC-721 — каждый токен уникален, один owner. Подходит для коллекций, где каждый NFT имеет индивидуальные атрибуты и прямую привязку owner → tokenId.
ERC-1155 — multi-token стандарт: один контракт хранит и fungible, и non-fungible токены. Использует balanceOf(address, tokenId) вместо ownerOf(tokenId). Одна транзакция может передать несколько разных токенов через safeBatchTransferFrom. Это экономит газ при массовых операциях — важно для игровых айтемов, тикетов, edition-коллекций.
| Критерий |
ERC-721 |
ERC-1155 |
| Уникальность токена |
Каждый токен уникален |
Один tokenId может иметь несколько копий |
| Баланс пользователя |
Только ownerOf (один) |
balanceOf(address, tokenId) |
| Газ на transfer |
~25 000 gas |
~18 000 gas (batch — ещё ниже) |
| Batch operations |
Нет нативной поддержки |
safeBatchTransferFrom |
| Идеальный сценарий |
Art-коллекции, PFPs |
Игры, тикеты, editions |
Конкретный кейс: игровой проект с 50 видами айтемов, каждый в тираже 10 000. ERC-721 — 500 000 уникальных токенов, огромный overhead на маппинги. ERC-1155 — 50 tokenId, balanceOf на каждого игрока. Газ на transfer ниже в 2–3 раза, деплой контракта дешевле. Для таких задач мы используем OpenZeppelin ERC-1155 с кастомными модификациями.
Метаданные: on-chain vs IPFS vs centralized
Стандартный путь — tokenURI() возвращает ссылку на JSON с полями name, description, image, attributes. Три варианта хранения:
-
Centralized server — самый дешёвый и гибкий. Риск: сервер падает, компания закрывается — NFT теряет метаданные. Не подходит для коллекций с претензией на долгосрочную ценность.
-
IPFS + Pinning — контентно-адресуемое хранилище, ссылка привязана к хешу содержимого. Pinata или NFT.Storage обеспечивают pinning. Важно: IPFS не гарантирует доступность сам по себе — нужен активный pinning service. Если он закроется, данные могут исчезнуть, если никто не хранит копию.
-
On-chain metadata — base64-encoded SVG или JSON прямо в tokenURI. Максимальная надёжность, но дорого: для коллекции из 10 000 токенов затраты на газ могут превысить $5000. Подходит для generative art проектов, где визуал генерируется из on-chain атрибутов (Nouns, Loot).
Для большинства коллекций мы выбираем IPFS с Pinata для images + on-chain атрибуты для трейтов — хороший баланс. Файлы перед загрузкой проверяем через валидатор JSON Schema; типичная ошибка — неэкранированные кавычки, из-за чего маркетплейсы показывают пустой экран.
Dynamic NFT: метаданные, которые меняются
Dynamic NFT обновляет метаданные в ответ на внешние события — результаты матчей, уровень персонажа, реальные данные через Chainlink. Архитектурно это связка: смарт-контракт хранит state → tokenURI() генерирует метаданные из state on-chain. Проблема с кешированием: OpenSea и другие маркетплейсы агрессивно кешируют. Стандартный механизм инвалидации — MetadataUpdate(tokenId) event из ERC-4906. OpenSea слушает этот event и сбрасывает кеш. Без него обновлённые метаданные могут не отображаться неделями.
Chainlink Automation (бывший Keepers) для автоматического обновления state на контракте по расписанию или по условию — стандартное решение для динамики.
Как защитить mint от ботов?
Allowlist через merkle tree — стандарт. Список адресов хешируется в merkle root, хранится в контракте. При mint пользователь предоставляет merkle proof — контракт проверяет без хранения полного списка. Используем OpenZeppelin MerkleProof library.
Reveal механика — при mint выдаётся placeholder, реальные трейты reveal-ятся после окончания продажи. Иначе боты могут сканировать pending транзакции и снайперить редкие трейты через frontrunning. Но reveal требует commitment scheme — случайный seed должен быть зафиксирован до mint или использовать Chainlink VRF.
Chainlink VRF для честной рандомизации трейтов. VRF request в момент mint → callback с verifiable random number → assign traits. Это добавляет ~2 транзакции и latency, но гарантирует честность. Ссылка на Chainlink VRF v2.5.
Rate limiting — require(mintedPerWallet[msg.sender] < maxPerWallet). Не защищает от мульти-кошельков, но поднимает стоимость атаки. Для премиум-проектов часто добавляем proof-of-work прямо в контракт (через EIP-2612 signatures).
Royalties: реальное состояние рынка
ERC-2981 — on-chain стандарт royalties. Контракт возвращает (recipient, amount) для любой sale price через royaltyInfo(tokenId, salePrice). Маркетплейсы опрашивают это при каждой продаже. Проблема: соблюдение royalties — добровольное решение маркетплейса. Blur запустился с нулевыми royalties, что вызвало волну других платформ. Сейчас ситуация частично стабилизировалась: OpenSea поддерживает ERC-2981, Blur добавил опциональные.
Попытки enforce royalties on-chain через ограничение transfers только на approved маркетплейсы (operator filtering) OpenSea предлагал через OperatorFilterRegistry. Это ломает composability — нельзя передать NFT через кастомный контракт. Большинство серьёзных проектов отказались от этого подхода. Для проектов, где royalties критичны, мы строим кастомный маркетплейс внутри экосистемы + incentive structure для пользователей торговать именно там.
Lazy minting и gas-free mint
Gas-free mint через подпись: создатель подписывает voucher (tokenId, tokenURI, price, signature), покупатель предоставляет voucher в mint() — контракт верифицирует подпись через ECDSA.recover() и минтит. Работает на OpenSea через их Seaport протокол. Seaport — оптимизированный контракт с минимальным gas usage. Понимание его механики важно при интеграции custom marketplace логики.
Стек для NFT-проектов
- Контракты: Solidity 0.8.x, OpenZeppelin ERC721Enumerable или ERC721A (Azuki) для gas-оптимизированного batch mint, ERC1155 от OpenZeppelin
- VRF и автоматизация: Chainlink VRF v2.5, Chainlink Automation
- Хранение: Pinata (IPFS pinning), NFT.Storage, Arweave для постоянного хранения
- Маркетплейс: OpenSea Seaport protocol, кастомная интеграция
- Фронтенд: wagmi v2 + viem, RainbowKit для wallet connection, React + TypeScript
Процесс разработки
-
Проектирование mint-механики — allowlist, public sale, price curve (Dutch auction или фиксированная), limits per wallet
-
Контракты — с Foundry fuzz-тестами на mint limits, merkle proof-верификацию, royalty calculations
-
IPFS деплой — загрузка метаданных и images до reveal, pinning на минимум двух сервисах
-
Reveal — если используется Chainlink VRF, тест на testnet обязателен: VRF subscription должен быть funded LINK токенами
-
Маркетплейс-интеграция — верификация коллекции на OpenSea, настройка royalties, тест MetadataUpdate events
-
Деплой и мониторинг — Tenderly для отлова reentrancy, Etherscan API для верификации контракта, настройка оповещений по событиям
Что входит в работу (deliverables)
- Исходный код смарт-контрактов (Solidity, Rust для Solana) с комментариями
- Тест-сьют (Foundry/Hardhat) с покрытием ≥90%
- Документация развёртывания и инструкции по интеграции
- Доступы к pinning-сервисам (Pinata/Pinfluence)
- Скрипты для генерации метаданных (Python/JS)
- Поддержка при верификации на маркетплейсах
- 30 дней технической поддержки после деплоя
Сроки
| Тип задачи |
Примерный срок |
| Базовый ERC-721 без reveal |
от 2 недель |
| NFT-коллекция с allowlist, reveal, VRF |
от 5 недель |
| ERC-1155 с marketplace и royalties |
от 6 недель |
| Dynamic NFT с внешними данными |
от 8 недель |
Стоимость рассчитывается индивидуально после аудита вашей задачи. Пришлите brief с описанием проекта — оценим прозрачно в течение 3 рабочих дней. Для постоянных клиентов действует гибкая система скидок на пакетные заказы. Свяжитесь с нами для детального обсуждения вашего NFT-проекта. Получите консультацию по архитектуре маркетплейса — оставьте заявку, и мы оценим проект за три дня.