0

In many posts, I've noticed discussions about handling this using the Ethers library. However, my implementation is based on Web3.js, and I'm facing a challenge.

My contract is set to revert a transaction when a particular condition isn't met. When I proceed to sign and dispatch the transaction, I can clearly identify that a revert takes place. However, I'm unable to view the associated error. The reason I need to see this error is that it will provide insights into certain values that were passed to the contract during the revert.

Based on what I've learned, the error details should be present in the logs array within the transaction receipt found in my error object. Surprisingly, this logs array is coming back empty for me.

I'd be grateful for any insights or suggestions on how to address this. Thank you!

Smart Contract

pragma solidity ^0.8.4
...
error NotOwner(address expected, address received);

function example(address _address) {
   if (_address == address(0)) {
      revert NotOwner({
         expected: _address,
         received: address(0)
      });
   }
   _performOtherAction()
}

Web3.js client

var signedTxn = await this.rpcProvider.eth.accounts.signTransaction(unsignedTxn, privateKey)
try {
   var txnResponse = await this.rpcProvider.eth.sendSignedTransaction(signedTxn.rawTransaction)
}
catch (error) {
   console.log(error)
}

2 Answers 2

1

Emit the error as an event in your Solidity contract:

Modify your contract like this:

pragma solidity ^0.8.4
...
event NotOwnerError(address expected, address received);

function example(address _address) {
    if (_address == address(0)) {
        emit NotOwnerError(_address, address(0));
        revert("NotOwner: Address cannot be zero");
    }
    _performOtherAction();
}

In your Web3.js client, listen for this event to capture error details:

try {
    var txnResponse = await this.rpcProvider.eth.sendSignedTransaction(signedTxn.rawTransaction);
    // Check if the transaction was successful
    if (txnResponse.status === "0x0") {
        // Transaction failed, let's listen for the event
        const receipt = await this.rpcProvider.eth.getTransactionReceipt(txnResponse.transactionHash);
        const errorLogs = receipt.logs.filter(log => log.topics[0] === web3.utils.sha3("NotOwnerError(address,address)"));
        if (errorLogs.length > 0) {
            const decodedData = web3.eth.abi.decodeLog(
                [{ type: "address", name: "expected" }, { type: "address", name: "received" }],
                errorLogs[0].data,
                errorLogs[0].topics.slice(1)
            );
            console.log("Error Details: ", decodedData);
        } else {
            console.log("Unknown error occurred.");
        }
    }
} catch (error) {
    console.log(error);
}

This code listens for the NotOwnerError event in the transaction receipt logs and decodes the error details if the transaction fails.

0

Refer to this article https://blog.emn178.cc/en/post/using-web3-js-to-parse-custom-error/

Define some methods

import { Eip838ExecutionError } from 'web3';
import { decodeContractErrorData } from 'web3-eth-abi';

function parseInnerError(e, contract) {
  if (e?.innerError?.errorSignature) {
    return e.innerError;
  }

  if (!e.innerError) {
    if (e?.data?.data.startsWith('0x')) {
      e.innerError = new Eip838ExecutionError(e.data);
    }
  }
  if (e?.innerError?.data?.startsWith('0x')) {
    decodeContractErrorData(contract._errorsInterface, e.innerError);
  }
  return e.innerError;
}

const KEYS = ['to', 'from', 'nonce', 'gas', 'gasPrice', 'maxPriorityFeePerGas', 'maxFeePerGas', 'data', 'value', 'chainId', 'type', 'accessList'];
async function parseTxError(txHash, web3, contract) {
  const tx = await web3.eth.getTransaction(txHash);
  const request = { };
  KEYS.forEach((key) => {
    request[key] = tx[key];
  });
  if (request.gasPrice) {
    delete request.maxPriorityFeePerGas;
    delete request.maxFeePerGas;
  }
  try {
    await web3.eth.call(request, tx.blockNumber);
    return null;
  } catch (e) {
    return parseInnerError(e, contract);
  }
}

And in your case, you can try

var signedTxn = await this.rpcProvider.eth.accounts.signTransaction(unsignedTxn, privateKey)
try {
   var txnResponse = await this.rpcProvider.eth.sendSignedTransaction(signedTxn.rawTransaction)
}
catch (error) {
  if (error.receipt && !error.receipt.status) {
    // assign your contract instance
    const innerError = await parseTxError(error.receipt.transactionHash, this.rpcProvider, contract);

    // check innerError...
  }
}

RPC does not seem to provide a way to get the reason of a transaction error. I can use this complicated method to get error, but it seem not working if the block is too old.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.