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

Ethers.js中tx.wait()方法无返回永久挂起问题排查

问题背景

开发批量区块链交易发送程序,预设执行流程:

  1. 逐笔发送交易
  2. 等待每笔交易上链确认
  3. 逐笔校验交易执行状态

当前故障现象:交易可正常发送,返回的交易对象可正常存入数组,代码可执行到wait()方法调用处,但wait()始终无返回:既不抛出错误,也无交易替换提示,不会返回交易成功/失败的确认信息。


故障根因
  • 核心逻辑缺陷:你采用先批量发送所有交易到节点,再逐个调用wait()等待确认的流程,而wait()方法默认无超时机制,只要交易长期处于pending状态(常见原因包括gas价格低于链上打包阈值、nonce冲突导致交易卡在内存池、交易被节点静默丢弃未返回错误),就会无限挂起,不会主动返回结果。
  • 代码隐性bug会放大故障概率:
    • 循环变量i未用let/const声明,会泄露为全局变量,极端场景下会出现索引错乱
    • 前端渲染消息的style属性缺少闭合单引号,会导致成功/失败状态消息样式渲染异常
    • 发送交易时未显式管理nonce和gas参数,多钱包批量发交易时很容易出现交易排队不打包的问题

修复方案

核心调整点

  • 给wait()增加超时兜底逻辑,避免无限等待
  • 发送交易前同步每个签名地址的链上nonce,避免nonce不匹配导致交易卡住
  • 修复全局变量污染、HTML标签语法错误
  • 增加gas参数兜底配置,避免gas估算偏差导致交易长期不打包

修复后完整代码:

let buyTransactions = []

// 声明循环变量,避免全局污染
for (let i = 0; i < buyConfig.numberWallets; i++) {
    const currentSigner = signers[i]
    const currentWallet = wallets[i]
    // 提前同步当前钱包的链上最新nonce,避免nonce冲突
    const latestNonce = await currentSigner.getNonce("latest")
    const connectedContract = mintContract.connect(currentSigner)
    // 合并交易配置,显式传入nonce
    const txConfig = {
        ...settings,
        nonce: latestNonce
        // 可根据对接链的情况补充maxFeePerGas、maxPriorityFeePerGas配置,避免gas过低
    }
    const buyTx = await connectedContract[buyConfig.mintFunction](...buyConfig.inputs, txConfig)
    buyTransactions.push({
        tx: buyTx,
        wallet: currentWallet
    })
    win.webContents.send("console", `<span> Transaction Sent For : ${currentWallet}, txHash: ${buyTx.hash} </span> <br/>`)
}

console.log("All transactions sent:", buyTransactions)
const buyTransactionsWaited = []

for (let i = 0; i < buyTransactions.length; i++) {
    const currentTxEntry = buyTransactions[i]
    console.log(`Start waiting for tx ${i}, hash: ${currentTxEntry.tx.hash}`)
    try {
        // 用Promise.race实现超时控制,120秒未返回直接判定超时,可根据链的出块时间调整阈值
        const confirmedTx = await Promise.race([
            currentTxEntry.tx.wait(1), // 参数1表示等待1个区块确认即可,可按需调整确认数
            new Promise((_, reject) => {
                setTimeout(() => reject(new Error(`Wait timeout, tx ${currentTxEntry.tx.hash} not confirmed in 120s`)), 120000)
            })
        ])
        buyTransactionsWaited.push(confirmedTx)
        console.log(`Tx ${i} confirmed`)
    } catch (err) {
        console.error("Tx confirm failed:", err)
        win.webContents.send("console", `<span style='color: red'> Transaction Failed : ${currentTxEntry.wallet}, error: ${err.message} </span> <br/>`)
    }
}

console.log("All confirmed transactions:", buyTransactionsWaited)

// 修复style属性未闭合的问题
for (let i = 0; i < buyTransactionsWaited.length; i++) {
    const confirmedTx = buyTransactionsWaited[i]
    if (confirmedTx.status == 1) {
        win.webContents.send("console", `<span style='color: green'> Transaction Successful : ${confirmedTx.transactionHash} </span> <br/>`)
    } else {
        win.webContents.send("console", `<span style='color: red'> Transaction Failed : ${confirmedTx.transactionHash} </span> <br/>`)
    }
}

额外排查指引
  • 如果加了超时逻辑后频繁报超时错误,先拿交易hash到对应链的区块浏览器查询:如果浏览器查不到该交易,说明交易广播阶段就被RPC节点丢弃,需要检查RPC节点可用性、交易签名格式是否合法。
  • 如果浏览器查到交易长期处于pending状态,说明gas价格设置过低或者nonce错误,需要发送同nonce、gas价格比原交易高10%以上的替换交易覆盖pending交易。
  • 检查你使用的ethers.js版本:v5版本wait()默认等待5个区块确认,v6版本默认等待1个区块确认,如果对接POA私链、测试网等出块不稳定的链,不要把确认数设置过高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 17:09:17