ERC-2981 стал важным шагом к тому, чтобы NFT-авторы действительно получали справедливые роялти с перепродаж. Но работает ли это в реальности? В статье разбираемся, как устроен этот стандарт, какие задачи он решает, как его поддерживают маркетплейсы — и почему даже с его появлением вопрос с роялти остаётся открытым.
Почему роялти важны для создателей NFT
NFT (Non-Fungible Tokens) — это уникальные цифровые активы, которые благодаря технологии блокчейн могут представлять право собственности на различные объекты: произведения искусства, музыкальные треки, игровые предметы и многое другое. Одним из ключевых аспектов NFT является возможность для создателей получать роялти от перепродаж их активов. Это гарантирует устойчивый доход художникам, музыкантам и другим создателям.
Основные проблемы до стандартизации:
Несовместимость реализаций механизма роялти между маркетплейсами. На одной площадке роялти так устроено, на другой по-другому, на третьей его вообще нет. Нельзя взять нфт, созданную на одном маркетплейсе с роялти и быть уверенным, что на другом роялти будет также взиматься (и это кстати этот стандарт не решил)
Обход механизма роялти. Лазейки, которые позволяли продавцам и покупателям избегать роялти
Что такое ERC-2981?
Обзор стандарта
ERC-2981 — это стандарт для смарт-контрактов, разработанный для унификации механизма определения роялти на продажу NFT. Он облегчает взаимодействие между создателями контента, держателями токенов и платформами, предоставляя простой способ описания условий выплаты роялти.
Основные характеристики:
Совместимость: Стандарт поддерживает как ERC-721, так и ERC-1155 , так и любой контракт который наследуется от реализации стандарта, что делает его универсальным.
Простота интеграции: Использует единый интерфейс для определения условий роялти, который легко интегрируется с платформами и маркетплейсами.
Гибкость (на уровне реализации): Хотя сам стандарт описывает только базовый механизм, разработчики могут реализовать дополнительные функции, такие как динамические роялти или более сложные условия.
Масштабируемость: Стандарт определяет интерфейс, который возвращает информацию о роялти, но не обязывает платформы автоматически их выплачивать. Это упрощает внедрение и не накладывает дополнительных ограничений.
Техническая реализация
Рассмотрим пример реализации стандарта от openzeppelin
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Импортируем интерфейс ERC2981 для реализации стандартного поведения роялти. Там объявляется метод получения информации royaltyInfo.
import {IERC2981} from "../../interfaces/IERC2981.sol";
// Импортируем интерфейс и реализацию ERC165 для поддержки проверки интерфейсов.
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
/**
* @dev Реализация стандарта ERC-2981 для управления информацией о роялти.
*
* Информация о роялти может быть установлена глобально (для всех токенов)
* через метод `_setDefaultRoyalty`, а также для конкретного токена
* через метод `_setTokenRoyalty`. Индивидуальная настройка имеет
* приоритет над глобальной.
*
* Важно: стандарт ERC-2981 описывает только передачу информации о роялти
* и не обязывает к их выплате. Выплаты зависят от поддержки стандартов
* на маркетплейсах.
*/
abstract contract ERC2981 is IERC2981, ERC165 {
// Структура для хранения информации о роялти
struct RoyaltyInfo {
address receiver; // Адрес получателя роялти
uint96 royaltyFraction; // Процент роялти (в базисных пунктах)
}
// Глобальная информация о роялти (по умолчанию для всех токенов)
RoyaltyInfo private _defaultRoyaltyInfo;
// Маппинг для индивидуальной информации о роялти (по токенам)
mapping(uint256 tokenId => RoyaltyInfo) private _tokenRoyaltyInfo;
error ERC2981InvalidDefaultRoyalty(uint256 numerator, uint256 denominator);
error ERC2981InvalidDefaultRoyaltyReceiver(address receiver);
error ERC2981InvalidTokenRoyalty(uint256 tokenId, uint256 numerator, uint256 denominator);
error ERC2981InvalidTokenRoyaltyReceiver(uint256 tokenId, address receiver);
/**
* @dev Поддержка интерфейсов, включая ERC2981.
* Возвращает `true`, если указанный `interfaceId` поддерживается.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) {
return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Метод для получения информации о роялти.
* Возвращает адрес получателя роялти и сумму роялти на основе цены продажи.
* Если для токена нет индивидуальной настройки, используется глобальная.
*/
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) public view virtual returns (address receiver, uint256 amount) {
// Получаем информацию о роялти для конкретного токена
RoyaltyInfo storage _royaltyInfo = _tokenRoyaltyInfo[tokenId];
address royaltyReceiver = _royaltyInfo.receiver;
uint96 royaltyFraction = _royaltyInfo.royaltyFraction;
// Если для токена нет индивидуальной информации, используем глобальную
if (royaltyReceiver == address(0)) {
royaltyReceiver = _defaultRoyaltyInfo.receiver;
royaltyFraction = _defaultRoyaltyInfo.royaltyFraction;
}
// Рассчитываем сумму роялти: (цена продажи * процент) / знаменатель
uint256 royaltyAmount = (salePrice * royaltyFraction) / _feeDenominator();
return (royaltyReceiver, royaltyAmount);
}
/**
* @dev Метод возвращает знаменатель для расчёта роялти.
* По умолчанию равен 10000, что позволяет указывать процент в базисных пунктах.
*/
function _feeDenominator() internal pure virtual returns (uint96) {
return 10000; // Базисные пункты (1 = 0.01%)
}
/**
* @dev Устанавливает глобальную информацию о роялти (для всех токенов).
* Требования:
* - `receiver` не должен быть нулевым адресом.
* - `feeNumerator` не должен превышать знаменатель.
*/
function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual {
uint256 denominator = _feeDenominator();
if (feeNumerator > denominator) {
revert ERC2981InvalidDefaultRoyalty(feeNumerator, denominator);
}
if (receiver == address(0)) {
revert ERC2981InvalidDefaultRoyaltyReceiver(address(0));
}
_defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator);
}
/**
* @dev Удаляет глобальную информацию о роялти.
*/
function _deleteDefaultRoyalty() internal virtual {
delete _defaultRoyaltyInfo;
}
/**
* @dev Устанавливает индивидуальную информацию о роялти для токена.
* Требования:
* - `receiver` не должен быть нулевым адресом.
* - `feeNumerator` не должен превышать знаменатель.
*/
function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual {
uint256 denominator = _feeDenominator();
if (feeNumerator > denominator) {
revert ERC2981InvalidTokenRoyalty(tokenId, feeNumerator, denominator);
}
if (receiver == address(0)) {
revert ERC2981InvalidTokenRoyaltyReceiver(tokenId, address(0));
}
_tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
}
/**
* @dev Удаляет индивидуальную информацию о роялти для токена,
* возвращая его к использованию глобальной настройки.
*/
function _resetTokenRoyalty(uint256 tokenId) internal virtual {
delete _tokenRoyaltyInfo[tokenId];
}
}
Использование стандарта в своем контракте:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Используем базовый контракт ERC-721
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
// Подключаем реализацию стандарта ERC-2981 для роялти
import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol";
/**
* @title SimplifiedERC721WithRoyalty
* @dev Упрощённая реализация ERC-721 с поддержкой роялти по стандарту ERC-2981.
*/
contract SimplifiedERC721WithRoyalty is ERC721, ERC2981 {
uint256 private _nextTokenId;
/**
* @dev Конструктор контракта.
* @param name Имя токена.
* @param symbol Символ токена.
*/
constructor(string memory name, string memory symbol) ERC721(name, symbol) {}
/**
* @dev Минт нового токена с настройкой роялти.
* @param to Адрес получателя токена.
* @param royaltyReceiver Адрес для получения роялти.
* @param royaltyFraction Процент роялти в базисных пунктах (500 = 5%).
*/
function mint(
address to,
address royaltyReceiver,
uint96 royaltyFraction
) external {
uint256 tokenId = _nextTokenId++; // Генерация нового токен ID
// Минт токена
_mint(to, tokenId);
// Установка роялти для токена
if (royaltyReceiver != address(0) && royaltyFraction > 0) {
_setTokenRoyalty(tokenId, royaltyReceiver, royaltyFraction);
}
}
/**
* @dev Установить глобальную роялти, применимую ко всем токенам.
* @param receiver Адрес получателя роялти.
* @param feeNumerator Процент роялти в базисных пунктах.
*/
function setDefaultRoyalty(address receiver, uint96 feeNumerator) external {
_setDefaultRoyalty(receiver, feeNumerator);
}
/**
* @dev Удалить глобальную роялти.
*/
function deleteDefaultRoyalty() external {
_deleteDefaultRoyalty();
}
/**
* @dev Удалить роялти для конкретного токена, возвращая его к использованию глобальной настройки.
* @param tokenId Идентификатор токена.
*/
function resetTokenRoyalty(uint256 tokenId) external {
_resetTokenRoyalty(tokenId);
}
/**
* @dev Переопределение метода `supportsInterface` для поддержки ERC2981.
* @param interfaceId Идентификатор интерфейса.
* @return Возвращает true, если интерфейс поддерживается.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC2981) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
Таким образом этот стандарт является очередным шагом для поддержания справедливых роялти для NFT авторов.
На момент написания статьи, ситуация по поддержке ERC-2981 такая:
Маркетплейс |
Поддержка ERC-2981 |
Примечания |
---|---|---|
OpenSea |
Нет |
Не поддерживает стандарт ERC-2981. Использует собственную систему настройки роялти на уровне коллекции через интерфейс платформы |
Rarible |
Да |
Полностью поддерживает ERC-2981. Дополнительно можно задать роялти через интерфейс платформы для старых контрактов. |
Magic Eden |
Да |
Поддерживает ERC-2981, но также предлагает ручную настройку роялти через админ-панель коллекции. |
Blur |
Нет |
Не поддерживает ERC-2981. Позволяет пользователям самостоятельно устанавливать размер роялти. |
Добавление ERC-2981 к существующим контрактам
Добавление роялти в уже развернутые контракты, которые не поддерживают эту функцию, является сложной задачей, особенно если контракт изначально не был спроектирован для обновлений. Однако существуют решения, позволяющие адаптировать такие контракты для учета роялти. Ниже рассмотрены основные подходы.
Если контракт upgradeable, то можно добавить ERC-2981, например от openzeppelin.
Создание обертки для существующих токенов
Одно из решений — это создание нового смарт-контракта, который действует как обертка (wrapper) для оригинальных токенов. Этот контракт реализует стандарт ERC-2981 или кастомные механизмы роялти.
Как это работает: Владельцы токенов отправляют их в контракт обертки, "стейкая" свои активы. Новый контракт выпускает "обернутые" токены, которые содержат логику роялти. Обернутые токены можно торговать на маркетплейсах, которые поддерживают этот стандарт, и создатели получают роялти при каждой перепродаже.
Данное решение не является оптимальным, потому что:
Требует взаимодействия владельца NFT с другим контрактом.
Остается возможность прямого взаимодействия с первоначальным смарт-контрактом нфт, что позволяет и обойти роялти при трансфере и возможно создать нфт без роялти (зависит от того, кто может минтить нфт)
Вывод
Стандарт ERC-2981 представляет собой важный шаг в развитии NFT-экосистемы, позволяя унифицировать механизм роялти и облегчая их применение для создателей контента. Основные преимущества стандарта — это совместимость с популярными токен-стандартами (ERC-721 и ERC-1155), гибкость реализации и простота интеграции для новых контрактов. Однако он также сталкивается с рядом ограничений:
Ограниченность применения: Стандарт лишь предоставляет способ описания роялти, но не гарантирует их автоматическую выплату. Это делает его зависимым от добросовестности маркетплейсов, что ограничивает его функциональность.
Отсутствие ретроактивной поддержки: ERC-2981 не применим к уже развернутым контрактам, что усложняет его внедрение для существующих коллекций. Решения, такие как обертки, требуют значительных усилий со стороны пользователей и редко применимы на практике.
Проблемы с поддержкой на маркетплейсах: Не все популярные платформы, такие как Blur или OpenSea, поддерживают стандарт. Они в свою очередь предлагают решения только в рамках своих маркетплейсов, что не решает проблему стандартизации выплаты роялти на всех маркетплейсах. Это создает разрыв в экосистеме, где роялти могут быть проигнорированы при продаже на определенных площадках.
Несмотря на эти недостатки, ERC-2981 остается важным инструментом для новых проектов, предоставляя создателям удобный способ задать условия роялти. Однако для решения проблем с роялти в уже существующих коллекциях и для защиты прав создателей необходимы новые подходы. Работы в сторону роялти продолжаются и существуют другие способы решения.
Мы с коллегами периодически пишем в нашем Telegram-канале. Иногда это просто мысли вслух, иногда какие-то наблюдения с проектной практики. Не всегда всё оформляем в статьи, иногда проще написать пост в телегу. Так что, если интересно, что у нас в работе и что обсуждаем, можете заглянуть.