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

React前端调用Chainlink VRF获取随机数返回undefined问题

问题根因
  • Chainlink VRF采用异步两阶段交互模型,不存在同步返回随机数的可能:调用getRandomNumber()仅完成第一阶段——向Chainlink预言机网络提交随机数申请,交易上链后预言机节点需要1~2个区块的时间生成随机数并提交零知识证明,之后才会调用合约的fulfillRandomness回调函数把最终随机数写入合约存储。原有代码在发起请求后立刻读取结果,此时回调尚未执行,randomResult还未被赋值,自然返回undefined。
  • 前端读取合约状态的写法错误:Solidity中public修饰的状态变量会自动生成同名的view类型getter函数,在ethers.js等前端库中必须以函数调用形式(加括号)才能拉取链上值,原有代码写await gameContract.randomResult仅访问了合约实例的静态属性,不会触发链上查询,本身就拿不到正确值。
  • 合约手续费配置存在逻辑漏洞:VRF服务手续费以LINK代币支付,不从合约原生币余额扣除,原有合约仅实现了原生币充值函数contractFund,若部署后未向合约地址转入至少0.1个对应网络的LINK代币,随机数请求会直接失败,永远不会触发结果回调。
修复方案

1. 合约端调整

新增事件方便前端监听回调结果,避免低效轮询,补充请求ID和结果的映射,避免多请求并发时结果互相覆盖:

pragma solidity ^0.8.0;

import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";

contract game is VRFConsumerBase{
    // 存储每个请求ID对应的随机数结果,避免并发请求覆盖值
    mapping(bytes32 => uint256) public requestIdToResult;
    bytes32 internal keyHash;
    uint internal fee;

    // 新增随机数就绪事件,供前端监听
    event RandomNumberReady(bytes32 indexed requestId, uint256 diceResult);

    constructor() VRFConsumerBase(
        0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B, // Goerli测试网VRF协调器地址
        0x01BE23585060835E02B77ef475b0Cc51aA1e0709  // Goerli测试网LINK代币地址
    ) {
        keyHash=0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311;
        fee=0.1 *10**18;
    }

    function getRandomNumber() public returns(bytes32 requestId){
        requestId = requestRandomness(keyHash,fee);
        return requestId;
    }

    function fulfillRandomness(bytes32 requestId,uint randomness) internal override{
        uint256 diceResult = randomness%7;
        requestIdToResult[requestId] = diceResult;
        emit RandomNumberReady(requestId, diceResult);
    }

    function contractBalance() public view returns(uint){
        return address(this).balance;
    }

    // 内置receive函数即可接收原生币,无需额外写空的充值函数
    receive() external payable {}
}

合约部署完成后,必须向合约地址转入至少0.1个对应网络的LINK代币,否则VRF请求会被节点拒绝。

2. 前端逻辑修正

移除发起请求后立刻读值的逻辑,改为等待交易上链后监听事件获取结果,修正合约状态调用写法:

async BelowSeven(amount){
  const gameContract = this.state.gameContract;
  // 发起随机数请求,等待交易上链
  const tx = await gameContract.getRandomNumber();
  window.alert("Rolling Dice, waiting for VRF response...");
  const txReceipt = await tx.wait();
  // 从交易日志里拿到本次请求的requestId
  const requestId = txReceipt.events[0].args.requestId;

  // 监听对应请求ID的随机数就绪事件
  const firstDiceNumber = await new Promise((resolve, reject) => {
    // 设置60秒超时,避免异常情况无限等待
    const timer = setTimeout(() => reject(new Error("VRF request timeout")), 60000);
    
    gameContract.on("RandomNumberReady", (emitRequestId, result) => {
      // 只处理本次发起的请求对应的结果
      if(emitRequestId === requestId) {
        clearTimeout(timer);
        gameContract.removeAllListeners("RandomNumberReady");
        // 将BigNumber类型转为普通JS数字
        resolve(result.toNumber());
      }
    });
  });

  console.log("First dice result:", firstDiceNumber);
  window.alert(`Roll completed! Dice number: ${firstDiceNumber}`);

  // 若需要掷第二个骰子,等第一个结果返回后再发起第二次请求即可
}

注意:编译合约时请选择0.8.x版本的Solidity编译器,和导入的Chainlink合约版本匹配,避免编译报错。

内容的提问来源于stack exchange,提问作者Azzor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 20:54:18