You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写仅允许持有指定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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 19:31:35