diff --git a/src/hooks/useSecureTransaction.ts b/src/hooks/useSecureTransaction.ts index 1be53dc..167f589 100644 --- a/src/hooks/useSecureTransaction.ts +++ b/src/hooks/useSecureTransaction.ts @@ -1,5 +1,6 @@ 'use client'; import { logger } from '@/utils/logger'; +import { getFriendlyWeb3ErrorMessage } from '@/utils/errorHandling'; import { useCallback, useState } from 'react'; import { ethers } from 'ethers'; @@ -146,9 +147,7 @@ export const useSecureTransaction = (): UseSecureTransactionReturn => { return signedTransaction; } catch (error) { logger.error('Failed to sign transaction:', error); - toast.error('Failed to sign transaction', { - description: error instanceof Error ? error.message : 'Unknown error' - }); + toast.error(getFriendlyWeb3ErrorMessage(error)); return null; } finally { setIsSigning(false); @@ -237,9 +236,7 @@ export const useSecureTransaction = (): UseSecureTransactionReturn => { return txHash; } catch (error) { logger.error('Failed to broadcast transaction:', error); - toast.error('Failed to broadcast transaction', { - description: error instanceof Error ? error.message : 'Unknown error' - }); + toast.error(getFriendlyWeb3ErrorMessage(error)); return null; } finally { setIsBroadcasting(false); diff --git a/src/hooks/useTransaction.ts b/src/hooks/useTransaction.ts index cfabd13..5e11368 100644 --- a/src/hooks/useTransaction.ts +++ b/src/hooks/useTransaction.ts @@ -1,5 +1,6 @@ 'use client'; import { logger } from '@/utils/logger'; +import { getFriendlyWeb3ErrorMessage } from '@/utils/errorHandling'; import { useCallback } from 'react'; import { ethers } from 'ethers'; @@ -91,9 +92,7 @@ export const useTransaction = () => { } } catch (error) { logger.error('Secure transaction failed:', error); - toast.error('Secure transaction failed', { - description: error instanceof Error ? error.message : 'Unknown error' - }); + toast.error(getFriendlyWeb3ErrorMessage(error)); return; } } diff --git a/src/hooks/useTxRetry.ts b/src/hooks/useTxRetry.ts index 5e9c7eb..c98477a 100644 --- a/src/hooks/useTxRetry.ts +++ b/src/hooks/useTxRetry.ts @@ -1,4 +1,5 @@ import { useState, useCallback } from "react"; +import { getFriendlyWeb3ErrorMessage } from '@/utils/errorHandling'; const MAX_RETRIES = 3; const RETRYABLE_CODES = new Set(["NETWORK_ERROR", "TIMEOUT", "UNPREDICTABLE_GAS_LIMIT"]); @@ -30,6 +31,7 @@ export function useTxRetry( setAttempts(0); options.onSuccess?.(hash); } catch (err: unknown) { + const friendlyMessage = getFriendlyWeb3ErrorMessage(err); const e = err as { code?: string; message?: string }; const isRetryable = e.code ? RETRYABLE_CODES.has(e.code) : true; const nextAttempt = retryCount + 1; @@ -37,12 +39,12 @@ export function useTxRetry( if (isRetryable && nextAttempt < MAX_RETRIES) { setAttempts(nextAttempt); setStatus("failed"); - setError(`Transaction failed: ${e.message ?? "unknown error"}. Retry ${nextAttempt}/${MAX_RETRIES} available.`); + setError(`Transaction failed: ${friendlyMessage}. Retry ${nextAttempt}/${MAX_RETRIES} available.`); } else { setStatus("failed"); setAttempts(0); - const finalError = new Error(e.message ?? "Transaction failed"); - setError(isRetryable ? "Max retries reached." : `Non-retryable error: ${e.message}`); + const finalError = new Error(friendlyMessage); + setError(isRetryable ? "Max retries reached." : `Non-retryable error: ${friendlyMessage}`); options.onFailure?.(finalError); } } diff --git a/src/hooks/useWalletConnector.ts b/src/hooks/useWalletConnector.ts index 9e804da..07ebf81 100644 --- a/src/hooks/useWalletConnector.ts +++ b/src/hooks/useWalletConnector.ts @@ -1,5 +1,6 @@ import { useCallback, useState } from 'react'; import { logger } from '@/utils/logger'; +import { getFriendlyWeb3ErrorMessage } from '@/utils/errorHandling'; export type SupportedWalletId = 'metamask' | 'walletconnect' | 'coinbase'; @@ -34,7 +35,7 @@ export const useWalletConnector = () => { logger.debug('MetaMask connector loaded and executed successfully'); return result; } catch (error) { - const message = error instanceof Error ? error.message : 'Failed to connect MetaMask'; + const message = getFriendlyWeb3ErrorMessage(error); setConnectorError(message); logger.error('MetaMask connector error:', error); throw error; @@ -61,7 +62,7 @@ export const useWalletConnector = () => { logger.debug('Coinbase connector loaded and executed successfully'); return result; } catch (error) { - const message = error instanceof Error ? error.message : 'Failed to connect Coinbase Wallet'; + const message = getFriendlyWeb3ErrorMessage(error); setConnectorError(message); logger.error('Coinbase connector error:', error); throw error; @@ -88,7 +89,7 @@ export const useWalletConnector = () => { logger.debug('WalletConnect connector loaded and executed successfully'); return result; } catch (error) { - const message = error instanceof Error ? error.message : 'Failed to connect with WalletConnect'; + const message = getFriendlyWeb3ErrorMessage(error); setConnectorError(message); logger.error('WalletConnect connector error:', error); throw error; diff --git a/src/utils/errorHandling.ts b/src/utils/errorHandling.ts index bd1c426..beb93dc 100644 --- a/src/utils/errorHandling.ts +++ b/src/utils/errorHandling.ts @@ -55,6 +55,86 @@ export const getWalletErrorMessage = (error: unknown): string => { return 'An unexpected error occurred'; }; +export const WEB3_ERROR_CODES = { + USER_REJECTED: 4001, + JSON_RPC_INTERNAL: -32603, + INSUFFICIENT_FUNDS: 'INSUFFICIENT_FUNDS', + UNPREDICTABLE_GAS_LIMIT: 'UNPREDICTABLE_GAS_LIMIT', + NETWORK_ERROR: 'NETWORK_ERROR', +} as const; + +export type Web3ErrorCode = (typeof WEB3_ERROR_CODES)[keyof typeof WEB3_ERROR_CODES]; + +const extractWeb3ErrorCode = (error: unknown): number | string | undefined => { + const topLevelCode = getErrorCode(error); + if (topLevelCode !== undefined) return topLevelCode; + + if (!isRecord(error)) return undefined; + + const nestedError = error.error; + if (isRecord(nestedError)) { + const nestedCode = getErrorCode(nestedError); + if (nestedCode !== undefined) return nestedCode; + const nestedDataCode = isRecord(nestedError.data) ? getErrorCode(nestedError.data) : undefined; + if (nestedDataCode !== undefined) return nestedDataCode; + } + + const nestedData = error.data; + if (isRecord(nestedData)) { + const nestedDataCode = getErrorCode(nestedData); + if (nestedDataCode !== undefined) return nestedDataCode; + } + + return undefined; +}; + +export const getFriendlyWeb3ErrorMessage = (error: unknown): string => { + const code = extractWeb3ErrorCode(error); + const message = getErrorMessage(error).toLowerCase(); + + if ( + code === WEB3_ERROR_CODES.USER_REJECTED || + message.includes('user rejected') || + message.includes('user denied') + ) { + return 'Transaction rejected by the user.'; + } + + if (code === WEB3_ERROR_CODES.JSON_RPC_INTERNAL || message.includes('internal json-rpc error')) { + return 'Internal blockchain error. Please try again later.'; + } + + if ( + code === WEB3_ERROR_CODES.INSUFFICIENT_FUNDS || + message.includes('insufficient funds') || + message.includes('insufficient balance') + ) { + return 'Not enough ETH available to cover gas fees.'; + } + + if ( + code === WEB3_ERROR_CODES.UNPREDICTABLE_GAS_LIMIT || + message.includes('unpredictable gas limit') || + message.includes('gas estimation') + ) { + return 'Gas estimation failed. This transaction may fail if submitted.'; + } + + if ( + code === WEB3_ERROR_CODES.NETWORK_ERROR || + message.includes('network error') || + message.includes('rpc connection failed') + ) { + return 'Network connection failed. Please check your RPC provider and internet connection.'; + } + + if (message.includes('transaction failed')) { + return 'Transaction failed. Please check your wallet and try again.'; + } + + return 'Something went wrong while processing the transaction. Please try again.'; +}; + const getMessage = (error: unknown): string => { if (isRecord(error) && typeof error.message === 'string') return error.message; return getErrorMessage(error, '');