ERC-2981 Token Development (Royalties)
Before ERC-2981, every marketplace implemented royalties differently. OpenSea used an off-chain database list. Rarible had its own contract. LooksRare had its own scheme. As a result, NFT creators only earned royalties where their collection was manually registered. EIP-2981 standardized this: a single on-chain interface that any marketplace can read.
How ERC-2981 Works
The standard adds one function to the contract:
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) external view returns (address receiver, uint256 royaltyAmount);
When selling an NFT, the marketplace calls royaltyInfo(tokenId, salePrice), receives the recipient address and royalty amount. That's it. The standard is intentionally minimal — it doesn't enforce payment (enforcement is off-chain), it just provides the data.
Basic implementation via 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%
}
// Override royalty for a specific token
function setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator)
external onlyOwner {
_setTokenRoyalty(tokenId, receiver, feeNumerator);
}
}
feeNumerator is a numerator from the denominator _feeDenominator() (default 10000). So 500 = 5%, 250 = 2.5%, maximum 10000 = 100% (don't use).
Advanced Royalty Patterns
Royalty splitter for multiple recipients
The standard supports only one receiver. To split between creator, team, and fund — you need an additional contract. Two approaches:
PaymentSplitter: the receiver in ERC-2981 points to a PaymentSplitter contract (OpenZeppelin). The marketplace transfers the full amount to the splitter, and the splitter distributes by shares. Simple, proven, but extra gas for release calls.
Push royalty splitter: a mechanism built into the NFT contract that automatically distributes on every incoming payment. Saves one call, but complicates the contract.
Dynamic Royalties
ERC-2981 allows royaltyInfo to return different values for different tokenIds. This opens possibilities:
- Decreasing royalties as sale price increases (progressive scale)
- Different rates for different token categories (tier system)
- Zero royalties for primary sale, 5% for 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 for Marketplaces
Marketplaces check ERC-2981 support via ERC-165:
function supportsInterface(bytes4 interfaceId)
public view override(ERC721, ERC2981) returns (bool) {
return super.supportsInterface(interfaceId);
}
Without proper supportsInterface, the marketplace won't apply royalties. OpenZeppelin automatically registers 0x2a55205a (ERC-2981 interface ID) when inheriting from ERC2981.
Timeline Estimates
Basic ERC-2981 implementation in an existing NFT contract — 1 day. Contract with dynamic royalties and splitter — 2–3 days.







