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

无法捕获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);
}

问题原因

  1. tx.wait()使用了.then()而非await,导致这个异步操作的错误脱离了外层try/catch的捕获范围,成为未处理的Promise异常。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 09:27:54