@circle-fin/adapter-ethers-v6 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs CHANGED
@@ -2851,9 +2851,71 @@ const RECOVERABILITY_VALUES = [
2851
2851
  'RESUMABLE',
2852
2852
  'FATAL',
2853
2853
  ];
2854
+ /**
2855
+ * Error type constants for categorizing errors by origin.
2856
+ *
2857
+ * This const object provides a reference for error types, enabling
2858
+ * IDE autocomplete and preventing typos when creating custom errors.
2859
+ *
2860
+ * @remarks
2861
+ * While internal error definitions use string literals with type annotations
2862
+ * for strict type safety, this constant is useful for developers creating
2863
+ * custom error instances or checking error types programmatically.
2864
+ *
2865
+ * @example
2866
+ * ```typescript
2867
+ * import { ERROR_TYPES, KitError } from '@core/errors'
2868
+ *
2869
+ * // Use for type checking
2870
+ * if (error.type === ERROR_TYPES.BALANCE) {
2871
+ * console.log('This is a balance error')
2872
+ * }
2873
+ * ```
2874
+ *
2875
+ * @example
2876
+ * ```typescript
2877
+ * // Use as reference when creating custom errors
2878
+ * const error = new KitError({
2879
+ * code: 9999,
2880
+ * name: 'CUSTOM_ERROR',
2881
+ * type: ERROR_TYPES.BALANCE, // IDE autocomplete works here
2882
+ * recoverability: 'FATAL',
2883
+ * message: 'Custom balance error'
2884
+ * })
2885
+ * ```
2886
+ */
2887
+ const ERROR_TYPES = {
2888
+ /** User input validation and parameter checking */
2889
+ INPUT: 'INPUT',
2890
+ /** Insufficient token balances and amount validation */
2891
+ BALANCE: 'BALANCE',
2892
+ /** On-chain execution: reverts, gas issues, transaction failures */
2893
+ ONCHAIN: 'ONCHAIN',
2894
+ /** Blockchain RPC provider issues and endpoint problems */
2895
+ RPC: 'RPC',
2896
+ /** Internet connectivity, DNS resolution, connection issues */
2897
+ NETWORK: 'NETWORK',
2898
+ };
2899
+ /**
2900
+ * Array of valid error type values for validation.
2901
+ * Derived from ERROR_TYPES const object.
2902
+ */
2903
+ const ERROR_TYPE_VALUES = Object.values(ERROR_TYPES);
2854
2904
 
2855
- // Create a mutable array for Zod enum validation
2905
+ // Create mutable arrays for Zod enum validation
2856
2906
  const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
2907
+ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
2908
+ /**
2909
+ * Error code ranges for validation.
2910
+ * Single source of truth for valid error code ranges.
2911
+ */
2912
+ const ERROR_CODE_RANGES = [
2913
+ { min: 1000, max: 1999, type: 'INPUT' },
2914
+ { min: 3000, max: 3999, type: 'NETWORK' },
2915
+ { min: 4000, max: 4999, type: 'RPC' },
2916
+ { min: 5000, max: 5999, type: 'ONCHAIN' },
2917
+ { min: 9000, max: 9999, type: 'BALANCE' },
2918
+ ];
2857
2919
  /**
2858
2920
  * Zod schema for validating ErrorDetails objects.
2859
2921
  *
@@ -2866,7 +2928,8 @@ const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
2866
2928
  *
2867
2929
  * const result = errorDetailsSchema.safeParse({
2868
2930
  * code: 1001,
2869
- * name: 'NETWORK_MISMATCH',
2931
+ * name: 'INPUT_NETWORK_MISMATCH',
2932
+ * type: 'INPUT',
2870
2933
  * recoverability: 'FATAL',
2871
2934
  * message: 'Source and destination networks must be different'
2872
2935
  * })
@@ -2875,30 +2938,56 @@ const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
2875
2938
  * console.error('Validation failed:', result.error.issues)
2876
2939
  * }
2877
2940
  * ```
2941
+ *
2942
+ * @example
2943
+ * ```typescript
2944
+ * // Runtime error
2945
+ * const result = errorDetailsSchema.safeParse({
2946
+ * code: 9001,
2947
+ * name: 'BALANCE_INSUFFICIENT_TOKEN',
2948
+ * type: 'BALANCE',
2949
+ * recoverability: 'FATAL',
2950
+ * message: 'Insufficient USDC balance'
2951
+ * })
2952
+ * ```
2878
2953
  */
