NFT with Royalties Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
NFT with Royalties Development
Simple
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Development of NFT with Royalties

In 2022-2023, marketplaces started making royalties optional—Blur offered zero fees, part of OpenSea's audience moved there. Collection creators lost millions. This led to two camps: those who enforce royalties on-chain and those who rely on marketplace goodwill. The choice between them is not technical but a product decision. We implement both approaches.

ERC-2981 as the Basic Standard

ERC-2981 is a signaling standard. The contract declares royaltyInfo(tokenId, salePrice), and the marketplace reads and (optionally) pays it. Blur can ignore it. OpenSea respects it. Magic Eden—depends.

Implementation via OpenZeppelin takes 10 lines:

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

contract MyCollection is ERC721, ERC2981 {
    constructor(address royaltyReceiver) ERC721("Collection", "COL") {
        _setDefaultRoyalty(royaltyReceiver, 750); // 7.5%
    }
    
    function supportsInterface(bytes4 interfaceId) 
        public view override(ERC721, ERC2981) returns (bool) {
        return super.supportsInterface(interfaceId);
    }
}

Without the supportsInterface override, the marketplace won't see ERC-2981 support on ERC-165 check.

Operator Filter: On-chain Enforcement

If royalties are commercially important—use operator filter. The idea: the contract checks every transferFrom and safeTransferFrom, allowing transfer only through approved marketplaces that honestly pay royalties.

OpenSea proposed OperatorFilterRegistry in 2022:

import {DefaultOperatorFilterer} from "operator-filter-registry/src/DefaultOperatorFilterer.sol";

contract MyCollection is ERC721, ERC2981, DefaultOperatorFilterer {
    function transferFrom(address from, address to, uint256 tokenId) 
        public override onlyAllowedOperator(from) {
        super.transferFrom(from, to, tokenId);
    }
    
    function safeTransferFrom(address from, address to, uint256 tokenId) 
        public override onlyAllowedOperatorApproval(from) {
        super.safeTransferFrom(from, to, tokenId);
    }
}

onlyAllowedOperator checks the operator's address in OperatorFilterRegistry. Blur was initially blocked, then added after negotiations.

Tradeoff: operator filter protects royalties but limits liquidity—users can't trade on unapproved platforms. For some collections, this is unacceptable.

Custom Royalty Enforcement Logic

Independence from OpenSea's registry—via custom logic. Approach: allow transfer only if initiated through a whitelist of contracts (marketplaces that explicitly integrated our royalty mechanism), or if it's wallet-to-wallet (not via a marketplace).

mapping(address => bool) public approvedMarketplaces;

function _beforeTokenTransfer(address from, address to, uint256 tokenId) 
    internal override {
    // Allow direct transfers (not via marketplace)
    if (from == tx.origin || to == tx.origin) return;
    // Check marketplace is approved
    require(approvedMarketplaces[msg.sender], "Marketplace not approved");
}

Less flexible but independent from external registries.

Splitter for Teams

If royalties are split among multiple addresses, set the receiver in ERC-2981 to PaymentSplitter:

address[] memory payees = [founder, artist, treasury];
uint256[] memory shares = [50, 30, 20];
PaymentSplitter splitter = new PaymentSplitter(payees, shares);
_setDefaultRoyalty(address(splitter), 500); // 5% royalties to splitter

Each recipient calls splitter.release(token) to withdraw accumulated funds. Pull pattern—no reentrancy risk with automatic distribution.

Common Mistakes

Forgotten supportsInterface—the marketplace doesn't see ERC-2981. Royalties to zero address with address(0) receiver—payments go nowhere. Too high royalties (>10%) reduce trading volume. No way to update the receiver—the creator can't change their wallet address.

To make the receiver updatable, add updateDefaultRoyalty() with onlyOwner:

function updateDefaultRoyalty(address receiver, uint96 feeNumerator) 
    external onlyOwner {
    _setDefaultRoyalty(receiver, feeNumerator);
}

Timeline Estimates

NFT contract with ERC-2981 royalties and PaymentSplitter—2-3 days. With operator filter and custom enforcement logic—3-4 days.