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

如何通过balanceOf()实现Solidity合约NFT铸造限额

NFT限量铸造限额失效问题修复方案

你的铸造限额失效问题分合约层核心bug和前端逻辑瑕疵两部分,按以下步骤修复即可正常运行:

1. 合约层修复(根本原因)

你最初的合约代码存在致命逻辑遗漏:mint函数里虽然做了walletMints的限额校验,但从来没有在铸造成功前更新walletMints映射的计数,导致每次调用mint时读到的用户已铸造数量永远是初始值0,自然无法拦截重复铸造。

❌ 错误思路提示:不要用balanceOf做限额判断,用户把铸造得到的NFT转出后,balanceOf数值会下降,攻击者可以通过转走NFT反复铸造突破限额。必须用独立的walletMints映射记录地址累计铸造量,这个值只增不减,才是安全的。

修正后的完整mint函数代码:

function mint(uint256 quantity_) public payable{
    require(isPublicMintEnabled, 'minting not enabled');
    require(msg.value == quantity_ * mintPrice, 'wrong mint value');
    require(totalSupply + quantity_ <= maxSupply, 'sold out');
    require(walletMints[msg.sender] + quantity_ <= maxPerWallet, 'exceed max wallet');

    // 新增:校验通过后立即更新用户铸造计数,必须在_safeMint前执行
    walletMints[msg.sender] += quantity_;

    for(uint256 i = 0; i < quantity_; i++){
        uint256 newTokenId = totalSupply + 1;
        totalSupply++;
        _safeMint(msg.sender, newTokenId);
    }
}

2. 前端逻辑修复

你调整后的前端代码存在三个问题:

  • 合约返回的计数是BigNumber类型,直接和普通数字比较会出现类型判断错误
  • 只判断了已铸造数量小于上限,没有把本次要铸造的数量计入校验,会出现用户已铸1个、本次铸2个仍然超过限额的漏洞
  • 没有提前获取签名者地址,直接取signer.address在部分ethers版本下会返回未解析的promise对象

修正后的铸造逻辑代码:

const MainMint = ({ accounts}) =>{
    const [mintAmount] = useState(1);
    // 和合约maxPerWallet保持一致的单地址铸造上限
    const MAX_PER_WALLET = 2;
    const isConnected = Boolean(accounts[0]);

    async function handleMint(){
        if(!window.ethereum || !isConnected) return;
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        const signer = provider.getSigner();
        // 显式获取用户地址,避免版本兼容问题
        const userAddress = await signer.getAddress();
        const contract = new ethers.Contract(
            NFTAddress,
            NFT.abi,
            signer
        );
        
        try{
            // 读取已铸造数量,转换为JS普通数字再做比较
            const mintedRes = await contract.walletMints(userAddress);
            const currentMinted = mintedRes.toNumber();

            if(currentMinted + mintAmount > MAX_PER_WALLET){
                console.log(`单地址最多铸造${MAX_PER_WALLET}个,你已铸造${currentMinted}个`);
                return;
            }

            // 0成本测试网铸造保留0即可,主网部署替换为实际单NFT价格
            const payValue = ethers.utils.parseEther((0 * mintAmount).toString());
            const tx = await contract.mint(BigNumber.from(mintAmount), {
                value: payValue
            });
            console.log('铸造交易已发送,交易哈希:', tx.hash);
            // 等待交易上链后再更新前端状态
            await tx.wait();
            console.log('铸造成功');
        }
        catch(err){
            console.log("铸造失败: ", err);
        }
    }

    // 其余组件渲染逻辑保持原有实现即可
}

关键提示

  • 前端的限额校验仅用于优化用户体验,所有安全规则必须在合约层实现,前端代码可以被用户随意篡改、绕过,只靠前端拦截没有任何安全保障
  • 建议给合约的mint函数加上重入保护修饰器,避免潜在的安全风险
  • 部署合约前在本地测试网多做边界场景测试:比如铸造到上限后再尝试铸造、转走NFT后再尝试铸造,确认规则符合预期

内容的提问来源于stack exchange,提问作者Ken Del Pen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 00:24:26