Ethers.js中tx.wait()方法无返回永久挂起问题排查
问题背景
开发批量区块链交易发送程序,预设执行流程:
- 逐笔发送交易
- 等待每笔交易上链确认
- 逐笔校验交易执行状态
当前故障现象:交易可正常发送,返回的交易对象可正常存入数组,代码可执行到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
相关产品推荐
相关产品推荐

