How to Properly Integrate ERC2981 Royalty Standard into ERC721 Token Contract

I’m struggling to understand how to correctly implement royalty payments using the ERC2981 standard in my NFT marketplace contract. I’ve looked through documentation but I’m still confused about several key concepts.

Specifically, I need help understanding what the feeNumerator parameter represents in the _setDefaultRoyalty(address recipient, uint96 feeNumerator) function. Does this value represent the actual NFT price or is it the royalty percentage?

Here’s my current smart contract implementation. Can someone review it and tell me if I’m handling the royalty setup correctly?

// SPDX-License-Identifier: MIT
pragma solidity 0.8.8;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract AudioWave is ERC721URIStorage, ERC2981, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenCounter;
    Counters.Counter private _itemCounter;

    struct AudioNFT {
        address creator;
        address currentOwner;
        string tokenName;
        string tokenDesc;
        uint256 tokenValue;
        uint256 royaltyRate;
        uint256 tokenId;
        bool isListed;
    }

    mapping(uint256 => AudioNFT) public audioItems;

    constructor() ERC721("AudioWave Tokens", "AUDIO") {}

    function createToken(
        string memory metadataURI,
        string memory _tokenName, 
        string memory _tokenDesc, 
        uint256 _tokenValue, 
        uint256 _royaltyRate) public returns(uint256) {
            require(_tokenValue > 0, "Token value must be greater than zero");
            require(_royaltyRate > 0 && _royaltyRate <= 25, "Royalty rate must be between 1-25 percent");

            _itemCounter.increment();
            _tokenCounter.increment();
            uint256 newTokenId = _tokenCounter.current();

            _safeMint(msg.sender, newTokenId);
            _setTokenURI(newTokenId, metadataURI);

            audioItems[_itemCounter.current()] = AudioNFT(
                payable(msg.sender), 
                payable(msg.sender), 
                _tokenName, 
                _tokenDesc, 
                _tokenValue, 
                _royaltyRate,
                newTokenId,
                false
            );

            return newTokenId;
    }

    function makeAvailable(uint256 _itemId) public {
        require(msg.sender == audioItems[_itemId].currentOwner && msg.sender == audioItems[_itemId].creator, "Only token owner can list for sale");
        audioItems[_itemId].isListed = true;
        approve(address(this), audioItems[_itemId].tokenId);
        setApprovalForAll(address(this), true);
        _setDefaultRoyalty(audioItems[_itemId].creator, uint96(audioItems[_itemId].royaltyRate));
    }
}

Any guidance on whether this implementation is correct would be really helpful. Thanks for your time!

Found a critical bug in your royalty calculation. The feeNumerator in _setDefaultRoyalty() expects basis points (out of 10,000). For 5% royalty, you pass 500, not 5. Your makeAvailable function passes _royaltyRate directly as feeNumerator. Since your validation allows 1-25 percent, you need to multiply by 100. For 10% royalty, call _setDefaultRoyalty(audioItems[_itemId].creator, uint96(audioItems[_itemId].royaltyRate * 100)). Move the royalty setup to createToken instead of makeAvailable. Set royalties when minting the NFT, not when listing for sale. This keeps things consistent and ensures royalty data exists even if the token isn’t listed right away.

your code has a big problem - you’re setting royalties in makeAvailable() when it should happen once during minting. also, feeNumerator uses basis points, so for 10% royalty you need 1000, not 10. move _setDefaultRoyalty to your createToken function and multiply royaltyRate by 100.

Hey Hugo! I’ve been messing with ERC2981 lately and spotted something in your code that might clear things up.

That feeNumerator parameter? It’s royalty percentage in basis points (out of 10,000). Want 5% royalty? Pass 500, not 5. This might be your issue.

Your contract stores _royaltyRate as a percentage (1-25), but you’re passing it straight to _setDefaultRoyalty() as uint96(audioItems[_itemId].royaltyRate). Someone sets 10% royalty rate? You’re passing 10 instead of 1000, giving them 0.1% royalties instead!

Why call _setDefaultRoyalty() inside makeAvailable()? Wouldn’t setting royalty info during createToken() make more sense? What happens when the same creator lists multiple tokens - doesn’t each _setDefaultRoyalty() call overwrite the previous default?

Ever consider _setTokenRoyalty() for individual token royalties? Curious about your use case and what led you this direction.