Final ERC standard. A minimal interface for signaling royalty recipient and amount on secondary NFT sales. Marketplaces query `royaltyInfo(tokenId, salePrice)` and choose whether to honor it — payment is voluntary, not enforced on-chain.
- 01NFT creator royalties
- 02marketplace-agnostic royalty disclosure
- 03pairing with ERC-721 / ERC-1155 collections
- 04multi-recipient royalty splits via a payment splitter
- pnpm add @openzeppelin/contracts
Implement ERC-2981 by extending `ERC2981` from `@openzeppelin/contracts/token/common/ERC2981.sol` alongside your `ERC721` or `ERC1155` base. The single interface function is `royaltyInfo(uint256 tokenId, uint256 salePrice) external view returns (address receiver, uint256 royaltyAmount)`. Use `_setDefaultRoyalty(receiver, feeNumerator)` (basis points out of 10_000) for collection-wide, or `_setTokenRoyalty(tokenId, receiver, feeNumerator)` for per-token overrides. Override `supportsInterface` to combine ERC-165 IDs from both the base token and ERC-2981. For multi-recipient splits, point the receiver at an OpenZeppelin `PaymentSplitter` or 0xSplits contract.
- ⚑Royalty payment is NOT enforced — many marketplaces (Blur, X2Y2, LooksRare zero-royalty mode) ignore `royaltyInfo` entirely. On-chain enforcement requires transfer hooks or operator filters, both of which are themselves bypassable.
- ⚑OpenSea's Operator Filter Registry was the most-used enforcement path but has been deprecated — don't add it to new contracts.
- ⚑`feeNumerator` is in basis points relative to a denominator of 10_000 (so 500 = 5%). Confusing this with percent or 1e18 scales is a frequent bug.
- ⚑`royaltyInfo` returning more than the sale price is invalid — guard against overflow when `feeNumerator * salePrice` is computed in lower precision.
- ⚑Per-token royalties are stored in storage and bloat gas on mint — only use them when a default + override is genuinely needed.