How to handle errors when sending a transaction using Ethers JS

In this tutorial, we are going to learn how to handle errors that might happen when you send a transaction using Ethers JS and JavaScript.

In this tutorial, we are going to learn how to handle errors that might happen when you send a transaction or call a smart contract function using Ethers JS and JavaScript.

When sending a transaction, it can fail for many reasons and you might not want to handle all the error cases the same way.

Here is an example of how to catch errors when sending a transaction:

const ethers = require("ethers")

const provider = new ethers.providers.Web3Provider(YOUR_PROVIDER_HERE)

const signer = provider.getSigner()


// Send Ethereum transaction

signer.sendTransaction({
    to: "0x5F70Ddd9908B04f952b9cB2A6F8E4D451725ceDC",
    value: ethers.utils.parseEther("0.1")
})
.then((transaction) => {
    transaction.wait()
    .then((receipt) => {
        console.log("the transaction was successful")
    })
    .catch((error) => {
        // handle errors here
        if (error.code === "INSUFFICIENT_FUNDS") {
            console.log("not enough funds to pay for gas fees")
        }
        
        if (error.code === "NETWORK_ERROR") {
            console.log("could not validate transaction, check that you're on the right network")
        }
        
        if (error.code === "TRANSACTION_REPLACED") {
            console.log("the transaction was replaced by another transaction with the same nonce")
        }
    })
})
.catch((error) => {
    // at this point, it's the sendTransaction that failed, not the transaction itself
})


// Calling a smart contract "write" function

const myNft = new ethers.Contract(address, ABI, provider)
myNft.mint(1, { value: ethers.utils.parseEther("0.1") })
.then((transaction) => {
    console.log("The transaction was successful")
    // you can wait for more confirmations if you want or get the receipt here
})
.catch((err) => {

    // need to simulate the transaction here to access a complete error object because err doesn't contain information, just an error message
    
    myNft.callStatic.mint(1, { value: ethers.utils.parseEther("0.1") })
    .catch((error) => {
        // handle errors exactly like above
    })
})

As you can see, it's pretty straightforward when sending a transaction using sendTransaction but not when calling a smart contract function.

When calling sendTransaction, the error object in the catch block will have all the information you need (see below for more information) but when you call a smart contract function that creates a transaction, if that transaction fails, the error inside the catch block will just be a stack trace.

To get information about the error, you need to simulate the transaction using the callStatic property. This will run the transaction locally but not affect the blockchain and it doesn't cost any gas, it just simulates the transaction.

That simulation will return the same error object as sendTransaction and that error object in sendTransaction has the following properties:

  • code: The error code that you can use in your code to know why the transaction failed (more about the error codes below)
  • reason: The reason why it failed (available for error codes CALL_EXCEPTION and TRANSACTION_REPLACED
  • data: A hexadecimal string containing information about the error
  • transaction: An object containing information about the transaction that was sent

Now, depending on the error code, the error object will be slightly different. Here are all the possible error codes and what they mean:

  • CALL_EXCEPTION : there was an error with the transaction on the blockchain. If you called a smart contract function, that means the smart contract reverted the transaction.

    In that case, the error object has 3 more properties:
     • reason: contains the message sent by the smart contract when it reverted.
     •  method: the signature of the function that was called (name, parameters   and return value).
     •  args: an array that contains the arguments you sent to the smart contract   function.
  • INSUFFICIENT_FUNDS: the wallet that sends this transaction doesn't have enough funds to pay for the gas fees
  • NETWORK_ERROR: There was a network error and the transaction couldn't be validated. A common reason why it happens is because the chain ID is wrong (the transaction was sent on the wrong network)
  • NONCE_EXPIRED: The nonce of the transaction was already used in a previous transaction that is completed. You need to pass a higher nonce to the transaction to make it work.
  • REPLACEMENT_UNDERPRICED: That means you attempted to replace a pending transaction with this transaction by passing the same nonce but you didn't pass enough gas for this transaction to work. Try again and pass more gas to the transaction.
  • TRANSACTION_REPLACED: This transaction has been replaced (in other words, cancelled) by another transaction you sent with the same nonce and more gas.

    In that case, the error object will have the following extra properties:
     •  hash: the hash of the transaction that was replaced
     •  reason: the reason why another transaction replaced this one. One of   "repriced", "cancelled" or "replaced".
     •  replacement: the transaction that replaced this transaction, a TransactionResponse object
     •  receipt: the receipt of the transaction that replaced this transaction, a   TransactionReceipt object
     •  cancelled: a boolean indicating if the transaction was cancelled or not, it   is true if reason is "cancelled" or "replaced" but false if reason is   "repriced"
  • UNPREDICTABLE_GAS_LIMIT: The node that you sent the transaction to couldn't estimate the gas needed for this transaction. The solution is to pass a gas limit yourself when sending the transaction.

With all that information, you can now handle errors properly, depending on what happens, when sending transactions.

And that's it 🎉

Thank you for reading this article