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

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.
Files changed (6) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +64 -52
  3. package/index.cjs +1743 -628
  4. package/index.d.ts +307 -69
  5. package/index.mjs +1742 -629
  6. package/package.json +1 -1
package/index.cjs CHANGED
@@ -20,247 +20,1055 @@
20
20
 
21
21
  var ethers = require('ethers');
22
22
  var zod = require('zod');
23
- require('@ethersproject/units');
24
23
  var bytes = require('@ethersproject/bytes');
25
24
  var address = require('@ethersproject/address');
26
25
  var bs58 = require('bs58');
26
+ require('@ethersproject/units');
27
27
 
28
28
  function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
29
29
 
30
30
  var bs58__default = /*#__PURE__*/_interopDefault(bs58);
31
31
 
32
32
  /**
33
- * Type-safe registry for managing and executing blockchain action handlers.
33
+ * Valid recoverability values for error handling strategies.
34
34
  *
35
- * Provides a centralized system for registering action handlers with full
36
- * TypeScript type safety, ensuring that handlers can only be registered
37
- * with compatible action keys and payload types. Supports both individual
38
- * handler registration and batch registration operations.
35
+ * - FATAL errors are thrown immediately (invalid inputs, insufficient funds)
36
+ * - RETRYABLE errors are returned when a flow fails to start but could work later
37
+ * - RESUMABLE errors are returned when a flow fails mid-execution but can be continued
38
+ */
39
+ const RECOVERABILITY_VALUES = [
40
+ 'RETRYABLE',
41
+ 'RESUMABLE',
42
+ 'FATAL',
43
+ ];
44
+ /**
45
+ * Error type constants for categorizing errors by origin.
46
+ *
47
+ * This const object provides a reference for error types, enabling
48
+ * IDE autocomplete and preventing typos when creating custom errors.
39
49
  *
40
50
  * @remarks
41
- * The registry uses a Map internally for O(1) lookups and maintains type
42
- * safety through generic constraints and careful type assertions. All
43
- * type assertions are validated at registration time to ensure runtime
44
- * type safety matches compile-time guarantees.
51
+ * While internal error definitions use string literals with type annotations
52
+ * for strict type safety, this constant is useful for developers creating
53
+ * custom error instances or checking error types programmatically.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { ERROR_TYPES, KitError } from '@core/errors'
58
+ *
59
+ * // Use for type checking
60
+ * if (error.type === ERROR_TYPES.BALANCE) {
61
+ * console.log('This is a balance error')
62
+ * }
63
+ * ```
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * // Use as reference when creating custom errors
68
+ * const error = new KitError({
69
+ * code: 9999,
70
+ * name: 'CUSTOM_ERROR',
71
+ * type: ERROR_TYPES.BALANCE, // IDE autocomplete works here
72
+ * recoverability: 'FATAL',
73
+ * message: 'Custom balance error'
74
+ * })
75
+ * ```
45
76
  */
