如何编写仅允许持有指定NFT时方可铸造特定NFT的合约?
铸造NFT的权限限制实现方案
首先明确:所有核心权限限制必须在智能合约内实现,前端/独立API的检查只能作为用户体验优化,不能替代合约校验——因为懂技术的用户可以直接绕过前端,用钱包直接调用合约方法,跳过外部检查,只有合约层面的校验才是安全的。
下面分场景给你具体的Solidity合约示例(基于OpenZeppelin库,新手友好),以及前端辅助检查的示例:
一、限制仅持有特定NFT/系列NFT的用户铸造
如果你要求用户持有至少一枚「Welcome」系列NFT(不管具体tokenId),可以通过调用该NFT合约的balanceOf方法检查用户持有数量:
// 导入OpenZeppelin的标准合约库 import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; // 你的目标游戏NFT合约 contract GamePlayNFT is ERC721, Ownable { // 存储Welcome NFT的合约地址(部署时传入) address public immutable welcomeNFTContract; constructor(address _welcomeNFTAddr) ERC721("GamePlayNFT", "GPNFT") { welcomeNFTContract = _welcomeNFTAddr; } // 带权限校验的铸造方法 function mint() external { // 实例化Welcome NFT合约,检查调用者持有数量>0 ERC721 welcomeNFT = ERC721(welcomeNFTContract); require(welcomeNFT.balanceOf(msg.sender) > 0, "必须持有Welcome NFT才能铸造"); // 执行铸造逻辑(这里用总供应量+1作为新tokenId,你可以根据需求调整) uint256 newTokenId = totalSupply() + 1; _safeMint(msg.sender, newTokenId); } }
如果要支持多个允许的NFT系列,只需要循环检查多个合约地址即可:
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract GamePlayNFT is ERC721, Ownable { address[] public allowedNFTContracts; constructor(address[] memory _allowedNFTAddrs) ERC721("GamePlayNFT", "GPNFT") { allowedNFTContracts = _allowedNFTAddrs; } function mint() external { bool hasAllowedNFT = false; // 遍历所有允许的NFT合约,检查用户是否持有其中任意一种 for (uint256 i = 0; i < allowedNFTContracts.length; i++) { ERC721 nft = ERC721(allowedNFTContracts[i]); if (nft.balanceOf(msg.sender) > 0) { hasAllowedNFT = true; break; } } require(hasAllowedNFT, "必须持有指定系列NFT才能铸造"); uint256 newTokenId = totalSupply() + 1; _safeMint(msg.sender, newTokenId); } }
二、限制仅指定钱包地址铸造(白名单机制)
如果要给特定地址开放铸造权限,可以用映射存储白名单地址,只有所有者能添加:
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract GamePlayNFT is ERC721, Ownable { // 白名单地址映射:地址 => 是否在白名单内 mapping(address => bool) public whitelistedAddresses; constructor() ERC721("GamePlayNFT", "GPNFT") {} // 所有者专属方法:添加地址到白名单 function addToWhitelist(address _addr) external onlyOwner { whitelistedAddresses[_addr] = true; } function mint() external { require(whitelistedAddresses[msg.sender], "地址不在白名单内,无法铸造"); uint256 newTokenId = totalSupply() + 1; _safeMint(msg.sender, newTokenId); } }
三、前端辅助检查(优化用户体验)
虽然合约校验是必须的,但你可以在前端先做预检查,避免用户浪费gas费:
// 假设你用ethers.js连接钱包和合约 const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); // 实例化Welcome NFT合约和目标GameNFT合约 const welcomeNFTContract = new ethers.Contract(welcomeNFTAddr, welcomeNFTAbi, signer); const gameNFTContract = new ethers.Contract(gameNFTAddr, gameNFTAbi, signer); // 检查用户是否持有Welcome NFT的函数 async function hasWelcomeNFT() { const userAddr = await signer.getAddress(); const balance = await welcomeNFTContract.balanceOf(userAddr); return balance > 0; } // 铸造按钮点击事件 document.getElementById('mintButton').addEventListener('click', async () => { try { const eligible = await hasWelcomeNFT(); if (!eligible) { alert("请先获取Welcome代币再尝试铸造!"); return; } // 调用合约铸造方法 const tx = await gameNFTContract.mint(); await tx.wait(); alert("铸造成功!"); } catch (err) { console.error(err); alert("铸造失败:" + err.message); } });
内容的提问来源于stack exchange,提问作者christian Muller
相关产品推荐
相关产品推荐

