How to properly use revert reason in web3.js to show meaningful error message in UI
Asked Answered
C

5

7

I want to use web3.js to show revert reason to user, for example in the case of user trying to mint erc721 token that has already been minted. I am using try catch block and see the error message but I want to isolate the error message to show the user a meaningful reason. Thanks in advance.

Campanulate answered 30/3, 2021 at 20:27 Comment(0)
L
6

The previous answer by @Petr Hejda didn't work for me, and neither did his suggestion in response to @Chakshu Jain's problem in the comments.

Instead, I removed some characters—from the start and the end, with slice()—that were causing the error when parsing the JSON, so I could handle the error message and get the error message.

 if (err) {
        
        var errorMessageInJson = JSON.parse(
          err.message.slice(58, err.message.length - 2)
        );

        var errorMessageToShow = errorMessageInJson.data.data[Object.keys(errorMessageInJson.data.data)[0]].reason;

        alert(errorMessageToShow);
        return; 
}
Longo answered 13/11, 2021 at 23:13 Comment(1)
I googled previous posts about this subject, but none of them worked. It always feels weird to do stuff like "slice()..." to only get an error message haha Thanks for the sharingTu
A
3

It's returned in the JS error object as data.<txHash>.reason.


This is a faulty Solidity code

pragma solidity ^0.8.0;

contract Test {
    function foo() public {
        revert('This is error message');
    }
}

So a transaction calling the foo() function should revert with the message This is error message.

try {
    await myContract.methods.foo().send();
} catch (e) {
    const data = e.data;
    const txHash = Object.keys(data)[0]; // TODO improve
    const reason = data[txHash].reason;

    console.log(reason); // prints "This is error message"
}
Assamese answered 31/3, 2021 at 8:52 Comment(4)
It is not working, I am getting "data" is undefined.Sousaphone
@ChakshuJain Did you name the error object e as well? Did you not forget about assigning data = e.data?Assamese
Yes did exactly what you mentioned, attaching an error image link.Sousaphone
Same here. The data itself is a hash, and data[txHash].reason is undefined.Flume
T
3

After trying out every solution on stackoverflow, random blogs, and even the officially documented "web3.eth.handleRevert = true", none is working for me.

I finally figured out after 25 failed attempts:

try {
  await obj.methods.do_something().call({
    gasLimit: String(GAS_LIMIT),
    to: CONTRACT_ADDRESS,
    from: wallet,
    value: String(PRICE),
  })
}
catch (err) {
  const endIndex = err.message.search('{')

  if (endIndex >= 0) {
    throw err.message.substring(0, endIndex)
  }
}

try {
  const res = await obj.methods.do_something().send({
    gasLimit: String(GAS_LIMIT),
    to: CONTRACT_ADDRESS,
    from: wallet,
    value: String(PRICE),
  })
  return res.events.Transfer.returnValues.tokenId
}
catch (err) {
  console.error(err)
  throw err
}

The idea is to use call first. This method doesn't interact with your Metamask, but merely checks if your input arguments go through the contract method. If it can't go through, it will throw exception in the first catch block. If it does go through, we are safe to do use send. This method interacts with your Metamask for real. We have a second catch block in case there are wallet connection or gas fee issues

Teufert answered 16/3, 2022 at 5:9 Comment(1)
Thank you so much :), this really works. I am able to get the error message that I gave to my require() statement. I got the error message in the err object in the catch block. To be precise I used err.message. The error message that I obtained was like => "message": "VM Exception while processing transaction: revert my custom message that i gave in require"Glace
R
1

It is really perplexing why Solidity/Web3 don't have an easy way to extract the require/revert reason from the error object. For me, the "require" reason is there in the message property of the error object, but it is surrounded by lot of other words which I don't need.

An example error message:

[ethjs-query] while formatting outputs from RPC '{"value":{"code":-32603,"data":{"message":"VM Exception while processing transaction: revert Voting is closed","code":-32000,"data":{"0xf901429f12096d3b5c23a80e56fd2230fa37411bb1f8d3cdbd5c8f91c2670771":{"error":"revert","program_counter":43,"return":"0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000165f5f5f566f74696e6720697320636c6f7365645f5f5f00000000000000000000","reason":"Voting is closed"},"stack":"RuntimeError: VM Exception while processing transaction: revert Voting is closed \n    at Function.RuntimeError.fromResults (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/utils/runtimeerror.js:94:13)\n    at BlockchainDouble.processBlock (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/blockchain_double.js:627:24)\n    at runMicrotasks (<anonymous>)\n    at processTicksAndRejections (internal/process/task_queues.js:93:5)","name":"RuntimeError"}}}}'

You can see the reason Voting is closed stuck in between. Not that user-friendly to read. I've seen answers that use regex to extract the error reason.

For those like me, who are not a big fan of the regex way, here is my approach.

  1. In your solidity contract, wrap the require reason with a unique delimiter of sorts. In my case, it is "___" (3 underscores).
    contract MyContract{
       ...
       ...
       function vote(address _addr) public payable{
          require(votingOpen, "___Voting closed___");
          ...
       }
       ...
       ...
    }
  1. Declare a helper function to extract the error using JavaScript string utilities. Here's where your delimiter coes in handy.
export const extractErrorCode = (str) => {
    const delimiter = '___'; //Replace it with the delimiter you used in the Solidity Contract.
    const firstOccurence = str.indexOf(delimiter);
    if(firstOccurence == -1) {
        return "An error occured";
    }

    const secondOccurence = str.indexOf(delimiter, firstOccurence + 1);
    if(secondOccurence == -1) {
        return "An error occured";
    }

    //Okay so far
    return str.substring(firstOccurence + delimiter.length, secondOccurence);
}
  1. Use this function where you catch the error in your frontend
const vote = async (_addr) => {
        setLoading(true);
        try {
            await contest.methods.vote(_addr).send({
                from: accounts[0],
            })
        }
        catch (e) {
            console.log('Voting failed with error object => ', e)
            console.log('Voting failed with the error => ', extractErrorCode(e.message))
        }
        setLoading(false);
    }

Until Solidity & Web3.js (and ether.js) come out with a clean way to parse errors, we are stuck with workarounds like this. I prefer this workaround over others because I am not that great with regex, and additionally, this one does not depend on a fixed starting position to extract the error code.

Rapscallion answered 31/7, 2022 at 7:41 Comment(0)
R
0

Did you try something like this?

error.toString()

It works for me just to show the revert error in the Smart Contract, and return it as a string message.

try {
  //Do something

  } catch (error) {

    res.send({
      'status': false,
      'result': error.toString()
    });
  }
Rocketeer answered 13/8, 2022 at 12:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.