# Implementing On-Chain NFT Royalties with ERC-2981: A Practical Deep-Dive

> ERC-2981 standardizes NFT royalty signals across marketplaces. Learn how to implement it correctly and what it actually enforces — and what it does not.

- **Published:** 2026-05-05
- **Author:** Clixo
- **Reading time:** 5 min read
- **Tags:** nft-royalties, erc-2981, smart-contracts, ethereum, creator-economics
- **Canonical URL:** https://clixo.sh/blog/implementing-nft-royalties-erc-2981

You want creators to earn on secondary sales. You add a royalty field to your contract and assume it will be paid. This assumption has cost creators a significant amount of money across the NFT ecosystem. ERC-2981 is the right foundation for royalties, but understanding what it actually does — and what it leaves to marketplaces — is essential before you ship.

## What ERC-2981 Is

ERC-2981 is an Ethereum Improvement Proposal that defines a standard interface for reporting royalty information. It answers the question: "Who should receive royalties on this token, and at what percentage?"

The interface is minimal:

```
function royaltyInfo(
    uint256 tokenId,
    uint256 salePrice
) external view returns (address receiver, uint256 royaltyAmount);
```

A marketplace calls `royaltyInfo` with the token ID and the sale price. Your contract returns the royalty recipient address and the royalty amount in the same currency as the sale. The marketplace then decides whether to honor it.

```mermaid
sequenceDiagram
  participant B as Buyer
  participant M as Marketplace
  participant C as NFT Contract
  participant R as Royalty Recipient
  B->>M: Purchase NFT at sale price
  M->>C: royaltyInfo(tokenId, salePrice)
  C-->>M: receiver address + royalty amount
  M->>R: Transfer royalty amount
  M->>B: Transfer NFT ownership
  M-->>B: Sale confirmed
```

That last sentence is the critical one.

## What ERC-2981 Does Not Do

ERC-2981 does **not** enforce payment. It does not transfer funds. It does not block transfers if royalties are not paid. It is a read-only signal — a standardized way for your contract to declare royalty intent. Whether a marketplace acts on that signal is entirely up to the marketplace.

This means:

- Peer-to-peer transfers through `transferFrom` bypass royalties entirely — there is no sale price in the transaction.
- Marketplaces that choose not to support ERC-2981 ignore your contract's declaration.
- Wrapped or bridged tokens may trade without the royalty signal being visible at all.

Knowing this upfront is not a reason to skip ERC-2981. It is the baseline that compliant marketplaces read. But it is not a complete solution on its own.

## Implementing ERC-2981 Correctly

### Basic Implementation

OpenZeppelin ships an `ERC2981` base contract. If you are using their library — which you should be — the implementation is straightforward:

```
import "@openzeppelin/contracts/token/common/ERC2981.sol";

contract MyNFT is ERC721, ERC2981 {
    constructor() ERC721("MyNFT", "MNFT") {
        _setDefaultRoyalty(msg.sender, 500); // 5% in basis points
    }
}
```

The second argument to `_setDefaultRoyalty` is expressed in basis points: 500 = 5%, 250 = 2.5%, 1000 = 10%.

### Per-Token Royalties

ERC-2981 supports per-token overrides. This is useful if different tokens in your collection should carry different royalty rates or route to different recipients:

```
_setTokenRoyalty(tokenId, creatorAddress, royaltyBips);
```

This is practical for multi-artist collections, split royalties by series, or tokens with special provenance.

### Resetting Royalties

If a token is transferred to a burn address or a specific condition is met, you can reset the token-level royalty:

```
_resetTokenRoyalty(tokenId);
```

This removes the per-token override and falls back to the default.

## ERC-165 Interface Support

ERC-2981 requires that your contract signal support via ERC-165. OpenZeppelin handles this automatically in their base contract, but if you are implementing from scratch:

```
function supportsInterface(bytes4 interfaceId)
    public view override returns (bool)
{
    return interfaceId == type(IERC2981).interfaceId
        || super.supportsInterface(interfaceId);
}
```

Marketplaces call `supportsInterface` before calling `royaltyInfo`. If you forget this, compliant platforms will not query your royalties at all.

## Royalty Recipient Patterns

### Single Wallet

The simplest setup. A single address receives all royalties. Fine for solo projects, but creates a single point of failure and requires off-chain splits if multiple collaborators are involved.

### Payment Splitter Contract

Deploy a payment splitter contract as the royalty recipient. Royalties flow to the splitter, which distributes to multiple parties at defined percentages. OpenZeppelin's `PaymentSplitter` is a battle-tested option. This keeps the distribution logic on-chain and removes trust assumptions between collaborators.

### Protocol-Controlled Treasury

For DAOs or protocol-owned collections, the royalty recipient is a multisig or governance-controlled treasury. Royalty usage becomes a governance decision.

## Beyond ERC-2981: Enforcement Approaches

Because ERC-2981 cannot enforce payment, projects that need stronger royalty guarantees have explored alternatives:

**ERC-721C** (from Limit Break) adds configurable transfer validators at the contract level. You can restrict transfers to approved operators — effectively blocking marketplaces that do not pay royalties. This gives real enforcement but reduces composability and may confuse users on unsupported platforms.

**Operator filter registries** were popularized by OpenSea. They allow contracts to block known royalty-bypassing operators. The approach is contested and has limitations when operator lists change.

**Soulbound or restricted transfer tokens** are the nuclear option — tokens that cannot be transferred at all except through specific contract functions that enforce payment. This works for specific use cases but eliminates free secondary markets.

## Recommended Approach for Most Projects

1. Implement ERC-2981 via OpenZeppelin. Always.
2. Use a payment splitter as the royalty recipient if multiple parties are involved.
3. Set royalties at a defensible rate — 5% to 10% is the common range. Higher rates create more incentive for marketplaces to bypass.
4. Monitor royalty payments from your contract address after launch. Most block explorers show incoming transactions to your recipient address.
5. Communicate your royalty policy explicitly to your community. Users who value creator economics will prefer compliant marketplaces.

If you need a royalty-aware NFT contract built to production standards, [reach out to the Clixo team](https://clixo.sh/#contact). We implement ERC-2981 with appropriate recipient patterns as part of every NFT engagement.

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
