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

如何在React前端用ethers.js提取Solidity payable交易的返回结果

问题原因

链上状态修改类交易(payable 类型属于写操作)在 ethers.js 中通过签名发送时,默认返回的是交易响应对象,不会直接返回函数执行结果,因为交易需要等待区块确认才会真正执行。Remix 能直接看到返回值是因为它内部做了预执行模拟和交易输出的额外解析处理。

解决方法

方法1:通过合约事件获取结果(生产环境推荐)

这是最稳定的方案,不会出现模拟执行和真实上链结果不一致的问题。

步骤1:修改Solidity合约,添加事件定义和触发逻辑

首先在合约中定义事件,然后在函数返回前触发事件:

// 合约顶部定义事件
event TicketCreated(uint matchedNumbers, uint[10] roll, uint winnings, address player);

function createTicket() external payable returns (uint _matchednumbers, uint[10] memory _generatednumbers, uint _winnings) {
    // 原有逻辑保持不变
    generateNumbers();
    uint[10] memory roll = getGenerateNumbers();
    uint matchednumbers;
    uint winnings;

    for(uint i = 0; i < 10; i++) {
        for (uint j = 0; j < 10; j++) {
            if (arrays[msg.sender][i] == roll[j]) {
                matchednumbers +=1;
            }
        }
        if (matchednumbers == 5) {
            winnings = msg.value * 5;
        }
        if (matchednumbers == 6) {
            winnings = msg.value * 24;
        }
        if (matchednumbers == 7) {
            winnings  = msg.value * 142;
        }
        if (matchednumbers == 8) {
            winnings  = msg.value * 1000;
        }
        if (matchednumbers == 9) {
            winnings  = msg.value * 4500;
        }
        if (matchednumbers == 10) {
            winnings  = msg.value * 10000;
        }            
    }
    // 新增:触发事件
    emit TicketCreated(matchednumbers, roll, winnings, msg.sender);
    return  (matchednumbers, roll, winnings);
}

步骤2:修改React前端代码,从交易回执中解析事件参数

注意原有代码中调用createTicket没有传递value参数,payable函数必须传递交易金额,否则会执行失败。

async function roll() {
  if (typeof window.ethereum !== "undefined") {
    await requestAccount();
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();
    const contract = new ethers.Contract(keno3Address, Keno3.abi, signer);
    try {
      // 替换为实际投注金额
      const betAmount = ethers.utils.parseEther("0.01");
      const tx = await contract.createTicket({ value: betAmount });
      const receipt = await tx.wait();
      // 解析事件参数
      const event = receipt.events.find(item => item.event === "TicketCreated");
      const [matchedNumbers, roll, winnings] = event.args;
      
      // 转换格式后可直接用于页面渲染
      const renderData = {
        matchedNumbers: matchedNumbers.toNumber(),
        roll: roll.map(num => num.toNumber()),
        winnings: ethers.utils.formatEther(winnings) // 转成ETH单位,要wei单位直接调用toNumber()即可
      };
      console.log("交易结果:", renderData);
      return renderData;
    } catch (err) {
      console.log("Error: ", err);
    }
  }
}

方法2:通过callStatic预执行获取结果(无需修改合约)

如果不想改动合约代码,可以先通过callStatic模拟执行函数拿到返回值,再发送真实交易。注意:模拟执行的结果可能和真实上链结果存在差异,仅适合测试场景使用

async function roll() {
  if (typeof window.ethereum !== "undefined") {
    await requestAccount();
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();
    const contract = new ethers.Contract(keno3Address, Keno3.abi, signer);
    try {
      const betAmount = ethers.utils.parseEther("0.01");
      // 先模拟执行拿结果
      const [matchedNumbers, roll, winnings] = await contract.callStatic.createTicket({ value: betAmount });
      // 再发送真实交易上链
      const tx = await contract.createTicket({ value: betAmount });
      await tx.wait();

      // 直接使用预执行拿到的结果渲染
      const renderData = {
        matchedNumbers: matchedNumbers.toNumber(),
        roll: roll.map(num => num.toNumber()),
        winnings: ethers.utils.formatEther(winnings)
      };
      console.log("交易结果:", renderData);
      return renderData;
    } catch (err) {
      console.log("Error: ", err);
    }
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 13:00:02