'How to properly use revert reason in web3.js to show meaningful error message in UI

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.



Solution 1:[1]

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; 
}

Solution 2:[2]

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"
}

Solution 3:[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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jeremy Caney
Solution 2 Petr Hejda
Solution 3 Ruofeng