import type { Address } from '../types' import { standardErrorCodes } from './constants' import type { ErrorResponse } from './types' import { getMessageFromCode } from './utils' export function isErrorResponse(response: unknown): response is ErrorResponse { return (response as ErrorResponse).errorMessage !== undefined } export const standardErrors = { rpc: { parse: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.parse, arg), invalidRequest: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.invalidRequest, arg), invalidParams: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.invalidParams, arg), methodNotFound: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.methodNotFound, arg), internal: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.internal, arg), server: (opts: ServerErrorOptions) => { if (!opts || typeof opts !== 'object' || Array.isArray(opts)) { throw new Error('Ethereum RPC Server errors must provide single object argument.') } const { code } = opts if (!Number.isInteger(code) || code > -32005 || code < -32099) { throw new Error('"code" must be an integer such that: -32099 <= code <= -32005') } return getEthJsonRpcError(code, opts) }, invalidInput: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.invalidInput, arg), resourceNotFound: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.resourceNotFound, arg), resourceUnavailable: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.resourceUnavailable, arg), transactionRejected: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.transactionRejected, arg), methodNotSupported: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.methodNotSupported, arg), limitExceeded: (arg?: EthErrorsArg) => getEthJsonRpcError(standardErrorCodes.rpc.limitExceeded, arg), }, provider: { userRejectedRequest: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.userRejectedRequest, arg) }, unauthorized: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.unauthorized, arg) }, unsupportedMethod: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.unsupportedMethod, arg) }, disconnected: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.disconnected, arg) }, chainDisconnected: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.chainDisconnected, arg) }, unsupportedChain: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.unsupportedChain, arg) }, // EIP-5792: Wallet Call API errors unsupportedCapability: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.unsupportedCapability, arg) }, unsupportedChainId: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.unsupportedChainId, arg) }, duplicateId: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.duplicateId, arg) }, unknownBundleId: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.unknownBundleId, arg) }, bundleTooLarge: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.bundleTooLarge, arg) }, atomicUpgradeRejected: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.atomicUpgradeRejected, arg) }, atomicityNotSupported: (arg?: EthErrorsArg) => { return getEthProviderError(standardErrorCodes.provider.atomicityNotSupported, arg) }, custom: (opts: CustomErrorArg) => { if (!opts || typeof opts !== 'object' || Array.isArray(opts)) { throw new Error('Ethereum Provider custom errors must provide single object argument.') } const { code, message, data } = opts if (!message || typeof message !== 'string') { throw new Error('"message" must be a nonempty string') } return new EthereumProviderError(code, message, data) }, }, } // Internal function getEthJsonRpcError(code: number, arg?: EthErrorsArg): EthereumRpcError { const [message, data] = parseOpts(arg) return new EthereumRpcError(code, message || getMessageFromCode(code), data) } function getEthProviderError(code: number, arg?: EthErrorsArg): EthereumProviderError { const [message, data] = parseOpts(arg) return new EthereumProviderError(code, message || getMessageFromCode(code), data) } function parseOpts(arg?: EthErrorsArg): [string?, T?] { if (arg) { if (typeof arg === 'string') { return [arg] } if (typeof arg === 'object' && !Array.isArray(arg)) { const { message, data } = arg if (message && typeof message !== 'string') { throw new Error('Must specify string message.') } return [message || undefined, data] } } return [] } interface EthereumErrorOptions { message?: string data?: T } interface ServerErrorOptions extends EthereumErrorOptions { code: number } type CustomErrorArg = ServerErrorOptions type EthErrorsArg = EthereumErrorOptions | string export class EthereumRpcError extends Error { code: number data?: T constructor(code: number, message: string, data?: T) { if (!Number.isInteger(code)) { throw new Error('"code" must be an integer.') } if (!message || typeof message !== 'string') { throw new Error('"message" must be a nonempty string.') } super(message) this.code = code if (data !== undefined) { this.data = data } } } export class EthereumProviderError extends EthereumRpcError { /** * Create an Ethereum Provider JSON-RPC error. * `code` must be an integer in the 1000 <= 4999 range. */ constructor(code: number, message: string, data?: T) { if (!isValidEthProviderCode(code)) { throw new Error('"code" must be an integer such that: 1000 <= code <= 4999') } super(code, message, data) } } export type InsufficientBalanceErrorData = { type: 'INSUFFICIENT_FUNDS' reason: 'NO_SUITABLE_SPEND_PERMISSION_FOUND' | 'SPEND_PERMISSION_ALLOWANCE_EXCEEDED' account: { address: Address } /** * The amount of each token that is required to send the transaction. */ required: Record< Address, { amount: `0x${string}` /** * Sources of funds available to the account with sufficient balance to cover the required amount */ sources: { address: Address; balance: `0x${string}` }[] } > } class ActionableInsufficientBalanceError extends EthereumRpcError {} function isValidEthProviderCode(code: number): boolean { return Number.isInteger(code) && code >= 1000 && code <= 5999 } export function isActionableHttpRequestError(errorObject: unknown): errorObject is ActionableInsufficientBalanceError { return ( typeof errorObject === 'object' && errorObject !== null && 'code' in errorObject && 'data' in errorObject && errorObject.code === -32090 && typeof errorObject.data === 'object' && errorObject.data !== null && 'type' in errorObject.data && errorObject.data.type === 'INSUFFICIENT_FUNDS' ) } // export function isViemError(error: unknown): error is HttpRequestError { // // Check if object and has code, message, and details // return typeof error === 'object' && error !== null && 'details' in error // } // export function viemHttpErrorToProviderError(error: HttpRequestError) { // try { // const details = JSON.parse(error.details) // return new EthereumRpcError(details.code, details.message, details.data) // } catch (_) { // return null // } // }