如何实现Chainlink VRF功能 铸造NFT时向随机NFT持有人发放铸造费分成
核心问题说明
你当前代码的核心矛盾是Chainlink VRF的请求为异步逻辑,从调用getRandomNumber发起请求到fulfillRandomness收到回调结果,中间需要链下节点运算、上链确认的延迟,不可能在同一次mint交易内拿到随机结果直接完成转账,必须拆分逻辑实现。
调整思路
- 把「铸造扣取奖励」和「随机选持有人发奖」拆为两个独立步骤:铸造时仅预留对应比例的奖励在合约中,同时发起VRF请求,等VRF回调返回随机数后再执行奖励发放
- 新增存储结构记录每次VRF请求对应的奖励金额、发起请求时的已发行NFT总量,供回调时直接调用
- 移除mint函数内的同步转账逻辑,所有奖励转账操作全部放到VRF回调函数中执行
具体代码修改
新增存储变量
// 存储每个VRF请求ID对应的待发放奖励金额 mapping(bytes32 => uint256) public requestIdToReward; // 存储每个VRF请求ID发起时的已发行NFT总量(即随机抽选的样本池大小) mapping(bytes32 => uint256) public requestIdToSupply;
修改mint函数
function mint() public payable { uint256 supply = totalSupply(); require(!paused); require(supply + 1 <= maxSupply); require(msg.value >= cost); if (msg.sender != owner()) { require(msg.value >= cost); } _safeMint(msg.sender, supply + 1); // 已有存量NFT时才需要发放奖励 if (supply > 0) { // 计算本次待发放的奖励金额 uint256 reward = ((supply + 1 == 5) || (supply + 1 == 10)) ? address(this).balance * 1 / 100 : msg.value * 10 / 100; // 发起VRF请求 bytes32 requestId = getRandomNumber(); // 关联本次请求对应的奖励和存量NFT数量 requestIdToReward[requestId] = reward; requestIdToSupply[requestId] = supply; } else { // 首次铸造无存量持有人,直接请求VRF供下一次铸造发奖使用 getRandomNumber(); } }
修改fulfillRandomness回调函数
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override { requestIdToRandomNumber[requestId] = randomness; uint256 currentSupply = requestIdToSupply[requestId]; uint256 reward = requestIdToReward[requestId]; // 仅有效奖励请求执行转账 if (reward > 0 && currentSupply > 0) { // 随机数模存量总量得到选中的tokenId,tokenId从1开始所以结果+1 uint256 selectedTokenId = (randomness % currentSupply) + 1; address payable winner = payable(ownerOf(selectedTokenId)); (bool success, ) = winner.call{value: reward}(""); require(success, "Reward transfer failed"); } }
注意事项
- 需提前给合约转入足够的LINK代币支付VRF请求手续费,否则铸造会失败
- 对公平性要求较高的场景可以使用Chainlink官方的随机数扩展方法避免模运算偏差
- 可根据需求新增VRF请求失败的兜底逻辑,比如超时未回调允许手动触发重试
内容的提问来源于stack exchange,提问作者daaavus
相关产品推荐
相关产品推荐

