无法捕获MetaMask交易异常问题求助
问题:ethers.js调用合约交易失败时无法捕获错误(非MetaMask拒绝场景)
我使用ethers.Contract结合Web3Provider对接MetaMask调用智能合约,想要在交易失败时捕获错误并重试/执行其他逻辑,但目前只有MetaMask用户主动拒绝交易时能正常捕获错误,交易本身执行失败(比如合约逻辑报错)时,无法触发外层try/catch,只会出现未捕获的Promise异常。
预期触发catch块输出Error while register...,实际得到未捕获异常:
Uncaught (in promise) Error: transaction failed [ CALL_EXCEPTION ] (transactionHash="0x829228b6c66278019247c93885b703958c0d7187b4c3307fe1a4cd03c5c495e1", transaction={"hash":"0x829228b6c66278019247c93885b703958c0d7187b4c3307fe1a4cd03c5c495e1","type":2,...})
现有代码
import { ethers } from "ethers"; import { SignedChannel } from "../api/contractTypes/channel"; import { Web3Provider } from "../client/Provider"; export async function register( params: SignedChannel, adj: ethers.Contract, provider: ethers.providers.WebSocketProvider | Web3Provider ) { const registerParams = [params.serialize(), []]; console.log("Calling register"); const gasPrice = await provider.getGasPrice(); const tx = await adj.functions["register"](...registerParams, { from: params.params.participants[0], gasPrice: gasPrice.mul(2), gasLimit: 30000000, }); console.log("Result:", tx); tx.wait().then(async (receipt: any) => { console.log("Transaction confirmed in block:", receipt.blockNumber); const updatedReceipt = await provider.getTransactionReceipt(tx.hash); console.log(updatedReceipt); }); return tx.hash; } try { //register channel await register(signedState, adj!, this.provider); this.listenToChannelUpdate(adj!); } catch (err) { console.log("Error while register:", err); process.status = "failedRegister"; process.error = new Error( ("Error while register: Invalid Signed State: " + err) as string ); this.state.emitUpdateProcess(process.id); }
问题原因
tx.wait()使用了.then()而非await,导致这个异步操作的错误脱离了外层try/catch的捕获范围,成为未处理的Promise异常。adj.functions["register"]()仅会在交易发送阶段(比如用户拒绝、网络失败)抛出错误,而交易上链后执行失败(比如合约revert)的错误,只会在tx.wait()阶段抛出。
解决方案
1. 修改register函数,统一异步错误捕获
将tx.wait()改为await,让交易确认阶段的错误进入函数的Promise链,被外层try/catch捕获:
export async function register( params: SignedChannel, adj: ethers.Contract, provider: ethers.providers.WebSocketProvider | Web3Provider ) { const registerParams = [params.serialize(), []]; console.log("Calling register"); const gasPrice = await provider.getGasPrice(); // 发送交易,捕获用户拒绝/发送失败类错误 const tx = await adj.functions["register"](...registerParams, { from: params.params.participants[0], gasPrice: gasPrice.mul(2), gasLimit: 30000000, }); console.log("Transaction sent, hash:", tx.hash); // 等待交易确认,捕获合约执行失败类错误 const receipt = await tx.wait(); console.log("Transaction confirmed in block:", receipt.blockNumber); const updatedReceipt = await provider.getTransactionReceipt(tx.hash); console.log(updatedReceipt); return tx.hash; }
2. 优化外层错误处理逻辑
在catch块中区分不同错误类型,做针对性处理:
try { await register(signedState, adj!, this.provider); this.listenToChannelUpdate(adj!); } catch (err) { console.log("Error while register:", err); process.status = "failedRegister"; // 区分错误类型 if (err instanceof Error) { if (err.message.includes("user rejected transaction")) { process.error = new Error("用户拒绝了交易"); } else if (err.message.includes("CALL_EXCEPTION")) { process.error = new Error("交易执行失败:合约逻辑错误或参数无效"); } else { process.error = new Error(`注册失败: ${err.message}`); } } else { process.error = new Error("注册失败: 未知错误"); } this.state.emitUpdateProcess(process.id); }
关键说明
- 必须用
await tx.wait()替代.then(),确保交易全流程的错误都能被外层捕获。 - 交易发送阶段和确认阶段的错误来源不同,需要分别处理:发送阶段对应用户操作/网络问题,确认阶段对应合约执行逻辑问题。
内容的提问来源于stack exchange,提问作者character706
相关产品推荐
相关产品推荐

