Разработка ERC-2981 токена (royalties)
До ERC-2981 каждый маркетплейс изобретал роялти по-своему. OpenSea — оффчейн список в базе данных. Rarible — собственный контракт. LooksRare — своя схема. Итог: создатель NFT получал роялти только там, где его коллекция была зарегистрирована вручную. EIP-2981 стандартизировал это: единый on-chain интерфейс, который любой маркетплейс может прочитать.
Как работает ERC-2981
Стандарт добавляет одну функцию в контракт:
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) external view returns (address receiver, uint256 royaltyAmount);
Маркетплейс при продаже вызывает royaltyInfo(tokenId, salePrice), получает адрес получателя и сумму роялти. Всё. Стандарт намеренно минималистичен — он не принуждает к выплате (enforcement off-chain), только предоставляет данные.
Базовая реализация через OpenZeppelin:
import "@openzeppelin/contracts/token/common/ERC2981.sol";
contract MyNFT is ERC721, ERC2981 {
constructor() ERC721("MyNFT", "MNFT") {
_setDefaultRoyalty(msg.sender, 500); // 500 basis points = 5%
}
// Переопределение роялти для конкретного токена
function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator)
external onlyOwner {
_setTokenRoyalty(tokenId, receiver, feeNumerator);
}
}
feeNumerator — числитель от знаменателя _feeDenominator() (по умолчанию 10000). Значит, 500 = 5%, 250 = 2.5%, максимум 10000 = 100% (не используйте).
Расширенные паттерны роялти
Splitter роялти для нескольких получателей
Стандарт поддерживает только одного receiver. Для сплита между создателем, командой, фондом — нужен дополнительный контракт. Два подхода:
PaymentSplitter: receiver в ERC-2981 указывает на PaymentSplitter контракт (OpenZeppelin). Маркетплейс переводит всю сумму на сплиттер, сплиттер распределяет по shares. Простой, проверенный, но дополнительный газ на release.
Push royalty splitter: встроенный в NFT контракт механизм, который при каждом поступлении автоматически распределяет по адресам. Экономит один вызов, но усложняет контракт.
Динамические роялти
ERC-2981 позволяет royaltyInfo возвращать разные значения для разных tokenId. Это открывает возможности:
- Снижение роялти с ростом цены продажи (прогрессивная шкала)
- Разные ставки для разных категорий токенов (tier system)
- Нулевые роялти для primary sale, 5% для secondary
function royaltyInfo(uint256 tokenId, uint256 salePrice)
public view override returns (address, uint256) {
uint96 rate = tokenId < 1000 ? 500 : 250; // Genesis: 5%, Regular: 2.5%
return (_royaltyReceiver, (salePrice * rate) / _feeDenominator());
}
supportsInterface для маркетплейсов
Маркетплейсы проверяют поддержку ERC-2981 через ERC-165:
function supportsInterface(bytes4 interfaceId)
public view override(ERC721, ERC2981) returns (bool) {
return super.supportsInterface(interfaceId);
}
Без правильного supportsInterface маркетплейс не будет применять роялти. OpenZeppelin автоматически регистрирует 0x2a55205a (interface ID ERC-2981) при наследовании от ERC2981.
Ориентиры по срокам
Базовая реализация ERC-2981 в существующем NFT контракте — 1 день. Контракт с динамическими роялти и splitter — 2-3 дня.







