如何实现固定预设数量的不同稀有度NFT随机Mint功能?
问题原因
- 未动态维护各稀有度剩余可铸造库存:原有代码使用初始固定总量做匹配,每次mint后没有扣减对应稀有度的可铸造数量,最终必然出现实际铸造量和预设值不符的问题
- 随机数匹配逻辑错误:原有判断条件
_tokenMetadata[i].amount <= randomNumber逻辑反向,无法正确按权重匹配稀有度
修复方案
第一步:调整元数据结构体
给TokenMetadata结构体增加剩余可铸造数量字段,初始化时将该字段值设为对应稀有度的预设总数量:
struct TokenMetadata { string uri; uint256 totalAmount; // 预设总铸造量,对应super=200/rare=300/common=500 uint256 remaining; // 剩余可铸造量,初始化时等于totalAmount }
第二步:修改Mint逻辑
改为按剩余库存动态计算权重区间,匹配到对应稀有度后实时扣减库存:
function safeMint(address to) public onlyOwner { uint256 currentTokenId = _tokenIdCounter.current(); require(currentTokenId < totalSupply(), "There's no token to mint."); require(mintCnt[msg.sender] < maxMintCntPerAddress, "One address can mint 1 tickets."); if(mintPrice > 0) { require(mintPrice == msg.value, "Mint price is not correct."); payable(serviceAddress).transfer(mintPrice); } // 随机数范围为当前所有剩余可铸造NFT的总数量 uint256 remainingTotal = totalSupply() - currentTokenId; uint256 randomNumber = random(remainingTotal); uint256 cumulativeWeight = 0; for (uint256 i = 0; i < _tokenMetadata.length; i++) { if (_tokenMetadata[i].remaining == 0) continue; // 跳过已售罄的稀有度 cumulativeWeight += _tokenMetadata[i].remaining; // 随机数落在当前稀有度的权重区间 if (randomNumber < cumulativeWeight) { _safeMint(to, currentTokenId); _setTokenURI(currentTokenId, _tokenMetadata[i].uri); _tokenMetadata[i].remaining -= 1; // 扣减对应稀有度剩余库存 _tokenIdCounter.increment(); break; } } } function random(uint maxValue) internal view returns (uint) { return uint(keccak256(abi.encodePacked(block.timestamp, msg.sender, _tokenIdCounter.current()))) % maxValue; }
额外说明
- 上述代码实现的效果是:每次mint时抽到各稀有度的概率等于该稀有度剩余数量占总剩余数量的比例,全部mint完成后各稀有度的铸造量和预设值完全一致
- 示例中保留了原有的随机数生成逻辑,链上原生随机数存在被操纵的风险,若NFT资产价值较高,建议替换为安全的可验证随机数方案
- 若要开放用户自主mint,删除
safeMint函数的onlyOwner修饰符即可
内容的提问来源于stack exchange,提问作者Marcus Ataide
相关产品推荐
相关产品推荐

