How to handle errors when a transaction fail in React using Wagmi
In this tutorial, we are going to learn how to handle errors when you send a transaction from your React app using Wagmi but the transaction fails on the blockchain.
In this tutorial, we are going to learn how to handle errors when you send a transaction from your React app using Wagmi but the transaction fails on the blockchain.
If you send the transaction using Wagmi, with either the useSendTransaction
hook or the useContractWrite
hook, you can know, before sending the transaction, if it's going to fail or not and why.
Here is an example (one with the smart contract of the LINK token and another one with a transaction sending 1 ETH):
import { useSendTransaction, usePrepareSendTransaction, usePrepareContractWrite, useContractWrite } from 'wagmi';
import { utils } from 'ethers';
export function Home() {
// Prepare the transaction
const { config, error: contractWriteError } = usePrepareContractWrite({
address: '0x514910771AF9Ca656af840dff83E8264EcF986CA',
abi: ['function transfer(address _to, uint256 _value)'],
functionName: 'transfer',
args: [
'0x5b8f1310A956ee1521A7bB56160451C786289aa9',
utils.parseEther('200')
],
});
// Get the function to send the transaction
const { data: writeContractData, write } = useContractWrite(config);
const { config: sendTxConfig, error: sendTxError } = usePrepareSendTransaction({
request: {
to: '0x5b8f1310A956ee1521A7bB56160451C786289aa9',
value: utils.parseEther('1'),
},
});
const { data: sendTxData, sendTransaction } = useSendTransaction(sendTxConfig);
console.log(contractWriteError, sendTxError);
return (
<div>
{sendTxError && (
<p>
That transaction will fail for this reason:
{sendTxError.reason}
</p>
)}
{contractWriteError && (
<p>
Calling that contract function will fail for this reason:
{contractWriteError.reason}
</p>
)}
<button disabled={!sendTransaction} onClick={() => sendTransaction()}>
Send transaction
</button>
<button disabled={!write} onClick={() => write()}>
Write function
</button>
</div>
);
}
As you can see, in the example above, we're able to know if the transactions are going to fail or not before sending them, using the error
property of the hooks that prepare the transaction.
Both usePrepareSendTransaction
and usePrepareContractWrite
return an object that contains an error
property.
That property is null
when the transaction is expected to succeed. Otherwise it will be an object like this:
code
: A string that represents what type of error it is. More information about it belowreason
: A human-readable string of the reason why the transaction failed (it's only available for the error codes"CALL_EXCEPTION"
and"TRANSACTION_REPLACED"
)data
: An hexadecimal string containing information about the errortransaction
: An object containing the transaction data
Depending on the code of the error, the error object will have other properties:
CALL_EXCEPTION
: this code means the transaction could not be validated on the blockchain. If you called a smart contract function and it reverted, this is the type of error that will be returned and thereason
property will contain the message that was sent by the smart contract.
For these types of errors, there will be 3 more properties:
•reason
: a human-readable message containing the reason why the transaction failed
•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 address that sent the transaction doesn't have enough funds to pay the gas fees or to send the Ethereum that the transaction sends
NETWORK_ERROR
: There was an error on the network and the transaction couldn't be validated. For example, that can happen when the chain ID of the transaction is wrong which means the transaction was sent on the wrong network
NONCE_EXPIRED
: Thenonce
passed to the transaction was already used in a previous transaction which is completed
REPLACEMENT_UNDERPRICED
: This transaction failed because it was meant to replace another transaction but not enough gas was sent along with the transaction. You can try sending the transaction again and pass more gas.
TRANSACTION_REPLACED
: This transaction has been replaced and cancelled by another transaction sent by the same address (a transaction with the samenonce
but more gas was sent while this transaction was pending)
For this type of errors, the error object will have the following extra properties:
•hash
: the hash of the replaced transaction
•reason
: One of"repriced"
,"cancelled"
or"replaced"
.
•replacement
: A TransactionResponse object containing information about the replacement transaction.
•receipt
: the TransactionReceipt of the transaction the replacement this transaction
•cancelled
: iftrue
, thereason
property will be"cancelled"
or"replaced"
but if it'sfalse
thereason
property will be"repriced"
UNPREDICTABLE_GAS_LIMIT
: The node where you created the transaction could not estimate the gas needed for this transaction. That happens when the node knows the transaction will fail before sending it.
If it happens, there will be areason
property indicating why the transaction failed.
That same error object is returned when you wait for a transaction using the useWaitForTransaction
hook or the .wait
function in the data
property of useSendTransaction
or useContractWrite
.
Here is an example using the .wait
method:
const { data } = useSendTransaction(config)
useEffect(() => {
async function waitAndHandleErrors() {
data
.wait(1)
.then((txReceipt) => {
if (txReceipt.status == 1) {
console.log('The transaction was successful');
console.log(txReceipt);
}
else {
console.log("the transaction failed")
}
})
.catch((error) => {
console.log('The transaction failed:', error);
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');
}
});
}
if (data?.wait) waitAndHandleErrors();
}, [data]);
The error object in the catch
block will be the same as the one described above.
Here is another example using the useWaitForTransaction
hook:
const { data: receipt, error, sendTransaction } = useSendTransaction(config)
Again here, the error object will be the same as the one described above.
So to summarize everything we've just talked about, this is how you can handle errors when sending transactions:
- When you prepare the transaction using the
usePrepareSendTransaction
orusePrepareContractWrite
hooks, the error object returned will tell you if the transaction you want to send will fail or not and why - When you sent a transaction and you're waiting for it to complete using
wait
, you cancatch
the error thrown by that this function when the transaction fails and it will tell you why it failed - When you sent a transaction and you're waiting for it using
useWaitForTransaction
, you can use the error property returned by that hook when the transaction fails (if it fails) to know the reason why it failed.
Lastly, if you want to get the reason why a transaction you didn't sent failed, you can get it using the useTransaction
hook and then call .wait
on the data returned and handle the errors like we've done above and know the reason why it failed.
And that's it 🎉
Thank you for reading this article