⚠️ Error Handling: Timeouts & Reverts
Learn how to debug RPC failures and retry strategies
Communicate with blockchain nodes programmatically
Your Progress
0 / 5 completed⚠️ RPC Error Handling
RPC calls fail constantly. Network issues, invalid transactions, rate limits, node errors—production dApps must handle dozens of error types gracefully. Poor error handling = bad UX. A user sees "Error -32000" and leaves forever. A good dApp shows: "Insufficient ETH for gas. You need 0.01 ETH more." Let's master common RPC errors and how to handle them properly.
🎮 Interactive: Error Scenario Explorer
Select an error scenario to see the error code, message, root cause, solution, and real-world example. Learn how to detect and fix each error type in your dApps.
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "insufficient funds for gas * price + value"
}
}Transaction costs more than wallet balance. Common when sending max ETH without leaving gas.
Check balance with eth_getBalance. Reserve ~0.01 ETH for gas. Calculate: (gasLimit × gasPrice) + value < balance.
User tries to send 1 ETH but only has 1 ETH (no gas left). Solution: Send 0.99 ETH instead.
🛡️ Error Handling Best Practices
Network errors (-32603, -32005) are often temporary. Retry 3-5 times with delays: 1s, 2s, 4s, 8s. Don't hammer the RPC provider—you'll get rate-limited. Code: await sleep(2 ** retryCount * 1000)
Use eth_call to simulate transactions before eth_sendRawTransaction. If simulation reverts, show user why ("Insufficient allowance") instead of wasting their gas on a failing transaction.
Parse error codes and translate to plain English. Error -32000 "insufficient funds" → "You need 0.05 more ETH for this transaction". Use error.data for detailed revert reasons from contracts.
If Infura is down, switch to Alchemy. If Alchemy fails, try Ankr. Use ethers.js FallbackProvider to automatically retry across multiple RPC endpoints. Never rely on single provider.
💻 Code Example: Robust Error Handling
async function sendTransaction(tx, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
// Simulate first to catch reverts
await provider.call(tx);
// Send transaction
const txResponse = await wallet.sendTransaction(tx);
return await txResponse.wait();
} catch (error) {
// Parse error
if (error.code === -32000) {
if (error.message.includes('insufficient funds')) {
throw new Error('You need more ETH for gas fees');
}
if (error.message.includes('nonce too low')) {
// Refresh nonce and retry
tx.nonce = await wallet.getTransactionCount();
continue;
}
}
// Network error - retry with backoff
if (error.code === -32603 && i < maxRetries - 1) {
await sleep(2 ** i * 1000);
continue;
}
// Unrecoverable error
throw error;
}
}
}This pattern handles nonce issues, validates before sending, retries network errors, and translates error codes to user-friendly messages.
💡 Key Insight
90% of user complaints about "broken dApps" are actually poor error handling. The blockchain didn't fail—your code showed a cryptic error instead of explaining the problem. Professional dApps catch every error type, validate before sending, retry network failures, and show clear messages like "Transaction will fail: Insufficient token allowance. Click to approve." Good error handling is what separates toy projects from production dApps.