ERC721市场中ERC2981 NFT版税实现咨询及合约纠错
ERC2981版税问题解答及合约修正
一、_setDefaultRoyalty中feeNumerator的含义
feeNumerator既不是NFT价格,也不是直接的百分比数值——它是版税比例的分子,ERC2981默认用10000作为分母(即100% = 10000)。比如你要设置5%的版税,feeNumerator需要传500;10%就传1000,以此类推。
二、你的PodShip合约版税实现问题
代码里有几个核心错误:
- 全局版税覆盖问题:
_setDefaultRoyalty是设置合约内所有NFT的默认版税,每次调用都会覆盖之前的设置。你在listNFT里调用它,会导致所有NFT最终都使用最后一次上架的那个NFT的版税参数,完全不符合每个NFT独立设置版税的需求。 - 版税数值错误:你传入的
nftRoyaltyPercent是1-50的数值,比如传5的话,实际计算出的版税是5/10000=0.05%,和你预期的5%差了100倍。 - 缺少接口声明:ERC721和ERC2981组合时,必须重写
supportsInterface函数,否则NFT市场无法识别你的合约支持ERC2981标准。 - 设置时机错误:版税是和NFT本身绑定的,应该在 mint 时设置,而不是上架时。
三、修正后的关键代码示例
// SPDX-License-Identifier: MIT pragma solidity 0.8.8; import "hardhat/console.sol"; 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 PodShip is ERC721URIStorage, ERC2981, Ownable { using Counters for Counters.Counter; Counters.Counter private _podcastId; Counters.Counter private _nftId; struct Podcast { address nftCreator; address nftOwner; string nftName; string nftDescription; uint256 nftPrice; uint256 nftRoyaltyBasisPoints; // 存储基点值,1%=100基点 uint256 nftId; bool listed; } mapping(uint256 => Podcast) podcastId; constructor() ERC721("PodShip NFTs", "PODSHIP") {} // 必须重写该函数,让合约支持ERC2981接口 function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721URIStorage, ERC2981) returns (bool) { return super.supportsInterface(interfaceId); } function mintNft( string memory ipfsURI, string memory _nftName, string memory _nftDescription, uint256 _nftPrice, uint256 _nftRoyaltyPercent) public returns(uint256) { require(_nftPrice > 0, "NFT Price not set correctly"); require(_nftRoyaltyPercent > 0 && _nftRoyaltyPercent <= 50, "NFT Royalty should be in-between 1 & 50"); // 把百分比转换成基点(1% = 100),匹配ERC2981的分母10000 uint256 royaltyBasisPoints = _nftRoyaltyPercent * 100; _podcastId.increment(); _nftId.increment(); uint256 nft_Id = _nftId.current(); _safeMint(msg.sender, nft_Id); _setTokenURI(nft_Id, ipfsURI); // 为当前NFT单独设置专属版税,不影响其他NFT _setTokenRoyalty(nft_Id, payable(msg.sender), uint96(royaltyBasisPoints)); podcastId[_podcastId.current()] = Podcast( payable(msg.sender), payable(msg.sender), _nftName, _nftDescription, _nftPrice, royaltyBasisPoints, nft_Id, false ); console.log(nft_Id); console.log(_podcastId.current()); return nft_Id; } function listNFT(uint256 _podcastID) public { require(msg.sender == podcastId[_podcastID].nftOwner && msg.sender == podcastId[_podcastID].nftCreator, "Only Owner can list his NFT"); podcastId[_podcastID].listed = true; approve(address(this), podcastId[_podcastID].nftId); setApprovalForAll(address(this), true); // 移除原有的_setDefaultRoyalty调用 } }
四、核心修正说明
- 用
_setTokenRoyalty替代_setDefaultRoyalty:这个函数是为单个NFT设置专属版税,不会干扰其他NFT的版税配置。 - 转换版税为基点:把1-50的百分比转换成100-5000的基点,确保实际版税比例和预期一致。
- 添加
supportsInterface重写:让合约正确暴露ERC2981接口,兼容主流NFT市场的版税读取逻辑。 - 调整版税设置时机:在 mint NFT 时就完成版税绑定,逻辑更合理。
内容的提问来源于stack exchange,提问作者DevABDee
相关产品推荐
相关产品推荐