2879
2954
  const errorDetailsSchema = zod.z.object({
2880
- /** Numeric identifier following standardized ranges (1000+ for INPUT errors) */
2955
+ /**
2956
+ * Numeric identifier following standardized ranges:
2957
+ * - 1000-1999: INPUT errors - Parameter validation
2958
+ * - 3000-3999: NETWORK errors - Connectivity issues
2959
+ * - 4000-4999: RPC errors - Provider issues, gas estimation
2960
+ * - 5000-5999: ONCHAIN errors - Transaction/simulation failures
2961
+ * - 9000-9999: BALANCE errors - Insufficient funds
2962
+ */
2881
2963
  code: zod.z
2882
2964
  .number()
2883
2965
  .int('Error code must be an integer')
2884
- .min(1000, 'Error code must be within valid range (1000+)')
2885
- .max(1099, 'Error code must be within valid range (1099 max)'),
2886
- /** Human-readable ID (e.g., "NETWORK_MISMATCH") */
2966
+ .refine((code) => ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
2967
+ message: 'Error code must be in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
2968
+ }),
2969
+ /** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
2887
2970
  name: zod.z
2888
2971
  .string()
2889
2972
  .min(1, 'Error name must be a non-empty string')
2890
2973
  .regex(/^[A-Z_][A-Z0-9_]*$/, 'Error name must match pattern: ^[A-Z_][A-Z0-9_]*$'),
2974
+ /** Error category indicating where the error originated */
2975
+ type: zod.z.enum(ERROR_TYPE_ARRAY, {
2976
+ errorMap: () => ({
2977
+ message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
2978
+ }),
2979
+ }),
2891
2980
  /** Error handling strategy */
2892
2981
  recoverability: zod.z.enum(RECOVERABILITY_ARRAY, {
2893
2982
  errorMap: () => ({
2894
2983
  message: 'Recoverability must be one of: RETRYABLE, RESUMABLE, FATAL',
2895
2984
  }),
2896
2985
  }),
2897
- /** User-friendly explanation with network context */
2986
+ /** User-friendly explanation with context */
2898
2987
  message: zod.z
2899
2988
  .string()
2900
2989
  .min(1, 'Error message must be a non-empty string')
2901
- .max(500, 'Error message must be 500 characters or less'),
2990
+ .max(1000, 'Error message must be 1000 characters or less'),
2902
2991
  /** Raw error details, context, or the original error that caused this one. */
2903
2992
  cause: zod.z
2904
2993
  .object({
@@ -2913,7 +3002,7 @@ const errorDetailsSchema = zod.z.object({
2913
3002
  *
2914
3003
  * @param details - The object to validate
2915
3004
  * @returns The validated ErrorDetails object
2916
- * @throws {TypeError} When validation fails
3005
+ * @throws TypeError When validation fails
2917
3006
  *
2918
3007
  * @example
2919
3008
  * ```typescript
@@ -2990,6 +3079,8 @@ class KitError extends Error {
2990
3079
  code;
2991
3080
  /** Human-readable ID (e.g., "NETWORK_MISMATCH") */
2992
3081
  name;
3082
+ /** Error category indicating where the error originated */
3083
+ type;
2993
3084
  /** Error handling strategy */
2994
3085
  recoverability;
2995
3086
  /** Raw error details, context, or the original error that caused this one. */
@@ -3018,6 +3109,12 @@ class KitError extends Error {
3018
3109
  enumerable: true,
3019
3110
  configurable: false,
3020
3111
  },
3112
+ type: {
3113
+ value: validatedDetails.type,
3114
+ writable: false,
3115
+ enumerable: true,
3116
+ configurable: false,
3117
+ },
3021
3118
  recoverability: {
3022
3119
  value: validatedDetails.recoverability,
3023
3120
  writable: false,
@@ -3037,14 +3134,19 @@ class KitError extends Error {
3037
3134
  }
3038
3135
 
3039
3136
  /**
3040
- * Minimum error code for INPUT type errors.
3041
- * INPUT errors represent validation failures and invalid parameters.
3137
+ * Standardized error code ranges for consistent categorization:
3138
+ *
3139
+ * - 1000-1999: INPUT errors - Parameter validation, input format errors
3140
+ * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
3141
+ * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
3142
+ * - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
3143
+ * - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
3042
3144
  */
3043
3145
  /**
3044
3146
  * Standardized error definitions for INPUT type errors.
3045
3147
  *
3046
- * Each entry combines the numeric error code with its corresponding
3047
- * string name to ensure consistency when creating error instances.
3148
+ * Each entry combines the numeric error code, string name, and type
3149
+ * to ensure consistency when creating error instances.
3048
3150
  *
3049
3151
  * Error codes follow a hierarchical numbering scheme where the first digit
3050
3152
  * indicates the error category (1 = INPUT) and subsequent digits provide
@@ -3061,48 +3163,609 @@ class KitError extends Error {
3061
3163
  * message: 'Source and destination networks must be different'
3062
3164
  * })
3063
3165
  *
3064
- * // Access code and name individually if needed
3166
+ * // Access code, name, and type individually if needed
3065
3167
  * console.log(InputError.NETWORK_MISMATCH.code) // 1001
3066
3168
  * console.log(InputError.NETWORK_MISMATCH.name) // 'INPUT_NETWORK_MISMATCH'
3169
+ * console.log(InputError.NETWORK_MISMATCH.type) // 'INPUT'
3067
3170
  * ```
3068
3171
  */
3069
3172
  const InputError = {
3070
- /** Unsupported or invalid bridge route configuration */
3071
- UNSUPPORTED_ROUTE: {
3072
- code: 1003,
3073
- name: 'INPUT_UNSUPPORTED_ROUTE',
3173
+ /** Invalid or unsupported chain identifier */
3174
+ INVALID_CHAIN: {
3175
+ code: 1005,
3176
+ name: 'INPUT_INVALID_CHAIN',
3177
+ type: 'INPUT',
3178
+ }};
3179
+ /**
3180
+ * Standardized error definitions for BALANCE type errors.
3181
+ *
3182
+ * BALANCE errors indicate insufficient funds or allowance issues
3183
+ * that prevent transaction execution.
3184
+ *
3185
+ * @example
3186
+ * ```typescript
3187
+ * import { BalanceError } from '@core/errors'
3188
+ *
3189
+ * const error = new KitError({
3190
+ * ...BalanceError.INSUFFICIENT_TOKEN,
3191
+ * recoverability: 'FATAL',
3192
+ * message: 'Insufficient USDC balance on Ethereum',
3193
+ * cause: { trace: { required: '100', available: '50' } }
3194
+ * })
3195
+ * ```
3196
+ */
3197
+ const BalanceError = {
3198
+ /** Insufficient token balance for transaction */
3199
+ INSUFFICIENT_TOKEN: {
3200
+ code: 9001,
3201
+ name: 'BALANCE_INSUFFICIENT_TOKEN',
3202
+ type: 'BALANCE',
3203
+ },
3204
+ /** Insufficient native token (ETH/SOL/etc) for gas fees */
3205
+ INSUFFICIENT_GAS: {
3206
+ code: 9002,
3207
+ name: 'BALANCE_INSUFFICIENT_GAS',
3208
+ type: 'BALANCE',
3209
+ }};
3210
+ /**
3211
+ * Standardized error definitions for ONCHAIN type errors.
3212
+ *
3213
+ * ONCHAIN errors occur during transaction execution, simulation,
3214
+ * or interaction with smart contracts on the blockchain.
3215
+ *
3216
+ * @example
3217
+ * ```typescript
3218
+ * import { OnchainError } from '@core/errors'
3219
+ *
3220
+ * const error = new KitError({
3221
+ * ...OnchainError.SIMULATION_FAILED,
3222
+ * recoverability: 'FATAL',
3223
+ * message: 'Simulation failed: ERC20 transfer amount exceeds balance',
3224
+ * cause: { trace: { reason: 'ERC20: transfer amount exceeds balance' } }
3225
+ * })
3226
+ * ```
3227
+ */
3228
+ const OnchainError = {
3229
+ /** Transaction reverted on-chain after execution */
3230
+ TRANSACTION_REVERTED: {
3231
+ code: 5001,
3232
+ name: 'ONCHAIN_TRANSACTION_REVERTED',
3233
+ type: 'ONCHAIN',
3234
+ },
3235
+ /** Pre-flight transaction simulation failed */
3236
+ SIMULATION_FAILED: {
3237
+ code: 5002,
3238
+ name: 'ONCHAIN_SIMULATION_FAILED',
3239
+ type: 'ONCHAIN',
3240
+ },
3241
+ /** Transaction ran out of gas during execution */
3242
+ OUT_OF_GAS: {
3243
+ code: 5003,
3244
+ name: 'ONCHAIN_OUT_OF_GAS',
3245
+ type: 'ONCHAIN',
3246
+ }};
3247
+ /**
3248
+ * Standardized error definitions for RPC type errors.
3249
+ *
3250
+ * RPC errors occur when communicating with blockchain RPC providers,
3251
+ * including endpoint failures, invalid responses, and provider-specific issues.
3252
+ *
3253
+ * @example
3254
+ * ```typescript
3255
+ * import { RpcError } from '@core/errors'
3256
+ *
3257
+ * const error = new KitError({
3258
+ * ...RpcError.ENDPOINT_ERROR,
3259
+ * recoverability: 'RETRYABLE',
3260
+ * message: 'RPC endpoint unavailable on Ethereum',
3261
+ * cause: { trace: { endpoint: 'https://mainnet.infura.io' } }
3262
+ * })
3263
+ * ```
3264
+ */
3265
+ const RpcError = {
3266
+ /** RPC endpoint returned error or is unavailable */
3267
+ ENDPOINT_ERROR: {
3268
+ code: 4001,
3269
+ name: 'RPC_ENDPOINT_ERROR',
3270
+ type: 'RPC',
3271
+ }};
3272
+ /**
3273
+ * Standardized error definitions for NETWORK type errors.
3274
+ *
3275
+ * NETWORK errors indicate connectivity issues at the network layer,
3276
+ * including DNS failures, connection timeouts, and unreachable endpoints.
3277
+ *
3278
+ * @example
3279
+ * ```typescript
3280
+ * import { NetworkError } from '@core/errors'
3281
+ *
3282
+ * const error = new KitError({
3283
+ * ...NetworkError.CONNECTION_FAILED,
3284
+ * recoverability: 'RETRYABLE',
3285
+ * message: 'Failed to connect to Ethereum network',
3286
+ * cause: { trace: { error: 'ECONNREFUSED' } }
3287
+ * })
3288
+ * ```
3289
+ */
3290
+ const NetworkError = {
3291
+ /** Network connection failed or unreachable */
3292
+ CONNECTION_FAILED: {
3293
+ code: 3001,
3294
+ name: 'NETWORK_CONNECTION_FAILED',
3295
+ type: 'NETWORK',
3074
3296
  }};
3075
3297
 
3076
3298
  /**
3077
- * Creates error for unsupported bridge route.
3299
+ * Creates error for invalid chain configuration.
3078
3300
  *
3079
- * This error is thrown when attempting to bridge between chains that don't
3080
- * have a supported bridge route configured.
3301
+ * This error is thrown when the provided chain doesn't meet the required
3302
+ * configuration or is not supported for the operation.
3081
3303
  *
3082
- * @param source - Source chain name
3083
- * @param destination - Destination chain name
3084
- * @returns KitError with specific route details
3304
+ * @param chain - The invalid chain name or identifier
3305
+ * @param reason - Specific reason why chain is invalid
3306
+ * @returns KitError with chain details and validation rule
3085
3307
  *
3086
3308
  * @example
3087
3309
  * ```typescript
3088
- * import { createUnsupportedRouteError } from '@core/errors'
3310
+ * import { createInvalidChainError } from '@core/errors'
3089
3311
  *
3090
- * throw createUnsupportedRouteError('Ethereum', 'Solana')
3091
- * // Message: "Route from Ethereum to Solana is not supported"
3312
+ * throw createInvalidChainError('UnknownChain', 'Chain is not supported by this bridge')
3313
+ * // Message: "Invalid chain 'UnknownChain': Chain is not supported by this bridge"
3092
3314
  * ```
3093
3315
  */
3094
- function createUnsupportedRouteError(source, destination) {
3316
+ function createInvalidChainError(chain, reason) {
3095
3317
  const errorDetails = {
3096
- ...InputError.UNSUPPORTED_ROUTE,
3318
+ ...InputError.INVALID_CHAIN,
3097
3319
  recoverability: 'FATAL',
3098
- message: `Route from ${source} to ${destination} is not supported.`,
3320
+ message: `Invalid chain '${chain}': ${reason}`,
3099
3321
  cause: {
3100
- trace: { source, destination },
3322
+ trace: { chain, reason },
3101
3323
  },
3102
3324
  };
3103
3325
  return new KitError(errorDetails);
3104
3326
  }
3105
3327
 
3328
+ /**
3329
+ * Creates error for insufficient token balance.
3330
+ *
3331
+ * This error is thrown when a wallet does not have enough tokens to
3332
+ * complete a transaction. The error is FATAL as it requires user
3333
+ * intervention to add funds.
3334
+ *
3335
+ * @param chain - The blockchain network where the balance check failed
3336
+ * @param token - The token symbol (e.g., 'USDC', 'ETH')
3337
+ * @param rawError - The original error from the underlying system (optional)
3338
+ * @returns KitError with insufficient token balance details
3339
+ *
3340
+ * @example
3341
+ * ```typescript
3342
+ * import { createInsufficientTokenBalanceError } from '@core/errors'
3343
+ *
3344
+ * throw createInsufficientTokenBalanceError('Ethereum', 'USDC')
3345
+ * // Message: "Insufficient USDC balance on Ethereum"
3346
+ * ```
3347
+ *
3348
+ * @example
3349
+ * ```typescript
3350
+ * // With raw error for debugging
3351
+ * try {
3352
+ * await transfer(...)
3353
+ * } catch (error) {
3354
+ * throw createInsufficientTokenBalanceError('Base', 'USDC', error)
3355
+ * }
3356
+ * ```
3357
+ */
3358
+ function createInsufficientTokenBalanceError(chain, token, rawError) {
3359
+ return new KitError({
3360
+ ...BalanceError.INSUFFICIENT_TOKEN,
3361
+ recoverability: 'FATAL',
3362
+ message: `Insufficient ${token} balance on ${chain}`,
3363
+ cause: {
3364
+ trace: {
3365
+ chain,
3366
+ token,
3367
+ rawError,
3368
+ },
3369
+ },
3370
+ });
3371
+ }
3372
+ /**
3373
+ * Creates error for insufficient gas funds.
3374
+ *
3375
+ * This error is thrown when a wallet does not have enough native tokens
3376
+ * (ETH, SOL, etc.) to pay for transaction gas fees. The error is FATAL
3377
+ * as it requires user intervention to add gas funds.
3378
+ *
3379
+ * @param chain - The blockchain network where the gas check failed
3380
+ * @param rawError - The original error from the underlying system (optional)
3381
+ * @returns KitError with insufficient gas details
3382
+ *
3383
+ * @example
3384
+ * ```typescript
3385
+ * import { createInsufficientGasError } from '@core/errors'
3386
+ *
3387
+ * throw createInsufficientGasError('Ethereum')
3388
+ * // Message: "Insufficient gas funds on Ethereum"
3389
+ * ```
3390
+ */
3391
+ function createInsufficientGasError(chain, rawError) {
3392
+ return new KitError({
3393
+ ...BalanceError.INSUFFICIENT_GAS,
3394
+ recoverability: 'FATAL',
3395
+ message: `Insufficient gas funds on ${chain}`,
3396
+ cause: {
3397
+ trace: {
3398
+ chain,
3399
+ rawError,
3400
+ },
3401
+ },
3402
+ });
3403
+ }
3404
+
3405
+ /**
3406
+ * Creates error for transaction simulation failures.
3407
+ *
3408
+ * This error is thrown when a pre-flight transaction simulation fails,
3409
+ * typically due to contract logic that would revert. The error is FATAL
3410
+ * as it indicates the transaction would fail if submitted.
3411
+ *
3412
+ * @param chain - The blockchain network where the simulation failed
3413
+ * @param reason - The reason for simulation failure (e.g., revert message)
3414
+ * @param rawError - The original error from the underlying system (optional)
3415
+ * @returns KitError with simulation failure details
3416
+ *
3417
+ * @example
3418
+ * ```typescript
3419
+ * import { createSimulationFailedError } from '@core/errors'
3420
+ *
3421
+ * throw createSimulationFailedError('Ethereum', 'ERC20: insufficient allowance')
3422
+ * // Message: "Simulation failed on Ethereum: ERC20: insufficient allowance"
3423
+ * ```
3424
+ */
3425
+ function createSimulationFailedError(chain, reason, rawError) {
3426
+ return new KitError({
3427
+ ...OnchainError.SIMULATION_FAILED,
3428
+ recoverability: 'FATAL',
3429
+ message: `Simulation failed on ${chain}: ${reason}`,
3430
+ cause: {
3431
+ trace: {
3432
+ chain,
3433
+ reason,
3434
+ rawError,
3435
+ },
3436
+ },
3437
+ });
3438
+ }
3439
+ /**
3440
+ * Creates error for transaction reverts.
3441
+ *
3442
+ * This error is thrown when a transaction is submitted and confirmed
3443
+ * but reverts on-chain. The error is FATAL as it indicates the
3444
+ * transaction executed but failed.
3445
+ *
3446
+ * @param chain - The blockchain network where the transaction reverted
3447
+ * @param reason - The reason for the revert (e.g., revert message)
3448
+ * @param rawError - The original error from the underlying system (optional)
3449
+ * @returns KitError with transaction revert details
3450
+ *
3451
+ * @example
3452
+ * ```typescript
3453
+ * import { createTransactionRevertedError } from '@core/errors'
3454
+ *
3455
+ * throw createTransactionRevertedError('Base', 'Slippage exceeded')
3456
+ * // Message: "Transaction reverted on Base: Slippage exceeded"
3457
+ * ```
3458
+ */
3459
+ function createTransactionRevertedError(chain, reason, rawError) {
3460
+ return new KitError({
3461
+ ...OnchainError.TRANSACTION_REVERTED,
3462
+ recoverability: 'FATAL',
3463
+ message: `Transaction reverted on ${chain}: ${reason}`,
3464
+ cause: {
3465
+ trace: {
3466
+ chain,
3467
+ reason,
3468
+ rawError,
3469
+ },
3470
+ },
3471
+ });
3472
+ }
3473
+ /**
3474
+ * Creates error for out of gas failures.
3475
+ *
3476
+ * This error is thrown when a transaction runs out of gas during execution.
3477
+ * The error is FATAL as it requires adjusting gas limits or transaction logic.
3478
+ *
3479
+ * @param chain - The blockchain network where the transaction ran out of gas
3480
+ * @param rawError - The original error from the underlying system (optional)
3481
+ * @returns KitError with out of gas details
3482
+ *
3483
+ * @example
3484
+ * ```typescript
3485
+ * import { createOutOfGasError } from '@core/errors'
3486
+ *
3487
+ * throw createOutOfGasError('Polygon')
3488
+ * // Message: "Transaction ran out of gas on Polygon"
3489
+ * ```
3490
+ */
3491
+ function createOutOfGasError(chain, rawError) {
3492
+ return new KitError({
3493
+ ...OnchainError.OUT_OF_GAS,
3494
+ recoverability: 'FATAL',
3495
+ message: `Transaction ran out of gas on ${chain}`,
3496
+ cause: {
3497
+ trace: {
3498
+ chain,
3499
+ rawError,
3500
+ },
3501
+ },
3502
+ });
3503
+ }
3504
+
3505
+ /**
3506
+ * Creates error for RPC endpoint failures.
3507
+ *
3508
+ * This error is thrown when an RPC provider endpoint fails, returns an error,
3509
+ * or is unavailable. The error is RETRYABLE as RPC issues are often temporary.
3510
+ *
3511
+ * @param chain - The blockchain network where the RPC error occurred
3512
+ * @param rawError - The original error from the underlying system (optional)
3513
+ * @returns KitError with RPC endpoint error details
3514
+ *
3515
+ * @example
3516
+ * ```typescript
3517
+ * import { createRpcEndpointError } from '@core/errors'
3518
+ *
3519
+ * throw createRpcEndpointError('Ethereum')
3520
+ * // Message: "RPC endpoint error on Ethereum"
3521
+ * ```
3522
+ */
3523
+ function createRpcEndpointError(chain, rawError) {
3524
+ return new KitError({
3525
+ ...RpcError.ENDPOINT_ERROR,
3526
+ recoverability: 'RETRYABLE',
3527
+ message: `RPC endpoint error on ${chain}`,
3528
+ cause: {
3529
+ trace: {
3530
+ chain,
3531
+ rawError,
3532
+ },
3533
+ },
3534
+ });
3535
+ }
3536
+
3537
+ /**
3538
+ * Creates error for network connection failures.
3539
+ *
3540
+ * This error is thrown when network connectivity issues prevent reaching
3541
+ * the blockchain network. The error is RETRYABLE as network issues are
3542
+ * often temporary.
3543
+ *
3544
+ * @param chain - The blockchain network where the connection failed
3545
+ * @param rawError - The original error from the underlying system (optional)
3546
+ * @returns KitError with network connection error details
3547
+ *
3548
+ * @example
3549
+ * ```typescript
3550
+ * import { createNetworkConnectionError } from '@core/errors'
3551
+ *
3552
+ * throw createNetworkConnectionError('Ethereum')
3553
+ * // Message: "Network connection failed for Ethereum"
3554
+ * ```
3555
+ */
3556
+ function createNetworkConnectionError(chain, rawError) {
3557
+ return new KitError({
3558
+ ...NetworkError.CONNECTION_FAILED,
3559
+ recoverability: 'RETRYABLE',
3560
+ message: `Network connection failed for ${chain}`,
3561
+ cause: {
3562
+ trace: {
3563
+ chain,
3564
+ rawError,
3565
+ },
3566
+ },
3567
+ });
3568
+ }
3569
+
3570
+ /**
3571
+ * Parses raw blockchain errors into structured KitError instances.
3572
+ *
3573
+ * This function uses pattern matching to identify common blockchain error
3574
+ * types and converts them into standardized KitError format. It handles
3575
+ * errors from viem, ethers, Solana web3.js, and other blockchain libraries.
3576
+ *
3577
+ * The parser recognizes 5 main error patterns:
3578
+ * 1. Insufficient balance errors
3579
+ * 2. Simulation/execution reverted errors
3580
+ * 3. Gas-related errors
3581
+ * 4. Network connectivity errors
3582
+ * 5. RPC provider errors
3583
+ *
3584
+ * @param error - The raw error from the blockchain library
3585
+ * @param context - Context information including chain and optional token
3586
+ * @returns A structured KitError instance
3587
+ *
3588
+ * @example
3589
+ * ```typescript
3590
+ * import { parseBlockchainError } from '@core/errors'
3591
+ *
3592
+ * try {
3593
+ * await walletClient.sendTransaction(...)
3594
+ * } catch (error) {
3595
+ * throw parseBlockchainError(error, {
3596
+ * chain: 'Ethereum',
3597
+ * token: 'USDC',
3598
+ * operation: 'transfer'
3599
+ * })
3600
+ * }
3601
+ * ```
3602
+ *
3603
+ * @example
3604
+ * ```typescript
3605
+ * // Minimal usage
3606
+ * try {
3607
+ * await connection.sendTransaction(...)
3608
+ * } catch (error) {
3609
+ * throw parseBlockchainError(error, { chain: 'Solana' })
3610
+ * }
3611
+ * ```
3612
+ */
3613
+ function parseBlockchainError(error, context) {
3614
+ const msg = extractMessage(error);
3615
+ const token = context.token ?? 'token';
3616
+ // Pattern 1: Insufficient balance errors
3617
+ // Matches balance-related errors from ERC20 contracts, native transfers, and Solana programs
3618
+ if (/transfer amount exceeds balance|insufficient (balance|funds)|burn amount exceeded/i.test(msg)) {
3619
+ return createInsufficientTokenBalanceError(context.chain, token, error);
3620
+ }
3621
+ // Pattern 2: Simulation and execution reverts
3622
+ // Matches contract revert errors and simulation failures
3623
+ if (/execution reverted|simulation failed|transaction reverted|transaction failed/i.test(msg)) {
3624
+ const reason = extractRevertReason(msg) ?? 'Transaction reverted';
3625
+ // Distinguish between simulation failures and transaction reverts
3626
+ // "simulation failed" or "eth_call" indicates pre-flight simulation
3627
+ // "transaction failed" or context.operation === 'transaction' indicates post-execution
3628
+ if (/simulation failed/i.test(msg) || context.operation === 'simulation') {
3629
+ return createSimulationFailedError(context.chain, reason, error);
3630
+ }
3631
+ // Transaction execution failures or reverts
3632
+ return createTransactionRevertedError(context.chain, reason, error);
3633
+ }
3634
+ // Pattern 3: Gas-related errors
3635
+ // Matches gas estimation failures and gas exhaustion
3636
+ // Check specific patterns first, then generic "gas" patterns
3637
+ // Gas estimation failures are RPC issues
3638
+ if (/gas estimation failed|cannot estimate gas/i.test(msg)) {
3639
+ return createRpcEndpointError(context.chain, error);
3640
+ }
3641
+ // Gas exhaustion errors
3642
+ // Use specific patterns without wildcards to avoid ReDoS
3643
+ if (/out of gas|gas limit exceeded|exceeds block gas limit/i.test(msg)) {
3644
+ return createOutOfGasError(context.chain, error);
3645
+ }
3646
+ // Insufficient funds for gas
3647
+ if (/insufficient funds for gas/i.test(msg)) {
3648
+ return createInsufficientGasError(context.chain, error);
3649
+ }
3650
+ // Pattern 4: Network connectivity errors
3651
+ // Matches connection failures, DNS errors, and timeouts
3652
+ if (/connection (refused|failed)|network|timeout|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
3653
+ return createNetworkConnectionError(context.chain, error);
3654
+ }
3655
+ // Pattern 5: RPC provider errors
3656
+ // Matches RPC endpoint errors, invalid responses, and rate limits
3657
+ if (/rpc|invalid response|rate limit|too many requests/i.test(msg)) {
3658
+ return createRpcEndpointError(context.chain, error);
3659
+ }
3660
+ // Fallback based on operation context
3661
+ // Gas-related operations are RPC calls
3662
+ if (context.operation === 'estimateGas' ||
3663
+ context.operation === 'getGasPrice') {
3664
+ return createRpcEndpointError(context.chain, error);
3665
+ }
3666
+ // Fallback for unrecognized errors
3667
+ // Defaults to simulation failed as transaction execution is the most common failure point
3668
+ return createSimulationFailedError(context.chain, msg.length > 0 ? msg : 'Unknown error', error);
3669
+ }
3670
+ /**
3671
+ * Type guard to check if error has Solana-Kit structure with logs.
3672
+ *
3673
+ * Checks if the error object contains a context with logs array,
3674
+ * which is the structure used by Solana Kit errors.
3675
+ *
3676
+ * @param error - Unknown error to check
3677
+ * @returns True if error has Solana-Kit logs structure
3678
+ */
3679
+ function hasSolanaLogs(error) {
3680
+ return (error !== null &&
3681
+ typeof error === 'object' &&
3682
+ 'context' in error &&
3683
+ error.context !== null &&
3684
+ typeof error.context === 'object' &&
3685
+ 'logs' in error.context &&
3686
+ Array.isArray(error.context.logs));
3687
+ }
3688
+ /**
3689
+ * Extracts a human-readable error message from various error types.
3690
+ *
3691
+ * Handles Error objects, string errors, objects with message properties,
3692
+ * Solana-Kit errors with context logs, and falls back to string representation.
3693
+ * For Solana-Kit errors, extracts Anchor error messages from transaction logs.
3694
+ *
3695
+ * @param error - Unknown error to extract message from
3696
+ * @returns Extracted error message string
3697
+ *
3698
+ * @example
3699
+ * ```typescript
3700
+ * const msg1 = extractMessage(new Error('test')) // 'test'
3701
+ * const msg2 = extractMessage('string error') // 'string error'
3702
+ * const msg3 = extractMessage({ message: 'obj' }) // 'obj'
3703
+ * ```
3704
+ */
3705
+ function extractMessage(error) {
3706
+ // Check for Solana-Kit errors with context.logs
3707
+ if (hasSolanaLogs(error)) {
3708
+ // Extract Anchor error message from logs
3709
+ const anchorLog = error.context.logs.find((log) => log.includes('AnchorError') || log.includes('Error Message'));
3710
+ if (anchorLog !== undefined) {
3711
+ // Return the anchor error log which contains the detailed message
3712
+ return anchorLog;
3713
+ }
3714
+ }
3715
+ if (error instanceof Error) {
3716
+ return error.message;
3717
+ }
3718
+ if (typeof error === 'string') {
3719
+ return error;
3720
+ }
3721
+ if (typeof error === 'object' && error !== null && 'message' in error) {
3722
+ return String(error.message);
3723
+ }
3724
+ return String(error);
3725
+ }
3726
+ /**
3727
+ * Extracts the revert reason from an error message.
3728
+ *
3729
+ * Attempts to parse out the meaningful reason from execution revert errors,
3730
+ * removing common prefixes like "execution reverted:" or "reverted:".
3731
+ *
3732
+ * @param msg - The error message to extract from
3733
+ * @returns The extracted revert reason, or null if not found
3734
+ *
3735
+ * @example
3736
+ * ```typescript
3737
+ * const reason = extractRevertReason(
3738
+ * 'execution reverted: ERC20: transfer amount exceeds balance'
3739
+ * )
3740
+ * // Returns: 'ERC20: transfer amount exceeds balance'
3741
+ * ```
3742
+ *
3743
+ * @example
3744
+ * ```typescript
3745
+ * const reason = extractRevertReason(
3746
+ * 'Simulation failed: Execution reverted with reason: Insufficient allowance'
3747
+ * )
3748
+ * // Returns: 'Insufficient allowance'
3749
+ * ```
3750
+ */
3751
+ function extractRevertReason(msg) {
3752
+ // Try to extract reason after "execution reverted:" or "reason:"
3753
+ // Use [^\n.]+ instead of .+? to avoid ReDoS vulnerability
3754
+ const patterns = [
3755
+ /(?:execution reverted|reverted):\s*([^\n.]+)/i,
3756
+ /reason:\s*([^\n.]+)/i,
3757
+ /with reason:\s*([^\n.]+)/i,
3758
+ ];
3759
+ for (const pattern of patterns) {
3760
+ const match = pattern.exec(msg);
3761
+ const extractedReason = match?.at(1);
3762
+ if (extractedReason !== undefined && extractedReason.length > 0) {
3763
+ return extractedReason.trim();
3764
+ }
3765
+ }
3766
+ return null;
3767
+ }
3768
+
3106
3769
  /**
3107
3770
  * Abstract class defining the standard interface for an adapter that interacts with a specific blockchain.
3108
3771
  *
@@ -3248,20 +3911,24 @@ class Adapter {
3248
3911
  * Validate that the target chain is supported by this adapter.
3249
3912
  *
3250
3913
  * @param targetChain - The chain to validate.
3251
- * @throws Error if the chain is not supported.
3914
+ * @throws KitError with INVALID_CHAIN code if the chain is not supported by this adapter.
3252
3915
  */
3253
3916
  validateChainSupport(targetChain) {
3254
3917
  if (this.capabilities?.supportedChains) {
3255
3918
  const isSupported = this.capabilities.supportedChains.some((supportedChain) => supportedChain.chain === targetChain.chain);
3256
3919
  if (!isSupported) {
3257
3920
  const supportedCount = this.capabilities.supportedChains.length;
3258
- const message = `Chain ${targetChain.name} (${targetChain.type}) is not supported by this adapter (supports ${supportedCount.toString()} chains)`;
3259
- throw createUnsupportedRouteError(targetChain.name, message);
3921
+ // List supported chain names for better user experience
3922
+ const supportedChainNames = this.capabilities.supportedChains
3923
+ .map((chainDef) => chainDef.name)
3924
+ .join(', ');
3925
+ const reason = `Not supported by this adapter. It supports ${supportedCount.toString()} ${supportedCount === 1 ? 'chain' : 'chains'}: ${supportedChainNames}`;
3926
+ throw createInvalidChainError(targetChain.name, reason);
3260
3927
  }
3261
3928
  }
3262
3929
  else if (this.chainType && targetChain.type !== this.chainType) {
3263
- const message = `Chain type mismatch: adapter supports ${this.chainType} chains, but received ${targetChain.type} chain: ${targetChain.name}`;
3264
- throw createUnsupportedRouteError(targetChain.name, message);
3930
+ const reason = `Chain type mismatch: adapter supports ${this.chainType} chains, but received ${targetChain.type ?? 'unknown'} chain`;
3931
+ throw createInvalidChainError(targetChain.name, reason);
3265
3932
  }
3266
3933
  }
3267
3934
  }
@@ -9957,14 +10624,17 @@ class EthersAdapter extends EvmAdapter {
9957
10624
  /**
9958
10625
  * Simulates a contract function call using Ethers v6 `.staticCall`.
9959
10626
  */
9960
- async simulateFunctionCall(contract, functionName, args) {
10627
+ async simulateFunctionCall(contract, functionName, args, chain) {
9961
10628
  try {
9962
10629
  const func = contract.getFunction(functionName);
9963
10630
  await func.staticCall(...args);
9964
10631
  }
9965
10632
  catch (err) {
9966
- const msg = err instanceof Error ? err.message : String(err);
9967
- throw new Error(`Simulation failed for ${functionName}: ${msg}`);
10633
+ // Wrap simulation errors with structured error format
10634
+ throw parseBlockchainError(err, {
10635
+ chain: chain.name,
10636
+ operation: 'simulation',
10637
+ });
9968
10638
  }
9969
10639
  }
9970
10640
  /**
@@ -9991,7 +10661,11 @@ class EthersAdapter extends EvmAdapter {
9991
10661
  errorMessage.toLocaleLowerCase().includes('execution reverted')) {
9992
10662
  return fallback;
9993
10663
  }
9994
- throw new Error(`Gas estimation failed: ${errorMessage}`);
10664
+ // Wrap gas estimation errors with structured error format
10665
+ throw parseBlockchainError(error, {
10666
+ chain: chain.name,
10667
+ operation: 'estimateGas',
10668
+ });
9995
10669
  }
9996
10670
  let gasPrice;
9997
10671
  try {
@@ -9999,7 +10673,11 @@ class EthersAdapter extends EvmAdapter {
9999
10673
  gasPrice = await this.fetchGasPrice(chain);
10000
10674
  }
10001
10675
  catch (error) {
10002
- throw new Error(`Gas price retrieval failed: ${error.message}`);
10676
+ // Wrap gas price errors with structured error format
10677
+ throw parseBlockchainError(error, {
10678
+ chain: chain.name,
10679
+ operation: 'getGasPrice',
10680
+ });
10003
10681
  }
10004
10682
  return { gas, gasPrice, fee: (gas * gasPrice).toString() };
10005
10683
  }
@@ -10055,7 +10733,11 @@ class EthersAdapter extends EvmAdapter {
10055
10733
  const shouldRetry = ethersNonceManager.shouldRetryNonceError(error, userProvidedNonce);
10056
10734
  // Don't retry if: not a nonce error, user provided nonce, or this is the last attempt
10057
10735
  if (!shouldRetry || attempt === maxAttempts) {
10058
- throw error;
10736
+ // Wrap transaction execution errors with structured error format
10737
+ throw parseBlockchainError(error, {
10738
+ chain: chain.name,
10739
+ operation: 'sendTransaction',
10740
+ });
10059
10741
  }
10060
10742
  // Retry with resync on nonce-related errors (this will set a new nonce)
10061
10743
  txRequest.nonce = await ethersNonceManager.resyncAndAllocate(provider, chain.chainId, fromAddress);
@@ -10228,7 +10910,7 @@ class EthersAdapter extends EvmAdapter {
10228
10910
  },
10229
10911
  execute: async (overrides) => {
10230
10912
  // Simulate the function call to catch errors before submission
10231
- await this.simulateFunctionCall(contract, functionName, args);
10913
+ await this.simulateFunctionCall(contract, functionName, args, targetChain);
10232
10914
  await this.ensureChain(targetChain);
10233
10915
  // Reconnect the contract with the current signer, which is on the correct
10234
10916
  // chain after `ensureChain`, to ensure the transaction is populated and
@@ -10257,7 +10939,7 @@ class EthersAdapter extends EvmAdapter {
10257
10939
  * adapter interface, it is effectively required and enforced at runtime. This ensures
10258
10940
  * the adapter is connected to the correct chain before querying the signer.
10259
10941
  *
10260
- * @param chain - The chain to use for address resolution (provided by OperationContext)
10942
+ * @param chain - The chain to use for address resolution.
10261
10943
  * @returns A promise that resolves to the signer's address
10262
10944
  * @throws Error when no chain is provided
10263
10945
  *
@@ -10269,26 +10951,15 @@ class EthersAdapter extends EvmAdapter {
10269
10951
  * const address = await adapter.getAddress(Ethereum)
10270
10952
  * console.log('Wallet address:', address)
10271
10953
  * ```
10272
- *
10273
- * @example
10274
- * ```typescript
10275
- * // In practice, address resolution happens automatically
10276
- * const prepared = await adapter.prepare(params, {
10277
- * chain: 'Ethereum'
10278
- * // Address automatically resolved via getAddress() internally
10279
- * })
10280
- * ```
10281
10954
  */
10282
10955
  async getAddress(chain) {
10283
10956
  // Prevent calling getAddress on developer-controlled adapters
10284
10957
  if (this.capabilities?.addressContext === 'developer-controlled') {
10285
- throw new Error('Cannot call getAddress() on developer-controlled adapters. ' +
10286
- 'Address must be provided explicitly in the operation context.');
10958
+ throw new Error('Cannot call getAddress() on developer-controlled adapters. Address must be provided explicitly in the operation context.');
10287
10959
  }
10288
10960
  // Chain parameter should now be provided by resolveOperationContext
10289
10961
  if (!chain) {
10290
- throw new Error('Chain parameter is required for address resolution. ' +
10291
- 'This should be provided by the OperationContext pattern.');
10962
+ throw new Error('Chain parameter is required for address resolution. This should be provided by the OperationContext pattern.');
10292
10963
  }
10293
10964
  // Ensure we're on the correct chain before getting address
10294
10965
  await this.ensureChain(chain);