Разработка NFT с royalties

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка NFT с royalties
Простая
~2-3 рабочих дня
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1062
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка NFT с royalties

В 2022-2023 годах маркетплейсы начали делать роялти опциональными — Blur предлагал нулевые комиссии, часть аудитории OpenSea перешла туда. Создатели коллекций потеряли миллионы. Это привело к двум лагерям: те, кто ставит on-chain enforcement роялти, и те, кто полагается на goodwill маркетплейсов. Выбор между ними — не технический, а продуктовый. Мы реализуем оба подхода.

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 проверке.

Operator Filter: on-chain enforcement

Если роялти важны коммерчески — нужен operator filter. Идея: контракт проверяет каждый transferFrom и safeTransferFrom, разрешает transfer только через апрувнутые маркетплейсы, которые честно выплачивают роялти.

OpenSea предложил OperatorFilterRegistry в 2022 году:

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 проверяет адрес оператора в OperatorFilterRegistry. Blur был изначально заблокирован, потом добавлен после переговоров.

Компромисс: operator filter защищает роялти, но ограничивает ликвидность — пользователи не могут торговать на неодобренных платформах. Для некоторых коллекций это неприемлемо.

Собственная royalty enforcement логика

Независимость от 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");
}

Это менее гибко, но независимо от внешних реестров.

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 при автоматической рассылке.

Типичные ошибки

Забытый supportsInterface — маркетплейс не видит ERC-2981. Роялти на нулевой адрес при address(0) receiver — выплаты уходят в никуда. Слишком высокие роялти (>10%) снижают торговый объём. Отсутствие возможности обновить receiver — создатель не может сменить адрес кошелька.

Для обновляемого receiver добавляем updateDefaultRoyalty() с onlyOwner:

function updateDefaultRoyalty(address receiver, uint96 feeNumerator) 
    external onlyOwner {
    _setDefaultRoyalty(receiver, feeNumerator);
}

Ориентиры по срокам

NFT контракт с ERC-2981 роялти и PaymentSplitter — 2-3 дня. С operator filter и кастомной enforcement логикой — 3-4 дня.