46
- class ActionRegistry {
47
- actionHandlers = new Map();
77
+ const ERROR_TYPES = {
78
+ /** User input validation and parameter checking */
79
+ INPUT: 'INPUT',
80
+ /** Insufficient token balances and amount validation */
81
+ BALANCE: 'BALANCE',
82
+ /** On-chain execution: reverts, gas issues, transaction failures */
83
+ ONCHAIN: 'ONCHAIN',
84
+ /** Blockchain RPC provider issues and endpoint problems */
85
+ RPC: 'RPC',
86
+ /** Internet connectivity, DNS resolution, connection issues */
87
+ NETWORK: 'NETWORK',
88
+ };
89
+ /**
90
+ * Array of valid error type values for validation.
91
+ * Derived from ERROR_TYPES const object.
92
+ */
93
+ const ERROR_TYPE_VALUES = Object.values(ERROR_TYPES);
94
+
95
+ // Create mutable arrays for Zod enum validation
96
+ const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
97
+ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
98
+ /**
99
+ * Error code ranges for validation.
100
+ * Single source of truth for valid error code ranges.
101
+ */
102
+ const ERROR_CODE_RANGES = [
103
+ { min: 1000, max: 1999, type: 'INPUT' },
104
+ { min: 3000, max: 3999, type: 'NETWORK' },
105
+ { min: 4000, max: 4999, type: 'RPC' },
106
+ { min: 5000, max: 5999, type: 'ONCHAIN' },
107
+ { min: 9000, max: 9999, type: 'BALANCE' },
108
+ ];
109
+ /**
110
+ * Zod schema for validating ErrorDetails objects.
111
+ *
112
+ * This schema provides runtime validation for all ErrorDetails properties,
113
+ * ensuring type safety and proper error handling for JavaScript consumers.
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * import { errorDetailsSchema } from '@core/errors'
118
+ *
119
+ * const result = errorDetailsSchema.safeParse({
120
+ * code: 1001,
121
+ * name: 'INPUT_NETWORK_MISMATCH',
122
+ * type: 'INPUT',
123
+ * recoverability: 'FATAL',
124
+ * message: 'Source and destination networks must be different'
125
+ * })
126
+ *
127
+ * if (!result.success) {
128
+ * console.error('Validation failed:', result.error.issues)
129
+ * }
130
+ * ```
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * // Runtime error
135
+ * const result = errorDetailsSchema.safeParse({
136
+ * code: 9001,
137
+ * name: 'BALANCE_INSUFFICIENT_TOKEN',
138
+ * type: 'BALANCE',
139
+ * recoverability: 'FATAL',
140
+ * message: 'Insufficient USDC balance'
141
+ * })
142
+ * ```
143
+ */
144
+ const errorDetailsSchema = zod.z.object({
48
145
  /**
49
- * Register a type-safe action handler for a specific action key.
50
- *
51
- * Associates an action handler function with its corresponding action key,
52
- * ensuring compile-time type safety between the action and its expected
53
- * payload structure. The handler will be available for execution via
54
- * {@link executeAction}.
55
- *
56
- * @typeParam TActionKey - The specific action key being registered.
57
- * @param action - The action key to register the handler for.
58
- * @param handler - The handler function for processing this action type.
59
- * @returns Void.
60
- *
61
- * @throws Error When action parameter is not a valid string.
62
- * @throws TypeError When handler parameter is not a function.
63
- *
64
- * @example
65
- * ```typescript
66
- * import { ActionRegistry } from '@core/adapter'
67
- * import type { ActionHandler } from '@core/adapter'
68
- *
69
- * const registry = new ActionRegistry()
70
- *
71
- * // Register a CCTP deposit handler
72
- * const depositHandler: ActionHandler<'cctp.v2.depositForBurn'> = async (params, resolved) => {
73
- * console.log('Processing deposit:', params.amount)
74
- * return {
75
- * chainId: params.chainId,
76
- * data: '0x...',
77
- * to: '0x...',
78
- * value: '0'
79
- * }
80
- * }
81
- *
82
- * registry.registerHandler('cctp.v2.depositForBurn', depositHandler)
83
- * ```
146
+ * Numeric identifier following standardized ranges:
147
+ * - 1000-1999: INPUT errors - Parameter validation
148
+ * - 3000-3999: NETWORK errors - Connectivity issues
149
+ * - 4000-4999: RPC errors - Provider issues, gas estimation
150
+ * - 5000-5999: ONCHAIN errors - Transaction/simulation failures
151
+ * - 9000-9999: BALANCE errors - Insufficient funds
84
152
  */
85
- registerHandler(action, handler) {
86
- // Runtime validation for JavaScript consumers
87
- if (typeof action !== 'string' || action.length === 0) {
88
- throw new TypeError(`Action must be a non-empty string, received: ${typeof action}`);
89
- }
90
- if (typeof handler !== 'function') {
91
- throw new TypeError(`Handler must be a function, received: ${typeof handler}`);
92
- }
93
- // The handler is upcast to ActionHandler<ActionKeys> for storage,
94
- // but type safety is maintained at the call site through the generic constraint
95
- this.actionHandlers.set(action, handler);
153
+ code: zod.z
154
+ .number()
155
+ .int('Error code must be an integer')
156
+ .refine((code) => ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
157
+ message: 'Error code must be in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
158
+ }),
159
+ /** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
160
+ name: zod.z
161
+ .string()
162
+ .min(1, 'Error name must be a non-empty string')
163
+ .regex(/^[A-Z_][A-Z0-9_]*$/, 'Error name must match pattern: ^[A-Z_][A-Z0-9_]*$'),
164
+ /** Error category indicating where the error originated */
165
+ type: zod.z.enum(ERROR_TYPE_ARRAY, {
166
+ errorMap: () => ({
167
+ message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
168
+ }),
169
+ }),
170
+ /** Error handling strategy */
171
+ recoverability: zod.z.enum(RECOVERABILITY_ARRAY, {
172
+ errorMap: () => ({
173
+ message: 'Recoverability must be one of: RETRYABLE, RESUMABLE, FATAL',
174
+ }),
175
+ }),
176
+ /** User-friendly explanation with context */
177
+ message: zod.z
178
+ .string()
179
+ .min(1, 'Error message must be a non-empty string')
180
+ .max(1000, 'Error message must be 1000 characters or less'),
181
+ /** Raw error details, context, or the original error that caused this one. */
182
+ cause: zod.z
183
+ .object({
184
+ /** Free-form error payload from underlying system */
185
+ trace: zod.z.unknown().optional(),
186
+ })
187
+ .optional(),
188
+ });
189
+
190
+ /**
191
+ * Validates an ErrorDetails object using Zod schema.
192
+ *
193
+ * @param details - The object to validate
194
+ * @returns The validated ErrorDetails object
195
+ * @throws TypeError When validation fails
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * import { validateErrorDetails } from '@core/errors'
200
+ *
201
+ * try {
202
+ * const validDetails = validateErrorDetails({
203
+ * code: 1001,
204
+ * name: 'NETWORK_MISMATCH',
205
+ * recoverability: 'FATAL',
206
+ * message: 'Source and destination networks must be different'
207
+ * })
208
+ * } catch (error) {
209
+ * console.error('Validation failed:', error.message)
210
+ * }
211
+ * ```
212
+ */
213
+ function validateErrorDetails(details) {
214
+ const result = errorDetailsSchema.safeParse(details);
215
+ if (!result.success) {
216
+ const issues = result.error.issues
217
+ .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
218
+ .join(', ');
219
+ throw new TypeError(`Invalid ErrorDetails: ${issues}`);
96
220
  }
221
+ return result.data;
222
+ }
223
+
224
+ /**
225
+ * Maximum length for error messages in fallback validation errors.
226
+ *
227
+ * KitError enforces a 1000-character limit on error messages. When creating
228
+ * fallback validation errors that combine multiple Zod issues, we use 950
229
+ * characters to leave a 50-character buffer for:
230
+ * - The error message prefix ("Invalid bridge parameters: ")
231
+ * - Potential encoding differences or formatting overhead
232
+ * - Safety margin to prevent KitError constructor failures
233
+ *
234
+ * This ensures that even with concatenated issue summaries, the final message
235
+ * stays within KitError's constraints.
236
+ */
237
+ const MAX_MESSAGE_LENGTH = 950;
238
+
239
+ /**
240
+ * Structured error class for Stablecoin Kit operations.
241
+ *
242
+ * This class extends the native Error class while implementing the ErrorDetails
243
+ * interface, providing a consistent error format for programmatic handling
244
+ * across the Stablecoin Kits ecosystem. All properties are immutable to ensure
245
+ * error objects cannot be modified after creation.
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * import { KitError } from '@core/errors'
250
+ *
251
+ * const error = new KitError({
252
+ * code: 1001,
253
+ * name: 'INPUT_NETWORK_MISMATCH',
254
+ * recoverability: 'FATAL',
255
+ * message: 'Cannot bridge between mainnet and testnet'
256
+ * })
257
+ *
258
+ * if (error instanceof KitError) {
259
+ * console.log(`Error ${error.code}: ${error.name}`)
260
+ * // → "Error 1001: INPUT_NETWORK_MISMATCH"
261
+ * }
262
+ * ```
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * import { KitError } from '@core/errors'
267
+ *
268
+ * // Error with cause information
269
+ * const error = new KitError({
270
+ * code: 1002,
271
+ * name: 'INVALID_AMOUNT',
272
+ * recoverability: 'FATAL',
273
+ * message: 'Amount must be greater than zero',
274
+ * cause: {
275
+ * trace: { providedAmount: -100, minimumAmount: 0 }
276
+ * }
277
+ * })
278
+ *
279
+ * throw error
280
+ * ```
281
+ */
282
+ class KitError extends Error {
283
+ /** Numeric identifier following standardized ranges (1000+ for INPUT errors) */
284
+ code;
285
+ /** Human-readable ID (e.g., "NETWORK_MISMATCH") */
286
+ name;
287
+ /** Error category indicating where the error originated */
288
+ type;
289
+ /** Error handling strategy */
290
+ recoverability;
291
+ /** Raw error details, context, or the original error that caused this one. */
292
+ cause;
97
293
  /**
98
- * Register multiple action handlers in a single operation.
99
- *
100
- * Efficiently register multiple handlers from a record object, where keys
101
- * are action identifiers and values are their corresponding handler
102
- * functions. Provides a convenient way to bulk-register handlers while
103
- * maintaining type safety.
104
- *
105
- * @param handlers - A record mapping action keys to their handler functions.
106
- * @returns Void.
107
- *
108
- * @throws {Error} When handlers parameter is not a valid object.
109
- * @throws {Error} When any individual handler registration fails.
294
+ * Create a new KitError instance.
110
295
  *
111
- * @example
112
- * ```typescript
113
- * import { ActionRegistry } from '@core/adapter'
114
- * import type { ActionHandler, ActionHandlers } from '@core/adapter'
115
- *
116
- * const registry = new ActionRegistry()
117
- *
118
- * // Register multiple handlers at once
119
- * const tokenHandlers: ActionHandlers = {
120
- * 'token.approve': async (params, resolved) => ({
121
- * chainId: resolved.chain,
122
- * data: '0x095ea7b3...',
123
- * to: params.tokenAddress,
124
- * value: '0'
125
- * }),
126
- * 'token.transfer': async (params, resolved) => ({
127
- * chainId: resolved.chain,
128
- * data: '0xa9059cbb...',
129
- * to: params.tokenAddress,
130
- * value: '0'
131
- * })
132
- * }
133
- *
134
- * registry.registerHandlers(tokenHandlers)
135
- * console.log('Registered multiple token handlers')
136
- * ```
296
+ * @param details - The error details object containing all required properties.
297
+ * @throws \{TypeError\} When details parameter is missing or invalid.
137
298
  */
138
- registerHandlers(handlers) {
139
- // Runtime validation for JavaScript consumers
140
- if (typeof handlers !== 'object' || handlers === null) {
141
- throw new Error(`Handlers must be a non-null object, received: ${typeof handlers}`);
142
- }
143
- // Register each handler individually to benefit from per-handler validation
144
- for (const [action, handler] of Object.entries(handlers)) {
145
- this.registerHandler(action, handler);
299
+ constructor(details) {
300
+ // Truncate message if it exceeds maximum length to prevent validation errors
301
+ let message = details.message;
302
+ if (message.length > MAX_MESSAGE_LENGTH) {
303
+ message = `${message.slice(0, MAX_MESSAGE_LENGTH - 3)}...`;
146
304
  }
305
+ const truncatedDetails = { ...details, message };
306
+ // Validate input at runtime for JavaScript consumers using Zod
307
+ const validatedDetails = validateErrorDetails(truncatedDetails);
308
+ super(validatedDetails.message);
309
+ // Set properties as readonly at runtime
310
+ Object.defineProperties(this, {
311
+ name: {
312
+ value: validatedDetails.name,
313
+ writable: false,
314
+ enumerable: true,
315
+ configurable: false,
316
+ },
317
+ code: {
318
+ value: validatedDetails.code,
319
+ writable: false,
320
+ enumerable: true,
321
+ configurable: false,
322
+ },
323
+ type: {
324
+ value: validatedDetails.type,
325
+ writable: false,
326
+ enumerable: true,
327
+ configurable: false,
328
+ },
329
+ recoverability: {
330
+ value: validatedDetails.recoverability,
331
+ writable: false,
332
+ enumerable: true,
333
+ configurable: false,
334
+ },
335
+ ...(validatedDetails.cause && {
336
+ cause: {
337
+ value: validatedDetails.cause,
338
+ writable: false,
339
+ enumerable: true,
340
+ configurable: false,
341
+ },
342
+ }),
343
+ });
147
344
  }
148
- /**
149
- * Check whether a specific action is supported by this registry.
150
- *
151
- * Determine if a handler has been registered for the given action key.
152
- * Use this method to conditionally execute actions or provide appropriate
153
- * error messages when actions are not available.
154
- *
155
- * @param action - The action key to check for support.
156
- * @returns True if the action is supported, false otherwise.
157
- *
158
- * @throws {Error} When action parameter is not a valid string.
159
- *
160
- * @example
161
- * ```typescript
162
- * import { ActionRegistry } from '@core/adapter'
163
- *
164
- * const registry = new ActionRegistry()
165
- *
166
- * // Check if actions are supported before attempting to use them
167
- * if (registry.supportsAction('token.approve')) {
168
- * console.log('Token approval is supported')
169
- * } else {
170
- * console.log('Token approval not available')
171
- * }
172
- *
173
- * // Conditional logic based on support
174
- * const action = 'cctp.v2.depositForBurn'
175
- * if (registry.supportsAction(action)) {
176
- * // Safe to execute
177
- * console.log(`${action} is available`)
178
- * } else {
179
- * console.warn(`${action} is not registered`)
180
- * }
181
- * ```
182
- */
183
- supportsAction(action) {
184
- // Runtime validation for JavaScript consumers
185
- if (typeof action !== 'string' || action.length === 0) {
186
- throw new TypeError(`Action must be a non-empty string, received: ${typeof action}`);
187
- }
188
- return this.actionHandlers.has(action);
345
+ }
346
+
347
+ /**
348
+ * Standardized error code ranges for consistent categorization:
349
+ *
350
+ * - 1000-1999: INPUT errors - Parameter validation, input format errors
351
+ * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
352
+ * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
353
+ * - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
354
+ * - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
355
+ */
356
+ /**
357
+ * Standardized error definitions for INPUT type errors.
358
+ *
359
+ * Each entry combines the numeric error code, string name, and type
360
+ * to ensure consistency when creating error instances.
361
+ *
362
+ * Error codes follow a hierarchical numbering scheme where the first digit
363
+ * indicates the error category (1 = INPUT) and subsequent digits provide
364
+ * specific error identification within that category.
365
+ *
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * import { InputError } from '@core/errors'
370
+ *
371
+ * const error = new KitError({
372
+ * ...InputError.NETWORK_MISMATCH,
373
+ * recoverability: 'FATAL',
374
+ * message: 'Source and destination networks must be different'
375
+ * })
376
+ *
377
+ * // Access code, name, and type individually if needed
378
+ * console.log(InputError.NETWORK_MISMATCH.code) // 1001
379
+ * console.log(InputError.NETWORK_MISMATCH.name) // 'INPUT_NETWORK_MISMATCH'
380
+ * console.log(InputError.NETWORK_MISMATCH.type) // 'INPUT'
381
+ * ```
382
+ */
383
+ const InputError = {
384
+ /** Invalid or unsupported chain identifier */
385
+ INVALID_CHAIN: {
386
+ code: 1005,
387
+ name: 'INPUT_INVALID_CHAIN',
388
+ type: 'INPUT',
389
+ },
390
+ /** General validation failure for complex validation rules */
391
+ VALIDATION_FAILED: {
392
+ code: 1098,
393
+ name: 'INPUT_VALIDATION_FAILED',
394
+ type: 'INPUT',
395
+ },
396
+ };
397
+ /**
398
+ * Standardized error definitions for BALANCE type errors.
399
+ *
400
+ * BALANCE errors indicate insufficient funds or allowance issues
401
+ * that prevent transaction execution.
402
+ *
403
+ * @example
404
+ * ```typescript
405
+ * import { BalanceError } from '@core/errors'
406
+ *
407
+ * const error = new KitError({
408
+ * ...BalanceError.INSUFFICIENT_TOKEN,
409
+ * recoverability: 'FATAL',
410
+ * message: 'Insufficient USDC balance on Ethereum',
411
+ * cause: { trace: { required: '100', available: '50' } }
412
+ * })
413
+ * ```
414
+ */
415
+ const BalanceError = {
416
+ /** Insufficient token balance for transaction */
417
+ INSUFFICIENT_TOKEN: {
418
+ code: 9001,
419
+ name: 'BALANCE_INSUFFICIENT_TOKEN',
420
+ type: 'BALANCE',
421
+ },
422
+ /** Insufficient native token (ETH/SOL/etc) for gas fees */
423
+ INSUFFICIENT_GAS: {
424
+ code: 9002,
425
+ name: 'BALANCE_INSUFFICIENT_GAS',
426
+ type: 'BALANCE',
427
+ }};
428
+ /**
429
+ * Standardized error definitions for ONCHAIN type errors.
430
+ *
431
+ * ONCHAIN errors occur during transaction execution, simulation,
432
+ * or interaction with smart contracts on the blockchain.
433
+ *
434
+ * @example
435
+ * ```typescript
436
+ * import { OnchainError } from '@core/errors'
437
+ *
438
+ * const error = new KitError({
439
+ * ...OnchainError.SIMULATION_FAILED,
440
+ * recoverability: 'FATAL',
441
+ * message: 'Simulation failed: ERC20 transfer amount exceeds balance',
442
+ * cause: { trace: { reason: 'ERC20: transfer amount exceeds balance' } }
443
+ * })
444
+ * ```
445
+ */
446
+ const OnchainError = {
447
+ /** Transaction reverted on-chain after execution */
448
+ TRANSACTION_REVERTED: {
449
+ code: 5001,
450
+ name: 'ONCHAIN_TRANSACTION_REVERTED',
451
+ type: 'ONCHAIN',
452
+ },
453
+ /** Pre-flight transaction simulation failed */
454
+ SIMULATION_FAILED: {
455
+ code: 5002,
456
+ name: 'ONCHAIN_SIMULATION_FAILED',
457
+ type: 'ONCHAIN',
458
+ },
459
+ /** Transaction ran out of gas during execution */
460
+ OUT_OF_GAS: {
461
+ code: 5003,
462
+ name: 'ONCHAIN_OUT_OF_GAS',
463
+ type: 'ONCHAIN',
464
+ }};
465
+ /**
466
+ * Standardized error definitions for RPC type errors.
467
+ *
468
+ * RPC errors occur when communicating with blockchain RPC providers,
469
+ * including endpoint failures, invalid responses, and provider-specific issues.
470
+ *
471
+ * @example
472
+ * ```typescript
473
+ * import { RpcError } from '@core/errors'
474
+ *
475
+ * const error = new KitError({
476
+ * ...RpcError.ENDPOINT_ERROR,
477
+ * recoverability: 'RETRYABLE',
478
+ * message: 'RPC endpoint unavailable on Ethereum',
479
+ * cause: { trace: { endpoint: 'https://mainnet.infura.io' } }
480
+ * })
481
+ * ```
482
+ */
483
+ const RpcError = {
484
+ /** RPC endpoint returned error or is unavailable */
485
+ ENDPOINT_ERROR: {
486
+ code: 4001,
487
+ name: 'RPC_ENDPOINT_ERROR',
488
+ type: 'RPC',
489
+ }};
490
+ /**
491
+ * Standardized error definitions for NETWORK type errors.
492
+ *
493
+ * NETWORK errors indicate connectivity issues at the network layer,
494
+ * including DNS failures, connection timeouts, and unreachable endpoints.
495
+ *
496
+ * @example
497
+ * ```typescript
498
+ * import { NetworkError } from '@core/errors'
499
+ *
500
+ * const error = new KitError({
501
+ * ...NetworkError.CONNECTION_FAILED,
502
+ * recoverability: 'RETRYABLE',
503
+ * message: 'Failed to connect to Ethereum network',
504
+ * cause: { trace: { error: 'ECONNREFUSED' } }
505
+ * })
506
+ * ```
507
+ */
508
+ const NetworkError = {
509
+ /** Network connection failed or unreachable */
510
+ CONNECTION_FAILED: {
511
+ code: 3001,
512
+ name: 'NETWORK_CONNECTION_FAILED',
513
+ type: 'NETWORK',
514
+ }};
515
+
516
+ /**
517
+ * Creates error for invalid chain configuration.
518
+ *
519
+ * This error is thrown when the provided chain doesn't meet the required
520
+ * configuration or is not supported for the operation.
521
+ *
522
+ * @param chain - The invalid chain name or identifier
523
+ * @param reason - Specific reason why chain is invalid
524
+ * @returns KitError with chain details and validation rule
525
+ *
526
+ * @example
527
+ * ```typescript
528
+ * import { createInvalidChainError } from '@core/errors'
529
+ *
530
+ * throw createInvalidChainError('UnknownChain', 'Chain is not supported by this bridge')
531
+ * // Message: "Invalid chain 'UnknownChain': Chain is not supported by this bridge"
532
+ * ```
533
+ */
534
+ function createInvalidChainError(chain, reason) {
535
+ const errorDetails = {
536
+ ...InputError.INVALID_CHAIN,
537
+ recoverability: 'FATAL',
538
+ message: `Invalid chain '${chain}': ${reason}`,
539
+ cause: {
540
+ trace: { chain, reason },
541
+ },
542
+ };
543
+ return new KitError(errorDetails);
544
+ }
545
+ /**
546
+ * Creates a KitError from a Zod validation error with detailed error information.
547
+ *
548
+ * This factory function converts Zod validation failures into standardized KitError
549
+ * instances with INPUT_VALIDATION_FAILED code (1098). It extracts all Zod issues
550
+ * and includes them in both the error message and trace for debugging, providing
551
+ * developers with comprehensive validation feedback.
552
+ *
553
+ * The error message includes all validation errors concatenated with semicolons.
554
+ *
555
+ * @param zodError - The Zod validation error containing one or more validation issues
556
+ * @param context - Context string describing what was being validated (e.g., 'bridge parameters', 'user input')
557
+ * @returns KitError with INPUT_VALIDATION_FAILED code and structured validation details
558
+ *
559
+ * @example
560
+ * ```typescript
561
+ * import { createValidationErrorFromZod } from '@core/errors'
562
+ * import { z } from 'zod'
563
+ *
564
+ * const schema = z.object({
565
+ * name: z.string().min(3),
566
+ * age: z.number().positive()
567
+ * })
568
+ *
569
+ * const result = schema.safeParse({ name: 'ab', age: -1 })
570
+ * if (!result.success) {
571
+ * throw createValidationErrorFromZod(result.error, 'user data')
572
+ * }
573
+ * // Throws: KitError with message:
574
+ * // "Invalid user data: name: String must contain at least 3 character(s); age: Number must be greater than 0"
575
+ * // And cause.trace.validationErrors containing all validation errors as an array
576
+ * ```
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * // Usage in validation functions
581
+ * import { createValidationErrorFromZod } from '@core/errors'
582
+ *
583
+ * function validateBridgeParams(params: unknown): asserts params is BridgeParams {
584
+ * const result = bridgeParamsSchema.safeParse(params)
585
+ * if (!result.success) {
586
+ * throw createValidationErrorFromZod(result.error, 'bridge parameters')
587
+ * }
588
+ * }
589
+ * ```
590
+ */
591
+ function createValidationErrorFromZod(zodError, context) {
592
+ // Format each Zod issue as "path: message"
593
+ const validationErrors = zodError.issues.map((issue) => {
594
+ const path = issue.path.length > 0 ? `${issue.path.join('.')}: ` : '';
595
+ return `${path}${issue.message}`;
596
+ });
597
+ // Join all errors with semicolons to show complete validation feedback
598
+ const issueSummary = validationErrors.join('; ');
599
+ const allErrors = issueSummary || 'Invalid Input';
600
+ // Build full message from context and validation errors
601
+ const fullMessage = `Invalid ${context}: ${allErrors}`;
602
+ const errorDetails = {
603
+ ...InputError.VALIDATION_FAILED,
604
+ recoverability: 'FATAL',
605
+ message: fullMessage,
606
+ cause: {
607
+ trace: {
608
+ validationErrors, // Array of formatted error strings for display
609
+ zodError: zodError.message, // Original Zod error message
610
+ zodIssues: zodError.issues, // Full Zod issues array for debugging
611
+ },
612
+ },
613
+ };
614
+ return new KitError(errorDetails);
615
+ }
616
+
617
+ /**
618
+ * Creates error for insufficient token balance.
619
+ *
620
+ * This error is thrown when a wallet does not have enough tokens to
621
+ * complete a transaction. The error is FATAL as it requires user
622
+ * intervention to add funds.
623
+ *
624
+ * @param chain - The blockchain network where the balance check failed
625
+ * @param token - The token symbol (e.g., 'USDC', 'ETH')
626
+ * @param rawError - The original error from the underlying system (optional)
627
+ * @returns KitError with insufficient token balance details
628
+ *
629
+ * @example
630
+ * ```typescript
631
+ * import { createInsufficientTokenBalanceError } from '@core/errors'
632
+ *
633
+ * throw createInsufficientTokenBalanceError('Ethereum', 'USDC')
634
+ * // Message: "Insufficient USDC balance on Ethereum"
635
+ * ```
636
+ *
637
+ * @example
638
+ * ```typescript
639
+ * // With raw error for debugging
640
+ * try {
641
+ * await transfer(...)
642
+ * } catch (error) {
643
+ * throw createInsufficientTokenBalanceError('Base', 'USDC', error)
644
+ * }
645
+ * ```
646
+ */
647
+ function createInsufficientTokenBalanceError(chain, token, rawError) {
648
+ return new KitError({
649
+ ...BalanceError.INSUFFICIENT_TOKEN,
650
+ recoverability: 'FATAL',
651
+ message: `Insufficient ${token} balance on ${chain}`,
652
+ cause: {
653
+ trace: {
654
+ chain,
655
+ token,
656
+ rawError,
657
+ },
658
+ },
659
+ });
660
+ }
661
+ /**
662
+ * Creates error for insufficient gas funds.
663
+ *
664
+ * This error is thrown when a wallet does not have enough native tokens
665
+ * (ETH, SOL, etc.) to pay for transaction gas fees. The error is FATAL
666
+ * as it requires user intervention to add gas funds.
667
+ *
668
+ * @param chain - The blockchain network where the gas check failed
669
+ * @param rawError - The original error from the underlying system (optional)
670
+ * @returns KitError with insufficient gas details
671
+ *
672
+ * @example
673
+ * ```typescript
674
+ * import { createInsufficientGasError } from '@core/errors'
675
+ *
676
+ * throw createInsufficientGasError('Ethereum')
677
+ * // Message: "Insufficient gas funds on Ethereum"
678
+ * ```
679
+ */
680
+ function createInsufficientGasError(chain, rawError) {
681
+ return new KitError({
682
+ ...BalanceError.INSUFFICIENT_GAS,
683
+ recoverability: 'FATAL',
684
+ message: `Insufficient gas funds on ${chain}`,
685
+ cause: {
686
+ trace: {
687
+ chain,
688
+ rawError,
689
+ },
690
+ },
691
+ });
692
+ }
693
+
694
+ /**
695
+ * Creates error for transaction simulation failures.
696
+ *
697
+ * This error is thrown when a pre-flight transaction simulation fails,
698
+ * typically due to contract logic that would revert. The error is FATAL
699
+ * as it indicates the transaction would fail if submitted.
700
+ *
701
+ * @param chain - The blockchain network where the simulation failed
702
+ * @param reason - The reason for simulation failure (e.g., revert message)
703
+ * @param rawError - The original error from the underlying system (optional)
704
+ * @returns KitError with simulation failure details
705
+ *
706
+ * @example
707
+ * ```typescript
708
+ * import { createSimulationFailedError } from '@core/errors'
709
+ *
710
+ * throw createSimulationFailedError('Ethereum', 'ERC20: insufficient allowance')
711
+ * // Message: "Simulation failed on Ethereum: ERC20: insufficient allowance"
712
+ * ```
713
+ */
714
+ function createSimulationFailedError(chain, reason, rawError) {
715
+ return new KitError({
716
+ ...OnchainError.SIMULATION_FAILED,
717
+ recoverability: 'FATAL',
718
+ message: `Simulation failed on ${chain}: ${reason}`,
719
+ cause: {
720
+ trace: {
721
+ chain,
722
+ reason,
723
+ rawError,
724
+ },
725
+ },
726
+ });
727
+ }
728
+ /**
729
+ * Creates error for transaction reverts.
730
+ *
731
+ * This error is thrown when a transaction is submitted and confirmed
732
+ * but reverts on-chain. The error is FATAL as it indicates the
733
+ * transaction executed but failed.
734
+ *
735
+ * @param chain - The blockchain network where the transaction reverted
736
+ * @param reason - The reason for the revert (e.g., revert message)
737
+ * @param rawError - The original error from the underlying system (optional)
738
+ * @returns KitError with transaction revert details
739
+ *
740
+ * @example
741
+ * ```typescript
742
+ * import { createTransactionRevertedError } from '@core/errors'
743
+ *
744
+ * throw createTransactionRevertedError('Base', 'Slippage exceeded')
745
+ * // Message: "Transaction reverted on Base: Slippage exceeded"
746
+ * ```
747
+ */
748
+ function createTransactionRevertedError(chain, reason, rawError) {
749
+ return new KitError({
750
+ ...OnchainError.TRANSACTION_REVERTED,
751
+ recoverability: 'FATAL',
752
+ message: `Transaction reverted on ${chain}: ${reason}`,
753
+ cause: {
754
+ trace: {
755
+ chain,
756
+ reason,
757
+ rawError,
758
+ },
759
+ },
760
+ });
761
+ }
762
+ /**
763
+ * Creates error for out of gas failures.
764
+ *
765
+ * This error is thrown when a transaction runs out of gas during execution.
766
+ * The error is FATAL as it requires adjusting gas limits or transaction logic.
767
+ *
768
+ * @param chain - The blockchain network where the transaction ran out of gas
769
+ * @param rawError - The original error from the underlying system (optional)
770
+ * @returns KitError with out of gas details
771
+ *
772
+ * @example
773
+ * ```typescript
774
+ * import { createOutOfGasError } from '@core/errors'
775
+ *
776
+ * throw createOutOfGasError('Polygon')
777
+ * // Message: "Transaction ran out of gas on Polygon"
778
+ * ```
779
+ */
780
+ function createOutOfGasError(chain, rawError) {
781
+ return new KitError({
782
+ ...OnchainError.OUT_OF_GAS,
783
+ recoverability: 'FATAL',
784
+ message: `Transaction ran out of gas on ${chain}`,
785
+ cause: {
786
+ trace: {
787
+ chain,
788
+ rawError,
789
+ },
790
+ },
791
+ });
792
+ }
793
+
794
+ /**
795
+ * Creates error for RPC endpoint failures.
796
+ *
797
+ * This error is thrown when an RPC provider endpoint fails, returns an error,
798
+ * or is unavailable. The error is RETRYABLE as RPC issues are often temporary.
799
+ *
800
+ * @param chain - The blockchain network where the RPC error occurred
801
+ * @param rawError - The original error from the underlying system (optional)
802
+ * @returns KitError with RPC endpoint error details
803
+ *
804
+ * @example
805
+ * ```typescript
806
+ * import { createRpcEndpointError } from '@core/errors'
807
+ *
808
+ * throw createRpcEndpointError('Ethereum')
809
+ * // Message: "RPC endpoint error on Ethereum"
810
+ * ```
811
+ */
812
+ function createRpcEndpointError(chain, rawError) {
813
+ return new KitError({
814
+ ...RpcError.ENDPOINT_ERROR,
815
+ recoverability: 'RETRYABLE',
816
+ message: `RPC endpoint error on ${chain}`,
817
+ cause: {
818
+ trace: {
819
+ chain,
820
+ rawError,
821
+ },
822
+ },
823
+ });
824
+ }
825
+
826
+ /**
827
+ * Creates error for network connection failures.
828
+ *
829
+ * This error is thrown when network connectivity issues prevent reaching
830
+ * the blockchain network. The error is RETRYABLE as network issues are
831
+ * often temporary.
832
+ *
833
+ * @param chain - The blockchain network where the connection failed
834
+ * @param rawError - The original error from the underlying system (optional)
835
+ * @returns KitError with network connection error details
836
+ *
837
+ * @example
838
+ * ```typescript
839
+ * import { createNetworkConnectionError } from '@core/errors'
840
+ *
841
+ * throw createNetworkConnectionError('Ethereum')
842
+ * // Message: "Network connection failed for Ethereum"
843
+ * ```
844
+ */
845
+ function createNetworkConnectionError(chain, rawError) {
846
+ return new KitError({
847
+ ...NetworkError.CONNECTION_FAILED,
848
+ recoverability: 'RETRYABLE',
849
+ message: `Network connection failed for ${chain}`,
850
+ cause: {
851
+ trace: {
852
+ chain,
853
+ rawError,
854
+ },
855
+ },
856
+ });
857
+ }
858
+
859
+ /**
860
+ * Parses raw blockchain errors into structured KitError instances.
861
+ *
862
+ * This function uses pattern matching to identify common blockchain error
863
+ * types and converts them into standardized KitError format. It handles
864
+ * errors from viem, ethers, Solana web3.js, and other blockchain libraries.
865
+ *
866
+ * The parser recognizes 5 main error patterns:
867
+ * 1. Insufficient balance errors
868
+ * 2. Simulation/execution reverted errors
869
+ * 3. Gas-related errors
870
+ * 4. Network connectivity errors
871
+ * 5. RPC provider errors
872
+ *
873
+ * @param error - The raw error from the blockchain library
874
+ * @param context - Context information including chain and optional token
875
+ * @returns A structured KitError instance
876
+ *
877
+ * @example
878
+ * ```typescript
879
+ * import { parseBlockchainError } from '@core/errors'
880
+ *
881
+ * try {
882
+ * await walletClient.sendTransaction(...)
883
+ * } catch (error) {
884
+ * throw parseBlockchainError(error, {
885
+ * chain: 'Ethereum',
886
+ * token: 'USDC',
887
+ * operation: 'transfer'
888
+ * })
889
+ * }
890
+ * ```
891
+ *
892
+ * @example
893
+ * ```typescript
894
+ * // Minimal usage
895
+ * try {
896
+ * await connection.sendTransaction(...)
897
+ * } catch (error) {
898
+ * throw parseBlockchainError(error, { chain: 'Solana' })
899
+ * }
900
+ * ```
901
+ */
902
+ function parseBlockchainError(error, context) {
903
+ const msg = extractMessage(error);
904
+ const token = context.token ?? 'token';
905
+ // Pattern 1: Insufficient balance errors
906
+ // Matches balance-related errors from ERC20 contracts, native transfers, and Solana programs
907
+ if (/transfer amount exceeds balance|insufficient (balance|funds)|burn amount exceeded/i.test(msg)) {
908
+ return createInsufficientTokenBalanceError(context.chain, token, error);
189
909
  }
190
- /**
191
- * Execute a registered action handler with type-safe parameters.
192
- *
193
- * Look up and execute the handler associated with the given action key,
194
- * passing the provided parameters and context, returning the resulting prepared
195
- * chain request. TypeScript ensures the parameters match the expected
196
- * structure for the specified action.
197
- *
198
- * @typeParam TActionKey - The specific action key being executed.
199
- * @param action - The action key identifying which handler to execute.
200
- * @param params - The parameters to pass to the action handler.
201
- * @param context - The resolved operation context with concrete chain and address values.
202
- * @returns A promise resolving to the prepared chain request.
203
- *
204
- * @throws {Error} When action parameter is not a valid string.
205
- * @throws {Error} When no handler is registered for the specified action.
206
- * @throws {Error} When the handler execution fails.
207
- *
208
- * @example
209
- * ```typescript
210
- * import { ActionRegistry } from '@core/adapter'
211
- * import type { ChainEnum } from '@core/chains'
212
- *
213
- * const registry = new ActionRegistry()
214
- *
215
- * // First register a handler
216
- * registry.registerHandler('token.approve', async (params, context) => ({
217
- * chainId: context.chain, // Always defined
218
- * data: '0x095ea7b3...',
219
- * to: params.tokenAddress,
220
- * value: '0'
221
- * }))
222
- *
223
- * // Execute the action with resolved context (typically called from adapter.prepareAction)
224
- * const resolvedContext = { chain: 'Base', address: '0x123...' }
225
- * const result = await registry.executeAction('token.approve', {
226
- * chainId: ChainEnum.Ethereum,
227
- * tokenAddress: '0xA0b86a33E6441c8C1c7C16e4c5e3e5b5e4c5e3e5b5e4c5e',
228
- * delegate: '0x1234567890123456789012345678901234567890',
229
- * amount: '1000000'
230
- * }, resolvedContext)
231
- *
232
- * console.log('Transaction prepared:', result.data)
233
- * ```
234
- */
235
- async executeAction(action, params, context) {
236
- // Runtime validation for JavaScript consumers
237
- if (typeof action !== 'string' || action.length === 0) {
238
- throw new TypeError(`Action must be a non-empty string, received: ${typeof action}`);
910
+ // Pattern 2: Simulation and execution reverts
911
+ // Matches contract revert errors and simulation failures
912
+ if (/execution reverted|simulation failed|transaction reverted|transaction failed/i.test(msg)) {
913
+ const reason = extractRevertReason(msg) ?? 'Transaction reverted';
914
+ // Distinguish between simulation failures and transaction reverts
915
+ // "simulation failed" or "eth_call" indicates pre-flight simulation
916
+ // "transaction failed" or context.operation === 'transaction' indicates post-execution
917
+ if (/simulation failed/i.test(msg) || context.operation === 'simulation') {
918
+ return createSimulationFailedError(context.chain, reason, error);
239
919
  }
240
- const handler = this.actionHandlers.get(action);
241
- if (!handler) {
242
- throw new Error(`Action ${action} is not supported`);
243
- }
244
- try {
245
- // Type safety is guaranteed by the registration process
246
- return await handler(params, context);
920
+ // Transaction execution failures or reverts
921
+ return createTransactionRevertedError(context.chain, reason, error);
922
+ }
923
+ // Pattern 3: Gas-related errors
924
+ // Matches gas estimation failures and gas exhaustion
925
+ // Check specific patterns first, then generic "gas" patterns
926
+ // Gas estimation failures are RPC issues
927
+ if (/gas estimation failed|cannot estimate gas/i.test(msg)) {
928
+ return createRpcEndpointError(context.chain, error);
929
+ }
930
+ // Gas exhaustion errors
931
+ // Use specific patterns without wildcards to avoid ReDoS
932
+ if (/out of gas|gas limit exceeded|exceeds block gas limit/i.test(msg)) {
933
+ return createOutOfGasError(context.chain, error);
934
+ }
935
+ // Insufficient funds for gas
936
+ if (/insufficient funds for gas/i.test(msg)) {
937
+ return createInsufficientGasError(context.chain, error);
938
+ }
939
+ // Pattern 4: Network connectivity errors
940
+ // Matches connection failures, DNS errors, and timeouts
941
+ if (/connection (refused|failed)|network|timeout|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
942
+ return createNetworkConnectionError(context.chain, error);
943
+ }
944
+ // Pattern 5: RPC provider errors
945
+ // Matches RPC endpoint errors, invalid responses, and rate limits
946
+ if (/rpc|invalid response|rate limit|too many requests/i.test(msg)) {
947
+ return createRpcEndpointError(context.chain, error);
948
+ }
949
+ // Fallback based on operation context
950
+ // Gas-related operations are RPC calls
951
+ if (context.operation === 'estimateGas' ||
952
+ context.operation === 'getGasPrice') {
953
+ return createRpcEndpointError(context.chain, error);
954
+ }
955
+ // Fallback for unrecognized errors
956
+ // Defaults to simulation failed as transaction execution is the most common failure point
957
+ return createSimulationFailedError(context.chain, msg.length > 0 ? msg : 'Unknown error', error);
958
+ }
959
+ /**
960
+ * Type guard to check if error has Solana-Kit structure with logs.
961
+ *
962
+ * Checks if the error object contains a context with logs array,
963
+ * which is the structure used by Solana Kit errors.
964
+ *
965
+ * @param error - Unknown error to check
966
+ * @returns True if error has Solana-Kit logs structure
967
+ */
968
+ function hasSolanaLogs(error) {
969
+ return (error !== null &&
970
+ typeof error === 'object' &&
971
+ 'context' in error &&
972
+ error.context !== null &&
973
+ typeof error.context === 'object' &&
974
+ 'logs' in error.context &&
975
+ Array.isArray(error.context.logs));
976
+ }
977
+ /**
978
+ * Extracts a human-readable error message from various error types.
979
+ *
980
+ * Handles Error objects, string errors, objects with message properties,
981
+ * Solana-Kit errors with context logs, and falls back to string representation.
982
+ * For Solana-Kit errors, extracts Anchor error messages from transaction logs.
983
+ *
984
+ * @param error - Unknown error to extract message from
985
+ * @returns Extracted error message string
986
+ *
987
+ * @example
988
+ * ```typescript
989
+ * const msg1 = extractMessage(new Error('test')) // 'test'
990
+ * const msg2 = extractMessage('string error') // 'string error'
991
+ * const msg3 = extractMessage({ message: 'obj' }) // 'obj'
992
+ * ```
993
+ */
994
+ function extractMessage(error) {
995
+ // Check for Solana-Kit errors with context.logs
996
+ if (hasSolanaLogs(error)) {
997
+ // Extract Anchor error message from logs
998
+ const anchorLog = error.context.logs.find((log) => log.includes('AnchorError') || log.includes('Error Message'));
999
+ if (anchorLog !== undefined) {
1000
+ // Return the anchor error log which contains the detailed message
1001
+ return anchorLog;
247
1002
  }
248
- catch (error) {
249
- // Re-throw with context for better debugging
250
- const message = error instanceof Error ? error.message : String(error);
251
- throw new Error(`Failed to execute action ${action}: ${message}`);
1003
+ }
1004
+ if (error instanceof Error) {
1005
+ return error.message;
1006
+ }
1007
+ if (typeof error === 'string') {
1008
+ return error;
1009
+ }
1010
+ if (typeof error === 'object' && error !== null && 'message' in error) {
1011
+ return String(error.message);
1012
+ }
1013
+ return String(error);
1014
+ }
1015
+ /**
1016
+ * Extracts the revert reason from an error message.
1017
+ *
1018
+ * Attempts to parse out the meaningful reason from execution revert errors,
1019
+ * removing common prefixes like "execution reverted:" or "reverted:".
1020
+ *
1021
+ * @param msg - The error message to extract from
1022
+ * @returns The extracted revert reason, or null if not found
1023
+ *
1024
+ * @example
1025
+ * ```typescript
1026
+ * const reason = extractRevertReason(
1027
+ * 'execution reverted: ERC20: transfer amount exceeds balance'
1028
+ * )
1029
+ * // Returns: 'ERC20: transfer amount exceeds balance'
1030
+ * ```
1031
+ *
1032
+ * @example
1033
+ * ```typescript
1034
+ * const reason = extractRevertReason(
1035
+ * 'Simulation failed: Execution reverted with reason: Insufficient allowance'
1036
+ * )
1037
+ * // Returns: 'Insufficient allowance'
1038
+ * ```
1039
+ */
1040
+ function extractRevertReason(msg) {
1041
+ // Try to extract reason after "execution reverted:" or "reason:"
1042
+ // Use [^\n.]+ instead of .+? to avoid ReDoS vulnerability
1043
+ const patterns = [
1044
+ /(?:execution reverted|reverted):\s*([^\n.]+)/i,
1045
+ /reason:\s*([^\n.]+)/i,
1046
+ /with reason:\s*([^\n.]+)/i,
1047
+ ];
1048
+ for (const pattern of patterns) {
1049
+ const match = pattern.exec(msg);
1050
+ const extractedReason = match?.at(1);
1051
+ if (extractedReason !== undefined && extractedReason.length > 0) {
1052
+ return extractedReason.trim();
252
1053
  }
253
1054
  }
1055
+ return null;
254
1056
  }
255
1057
 
256
1058
  // -----------------------------------------------------------------------------
257
1059
  // Blockchain Enum
258
1060
  // -----------------------------------------------------------------------------
259
1061
  /**
260
- * Enumeration of all blockchains supported by this library.
1062
+ * Enumeration of all blockchains known to this library.
1063
+ *
1064
+ * This enum contains every blockchain that has a chain definition, regardless
1065
+ * of whether bridging is currently supported. For chains that support bridging
1066
+ * via CCTPv2, see {@link BridgeChain}.
1067
+ *
261
1068
  * @enum
262
1069
  * @category Enums
263
- * @description Provides string identifiers for each supported blockchain.
1070
+ * @description Provides string identifiers for each blockchain with a definition.
1071
+ * @see {@link BridgeChain} for the subset of chains that support CCTPv2 bridging.
264
1072
  */
265
1073
  exports.Blockchain = void 0;
266
1074
  (function (Blockchain) {
@@ -320,6 +1128,95 @@ exports.Blockchain = void 0;
320
1128
  Blockchain["ZKSync_Era"] = "ZKSync_Era";
321
1129
  Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
322
1130
  })(exports.Blockchain || (exports.Blockchain = {}));
1131
+ // -----------------------------------------------------------------------------
1132
+ // Bridge Chain Enum (CCTPv2 Supported Chains)
1133
+ // -----------------------------------------------------------------------------
1134
+ /**
1135
+ * Enumeration of blockchains that support cross-chain bridging via CCTPv2.
1136
+ *
1137
+ * The enum is derived from the full {@link Blockchain} enum but filtered to only
1138
+ * include chains with active CCTPv2 support. When new chains gain CCTPv2 support,
1139
+ * they are added to this enum.
1140
+ *
1141
+ * @enum
1142
+ * @category Enums
1143
+ *
1144
+ * @remarks
1145
+ * - This enum is the **canonical source** of bridging-supported chains.
1146
+ * - Use this enum (or its string literals) in `kit.bridge()` calls for type safety.
1147
+ * - Attempting to use a chain not in this enum will produce a TypeScript compile error.
1148
+ *
1149
+ * @example
1150
+ * ```typescript
1151
+ * import { BridgeKit, BridgeChain } from '@circle-fin/bridge-kit'
1152
+ *
1153
+ * const kit = new BridgeKit()
1154
+ *
1155
+ * // ✅ Valid - autocomplete suggests only supported chains
1156
+ * await kit.bridge({
1157
+ * from: { adapter, chain: BridgeChain.Ethereum },
1158
+ * to: { adapter, chain: BridgeChain.Base },
1159
+ * amount: '100'
1160
+ * })
1161
+ *
1162
+ * // ✅ Also valid - string literals work with autocomplete
1163
+ * await kit.bridge({
1164
+ * from: { adapter, chain: 'Ethereum_Sepolia' },
1165
+ * to: { adapter, chain: 'Base_Sepolia' },
1166
+ * amount: '100'
1167
+ * })
1168
+ *
1169
+ * // ❌ Compile error - Algorand is not in BridgeChain
1170
+ * await kit.bridge({
1171
+ * from: { adapter, chain: 'Algorand' }, // TypeScript error!
1172
+ * to: { adapter, chain: 'Base' },
1173
+ * amount: '100'
1174
+ * })
1175
+ * ```
1176
+ *
1177
+ * @see {@link Blockchain} for the complete list of all known blockchains.
1178
+ * @see {@link BridgeChainIdentifier} for the type that accepts these values.
1179
+ */
1180
+ var BridgeChain;
1181
+ (function (BridgeChain) {
1182
+ // Mainnet chains with CCTPv2 support
1183
+ BridgeChain["Arbitrum"] = "Arbitrum";
1184
+ BridgeChain["Avalanche"] = "Avalanche";
1185
+ BridgeChain["Base"] = "Base";
1186
+ BridgeChain["Codex"] = "Codex";
1187
+ BridgeChain["Ethereum"] = "Ethereum";
1188
+ BridgeChain["HyperEVM"] = "HyperEVM";
1189
+ BridgeChain["Ink"] = "Ink";
1190
+ BridgeChain["Linea"] = "Linea";
1191
+ BridgeChain["Optimism"] = "Optimism";
1192
+ BridgeChain["Plume"] = "Plume";
1193
+ BridgeChain["Polygon"] = "Polygon";
1194
+ BridgeChain["Sei"] = "Sei";
1195
+ BridgeChain["Solana"] = "Solana";
1196
+ BridgeChain["Sonic"] = "Sonic";
1197
+ BridgeChain["Unichain"] = "Unichain";
1198
+ BridgeChain["World_Chain"] = "World_Chain";
1199
+ BridgeChain["XDC"] = "XDC";
1200
+ // Testnet chains with CCTPv2 support
1201
+ BridgeChain["Arc_Testnet"] = "Arc_Testnet";
1202
+ BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
1203
+ BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
1204
+ BridgeChain["Base_Sepolia"] = "Base_Sepolia";
1205
+ BridgeChain["Codex_Testnet"] = "Codex_Testnet";
1206
+ BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
1207
+ BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
1208
+ BridgeChain["Ink_Testnet"] = "Ink_Testnet";
1209
+ BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
1210
+ BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
1211
+ BridgeChain["Plume_Testnet"] = "Plume_Testnet";
1212
+ BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
1213
+ BridgeChain["Sei_Testnet"] = "Sei_Testnet";
1214
+ BridgeChain["Solana_Devnet"] = "Solana_Devnet";
1215
+ BridgeChain["Sonic_Testnet"] = "Sonic_Testnet";
1216
+ BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
1217
+ BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
1218
+ BridgeChain["XDC_Apothem"] = "XDC_Apothem";
1219
+ })(BridgeChain || (BridgeChain = {}));
323
1220
 
324
1221
  /**
325
1222
  * Helper function to define a chain with proper TypeScript typing.
@@ -2608,6 +3505,44 @@ zod.z.union([
2608
3505
  zod.z.nativeEnum(exports.Blockchain),
2609
3506
  chainDefinitionSchema$1,
2610
3507
  ]);
3508
+ /**
3509
+ * Zod schema for validating bridge chain identifiers.
3510
+ *
3511
+ * This schema validates that the provided chain is supported for CCTPv2 bridging.
3512
+ * It accepts either a BridgeChain enum value, a string matching a BridgeChain value,
3513
+ * or a ChainDefinition for a supported chain.
3514
+ *
3515
+ * Use this schema when validating chain parameters for bridge operations to ensure
3516
+ * only CCTPv2-supported chains are accepted at runtime.
3517
+ *
3518
+ * @example
3519
+ * ```typescript
3520
+ * import { bridgeChainIdentifierSchema } from '@core/chains/validation'
3521
+ * import { BridgeChain, Chains } from '@core/chains'
3522
+ *
3523
+ * // Valid - BridgeChain enum value
3524
+ * bridgeChainIdentifierSchema.parse(BridgeChain.Ethereum)
3525
+ *
3526
+ * // Valid - string literal
3527
+ * bridgeChainIdentifierSchema.parse('Base_Sepolia')
3528
+ *
3529
+ * // Valid - ChainDefinition (validated by CCTP support)
3530
+ * bridgeChainIdentifierSchema.parse(Chains.Solana)
3531
+ *
3532
+ * // Invalid - Algorand is not in BridgeChain (throws ZodError)
3533
+ * bridgeChainIdentifierSchema.parse('Algorand')
3534
+ * ```
3535
+ *
3536
+ * @see {@link BridgeChain} for the enum of supported chains.
3537
+ */
3538
+ zod.z.union([
3539
+ zod.z.string().refine((val) => val in BridgeChain, (val) => ({
3540
+ message: `Chain "${val}" is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3541
+ })),
3542
+ chainDefinitionSchema$1.refine((chainDef) => chainDef.chain in BridgeChain, (chainDef) => ({
3543
+ message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3544
+ })),
3545
+ ]);
2611
3546
 
2612
3547
  /**
2613
3548
  * Get all supported EVM chain definitions.
@@ -2755,7 +3690,235 @@ const resolveCCTPV2ContractAddress = (chain, contractType) => {
2755
3690
  throw new Error(`Unsupported CCTP v2 contract type on chain ${chain.name}. Expected "split" or "merged", but received '${unknownContract.type ?? 'unknown'}'.`);
2756
3691
  }
2757
3692
  }
2758
- };
3693
+ };
3694
+
3695
+ /**
3696
+ * Type-safe registry for managing and executing blockchain action handlers.
3697
+ *
3698
+ * Provides a centralized system for registering action handlers with full
3699
+ * TypeScript type safety, ensuring that handlers can only be registered
3700
+ * with compatible action keys and payload types. Supports both individual
3701
+ * handler registration and batch registration operations.
3702
+ *
3703
+ * @remarks
3704
+ * The registry uses a Map internally for O(1) lookups and maintains type
3705
+ * safety through generic constraints and careful type assertions. All
3706
+ * type assertions are validated at registration time to ensure runtime
3707
+ * type safety matches compile-time guarantees.
3708
+ */
3709
+ class ActionRegistry {
3710
+ actionHandlers = new Map();
3711
+ /**
3712
+ * Register a type-safe action handler for a specific action key.
3713
+ *
3714
+ * Associates an action handler function with its corresponding action key,
3715
+ * ensuring compile-time type safety between the action and its expected
3716
+ * payload structure. The handler will be available for execution via
3717
+ * {@link executeAction}.
3718
+ *
3719
+ * @typeParam TActionKey - The specific action key being registered.
3720
+ * @param action - The action key to register the handler for.
3721
+ * @param handler - The handler function for processing this action type.
3722
+ * @returns Void.
3723
+ *
3724
+ * @throws Error When action parameter is not a valid string.
3725
+ * @throws TypeError When handler parameter is not a function.
3726
+ *
3727
+ * @example
3728
+ * ```typescript
3729
+ * import { ActionRegistry } from '@core/adapter'
3730
+ * import type { ActionHandler } from '@core/adapter'
3731
+ *
3732
+ * const registry = new ActionRegistry()
3733
+ *
3734
+ * // Register a CCTP deposit handler
3735
+ * const depositHandler: ActionHandler<'cctp.v2.depositForBurn'> = async (params, resolved) => {
3736
+ * console.log('Processing deposit:', params.amount)
3737
+ * return {
3738
+ * chainId: params.chainId,
3739
+ * data: '0x...',
3740
+ * to: '0x...',
3741
+ * value: '0'
3742
+ * }
3743
+ * }
3744
+ *
3745
+ * registry.registerHandler('cctp.v2.depositForBurn', depositHandler)
3746
+ * ```
3747
+ */
3748
+ registerHandler(action, handler) {
3749
+ // Runtime validation for JavaScript consumers
3750
+ if (typeof action !== 'string' || action.length === 0) {
3751
+ throw new TypeError(`Action must be a non-empty string, received: ${typeof action}`);
3752
+ }
3753
+ if (typeof handler !== 'function') {
3754
+ throw new TypeError(`Handler must be a function, received: ${typeof handler}`);
3755
+ }
3756
+ // The handler is upcast to ActionHandler<ActionKeys> for storage,
3757
+ // but type safety is maintained at the call site through the generic constraint
3758
+ this.actionHandlers.set(action, handler);
3759
+ }
3760
+ /**
3761
+ * Register multiple action handlers in a single operation.
3762
+ *
3763
+ * Efficiently register multiple handlers from a record object, where keys
3764
+ * are action identifiers and values are their corresponding handler
3765
+ * functions. Provides a convenient way to bulk-register handlers while
3766
+ * maintaining type safety.
3767
+ *
3768
+ * @param handlers - A record mapping action keys to their handler functions.
3769
+ * @returns Void.
3770
+ *
3771
+ * @throws {Error} When handlers parameter is not a valid object.
3772
+ * @throws {Error} When any individual handler registration fails.
3773
+ *
3774
+ * @example
3775
+ * ```typescript
3776
+ * import { ActionRegistry } from '@core/adapter'
3777
+ * import type { ActionHandler, ActionHandlers } from '@core/adapter'
3778
+ *
3779
+ * const registry = new ActionRegistry()
3780
+ *
3781
+ * // Register multiple handlers at once
3782
+ * const tokenHandlers: ActionHandlers = {
3783
+ * 'token.approve': async (params, resolved) => ({
3784
+ * chainId: resolved.chain,
3785
+ * data: '0x095ea7b3...',
3786
+ * to: params.tokenAddress,
3787
+ * value: '0'
3788
+ * }),
3789
+ * 'token.transfer': async (params, resolved) => ({
3790
+ * chainId: resolved.chain,
3791
+ * data: '0xa9059cbb...',
3792
+ * to: params.tokenAddress,
3793
+ * value: '0'
3794
+ * })
3795
+ * }
3796
+ *
3797
+ * registry.registerHandlers(tokenHandlers)
3798
+ * console.log('Registered multiple token handlers')
3799
+ * ```
3800
+ */
3801
+ registerHandlers(handlers) {
3802
+ // Runtime validation for JavaScript consumers
3803
+ if (typeof handlers !== 'object' || handlers === null) {
3804
+ throw new Error(`Handlers must be a non-null object, received: ${typeof handlers}`);
3805
+ }
3806
+ // Register each handler individually to benefit from per-handler validation
3807
+ for (const [action, handler] of Object.entries(handlers)) {
3808
+ this.registerHandler(action, handler);
3809
+ }
3810
+ }
3811
+ /**
3812
+ * Check whether a specific action is supported by this registry.
3813
+ *
3814
+ * Determine if a handler has been registered for the given action key.
3815
+ * Use this method to conditionally execute actions or provide appropriate
3816
+ * error messages when actions are not available.
3817
+ *
3818
+ * @param action - The action key to check for support.
3819
+ * @returns True if the action is supported, false otherwise.
3820
+ *
3821
+ * @throws {Error} When action parameter is not a valid string.
3822
+ *
3823
+ * @example
3824
+ * ```typescript
3825
+ * import { ActionRegistry } from '@core/adapter'
3826
+ *
3827
+ * const registry = new ActionRegistry()
3828
+ *
3829
+ * // Check if actions are supported before attempting to use them
3830
+ * if (registry.supportsAction('token.approve')) {
3831
+ * console.log('Token approval is supported')
3832
+ * } else {
3833
+ * console.log('Token approval not available')
3834
+ * }
3835
+ *
3836
+ * // Conditional logic based on support
3837
+ * const action = 'cctp.v2.depositForBurn'
3838
+ * if (registry.supportsAction(action)) {
3839
+ * // Safe to execute
3840
+ * console.log(`${action} is available`)
3841
+ * } else {
3842
+ * console.warn(`${action} is not registered`)
3843
+ * }
3844
+ * ```
3845
+ */
3846
+ supportsAction(action) {
3847
+ // Runtime validation for JavaScript consumers
3848
+ if (typeof action !== 'string' || action.length === 0) {
3849
+ throw new TypeError(`Action must be a non-empty string, received: ${typeof action}`);
3850
+ }
3851
+ return this.actionHandlers.has(action);
3852
+ }
3853
+ /**
3854
+ * Execute a registered action handler with type-safe parameters.
3855
+ *
3856
+ * Look up and execute the handler associated with the given action key,
3857
+ * passing the provided parameters and context, returning the resulting prepared
3858
+ * chain request. TypeScript ensures the parameters match the expected
3859
+ * structure for the specified action.
3860
+ *
3861
+ * @typeParam TActionKey - The specific action key being executed.
3862
+ * @param action - The action key identifying which handler to execute.
3863
+ * @param params - The parameters to pass to the action handler.
3864
+ * @param context - The resolved operation context with concrete chain and address values.
3865
+ * @returns A promise resolving to the prepared chain request.
3866
+ * @throws {KitError} When the handler execution fails with a structured error.
3867
+ * @throws {Error} When no handler is registered for the specified action.
3868
+ * @throws {Error} When the handler execution fails with an unstructured error.
3869
+ *
3870
+ * @example
3871
+ * ```typescript
3872
+ * import { ActionRegistry } from '@core/adapter'
3873
+ * import type { ChainEnum } from '@core/chains'
3874
+ *
3875
+ * const registry = new ActionRegistry()
3876
+ *
3877
+ * // First register a handler
3878
+ * registry.registerHandler('token.approve', async (params, context) => ({
3879
+ * chainId: context.chain, // Always defined
3880
+ * data: '0x095ea7b3...',
3881
+ * to: params.tokenAddress,
3882
+ * value: '0'
3883
+ * }))
3884
+ *
3885
+ * // Execute the action with resolved context (typically called from adapter.prepareAction)
3886
+ * const resolvedContext = { chain: 'Base', address: '0x123...' }
3887
+ * const result = await registry.executeAction('token.approve', {
3888
+ * chainId: ChainEnum.Ethereum,
3889
+ * tokenAddress: '0xA0b86a33E6441c8C1c7C16e4c5e3e5b5e4c5e3e5b5e4c5e',
3890
+ * delegate: '0x1234567890123456789012345678901234567890',
3891
+ * amount: '1000000'
3892
+ * }, resolvedContext)
3893
+ *
3894
+ * console.log('Transaction prepared:', result.data)
3895
+ * ```
3896
+ */
3897
+ async executeAction(action, params, context) {
3898
+ // Runtime validation for JavaScript consumers
3899
+ if (typeof action !== 'string' || action.length === 0) {
3900
+ throw new TypeError(`Action must be a non-empty string, received: ${typeof action}`);
3901
+ }
3902
+ const handler = this.actionHandlers.get(action);
3903
+ if (!handler) {
3904
+ throw new Error(`Action ${action} is not supported`);
3905
+ }
3906
+ try {
3907
+ // Type safety is guaranteed by the registration process
3908
+ return await handler(params, context);
3909
+ }
3910
+ catch (error) {
3911
+ // If it's already a KitError with structured information, re-throw as-is
3912
+ // to preserve error code, name, type, recoverability, and cause
3913
+ if (error instanceof KitError) {
3914
+ throw error;
3915
+ }
3916
+ // For other errors, re-throw with context for better debugging
3917
+ const message = error instanceof Error ? error.message : String(error);
3918
+ throw new Error(`Failed to execute action ${action}: ${message}`);
3919
+ }
3920
+ }
3921
+ }
2759
3922
 
2760
3923
  /**
2761
3924
  * Resolves an operation context into concrete chain and address values.
@@ -2826,281 +3989,17 @@ async function resolveOperationContext(adapter, ctx) {
2826
3989
  try {
2827
3990
  // Pass resolved chain to getAddress for adapters that support it (like ViemAdapter)
2828
3991
  // The chain parameter is optional in implementations, so this is safe
2829
- resolvedAddress = await adapter.getAddress(resolvedChain);
2830
- }
2831
- catch (error) {
2832
- const message = error instanceof Error ? error.message : String(error);
2833
- throw new Error(`Failed to resolve address from user-controlled adapter: ${message}`);
2834
- }
2835
- }
2836
- return {
2837
- chain: resolvedChain,
2838
- address: resolvedAddress,
2839
- };
2840
- }
2841
-
2842
- /**
2843
- * Valid recoverability values for error handling strategies.
2844
- *
2845
- * - FATAL errors are thrown immediately (invalid inputs, insufficient funds)
2846
- * - RETRYABLE errors are returned when a flow fails to start but could work later
2847
- * - RESUMABLE errors are returned when a flow fails mid-execution but can be continued
2848
- */
2849
- const RECOVERABILITY_VALUES = [
2850
- 'RETRYABLE',
2851
- 'RESUMABLE',
2852
- 'FATAL',
2853
- ];
2854
-
2855
- // Create a mutable array for Zod enum validation
2856
- const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
2857
- /**
2858
- * Zod schema for validating ErrorDetails objects.
2859
- *
2860
- * This schema provides runtime validation for all ErrorDetails properties,
2861
- * ensuring type safety and proper error handling for JavaScript consumers.
2862
- *
2863
- * @example
2864
- * ```typescript
2865
- * import { errorDetailsSchema } from '@core/errors'
2866
- *
2867
- * const result = errorDetailsSchema.safeParse({
2868
- * code: 1001,
2869
- * name: 'NETWORK_MISMATCH',
2870
- * recoverability: 'FATAL',
2871
- * message: 'Source and destination networks must be different'
2872
- * })
2873
- *
2874
- * if (!result.success) {
2875
- * console.error('Validation failed:', result.error.issues)
2876
- * }
2877
- * ```
2878
- */
2879
- const errorDetailsSchema = zod.z.object({
2880
- /** Numeric identifier following standardized ranges (1000+ for INPUT errors) */
2881
- code: zod.z
2882
- .number()
2883
- .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") */
2887
- name: zod.z
2888
- .string()
2889
- .min(1, 'Error name must be a non-empty string')
2890
- .regex(/^[A-Z_][A-Z0-9_]*$/, 'Error name must match pattern: ^[A-Z_][A-Z0-9_]*$'),
2891
- /** Error handling strategy */
2892
- recoverability: zod.z.enum(RECOVERABILITY_ARRAY, {
2893
- errorMap: () => ({
2894
- message: 'Recoverability must be one of: RETRYABLE, RESUMABLE, FATAL',
2895
- }),
2896
- }),
2897
- /** User-friendly explanation with network context */
2898
- message: zod.z
2899
- .string()
2900
- .min(1, 'Error message must be a non-empty string')
2901
- .max(500, 'Error message must be 500 characters or less'),
2902
- /** Raw error details, context, or the original error that caused this one. */
2903
- cause: zod.z
2904
- .object({
2905
- /** Free-form error payload from underlying system */
2906
- trace: zod.z.unknown().optional(),
2907
- })
2908
- .optional(),
2909
- });
2910
-
2911
- /**
2912
- * Validates an ErrorDetails object using Zod schema.
2913
- *
2914
- * @param details - The object to validate
2915
- * @returns The validated ErrorDetails object
2916
- * @throws {TypeError} When validation fails
2917
- *
2918
- * @example
2919
- * ```typescript
2920
- * import { validateErrorDetails } from '@core/errors'
2921
- *
2922
- * try {
2923
- * const validDetails = validateErrorDetails({
2924
- * code: 1001,
2925
- * name: 'NETWORK_MISMATCH',
2926
- * recoverability: 'FATAL',
2927
- * message: 'Source and destination networks must be different'
2928
- * })
2929
- * } catch (error) {
2930
- * console.error('Validation failed:', error.message)
2931
- * }
2932
- * ```
2933
- */
2934
- function validateErrorDetails(details) {
2935
- const result = errorDetailsSchema.safeParse(details);
2936
- if (!result.success) {
2937
- const issues = result.error.issues
2938
- .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
2939
- .join(', ');
2940
- throw new TypeError(`Invalid ErrorDetails: ${issues}`);
2941
- }
2942
- return result.data;
2943
- }
2944
-
2945
- /**
2946
- * Structured error class for Stablecoin Kit operations.
2947
- *
2948
- * This class extends the native Error class while implementing the ErrorDetails
2949
- * interface, providing a consistent error format for programmatic handling
2950
- * across the Stablecoin Kits ecosystem. All properties are immutable to ensure
2951
- * error objects cannot be modified after creation.
2952
- *
2953
- * @example
2954
- * ```typescript
2955
- * import { KitError } from '@core/errors'
2956
- *
2957
- * const error = new KitError({
2958
- * code: 1001,
2959
- * name: 'INPUT_NETWORK_MISMATCH',
2960
- * recoverability: 'FATAL',
2961
- * message: 'Cannot bridge between mainnet and testnet'
2962
- * })
2963
- *
2964
- * if (error instanceof KitError) {
2965
- * console.log(`Error ${error.code}: ${error.name}`)
2966
- * // → "Error 1001: INPUT_NETWORK_MISMATCH"
2967
- * }
2968
- * ```
2969
- *
2970
- * @example
2971
- * ```typescript
2972
- * import { KitError } from '@core/errors'
2973
- *
2974
- * // Error with cause information
2975
- * const error = new KitError({
2976
- * code: 1002,
2977
- * name: 'INVALID_AMOUNT',
2978
- * recoverability: 'FATAL',
2979
- * message: 'Amount must be greater than zero',
2980
- * cause: {
2981
- * trace: { providedAmount: -100, minimumAmount: 0 }
2982
- * }
2983
- * })
2984
- *
2985
- * throw error
2986
- * ```
2987
- */
2988
- class KitError extends Error {
2989
- /** Numeric identifier following standardized ranges (1000+ for INPUT errors) */
2990
- code;
2991
- /** Human-readable ID (e.g., "NETWORK_MISMATCH") */
2992
- name;
2993
- /** Error handling strategy */
2994
- recoverability;
2995
- /** Raw error details, context, or the original error that caused this one. */
2996
- cause;
2997
- /**
2998
- * Create a new KitError instance.
2999
- *
3000
- * @param details - The error details object containing all required properties.
3001
- * @throws \{TypeError\} When details parameter is missing or invalid.
3002
- */
3003
- constructor(details) {
3004
- // Validate input at runtime for JavaScript consumers using Zod
3005
- const validatedDetails = validateErrorDetails(details);
3006
- super(validatedDetails.message);
3007
- // Set properties as readonly at runtime
3008
- Object.defineProperties(this, {
3009
- name: {
3010
- value: validatedDetails.name,
3011
- writable: false,
3012
- enumerable: true,
3013
- configurable: false,
3014
- },
3015
- code: {
3016
- value: validatedDetails.code,
3017
- writable: false,
3018
- enumerable: true,
3019
- configurable: false,
3020
- },
3021
- recoverability: {
3022
- value: validatedDetails.recoverability,
3023
- writable: false,
3024
- enumerable: true,
3025
- configurable: false,
3026
- },
3027
- ...(validatedDetails.cause && {
3028
- cause: {
3029
- value: validatedDetails.cause,
3030
- writable: false,
3031
- enumerable: true,
3032
- configurable: false,
3033
- },
3034
- }),
3035
- });
3036
- }
3037
- }
3038
-
3039
- /**
3040
- * Minimum error code for INPUT type errors.
3041
- * INPUT errors represent validation failures and invalid parameters.
3042
- */
3043
- /**
3044
- * Standardized error definitions for INPUT type errors.
3045
- *
3046
- * Each entry combines the numeric error code with its corresponding
3047
- * string name to ensure consistency when creating error instances.
3048
- *
3049
- * Error codes follow a hierarchical numbering scheme where the first digit
3050
- * indicates the error category (1 = INPUT) and subsequent digits provide
3051
- * specific error identification within that category.
3052
- *
3053
- *
3054
- * @example
3055
- * ```typescript
3056
- * import { InputError } from '@core/errors'
3057
- *
3058
- * const error = new KitError({
3059
- * ...InputError.NETWORK_MISMATCH,
3060
- * recoverability: 'FATAL',
3061
- * message: 'Source and destination networks must be different'
3062
- * })
3063
- *
3064
- * // Access code and name individually if needed
3065
- * console.log(InputError.NETWORK_MISMATCH.code) // 1001
3066
- * console.log(InputError.NETWORK_MISMATCH.name) // 'INPUT_NETWORK_MISMATCH'
3067
- * ```
3068
- */
3069
- const InputError = {
3070
- /** Unsupported or invalid bridge route configuration */
3071
- UNSUPPORTED_ROUTE: {
3072
- code: 1003,
3073
- name: 'INPUT_UNSUPPORTED_ROUTE',
3074
- }};
3075
-
3076
- /**
3077
- * Creates error for unsupported bridge route.
3078
- *
3079
- * This error is thrown when attempting to bridge between chains that don't
3080
- * have a supported bridge route configured.
3081
- *
3082
- * @param source - Source chain name
3083
- * @param destination - Destination chain name
3084
- * @returns KitError with specific route details
3085
- *
3086
- * @example
3087
- * ```typescript
3088
- * import { createUnsupportedRouteError } from '@core/errors'
3089
- *
3090
- * throw createUnsupportedRouteError('Ethereum', 'Solana')
3091
- * // Message: "Route from Ethereum to Solana is not supported"
3092
- * ```
3093
- */
3094
- function createUnsupportedRouteError(source, destination) {
3095
- const errorDetails = {
3096
- ...InputError.UNSUPPORTED_ROUTE,
3097
- recoverability: 'FATAL',
3098
- message: `Route from ${source} to ${destination} is not supported.`,
3099
- cause: {
3100
- trace: { source, destination },
3101
- },
3992
+ resolvedAddress = await adapter.getAddress(resolvedChain);
3993
+ }
3994
+ catch (error) {
3995
+ const message = error instanceof Error ? error.message : String(error);
3996
+ throw new Error(`Failed to resolve address from user-controlled adapter: ${message}`);
3997
+ }
3998
+ }
3999
+ return {
4000
+ chain: resolvedChain,
4001
+ address: resolvedAddress,
3102
4002
  };
3103
- return new KitError(errorDetails);
3104
4003
  }
3105
4004
 
3106
4005
  /**
@@ -3248,45 +4147,28 @@ class Adapter {
3248
4147
  * Validate that the target chain is supported by this adapter.
3249
4148
  *
3250
4149
  * @param targetChain - The chain to validate.
3251
- * @throws Error if the chain is not supported.
4150
+ * @throws KitError with INVALID_CHAIN code if the chain is not supported by this adapter.
3252
4151
  */
3253
4152
  validateChainSupport(targetChain) {
3254
4153
  if (this.capabilities?.supportedChains) {
3255
4154
  const isSupported = this.capabilities.supportedChains.some((supportedChain) => supportedChain.chain === targetChain.chain);
3256
4155
  if (!isSupported) {
3257
4156
  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);
4157
+ // List supported chain names for better user experience
4158
+ const supportedChainNames = this.capabilities.supportedChains
4159
+ .map((chainDef) => chainDef.name)
4160
+ .join(', ');
4161
+ const reason = `Not supported by this adapter. It supports ${supportedCount.toString()} ${supportedCount === 1 ? 'chain' : 'chains'}: ${supportedChainNames}`;
4162
+ throw createInvalidChainError(targetChain.name, reason);
3260
4163
  }
3261
4164
  }
3262
4165
  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);
4166
+ const reason = `Chain type mismatch: adapter supports ${this.chainType} chains, but received ${targetChain.type ?? 'unknown'} chain`;
4167
+ throw createInvalidChainError(targetChain.name, reason);
3265
4168
  }
3266
4169
  }
3267
4170
  }
3268
4171
 
3269
- /**
3270
- * Custom error class for validation errors.
3271
- * Provides structured error information while hiding implementation details.
3272
- */
3273
- class ValidationError extends Error {
3274
- errors;
3275
- constructor(message, errors) {
3276
- super(message);
3277
- this.errors = errors;
3278
- this.name = 'ValidationError';
3279
- }
3280
- }
3281
-
3282
- /**
3283
- * Formats a Zod error for display.
3284
- * @internal
3285
- */
3286
- const formatZodError = (error) => {
3287
- const path = error.path.length > 0 ? `${error.path.join('.')}: ` : '';
3288
- return `${path}${error.message}`;
3289
- };
3290
4172
  /**
3291
4173
  * Validates data against a Zod schema with enhanced error reporting.
3292
4174
  *
@@ -3294,23 +4176,22 @@ const formatZodError = (error) => {
3294
4176
  * messages that include the validation context. It's designed to give developers
3295
4177
  * clear feedback about what went wrong during validation.
3296
4178
  *
4179
+ * @param value - The value to validate
3297
4180
  * @param schema - The Zod schema to validate against
3298
- * @param data - The data to validate
3299
4181
  * @param context - Context string to include in error messages (e.g., 'bridge parameters')
3300
- * @returns The validated and parsed data
3301
- * @throws {ValidationError} If validation fails
4182
+ * @returns Asserts that value is of type T (type narrowing)
4183
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098)
3302
4184
  *
3303
4185
  * @example
3304
4186
  * ```typescript
3305
- * const result = validate(BridgeParamsSchema, params, 'bridge parameters')
4187
+ * validate(params, BridgeParamsSchema, 'bridge parameters')
4188
+ * // After this call, TypeScript knows params is of type BridgeParams
3306
4189
  * ```
3307
4190
  */
3308
4191
  function validate(value, schema, context) {
3309
4192
  const result = schema.safeParse(value);
3310
4193
  if (!result.success) {
3311
- const errors = result.error.errors.map(formatZodError);
3312
- const firstError = errors[0] ?? 'Invalid value';
3313
- throw new ValidationError(`Invalid ${context}: ${firstError}`, errors);
4194
+ throw createValidationErrorFromZod(result.error, context);
3314
4195
  }
3315
4196
  }
3316
4197
 
@@ -3328,11 +4209,12 @@ const VALIDATION_STATE = Symbol('validationState');
3328
4209
  * and providing detailed error messages. It's designed for use in scenarios where
3329
4210
  * validation state needs to be monitored and reported.
3330
4211
  *
4212
+ * @param value - The value to validate
3331
4213
  * @param schema - The Zod schema to validate against
3332
- * @param data - The data to validate
3333
4214
  * @param context - Context string to include in error messages (e.g., 'bridge parameters')
3334
- * @returns Object containing validation result and state information
3335
- * @throws {ValidationError} If validation fails
4215
+ * @param validatorName - Symbol identifying the validator for state tracking
4216
+ * @returns Asserts that value is of type T (type narrowing)
4217
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098)
3336
4218
  *
3337
4219
  * @example
3338
4220
  * ```typescript
@@ -3342,20 +4224,41 @@ const VALIDATION_STATE = Symbol('validationState');
3342
4224
  function validateWithStateTracking(value, schema, context, validatorName) {
3343
4225
  // Skip validation for null or undefined values
3344
4226
  if (value === null) {
3345
- throw new ValidationError(`Invalid ${context}: Value is null`, [
3346
- `Value is null`,
3347
- ]);
4227
+ throw new KitError({
4228
+ ...InputError.VALIDATION_FAILED,
4229
+ recoverability: 'FATAL',
4230
+ message: `Invalid ${context}: Value is null`,
4231
+ cause: {
4232
+ trace: {
4233
+ validationErrors: ['Value is null'],
4234
+ },
4235
+ },
4236
+ });
3348
4237
  }
3349
4238
  if (value === undefined) {
3350
- throw new ValidationError(`Invalid ${context}: Value is undefined`, [
3351
- `Value is undefined`,
3352
- ]);
4239
+ throw new KitError({
4240
+ ...InputError.VALIDATION_FAILED,
4241
+ recoverability: 'FATAL',
4242
+ message: `Invalid ${context}: Value is undefined`,
4243
+ cause: {
4244
+ trace: {
4245
+ validationErrors: ['Value is undefined'],
4246
+ },
4247
+ },
4248
+ });
3353
4249
  }
3354
4250
  // Ensure value is an object that can hold validation state
3355
4251
  if (typeof value !== 'object') {
3356
- throw new ValidationError(`Invalid ${context}: Value must be an object`, [
3357
- `Value must be an object, got ${typeof value}`,
3358
- ]);
4252
+ throw new KitError({
4253
+ ...InputError.VALIDATION_FAILED,
4254
+ recoverability: 'FATAL',
4255
+ message: `Invalid ${context}: Value must be an object`,
4256
+ cause: {
4257
+ trace: {
4258
+ validationErrors: [`Value must be an object, got ${typeof value}`],
4259
+ },
4260
+ },
4261
+ });
3359
4262
  }
3360
4263
  // Get or initialize validation state
3361
4264
  const valueWithState = value;
@@ -3364,7 +4267,7 @@ function validateWithStateTracking(value, schema, context, validatorName) {
3364
4267
  if (state.validatedBy.includes(validatorName)) {
3365
4268
  return;
3366
4269
  }
3367
- // Delegate to the validate function for actual validation
4270
+ // Delegate to the validate function for actual validation (now throws KitError)
3368
4271
  validate(value, schema, context);
3369
4272
  // Update validation state
3370
4273
  state.validatedBy.push(validatorName);
@@ -3419,7 +4322,7 @@ zod.z
3419
4322
  .url('Generated explorer URL is invalid');
3420
4323
 
3421
4324
  /**
3422
- * Validates data against a Zod schema and throws a ValidationError on failure.
4325
+ * Validates data against a Zod schema and throws a KitError on failure.
3423
4326
  *
3424
4327
  * This utility function provides consistent validation and error formatting across the codebase.
3425
4328
  * It performs the validation and formats error messages with contextual information while
@@ -3428,7 +4331,8 @@ zod.z
3428
4331
  * @param value - The value to validate
3429
4332
  * @param schema - The Zod schema to validate against
3430
4333
  * @param message - Error message (e.g., 'Invalid EVM address', 'Configuration error')
3431
- * @throws {ValidationError} If validation fails, with formatted error details and all individual errors
4334
+ * @returns Asserts that value is of type T (type narrowing)
4335
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098) and detailed error information
3432
4336
  *
3433
4337
  * @example
3434
4338
  * ```typescript
@@ -3440,10 +4344,9 @@ zod.z
3440
4344
  * age: z.number().positive()
3441
4345
  * })
3442
4346
  *
3443
- * // This will throw ValidationError if validation fails
4347
+ * // This will throw KitError if validation fails
3444
4348
  * validateOrThrow({ name: 'Jo', age: -1 }, userSchema, 'Invalid user data')
3445
- * // Throws: ValidationError("Invalid user data: name: String must contain at least 3 character(s)",
3446
- * // ["name: String must contain at least 3 character(s)", "age: Number must be greater than 0"])
4349
+ * // Throws: KitError with code 1098 and message "Invalid user data: name: String must contain at least 3 character(s)"
3447
4350
  * ```
3448
4351
  *
3449
4352
  * @example
@@ -3466,13 +4369,7 @@ zod.z
3466
4369
  function validateOrThrow(value, schema, message) {
3467
4370
  const result = schema.safeParse(value);
3468
4371
  if (!result.success) {
3469
- const errors = result.error.errors.map((error) => {
3470
- const path = error.path.length > 0 ? `${error.path.join('.')}: ` : '';
3471
- return `${path}${error.message}`;
3472
- });
3473
- const firstError = errors[0];
3474
- const errorMessage = firstError !== undefined ? `${message}: ${firstError}` : message;
3475
- throw new ValidationError(errorMessage, errors);
4372
+ throw createValidationErrorFromZod(result.error, message);
3476
4373
  }
3477
4374
  }
3478
4375
 
@@ -3650,7 +4547,7 @@ const convertAddress = (address, targetFormat) => {
3650
4547
  * This schema does not validate length, making it suitable for various hex string types
3651
4548
  * like addresses, transaction hashes, and other hex-encoded data.
3652
4549
  *
3653
- * @throws {ValidationError} If validation fails, with details about which properties failed
4550
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
3654
4551
  *
3655
4552
  * @example
3656
4553
  * ```typescript
@@ -3681,7 +4578,7 @@ const hexStringSchema = zod.z
3681
4578
  * - Must be a valid hex string with '0x' prefix
3682
4579
  * - Must be exactly 42 characters long (0x + 40 hex characters)
3683
4580
  *
3684
- * @throws {ValidationError} If validation fails, with details about which properties failed
4581
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
3685
4582
  *
3686
4583
  * @example
3687
4584
  * ```typescript
@@ -3701,7 +4598,7 @@ const evmAddressSchema = hexStringSchema.refine((value) => value.length === 42,
3701
4598
  * - Must be a valid hex string with '0x' prefix
3702
4599
  * - Must be exactly 66 characters long (0x + 64 hex characters)
3703
4600
  *
3704
- * @throws {ValidationError} If validation fails, with details about which properties failed
4601
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
3705
4602
  *
3706
4603
  * @example
3707
4604
  * ```typescript
@@ -3727,7 +4624,7 @@ hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be
3727
4624
  * This schema does not validate length, making it suitable for various base58-encoded data
3728
4625
  * like Solana addresses, transaction signatures, and other base58-encoded data.
3729
4626
  *
3730
- * @throws {ValidationError} If validation fails, with details about which properties failed
4627
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
3731
4628
  *
3732
4629
  * @example
3733
4630
  * ```typescript
@@ -3759,7 +4656,7 @@ const base58StringSchema = zod.z
3759
4656
  * - Must be a valid base58-encoded string
3760
4657
  * - Must be between 32-44 characters long (typical length for base58-encoded 32-byte addresses)
3761
4658
  *
3762
- * @throws {ValidationError} If validation fails, with details about which properties failed
4659
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
3763
4660
  *
3764
4661
  * @example
3765
4662
  * ```typescript
@@ -3779,7 +4676,7 @@ base58StringSchema.refine((value) => value.length >= 32 && value.length <= 44, '
3779
4676
  * - Must be a valid base58-encoded string
3780
4677
  * - Must be between 86-88 characters long (typical length for base58-encoded 64-byte signatures)
3781
4678
  *
3782
- * @throws {ValidationError} If validation fails, with details about which properties failed
4679
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
3783
4680
  *
3784
4681
  * @example
3785
4682
  * ```typescript
@@ -3808,7 +4705,7 @@ const adapterSchema = zod.z.object({
3808
4705
  * - A valid EVM address
3809
4706
  *
3810
4707
  * @param address - The address to validate
3811
- * @throws {ValidationError} If validation fails, with details about which properties failed
4708
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
3812
4709
  *
3813
4710
  * @example
3814
4711
  * ```typescript
@@ -8348,7 +9245,9 @@ function isReadOnlyFunction(abi, functionName, defaultValue) {
8348
9245
  * to EVM adapters (Ethers, Viem, etc.). For non-EVM adapters, a different validation
8349
9246
  * schema should be used.
8350
9247
  *
8351
- * @throws Error if validation fails, with details about which properties failed
9248
+ * When used with the validate() function from @core/utils, this throws KitError
9249
+ * with INPUT_VALIDATION_FAILED code if validation fails, with details about which
9250
+ * properties failed.
8352
9251
  *
8353
9252
  * @example
8354
9253
  * ```typescript
@@ -8379,7 +9278,7 @@ function isReadOnlyFunction(abi, functionName, defaultValue) {
8379
9278
  * supportedChains: [{ type: 'solana', name: 'Solana', chainId: 0 }]
8380
9279
  * }
8381
9280
  *
8382
- * AdapterCapabilitiesSchema.parse(invalidCapabilities) // throws ValidationError
9281
+ * AdapterCapabilitiesSchema.parse(invalidCapabilities) // throws Zod validation error
8383
9282
  * ```
8384
9283
  */
8385
9284
  const AdapterCapabilitiesSchema = zod.z
@@ -8424,7 +9323,7 @@ const AdapterCapabilitiesSchema = zod.z
8424
9323
  * specification with all required fields and proper types. The domain
8425
9324
  * separator prevents signature replay attacks across different domains.
8426
9325
  *
8427
- * @throws \{ValidationError\} If validation fails, with details about which properties failed
9326
+ * @throws KitError if validation fails
8428
9327
  *
8429
9328
  * @example
8430
9329
  * ```typescript
@@ -8482,7 +9381,7 @@ const eip712DomainSchema = zod.z.object({
8482
9381
  * This schema validates individual field definitions within EIP-712 type
8483
9382
  * definitions. Each field must have a name and a valid Solidity type.
8484
9383
  *
8485
- * @throws \{ValidationError\} If validation fails, with details about which properties failed
9384
+ * @throws KitError if validation fails
8486
9385
  *
8487
9386
  * @example
8488
9387
  * ```typescript
@@ -8522,7 +9421,7 @@ const typedDataFieldSchema = zod.z.object({
8522
9421
  * domain, types, primaryType, and message. It ensures all components are
8523
9422
  * properly structured and the primaryType exists in the types definition.
8524
9423
  *
8525
- * @throws \{ValidationError\} If validation fails, with details about which properties failed
9424
+ * @throws KitError if validation fails
8526
9425
  *
8527
9426
  * @example
8528
9427
  * ```typescript
@@ -8600,7 +9499,7 @@ const abiParameterSchema = zod.z.object({
8600
9499
  * This schema validates individual ABI entries that describe contract functions,
8601
9500
  * constructors, events, and other contract interface elements.
8602
9501
  *
8603
- * @throws \{ValidationError\} If validation fails, with details about which properties failed
9502
+ * @throws KitError if validation fails
8604
9503
  *
8605
9504
  * @example
8606
9505
  * ```typescript
@@ -8644,7 +9543,7 @@ const abiEntrySchema = zod.z.object({
8644
9543
  * This schema validates arrays of ABI entries, ensuring each entry is properly
8645
9544
  * structured for contract interaction.
8646
9545
  *
8647
- * @throws \{ValidationError\} If validation fails, with details about which properties failed
9546
+ * @throws KitError if validation fails
8648
9547
  *
8649
9548
  * @example
8650
9549
  * ```typescript
@@ -8675,7 +9574,7 @@ const abiSchema = zod.z
8675
9574
  * - An actionRegistry with registerHandlers method and actionHandlers record
8676
9575
  * - A prepare method for creating chain requests
8677
9576
  *
8678
- * @throws \{ValidationError\} If validation fails, with details about which properties failed
9577
+ * @throws KitError if validation fails
8679
9578
  *
8680
9579
  * @example
8681
9580
  * ```typescript
@@ -8709,7 +9608,7 @@ adapterSchema.extend({
8709
9608
  * This schema validates the parameters needed to execute a contract call on an EVM-compatible chain.
8710
9609
  * It ensures proper formatting of addresses, ABI structure, and function parameters.
8711
9610
  *
8712
- * @throws \{ValidationError\} If validation fails, with details about which properties failed
9611
+ * @throws KitError if validation fails
8713
9612
  *
8714
9613
  * @example
8715
9614
  * ```typescript
@@ -8761,7 +9660,7 @@ const evmPreparedChainRequestParamsSchema = zod.z.object({
8761
9660
  * - Must be exactly 66 characters long (0x + 64 hex characters)
8762
9661
  * - Must contain only valid hexadecimal characters (0-9, a-f, A-F)
8763
9662
  *
8764
- * @throws \{ValidationError\} If validation fails, with details about which properties failed
9663
+ * @throws KitError if validation fails
8765
9664
  *
8766
9665
  * @example
8767
9666
  * ```typescript
@@ -8804,7 +9703,7 @@ const evmTransactionHashSchema = zod.z
8804
9703
  * This is a shared schema used by both ethers.v6 and viem.v2 adapters to ensure
8805
9704
  * consistent private key validation across all EVM adapters.
8806
9705
  *
8807
- * @throws \{ValidationError\} If validation fails, with details about the issue
9706
+ * @throws KitError if validation fails
8808
9707
  *
8809
9708
  * @example
8810
9709
  * ```typescript
@@ -8840,7 +9739,7 @@ const assertEvmPreparedChainRequestParamsSymbol = Symbol('assertEvmPreparedChain
8840
9739
  * - Arguments that match the function signature
8841
9740
  *
8842
9741
  * @param params - The parameters to validate
8843
- * @throws ValidationError If validation fails, with details about which properties failed
9742
+ * @throws KitError with INPUT_VALIDATION_FAILED code if validation fails, with details about which properties failed
8844
9743
  *
8845
9744
  * @example
8846
9745
  * ```typescript
@@ -8855,7 +9754,7 @@ const assertEvmPreparedChainRequestParamsSymbol = Symbol('assertEvmPreparedChain
8855
9754
  * args: ['0xSpenderAddress', BigInt(1000000)]
8856
9755
  * }
8857
9756
  *
8858
- * // This will throw if validation fails
9757
+ * // This will throw KitError if validation fails
8859
9758
  * assertEvmPreparedChainRequestParams(requestParams)
8860
9759
  *
8861
9760
  * // If we get here, request parameters are guaranteed to be valid
@@ -8876,7 +9775,7 @@ function assertEvmPreparedChainRequestParams(params) {
8876
9775
  * - Must contain only valid hexadecimal characters (0-9, a-f, A-F)
8877
9776
  *
8878
9777
  * @param txHash - The transaction hash to validate
8879
- * @throws ValidationError If validation fails, with details about which properties failed
9778
+ * @throws KitError with INPUT_VALIDATION_FAILED code if validation fails, with details about which properties failed
8880
9779
  *
8881
9780
  * @example
8882
9781
  * ```typescript
@@ -8888,7 +9787,7 @@ function assertEvmPreparedChainRequestParams(params) {
8888
9787
  *
8889
9788
  * // Invalid transaction hash
8890
9789
  * assertEvmTransactionHash('invalid-hash')
8891
- * // Throws ValidationError with descriptive message
9790
+ * // Throws KitError with descriptive message
8892
9791
  * ```
8893
9792
  */
8894
9793
  function assertEvmTransactionHash(txHash) {
@@ -8912,7 +9811,7 @@ function assertEvmTransactionHash(txHash) {
8912
9811
  * @typeParam Types - The EIP-712 types definition for the standard being validated
8913
9812
  * @typeParam Message - The message structure for the standard being validated
8914
9813
  * @param typedData - The typed data to validate
8915
- * @throws {ValidationError} If validation fails, with details about which properties failed
9814
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
8916
9815
  *
8917
9816
  * @example
8918
9817
  * ```typescript
@@ -9711,7 +10610,7 @@ zod.z.object({
9711
10610
  * Validates EthersAdapterOptions configuration.
9712
10611
  *
9713
10612
  * @param options - The EthersAdapterOptions to validate. Must include a `getProvider` function and a `signer` object.
9714
- * @throws \{ValidationError\} If validation fails.
10613
+ * @throws KitError with INPUT_VALIDATION_FAILED code if validation fails.
9715
10614
  *
9716
10615
  * @example
9717
10616
  * ```typescript
@@ -9723,7 +10622,7 @@ zod.z.object({
9723
10622
  * signer: Wallet.createRandom(),
9724
10623
  * }
9725
10624
  *
9726
- * validateEthersAdapterOptions(options) // throws if invalid
10625
+ * validateEthersAdapterOptions(options) // throws KitError if invalid
9727
10626
  * ```
9728
10627
  */
9729
10628
  function validateEthersAdapterOptions(options) {
@@ -9732,11 +10631,11 @@ function validateEthersAdapterOptions(options) {
9732
10631
  /**
9733
10632
  * Validates parameters for creating an Ethers adapter from an EIP-1193 provider.
9734
10633
  *
9735
- * This function validates the parameters used by the createAdapterFromProvider
10634
+ * This function validates the parameters used by the createEthersAdapterFromProvider
9736
10635
  * factory function for the Ethers adapter.
9737
10636
  *
9738
10637
  * @param params - The parameters to validate. Must include a `provider` object (EIP-1193 compatible) and optionally a `getProvider` function.
9739
- * @throws \{ValidationError\} If validation fails.
10638
+ * @throws KitError with INPUT_VALIDATION_FAILED code if validation fails.
9740
10639
  * @example
9741
10640
  * ```typescript
9742
10641
  * import { validateCreateAdapterFromProviderParams } from '@circle-fin/adapter-ethers-v6/validation'
@@ -9745,7 +10644,7 @@ function validateEthersAdapterOptions(options) {
9745
10644
  * provider: window.ethereum,
9746
10645
  * }
9747
10646
  *
9748
- * validateCreateAdapterFromProviderParams(params) // throws if invalid
10647
+ * validateCreateAdapterFromProviderParams(params) // throws KitError if invalid
9749
10648
  * ```
9750
10649
  */
9751
10650
  function validateCreateAdapterFromProviderParams(params) {
@@ -9759,7 +10658,7 @@ function validateCreateAdapterFromProviderParams(params) {
9759
10658
  * checking the supportedChains property for EVM compatibility.
9760
10659
  *
9761
10660
  * @param capabilities - The adapter capabilities to validate
9762
- * @throws ValidationError when capabilities validation fails
10661
+ * @throws KitError with INPUT_VALIDATION_FAILED code when capabilities validation fails
9763
10662
  *
9764
10663
  * @example
9765
10664
  * ```typescript
@@ -9790,7 +10689,7 @@ function validateAdapterCapabilities(capabilities) {
9790
10689
  * Creates a new EthersAdapter instance with explicit capabilities and OperationContext support.
9791
10690
  *
9792
10691
  * This constructor is typically not called directly. Instead, use the factory functions
9793
- * {@link createAdapterFromPrivateKey} or {@link createAdapterFromProvider} which provide
10692
+ * {@link createEthersAdapterFromPrivateKey} or {@link createEthersAdapterFromProvider} which provide
9794
10693
  * smart defaults and better developer experience.
9795
10694
  *
9796
10695
  * @remarks
@@ -9802,8 +10701,8 @@ function validateAdapterCapabilities(capabilities) {
9802
10701
  * - `supportedChains`: Array of EVM chains this adapter can operate on
9803
10702
  *
9804
10703
  * **Factory Functions (Recommended):**
9805
- * - {@link createAdapterFromPrivateKey} - For server-side/programmatic use with private keys
9806
- * - {@link createAdapterFromProvider} - For browser wallets (MetaMask, WalletConnect, etc.)
10704
+ * - {@link createEthersAdapterFromPrivateKey} - For server-side/programmatic use with private keys
10705
+ * - {@link createEthersAdapterFromProvider} - For browser wallets (MetaMask, WalletConnect, etc.)
9807
10706
  *
9808
10707
  * @param options - Configuration options including provider getter and signer
9809
10708
  * @param capabilities - Adapter capabilities defining address control and supported chains
@@ -9827,10 +10726,10 @@ function validateAdapterCapabilities(capabilities) {
9827
10726
  * @example
9828
10727
  * ```typescript
9829
10728
  * // Recommended: Use factory functions instead
9830
- * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
10729
+ * import { createEthersAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
9831
10730
  *
9832
10731
  * // Minimal configuration with default capabilities
9833
- * const adapter = createAdapterFromPrivateKey({
10732
+ * const adapter = createEthersAdapterFromPrivateKey({
9834
10733
  * privateKey: '0x...'
9835
10734
  * // Defaults: user-controlled + all EVM chains
9836
10735
  * })
@@ -9890,8 +10789,31 @@ class EthersAdapter extends EvmAdapter {
9890
10789
  if (chain.type !== 'evm') {
9891
10790
  throw new Error(`EthersAdapter can only switch to EVM chains. Received: ${String(chain.type)} (${chain.name})`);
9892
10791
  }
9893
- // TypeScript now knows chain is EVMChainDefinition after the validation above
9894
- this._signer = await switchChain(this.getSigner(), chain);
10792
+ // Check if we're already on the target chain
10793
+ const currentSigner = this.getSigner();
10794
+ const currentProvider = currentSigner?.provider;
10795
+ if (currentProvider) {
10796
+ try {
10797
+ const network = await currentProvider.getNetwork();
10798
+ // If already on the target chain, skip switching
10799
+ if (network.chainId === BigInt(chain.chainId)) {
10800
+ return;
10801
+ }
10802
+ }
10803
+ catch {
10804
+ // If we can't get the current chain ID, proceed with switching
10805
+ // This ensures we don't break existing functionality if getNetwork fails
10806
+ }
10807
+ }
10808
+ const provider = await this.getProvider(chain);
10809
+ // If the signer is a Wallet, reconnect it with the correct provider
10810
+ if (currentSigner instanceof ethers.Wallet) {
10811
+ this._signer = currentSigner.connect(provider);
10812
+ }
10813
+ else {
10814
+ // For browser wallets, use the original switchChain logic
10815
+ this._signer = await switchChain(currentSigner, chain);
10816
+ }
9895
10817
  }
9896
10818
  /**
9897
10819
  * Gets the cached Provider or initializes it from options if not already cached.
@@ -9957,14 +10879,17 @@ class EthersAdapter extends EvmAdapter {
9957
10879
  /**
9958
10880
  * Simulates a contract function call using Ethers v6 `.staticCall`.
9959
10881
  */
9960
- async simulateFunctionCall(contract, functionName, args) {
10882
+ async simulateFunctionCall(contract, functionName, args, chain) {
9961
10883
  try {
9962
10884
  const func = contract.getFunction(functionName);
9963
10885
  await func.staticCall(...args);
9964
10886
  }
9965
10887
  catch (err) {
9966
- const msg = err instanceof Error ? err.message : String(err);
9967
- throw new Error(`Simulation failed for ${functionName}: ${msg}`);
10888
+ // Wrap simulation errors with structured error format
10889
+ throw parseBlockchainError(err, {
10890
+ chain: chain.name,
10891
+ operation: 'simulation',
10892
+ });
9968
10893
  }
9969
10894
  }
9970
10895
  /**
@@ -9991,7 +10916,11 @@ class EthersAdapter extends EvmAdapter {
9991
10916
  errorMessage.toLocaleLowerCase().includes('execution reverted')) {
9992
10917
  return fallback;
9993
10918
  }
9994
- throw new Error(`Gas estimation failed: ${errorMessage}`);
10919
+ // Wrap gas estimation errors with structured error format
10920
+ throw parseBlockchainError(error, {
10921
+ chain: chain.name,
10922
+ operation: 'estimateGas',
10923
+ });
9995
10924
  }
9996
10925
  let gasPrice;
9997
10926
  try {
@@ -9999,7 +10928,11 @@ class EthersAdapter extends EvmAdapter {
9999
10928
  gasPrice = await this.fetchGasPrice(chain);
10000
10929
  }
10001
10930
  catch (error) {
10002
- throw new Error(`Gas price retrieval failed: ${error.message}`);
10931
+ // Wrap gas price errors with structured error format
10932
+ throw parseBlockchainError(error, {
10933
+ chain: chain.name,
10934
+ operation: 'getGasPrice',
10935
+ });
10003
10936
  }
10004
10937
  return { gas, gasPrice, fee: (gas * gasPrice).toString() };
10005
10938
  }
@@ -10055,7 +10988,11 @@ class EthersAdapter extends EvmAdapter {
10055
10988
  const shouldRetry = ethersNonceManager.shouldRetryNonceError(error, userProvidedNonce);
10056
10989
  // Don't retry if: not a nonce error, user provided nonce, or this is the last attempt
10057
10990
  if (!shouldRetry || attempt === maxAttempts) {
10058
- throw error;
10991
+ // Wrap transaction execution errors with structured error format
10992
+ throw parseBlockchainError(error, {
10993
+ chain: chain.name,
10994
+ operation: 'sendTransaction',
10995
+ });
10059
10996
  }
10060
10997
  // Retry with resync on nonce-related errors (this will set a new nonce)
10061
10998
  txRequest.nonce = await ethersNonceManager.resyncAndAllocate(provider, chain.chainId, fromAddress);
@@ -10094,10 +11031,10 @@ class EthersAdapter extends EvmAdapter {
10094
11031
  *
10095
11032
  * @example
10096
11033
  * ```typescript
10097
- * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
11034
+ * import { createEthersAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
10098
11035
  * import { parseAbi } from 'ethers'
10099
11036
  *
10100
- * const adapter = createAdapterFromPrivateKey({
11037
+ * const adapter = createEthersAdapterFromPrivateKey({
10101
11038
  * privateKey: process.env.PRIVATE_KEY
10102
11039
  * })
10103
11040
  *
@@ -10121,7 +11058,7 @@ class EthersAdapter extends EvmAdapter {
10121
11058
  * @example
10122
11059
  * ```typescript
10123
11060
  * // Multi-chain usage with same adapter instance
10124
- * const adapter = createAdapterFromPrivateKey({
11061
+ * const adapter = createEthersAdapterFromPrivateKey({
10125
11062
  * privateKey: process.env.PRIVATE_KEY
10126
11063
  * })
10127
11064
  *
@@ -10149,9 +11086,9 @@ class EthersAdapter extends EvmAdapter {
10149
11086
  * @example
10150
11087
  * ```typescript
10151
11088
  * // Address resolution patterns
10152
- * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11089
+ * import { createEthersAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
10153
11090
  *
10154
- * const adapter = await createAdapterFromProvider({
11091
+ * const adapter = await createEthersAdapterFromProvider({
10155
11092
  * provider: window.ethereum
10156
11093
  * // Defaults to user-controlled address context
10157
11094
  * })
@@ -10228,7 +11165,7 @@ class EthersAdapter extends EvmAdapter {
10228
11165
  },
10229
11166
  execute: async (overrides) => {
10230
11167
  // Simulate the function call to catch errors before submission
10231
- await this.simulateFunctionCall(contract, functionName, args);
11168
+ await this.simulateFunctionCall(contract, functionName, args, targetChain);
10232
11169
  await this.ensureChain(targetChain);
10233
11170
  // Reconnect the contract with the current signer, which is on the correct
10234
11171
  // chain after `ensureChain`, to ensure the transaction is populated and
@@ -10257,7 +11194,7 @@ class EthersAdapter extends EvmAdapter {
10257
11194
  * adapter interface, it is effectively required and enforced at runtime. This ensures
10258
11195
  * the adapter is connected to the correct chain before querying the signer.
10259
11196
  *
10260
- * @param chain - The chain to use for address resolution (provided by OperationContext)
11197
+ * @param chain - The chain to use for address resolution.
10261
11198
  * @returns A promise that resolves to the signer's address
10262
11199
  * @throws Error when no chain is provided
10263
11200
  *
@@ -10269,26 +11206,15 @@ class EthersAdapter extends EvmAdapter {
10269
11206
  * const address = await adapter.getAddress(Ethereum)
10270
11207
  * console.log('Wallet address:', address)
10271
11208
  * ```
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
11209
  */
10282
11210
  async getAddress(chain) {
10283
11211
  // Prevent calling getAddress on developer-controlled adapters
10284
11212
  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.');
11213
+ throw new Error('Cannot call getAddress() on developer-controlled adapters. Address must be provided explicitly in the operation context.');
10287
11214
  }
10288
11215
  // Chain parameter should now be provided by resolveOperationContext
10289
11216
  if (!chain) {
10290
- throw new Error('Chain parameter is required for address resolution. ' +
10291
- 'This should be provided by the OperationContext pattern.');
11217
+ throw new Error('Chain parameter is required for address resolution. This should be provided by the OperationContext pattern.');
10292
11218
  }
10293
11219
  // Ensure we're on the correct chain before getting address
10294
11220
  await this.ensureChain(chain);
@@ -10376,9 +11302,9 @@ class EthersAdapter extends EvmAdapter {
10376
11302
  *
10377
11303
  * @example
10378
11304
  * ```typescript
10379
- * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
11305
+ * import { createEthersAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
10380
11306
  *
10381
- * const adapter = createAdapterFromPrivateKey({
11307
+ * const adapter = createEthersAdapterFromPrivateKey({
10382
11308
  * privateKey: process.env.PRIVATE_KEY
10383
11309
  * })
10384
11310
  *
@@ -10416,7 +11342,7 @@ class EthersAdapter extends EvmAdapter {
10416
11342
  * @example
10417
11343
  * ```typescript
10418
11344
  * // Multi-chain permit signing with same adapter
10419
- * const adapter = createAdapterFromPrivateKey({
11345
+ * const adapter = createEthersAdapterFromPrivateKey({
10420
11346
  * privateKey: process.env.PRIVATE_KEY
10421
11347
  * })
10422
11348
  *
@@ -10434,9 +11360,9 @@ class EthersAdapter extends EvmAdapter {
10434
11360
  * @example
10435
11361
  * ```typescript
10436
11362
  * // Browser wallet usage
10437
- * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11363
+ * import { createEthersAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
10438
11364
  *
10439
- * const adapter = await createAdapterFromProvider({
11365
+ * const adapter = await createEthersAdapterFromProvider({
10440
11366
  * provider: window.ethereum
10441
11367
  * })
10442
11368
  *
@@ -10568,14 +11494,14 @@ class EthersAdapter extends EvmAdapter {
10568
11494
  *
10569
11495
  * @example
10570
11496
  * ```typescript
10571
- * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
11497
+ * import { createEthersAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
10572
11498
  *
10573
11499
  * // Both private key formats are supported (with or without '0x' prefix):
10574
- * const adapter1 = createAdapterFromPrivateKey({
11500
+ * const adapter1 = createEthersAdapterFromPrivateKey({
10575
11501
  * privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' // With prefix
10576
11502
  * })
10577
11503
  *
10578
- * const adapter2 = createAdapterFromPrivateKey({
11504
+ * const adapter2 = createEthersAdapterFromPrivateKey({
10579
11505
  * privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' // Without prefix (automatically normalized)
10580
11506
  * })
10581
11507
  *
@@ -10590,10 +11516,10 @@ class EthersAdapter extends EvmAdapter {
10590
11516
  *
10591
11517
  * @example
10592
11518
  * ```typescript
10593
- * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
11519
+ * import { createEthersAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
10594
11520
  *
10595
11521
  * // Multi-chain usage with same adapter instance
10596
- * const adapter = createAdapterFromPrivateKey({
11522
+ * const adapter = createEthersAdapterFromPrivateKey({
10597
11523
  * privateKey: process.env.PRIVATE_KEY as `0x${string}`
10598
11524
  * })
10599
11525
  *
@@ -10606,11 +11532,11 @@ class EthersAdapter extends EvmAdapter {
10606
11532
  *
10607
11533
  * @example
10608
11534
  * ```typescript
10609
- * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
11535
+ * import { createEthersAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
10610
11536
  * import { Ethereum, Base, Polygon } from '@core/chains'
10611
11537
  *
10612
11538
  * // Custom capabilities configuration
10613
- * const adapter = createAdapterFromPrivateKey({
11539
+ * const adapter = createEthersAdapterFromPrivateKey({
10614
11540
  * privateKey: process.env.PRIVATE_KEY as `0x${string}`,
10615
11541
  * capabilities: {
10616
11542
  * supportedChains: [Ethereum, Base, Polygon]
@@ -10620,11 +11546,11 @@ class EthersAdapter extends EvmAdapter {
10620
11546
  *
10621
11547
  * @example
10622
11548
  * ```typescript
10623
- * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
11549
+ * import { createEthersAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
10624
11550
  * import { JsonRpcProvider } from 'ethers'
10625
11551
  *
10626
11552
  * // Custom provider configuration with explicit chain mapping
10627
- * const adapter = createAdapterFromPrivateKey({
11553
+ * const adapter = createEthersAdapterFromPrivateKey({
10628
11554
  * privateKey: process.env.PRIVATE_KEY as `0x${string}`,
10629
11555
  * getProvider: ({ chain }) => {
10630
11556
  * const rpcEndpoints: Record<string, string> = {
@@ -10638,7 +11564,7 @@ class EthersAdapter extends EvmAdapter {
10638
11564
  * })
10639
11565
  * ```
10640
11566
  */
10641
- function createAdapterFromPrivateKey(params) {
11567
+ function createEthersAdapterFromPrivateKey(params) {
10642
11568
  // Parse and validate input parameters at runtime (normalizes the private key by adding '0x' prefix if missing)
10643
11569
  const { privateKey } = createAdapterFromPrivateKeyParamsSchema.parse(params);
10644
11570
  const { getProvider, capabilities } = params;
@@ -10664,6 +11590,94 @@ function createAdapterFromPrivateKey(params) {
10664
11590
  signer: wallet,
10665
11591
  }, resolvedCapabilities);
10666
11592
  }
11593
+ /**
11594
+ * @deprecated Use {@link createEthersAdapterFromPrivateKey} instead.
11595
+ *
11596
+ * Creates an EthersAdapter instance from a private key with automatic type inference.
11597
+ *
11598
+ * This function creates an EthersAdapter for server-side or programmatic use
11599
+ * by deriving a wallet from the provided private key. It uses lazy initialization
11600
+ * where the wallet is created without a provider and is connected to chain-specific
11601
+ * providers on-demand during operations. This matches the Viem adapter pattern and
11602
+ * enables seamless multi-chain operations with a single adapter instance.
11603
+ *
11604
+ * @remarks
11605
+ * The function performs the following operations:
11606
+ * 1. Validates the input parameters at runtime
11607
+ * 2. Creates a Wallet from the private key (without provider - lazy initialization)
11608
+ * 3. Applies smart defaults for capabilities (user-controlled + all EVM chains)
11609
+ * 4. Returns a configured EthersAdapter instance
11610
+ *
11611
+ * Chain context is provided through OperationContext during individual operations.
11612
+ * The wallet will be connected to a provider when ensureChain() is called in prepare().
11613
+ *
11614
+ * **Default Configuration:**
11615
+ * - `addressContext`: `'user-controlled'` (address derived from private key automatically)
11616
+ * - `supportedChains`: All EVM-compatible chains (~29 networks)
11617
+ * - Lazy initialization: Chain connection deferred until first operation
11618
+ *
11619
+ * **Security Warning:** Never use this function in client-side code or expose
11620
+ * private keys in any way. This function is intended for server-side use only.
11621
+ * Store private keys securely using environment variables or secret management systems.
11622
+ *
11623
+ * @typeParam TCapabilities - The adapter capabilities type for compile-time address validation
11624
+ * @param params - Configuration parameters for creating the adapter
11625
+ * @returns A configured EthersAdapter instance with automatically inferred capabilities
11626
+ * @throws Error when validation fails or wallet derivation fails
11627
+ *
11628
+ * @example
11629
+ * ```typescript
11630
+ * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
11631
+ *
11632
+ * // Both private key formats are supported (with or without '0x' prefix):
11633
+ * const adapter1 = createAdapterFromPrivateKey({
11634
+ * privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' // With prefix
11635
+ * })
11636
+ *
11637
+ * const adapter2 = createAdapterFromPrivateKey({
11638
+ * privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' // Without prefix (automatically normalized)
11639
+ * })
11640
+ *
11641
+ * // Chain specified per-operation via OperationContext
11642
+ * const prepared = await adapter1.prepare({
11643
+ * address: '0x...',
11644
+ * abi: contractAbi,
11645
+ * functionName: 'transfer',
11646
+ * args: ['0xto', '1000']
11647
+ * }, { chain: 'Ethereum' })
11648
+ * ```
11649
+ *
11650
+ * @example
11651
+ * ```typescript
11652
+ * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
11653
+ *
11654
+ * // Multi-chain usage with same adapter instance
11655
+ * const adapter = createAdapterFromPrivateKey({
11656
+ * privateKey: process.env.PRIVATE_KEY as `0x${string}`
11657
+ * })
11658
+ *
11659
+ * // Transfer USDC on Ethereum
11660
+ * await adapter.prepare(params, { chain: 'Ethereum' })
11661
+ *
11662
+ * // Transfer USDC on Base using the same adapter
11663
+ * await adapter.prepare(params, { chain: 'Base' })
11664
+ * ```
11665
+ *
11666
+ * @example
11667
+ * ```typescript
11668
+ * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
11669
+ * import { Ethereum, Base, Polygon } from '@core/chains'
11670
+ *
11671
+ * // Custom capabilities configuration
11672
+ * const adapter = createAdapterFromPrivateKey({
11673
+ * privateKey: process.env.PRIVATE_KEY as `0x${string}`,
11674
+ * capabilities: {
11675
+ * supportedChains: [Ethereum, Base, Polygon]
11676
+ * }
11677
+ * })
11678
+ * ```
11679
+ */
11680
+ const createAdapterFromPrivateKey = createEthersAdapterFromPrivateKey;
10667
11681
 
10668
11682
  /**
10669
11683
  * Creates an EthersAdapter instance from an EIP-1193 provider with smart default capabilities.
@@ -10707,10 +11721,10 @@ function createAdapterFromPrivateKey(params) {
10707
11721
  *
10708
11722
  * @example
10709
11723
  * ```typescript
10710
- * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11724
+ * import { createEthersAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
10711
11725
  *
10712
11726
  * // Minimal browser wallet configuration
10713
- * const adapter = await createAdapterFromProvider({
11727
+ * const adapter = await createEthersAdapterFromProvider({
10714
11728
  * provider: window.ethereum
10715
11729
  * // Smart defaults applied:
10716
11730
  * // - addressContext: 'user-controlled'
@@ -10728,11 +11742,11 @@ function createAdapterFromPrivateKey(params) {
10728
11742
  *
10729
11743
  * @example
10730
11744
  * ```typescript
10731
- * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11745
+ * import { createEthersAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
10732
11746
  * import { Ethereum, Base, Polygon } from '@core/chains'
10733
11747
  *
10734
11748
  * // Advanced: custom capabilities for production use
10735
- * const adapter = await createAdapterFromProvider({
11749
+ * const adapter = await createEthersAdapterFromProvider({
10736
11750
  * provider: window.ethereum,
10737
11751
  * capabilities: {
10738
11752
  * supportedChains: [Ethereum, Base, Polygon] // Restrict to specific chains
@@ -10743,11 +11757,11 @@ function createAdapterFromPrivateKey(params) {
10743
11757
  *
10744
11758
  * @example
10745
11759
  * ```typescript
10746
- * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11760
+ * import { createEthersAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
10747
11761
  * import { JsonRpcProvider } from 'ethers'
10748
11762
  *
10749
11763
  * // Advanced usage with custom provider logic for production
10750
- * const adapter = await createAdapterFromProvider({
11764
+ * const adapter = await createEthersAdapterFromProvider({
10751
11765
  * provider: window.ethereum,
10752
11766
  * getProvider: ({ chain }) => new JsonRpcProvider(
10753
11767
  * `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
@@ -10764,10 +11778,10 @@ function createAdapterFromPrivateKey(params) {
10764
11778
  * @example
10765
11779
  * ```typescript
10766
11780
  * // Cross-chain transfer using a single adapter
10767
- * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11781
+ * import { createEthersAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
10768
11782
  * import { BridgeKit } from '@circle-fin/bridge-kit'
10769
11783
  *
10770
- * const adapter = await createAdapterFromProvider({
11784
+ * const adapter = await createEthersAdapterFromProvider({
10771
11785
  * provider: window.ethereum
10772
11786
  * })
10773
11787
  *
@@ -10783,11 +11797,11 @@ function createAdapterFromPrivateKey(params) {
10783
11797
  *
10784
11798
  * @example
10785
11799
  * ```typescript
10786
- * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11800
+ * import { createEthersAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
10787
11801
  *
10788
11802
  * // Error handling example - wallet prompt occurs during factory creation
10789
11803
  * try {
10790
- * const adapter = await createAdapterFromProvider({
11804
+ * const adapter = await createEthersAdapterFromProvider({
10791
11805
  * provider: window.ethereum
10792
11806
  * })
10793
11807
  *
@@ -10804,7 +11818,7 @@ function createAdapterFromPrivateKey(params) {
10804
11818
  * }
10805
11819
  * ```
10806
11820
  */
10807
- const createAdapterFromProvider = async (params) => {
11821
+ const createEthersAdapterFromProvider = async (params) => {
10808
11822
  // Validate input
10809
11823
  // Type assertion needed due to Partial<AdapterCapabilities> with exactOptionalPropertyTypes
10810
11824
  validateCreateAdapterFromProviderParams(params);
@@ -10833,6 +11847,105 @@ const createAdapterFromProvider = async (params) => {
10833
11847
  signer,
10834
11848
  }, resolvedCapabilities);
10835
11849
  };
11850
+ /**
11851
+ * @deprecated Use {@link createEthersAdapterFromProvider} instead.
11852
+ *
11853
+ * Creates an EthersAdapter instance from an EIP-1193 provider with smart default capabilities.
11854
+ *
11855
+ * This function creates an EthersAdapter for browser or injected wallet use
11856
+ * by connecting to the provided EIP-1193-compatible provider (such as MetaMask,
11857
+ * WalletConnect, or any browser wallet). The adapter is automatically configured
11858
+ * with user-controlled address context and support for all EVM-compatible chains.
11859
+ *
11860
+ * @remarks
11861
+ * The function performs the following operations:
11862
+ * 1. Validates the input parameters at runtime
11863
+ * 2. Creates a BrowserProvider for the injected provider
11864
+ * 3. Requests account access from the user (wallet prompt appears here)
11865
+ * 4. Retrieves the signer from the connected wallet
11866
+ * 5. Applies smart defaults for capabilities (user-controlled + all EVM chains)
11867
+ * 6. Returns a configured EthersAdapter instance ready for immediate use
11868
+ *
11869
+ * The adapter will be ready to use immediately after creation with the
11870
+ * specified provider and signer active. Chain context is provided through
11871
+ * OperationContext during individual operations.
11872
+ *
11873
+ * **Smart Defaults:**
11874
+ * - `addressContext`: `'user-controlled'` (addresses managed through wallet UI)
11875
+ * - `supportedChains`: All EVM-compatible chains (~29 networks) for maximum flexibility
11876
+ * - OperationContext support for per-operation chain specification
11877
+ *
11878
+ * **Account Access:**
11879
+ * Note: Unlike Viem, Ethers requires eager signer initialization because:
11880
+ * - Ethers Signer objects are chain-specific and must be recreated during chain switches
11881
+ * - The switchChain utility requires an initialized Signer as input
11882
+ * - This is an architectural difference between Ethers and Viem, not a limitation
11883
+ * The user will be prompted for account access when this factory is called (before adapter is returned).
11884
+ *
11885
+ * **Security Note:** This function is intended for use with injected/browser wallets.
11886
+ * Do not use in server-side code with private keys.
11887
+ *
11888
+ * @param params - Configuration parameters for creating the adapter
11889
+ * @returns A configured EthersAdapter instance
11890
+ * @throws Error when validation fails, user rejects connection, or provider initialization fails
11891
+ *
11892
+ * @example
11893
+ * ```typescript
11894
+ * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11895
+ *
11896
+ * // Minimal browser wallet configuration
11897
+ * const adapter = await createAdapterFromProvider({
11898
+ * provider: window.ethereum
11899
+ * // Smart defaults applied:
11900
+ * // - addressContext: 'user-controlled'
11901
+ * // - supportedChains: all EVM chains (~29 networks)
11902
+ * })
11903
+ *
11904
+ * // Use with OperationContext pattern
11905
+ * const prepared = await adapter.prepare({
11906
+ * address: '0x...',
11907
+ * abi: contractAbi,
11908
+ * functionName: 'transfer',
11909
+ * args: ['0xto', '1000']
11910
+ * }, { chain: 'Ethereum' }) // Chain specified in context
11911
+ * ```
11912
+ *
11913
+ * @example
11914
+ * ```typescript
11915
+ * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11916
+ * import { Ethereum, Base, Polygon } from '@core/chains'
11917
+ *
11918
+ * // Advanced: custom capabilities for production use
11919
+ * const adapter = await createAdapterFromProvider({
11920
+ * provider: window.ethereum,
11921
+ * capabilities: {
11922
+ * supportedChains: [Ethereum, Base, Polygon] // Restrict to specific chains
11923
+ * // addressContext still defaults to 'user-controlled'
11924
+ * }
11925
+ * })
11926
+ * ```
11927
+ *
11928
+ * @example
11929
+ * ```typescript
11930
+ * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
11931
+ * import { JsonRpcProvider } from 'ethers'
11932
+ *
11933
+ * // Advanced usage with custom provider logic for production
11934
+ * const adapter = await createAdapterFromProvider({
11935
+ * provider: window.ethereum,
11936
+ * getProvider: ({ chain }) => new JsonRpcProvider(
11937
+ * `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_KEY}`,
11938
+ * { name: chain.name, chainId: chain.chainId }
11939
+ * )
11940
+ * })
11941
+ *
11942
+ * // Address is automatically resolved from connected wallet
11943
+ * const prepared = await adapter.prepare(params, {
11944
+ * chain: 'Polygon' // Address comes from wallet UI
11945
+ * })
11946
+ * ```
11947
+ */
11948
+ const createAdapterFromProvider = createEthersAdapterFromProvider;
10836
11949
 
10837
11950
  exports.JsonRpcProvider = ethers.JsonRpcProvider;
10838
11951
  exports.EthersAdapter = EthersAdapter;
@@ -10840,6 +11953,8 @@ exports.buildEIP2612TypedData = buildEIP2612TypedData;
10840
11953
  exports.computeDefaultDeadline = computeDefaultDeadline;
10841
11954
  exports.createAdapterFromPrivateKey = createAdapterFromPrivateKey;
10842
11955
  exports.createAdapterFromProvider = createAdapterFromProvider;
11956
+ exports.createEthersAdapterFromPrivateKey = createEthersAdapterFromPrivateKey;
11957
+ exports.createEthersAdapterFromProvider = createEthersAdapterFromProvider;
10843
11958
  exports.parseSignature = parseSignature;
10844
11959
  exports.validateAdapterCapabilities = validateAdapterCapabilities;
10845
11960
  //# sourceMappingURL=index.cjs.map