@circle-fin/provider-cctp-v2 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @circle-fin/provider-cctp-v2
2
2
 
3
+ ## 1.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed bug where tokens could be burned when bridging to unsupported chains, and improved error messages to clearly show which chains are supported.
8
+
9
+ **What's Fixed:**
10
+ - **Prevents fund loss**: Bridge operations now fail immediately if your adapter doesn't support the source or destination chain, **before** any tokens are approved or burned. Previously, tokens could be burned on the source chain before discovering the destination chain was unsupported, requiring manual recovery.
11
+ - **Better error messages**: When you attempt to use an unsupported chain, the error now clearly lists all chains your adapter supports, making it easy to pick an alternative:
12
+ ```
13
+ Invalid chain 'Linea Sepolia': Not supported by this adapter.
14
+ It supports 17 chains: Arbitrum, Base, Ethereum, Polygon, Solana, ...
15
+ ```
16
+ - **Correct error codes**: Chain validation errors now use the correct `INVALID_CHAIN` error code instead of `UNSUPPORTED_ROUTE`, making it easier to handle errors programmatically.
17
+
18
+ - Improved error handling with more informative and consistent error messages.
19
+
20
+ Errors now include:
21
+ - Specific error codes for programmatic handling
22
+ - Error type categorization (BALANCE, ONCHAIN, RPC, NETWORK)
23
+ - Recoverability information (FATAL vs RETRYABLE)
24
+ - Clearer error messages with chain context
25
+ - Original error details preserved for debugging
26
+
27
+ - Fixed an issue where bridging USDC to Solana would fail when specifying a recipient address different from the transaction signing wallet. The adapter now correctly creates the Associated Token Account (ATA) for the specified recipient address and delivers USDC to that address.
28
+
29
+ ## 1.0.3
30
+
31
+ ### Patch Changes
32
+
33
+ - Improves the Solana CCTP v2 custom burn flow to ensure developer fee payouts never fail due to a missing associated token account (ATA). The instruction builder now checks for the existence of the developer fee recipient's ATA and, if absent, emits an idempotent creation instruction.
34
+
3
35
  ## 1.0.2
4
36
 
5
37
  ### Patch Changes
package/index.cjs CHANGED
@@ -4191,9 +4191,71 @@ const RECOVERABILITY_VALUES = [
4191
4191
  'RESUMABLE',
4192
4192
  'FATAL',
4193
4193
  ];
4194
+ /**
4195
+ * Error type constants for categorizing errors by origin.
4196
+ *
4197
+ * This const object provides a reference for error types, enabling
4198
+ * IDE autocomplete and preventing typos when creating custom errors.
4199
+ *
4200
+ * @remarks
4201
+ * While internal error definitions use string literals with type annotations
4202
+ * for strict type safety, this constant is useful for developers creating
4203
+ * custom error instances or checking error types programmatically.
4204
+ *
4205
+ * @example
4206
+ * ```typescript
4207
+ * import { ERROR_TYPES, KitError } from '@core/errors'
4208
+ *
4209
+ * // Use for type checking
4210
+ * if (error.type === ERROR_TYPES.BALANCE) {
4211
+ * console.log('This is a balance error')
4212
+ * }
4213
+ * ```
4214
+ *
4215
+ * @example
4216
+ * ```typescript
4217
+ * // Use as reference when creating custom errors
4218
+ * const error = new KitError({
4219
+ * code: 9999,
4220
+ * name: 'CUSTOM_ERROR',
4221
+ * type: ERROR_TYPES.BALANCE, // IDE autocomplete works here
4222
+ * recoverability: 'FATAL',
4223
+ * message: 'Custom balance error'
4224
+ * })
4225
+ * ```
4226
+ */
4227
+ const ERROR_TYPES = {
4228
+ /** User input validation and parameter checking */
4229
+ INPUT: 'INPUT',
4230
+ /** Insufficient token balances and amount validation */
4231
+ BALANCE: 'BALANCE',
4232
+ /** On-chain execution: reverts, gas issues, transaction failures */
4233
+ ONCHAIN: 'ONCHAIN',
4234
+ /** Blockchain RPC provider issues and endpoint problems */
4235
+ RPC: 'RPC',
4236
+ /** Internet connectivity, DNS resolution, connection issues */
4237
+ NETWORK: 'NETWORK',
4238
+ };
4239
+ /**
4240
+ * Array of valid error type values for validation.
4241
+ * Derived from ERROR_TYPES const object.
4242
+ */
4243
+ const ERROR_TYPE_VALUES = Object.values(ERROR_TYPES);
4194
4244
 
4195
- // Create a mutable array for Zod enum validation
4245
+ // Create mutable arrays for Zod enum validation
4196
4246
  const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
4247
+ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
4248
+ /**
4249
+ * Error code ranges for validation.
4250
+ * Single source of truth for valid error code ranges.
4251
+ */
4252
+ const ERROR_CODE_RANGES = [
4253
+ { min: 1000, max: 1999, type: 'INPUT' },
4254
+ { min: 3000, max: 3999, type: 'NETWORK' },
4255
+ { min: 4000, max: 4999, type: 'RPC' },
4256
+ { min: 5000, max: 5999, type: 'ONCHAIN' },
4257
+ { min: 9000, max: 9999, type: 'BALANCE' },
4258
+ ];
4197
4259
  /**
4198
4260
  * Zod schema for validating ErrorDetails objects.
4199
4261
  *
@@ -4206,7 +4268,8 @@ const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
4206
4268
  *
4207
4269
  * const result = errorDetailsSchema.safeParse({
4208
4270
  * code: 1001,
4209
- * name: 'NETWORK_MISMATCH',
4271
+ * name: 'INPUT_NETWORK_MISMATCH',
4272
+ * type: 'INPUT',
4210
4273
  * recoverability: 'FATAL',
4211
4274
  * message: 'Source and destination networks must be different'
4212
4275
  * })
@@ -4215,30 +4278,56 @@ const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
4215
4278
  * console.error('Validation failed:', result.error.issues)
4216
4279
  * }
4217
4280
  * ```
4281
+ *
4282
+ * @example
4283
+ * ```typescript
4284
+ * // Runtime error
4285
+ * const result = errorDetailsSchema.safeParse({
4286
+ * code: 9001,
4287
+ * name: 'BALANCE_INSUFFICIENT_TOKEN',
4288
+ * type: 'BALANCE',
4289
+ * recoverability: 'FATAL',
4290
+ * message: 'Insufficient USDC balance'
4291
+ * })
4292
+ * ```
4218
4293
  */
4219
4294
  const errorDetailsSchema = zod.z.object({
4220
- /** Numeric identifier following standardized ranges (1000+ for INPUT errors) */
4295
+ /**
4296
+ * Numeric identifier following standardized ranges:
4297
+ * - 1000-1999: INPUT errors - Parameter validation
4298
+ * - 3000-3999: NETWORK errors - Connectivity issues
4299
+ * - 4000-4999: RPC errors - Provider issues, gas estimation
4300
+ * - 5000-5999: ONCHAIN errors - Transaction/simulation failures
4301
+ * - 9000-9999: BALANCE errors - Insufficient funds
4302
+ */
4221
4303
  code: zod.z
4222
4304
  .number()
4223
4305
  .int('Error code must be an integer')
4224
- .min(1000, 'Error code must be within valid range (1000+)')
4225
- .max(1099, 'Error code must be within valid range (1099 max)'),
4226
- /** Human-readable ID (e.g., "NETWORK_MISMATCH") */
4306
+ .refine((code) => ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
4307
+ message: 'Error code must be in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
4308
+ }),
4309
+ /** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
4227
4310
  name: zod.z
4228
4311
  .string()
4229
4312
  .min(1, 'Error name must be a non-empty string')
4230
4313
  .regex(/^[A-Z_][A-Z0-9_]*$/, 'Error name must match pattern: ^[A-Z_][A-Z0-9_]*$'),
4314
+ /** Error category indicating where the error originated */
4315
+ type: zod.z.enum(ERROR_TYPE_ARRAY, {
4316
+ errorMap: () => ({
4317
+ message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
4318
+ }),
4319
+ }),
4231
4320
  /** Error handling strategy */
4232
4321
  recoverability: zod.z.enum(RECOVERABILITY_ARRAY, {
4233
4322
  errorMap: () => ({
4234
4323
  message: 'Recoverability must be one of: RETRYABLE, RESUMABLE, FATAL',
4235
4324
  }),
4236
4325
  }),
4237
- /** User-friendly explanation with network context */
4326
+ /** User-friendly explanation with context */
4238
4327
  message: zod.z
4239
4328
  .string()
4240
4329
  .min(1, 'Error message must be a non-empty string')
4241
- .max(500, 'Error message must be 500 characters or less'),
4330
+ .max(1000, 'Error message must be 1000 characters or less'),
4242
4331
  /** Raw error details, context, or the original error that caused this one. */
4243
4332
  cause: zod.z
4244
4333
  .object({
@@ -4253,7 +4342,7 @@ const errorDetailsSchema = zod.z.object({
4253
4342
  *
4254
4343
  * @param details - The object to validate
4255
4344
  * @returns The validated ErrorDetails object
4256
- * @throws {TypeError} When validation fails
4345
+ * @throws TypeError When validation fails
4257
4346
  *
4258
4347
  * @example
4259
4348
  * ```typescript
@@ -4330,6 +4419,8 @@ class KitError extends Error {
4330
4419
  code;
4331
4420
  /** Human-readable ID (e.g., "NETWORK_MISMATCH") */
4332
4421
  name;
4422
+ /** Error category indicating where the error originated */
4423
+ type;
4333
4424
  /** Error handling strategy */
4334
4425
  recoverability;
4335
4426
  /** Raw error details, context, or the original error that caused this one. */
@@ -4358,6 +4449,12 @@ class KitError extends Error {
4358
4449
  enumerable: true,
4359
4450
  configurable: false,
4360
4451
  },
4452
+ type: {
4453
+ value: validatedDetails.type,
4454
+ writable: false,
4455
+ enumerable: true,
4456
+ configurable: false,
4457
+ },
4361
4458
  recoverability: {
4362
4459
  value: validatedDetails.recoverability,
4363
4460
  writable: false,
@@ -4377,14 +4474,19 @@ class KitError extends Error {
4377
4474
  }
4378
4475
 
4379
4476
  /**
4380
- * Minimum error code for INPUT type errors.
4381
- * INPUT errors represent validation failures and invalid parameters.
4477
+ * Standardized error code ranges for consistent categorization:
4478
+ *
4479
+ * - 1000-1999: INPUT errors - Parameter validation, input format errors
4480
+ * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
4481
+ * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
4482
+ * - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
4483
+ * - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
4382
4484
  */
4383
4485
  /**
4384
4486
  * Standardized error definitions for INPUT type errors.
4385
4487
  *
4386
- * Each entry combines the numeric error code with its corresponding
4387
- * string name to ensure consistency when creating error instances.
4488
+ * Each entry combines the numeric error code, string name, and type
4489
+ * to ensure consistency when creating error instances.
4388
4490
  *
4389
4491
  * Error codes follow a hierarchical numbering scheme where the first digit
4390
4492
  * indicates the error category (1 = INPUT) and subsequent digits provide
@@ -4401,9 +4503,10 @@ class KitError extends Error {
4401
4503
  * message: 'Source and destination networks must be different'
4402
4504
  * })
4403
4505
  *
4404
- * // Access code and name individually if needed
4506
+ * // Access code, name, and type individually if needed
4405
4507
  * console.log(InputError.NETWORK_MISMATCH.code) // 1001
4406
4508
  * console.log(InputError.NETWORK_MISMATCH.name) // 'INPUT_NETWORK_MISMATCH'
4509
+ * console.log(InputError.NETWORK_MISMATCH.type) // 'INPUT'
4407
4510
  * ```
4408
4511
  */
4409
4512
  const InputError = {
@@ -4411,16 +4514,19 @@ const InputError = {
4411
4514
  NETWORK_MISMATCH: {
4412
4515
  code: 1001,
4413
4516
  name: 'INPUT_NETWORK_MISMATCH',
4517
+ type: 'INPUT',
4414
4518
  },
4415
4519
  /** Unsupported or invalid bridge route configuration */
4416
4520
  UNSUPPORTED_ROUTE: {
4417
4521
  code: 1003,
4418
4522
  name: 'INPUT_UNSUPPORTED_ROUTE',
4523
+ type: 'INPUT',
4419
4524
  },
4420
4525
  /** General validation failure for complex validation rules */
4421
4526
  VALIDATION_FAILED: {
4422
4527
  code: 1098,
4423
4528
  name: 'INPUT_VALIDATION_FAILED',
4529
+ type: 'INPUT',
4424
4530
  },
4425
4531
  };
4426
4532
 
@@ -4535,6 +4641,36 @@ function createValidationFailedError(field, value, reason) {
4535
4641
  return new KitError(errorDetails);
4536
4642
  }
4537
4643
 
4644
+ /**
4645
+ * Type guard to check if an error is a KitError instance.
4646
+ *
4647
+ * This guard enables TypeScript to narrow the type from `unknown` to
4648
+ * `KitError`, providing access to structured error properties like
4649
+ * code, name, and recoverability.
4650
+ *
4651
+ * @param error - Unknown error to check
4652
+ * @returns True if error is KitError with proper type narrowing
4653
+ *
4654
+ * @example
4655
+ * ```typescript
4656
+ * import { isKitError } from '@core/errors'
4657
+ *
4658
+ * try {
4659
+ * await kit.bridge(params)
4660
+ * } catch (error) {
4661
+ * if (isKitError(error)) {
4662
+ * // TypeScript knows this is KitError
4663
+ * console.log(`Structured error: ${error.name} (${error.code})`)
4664
+ * } else {
4665
+ * console.log('Regular error:', error)
4666
+ * }
4667
+ * }
4668
+ * ```
4669
+ */
4670
+ function isKitError(error) {
4671
+ return error instanceof KitError;
4672
+ }
4673
+
4538
4674
  const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
4539
4675
  /**
4540
4676
  * Asserts that the provided parameters match the CCTPv2 bridge parameters interface.
@@ -4629,6 +4765,11 @@ function assertCCTPv2BridgeParams(params) {
4629
4765
  // Validate CCTP v2 specific requirements for both wallets
4630
4766
  assertCCTPv2WalletContext(bridgeParams.source);
4631
4767
  assertCCTPv2WalletContext(bridgeParams.destination);
4768
+ // Validate that adapters support their respective chains (defense-in-depth)
4769
+ // This provides additional validation for users calling the provider directly
4770
+ // without going through the kit layer, ensuring early detection of unsupported chains
4771
+ bridgeParams.source.adapter.validateChainSupport(bridgeParams.source.chain);
4772
+ bridgeParams.destination.adapter.validateChainSupport(bridgeParams.destination.chain);
4632
4773
  }
4633
4774
  /**
4634
4775
  * Validate CCTP v2 support on both chains
@@ -5211,7 +5352,11 @@ async function bridge(params, provider) {
5211
5352
  try {
5212
5353
  const step = await executor(params, provider, context);
5213
5354
  if (step.state === 'error') {
5214
- // Include error details from the step in the thrown error. Fall back to step.error.
5355
+ // If step.error is a KitError, preserve it by re-throwing directly
5356
+ if (isKitError(step.error)) {
5357
+ throw step.error;
5358
+ }
5359
+ // For non-KitError errors, wrap in descriptive Error
5215
5360
  let fallbackErrorMessage = 'Unknown error';
5216
5361
  if (step.error instanceof Error) {
5217
5362
  fallbackErrorMessage = step.error.message;
package/index.d.ts CHANGED
@@ -1177,6 +1177,14 @@ interface CCTPv2ActionMap {
1177
1177
  * the adapter's default address.
1178
1178
  */
1179
1179
  readonly destinationAddress?: string;
1180
+ /**
1181
+ * The mint recipient address from the decoded CCTP message.
1182
+ *
1183
+ * This is the actual address encoded in the burn message where tokens will be minted.
1184
+ * For Solana, this is already the Associated Token Account (ATA) address, not the owner.
1185
+ * For EVM chains, this is the recipient's wallet address.
1186
+ */
1187
+ readonly mintRecipient?: string;
1180
1188
  };
1181
1189
  /**
1182
1190
  * Initiate a cross-chain USDC transfer using a custom bridge contract with preapproval funnel.
@@ -2118,10 +2126,10 @@ declare abstract class Adapter<TAdapterCapabilities extends AdapterCapabilities
2118
2126
  * This address is used as the default sender for transactions
2119
2127
  * and interactions initiated by this adapter.
2120
2128
  *
2121
- * @param chain - Optional chain definition for chain-specific address resolution (used by EVM adapters)
2129
+ * @param chain - The chain to use for address resolution.
2122
2130
  * @returns A promise that resolves to the blockchain address as a string.
2123
2131
  */
2124
- abstract getAddress(chain?: ChainDefinition): Promise<string>;
2132
+ abstract getAddress(chain: ChainDefinition): Promise<string>;
2125
2133
  /**
2126
2134
  * Switches the adapter to operate on the specified chain.
2127
2135
  *
@@ -2192,7 +2200,7 @@ declare abstract class Adapter<TAdapterCapabilities extends AdapterCapabilities
2192
2200
  * Validate that the target chain is supported by this adapter.
2193
2201
  *
2194
2202
  * @param targetChain - The chain to validate.
2195
- * @throws Error if the chain is not supported.
2203
+ * @throws KitError with INVALID_CHAIN code if the chain is not supported by this adapter.
2196
2204
  */
2197
2205
  validateChainSupport(targetChain: ChainDefinition): void;
2198
2206
  /**
package/index.mjs CHANGED
@@ -4185,9 +4185,71 @@ const RECOVERABILITY_VALUES = [
4185
4185
  'RESUMABLE',
4186
4186
  'FATAL',
4187
4187
  ];
4188
+ /**
4189
+ * Error type constants for categorizing errors by origin.
4190
+ *
4191
+ * This const object provides a reference for error types, enabling
4192
+ * IDE autocomplete and preventing typos when creating custom errors.
4193
+ *
4194
+ * @remarks
4195
+ * While internal error definitions use string literals with type annotations
4196
+ * for strict type safety, this constant is useful for developers creating
4197
+ * custom error instances or checking error types programmatically.
4198
+ *
4199
+ * @example
4200
+ * ```typescript
4201
+ * import { ERROR_TYPES, KitError } from '@core/errors'
4202
+ *
4203
+ * // Use for type checking
4204
+ * if (error.type === ERROR_TYPES.BALANCE) {
4205
+ * console.log('This is a balance error')
4206
+ * }
4207
+ * ```
4208
+ *
4209
+ * @example
4210
+ * ```typescript
4211
+ * // Use as reference when creating custom errors
4212
+ * const error = new KitError({
4213
+ * code: 9999,
4214
+ * name: 'CUSTOM_ERROR',
4215
+ * type: ERROR_TYPES.BALANCE, // IDE autocomplete works here
4216
+ * recoverability: 'FATAL',
4217
+ * message: 'Custom balance error'
4218
+ * })
4219
+ * ```
4220
+ */
4221
+ const ERROR_TYPES = {
4222
+ /** User input validation and parameter checking */
4223
+ INPUT: 'INPUT',
4224
+ /** Insufficient token balances and amount validation */
4225
+ BALANCE: 'BALANCE',
4226
+ /** On-chain execution: reverts, gas issues, transaction failures */
4227
+ ONCHAIN: 'ONCHAIN',
4228
+ /** Blockchain RPC provider issues and endpoint problems */
4229
+ RPC: 'RPC',
4230
+ /** Internet connectivity, DNS resolution, connection issues */
4231
+ NETWORK: 'NETWORK',
4232
+ };
4233
+ /**
4234
+ * Array of valid error type values for validation.
4235
+ * Derived from ERROR_TYPES const object.
4236
+ */
4237
+ const ERROR_TYPE_VALUES = Object.values(ERROR_TYPES);
4188
4238
 
4189
- // Create a mutable array for Zod enum validation
4239
+ // Create mutable arrays for Zod enum validation
4190
4240
  const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
4241
+ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
4242
+ /**
4243
+ * Error code ranges for validation.
4244
+ * Single source of truth for valid error code ranges.
4245
+ */
4246
+ const ERROR_CODE_RANGES = [
4247
+ { min: 1000, max: 1999, type: 'INPUT' },
4248
+ { min: 3000, max: 3999, type: 'NETWORK' },
4249
+ { min: 4000, max: 4999, type: 'RPC' },
4250
+ { min: 5000, max: 5999, type: 'ONCHAIN' },
4251
+ { min: 9000, max: 9999, type: 'BALANCE' },
4252
+ ];
4191
4253
  /**
4192
4254
  * Zod schema for validating ErrorDetails objects.
4193
4255
  *
@@ -4200,7 +4262,8 @@ const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
4200
4262
  *
4201
4263
  * const result = errorDetailsSchema.safeParse({
4202
4264
  * code: 1001,
4203
- * name: 'NETWORK_MISMATCH',
4265
+ * name: 'INPUT_NETWORK_MISMATCH',
4266
+ * type: 'INPUT',
4204
4267
  * recoverability: 'FATAL',
4205
4268
  * message: 'Source and destination networks must be different'
4206
4269
  * })
@@ -4209,30 +4272,56 @@ const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
4209
4272
  * console.error('Validation failed:', result.error.issues)
4210
4273
  * }
4211
4274
  * ```
4275
+ *
4276
+ * @example
4277
+ * ```typescript
4278
+ * // Runtime error
4279
+ * const result = errorDetailsSchema.safeParse({
4280
+ * code: 9001,
4281
+ * name: 'BALANCE_INSUFFICIENT_TOKEN',
4282
+ * type: 'BALANCE',
4283
+ * recoverability: 'FATAL',
4284
+ * message: 'Insufficient USDC balance'
4285
+ * })
4286
+ * ```
4212
4287
  */
4213
4288
  const errorDetailsSchema = z.object({
4214
- /** Numeric identifier following standardized ranges (1000+ for INPUT errors) */
4289
+ /**
4290
+ * Numeric identifier following standardized ranges:
4291
+ * - 1000-1999: INPUT errors - Parameter validation
4292
+ * - 3000-3999: NETWORK errors - Connectivity issues
4293
+ * - 4000-4999: RPC errors - Provider issues, gas estimation
4294
+ * - 5000-5999: ONCHAIN errors - Transaction/simulation failures
4295
+ * - 9000-9999: BALANCE errors - Insufficient funds
4296
+ */
4215
4297
  code: z
4216
4298
  .number()
4217
4299
  .int('Error code must be an integer')
4218
- .min(1000, 'Error code must be within valid range (1000+)')
4219
- .max(1099, 'Error code must be within valid range (1099 max)'),
4220
- /** Human-readable ID (e.g., "NETWORK_MISMATCH") */
4300
+ .refine((code) => ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
4301
+ message: 'Error code must be in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
4302
+ }),
4303
+ /** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
4221
4304
  name: z
4222
4305
  .string()
4223
4306
  .min(1, 'Error name must be a non-empty string')
4224
4307
  .regex(/^[A-Z_][A-Z0-9_]*$/, 'Error name must match pattern: ^[A-Z_][A-Z0-9_]*$'),
4308
+ /** Error category indicating where the error originated */
4309
+ type: z.enum(ERROR_TYPE_ARRAY, {
4310
+ errorMap: () => ({
4311
+ message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
4312
+ }),
4313
+ }),
4225
4314
  /** Error handling strategy */
4226
4315
  recoverability: z.enum(RECOVERABILITY_ARRAY, {
4227
4316
  errorMap: () => ({
4228
4317
  message: 'Recoverability must be one of: RETRYABLE, RESUMABLE, FATAL',
4229
4318
  }),
4230
4319
  }),
4231
- /** User-friendly explanation with network context */
4320
+ /** User-friendly explanation with context */
4232
4321
  message: z
4233
4322
  .string()
4234
4323
  .min(1, 'Error message must be a non-empty string')
4235
- .max(500, 'Error message must be 500 characters or less'),
4324
+ .max(1000, 'Error message must be 1000 characters or less'),
4236
4325
  /** Raw error details, context, or the original error that caused this one. */
4237
4326
  cause: z
4238
4327
  .object({
@@ -4247,7 +4336,7 @@ const errorDetailsSchema = z.object({
4247
4336
  *
4248
4337
  * @param details - The object to validate
4249
4338
  * @returns The validated ErrorDetails object
4250
- * @throws {TypeError} When validation fails
4339
+ * @throws TypeError When validation fails
4251
4340
  *
4252
4341
  * @example
4253
4342
  * ```typescript
@@ -4324,6 +4413,8 @@ class KitError extends Error {
4324
4413
  code;
4325
4414
  /** Human-readable ID (e.g., "NETWORK_MISMATCH") */
4326
4415
  name;
4416
+ /** Error category indicating where the error originated */
4417
+ type;
4327
4418
  /** Error handling strategy */
4328
4419
  recoverability;
4329
4420
  /** Raw error details, context, or the original error that caused this one. */
@@ -4352,6 +4443,12 @@ class KitError extends Error {
4352
4443
  enumerable: true,
4353
4444
  configurable: false,
4354
4445
  },
4446
+ type: {
4447
+ value: validatedDetails.type,
4448
+ writable: false,
4449
+ enumerable: true,
4450
+ configurable: false,
4451
+ },
4355
4452
  recoverability: {
4356
4453
  value: validatedDetails.recoverability,
4357
4454
  writable: false,
@@ -4371,14 +4468,19 @@ class KitError extends Error {
4371
4468
  }
4372
4469
 
4373
4470
  /**
4374
- * Minimum error code for INPUT type errors.
4375
- * INPUT errors represent validation failures and invalid parameters.
4471
+ * Standardized error code ranges for consistent categorization:
4472
+ *
4473
+ * - 1000-1999: INPUT errors - Parameter validation, input format errors
4474
+ * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
4475
+ * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
4476
+ * - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
4477
+ * - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
4376
4478
  */
4377
4479
  /**
4378
4480
  * Standardized error definitions for INPUT type errors.
4379
4481
  *
4380
- * Each entry combines the numeric error code with its corresponding
4381
- * string name to ensure consistency when creating error instances.
4482
+ * Each entry combines the numeric error code, string name, and type
4483
+ * to ensure consistency when creating error instances.
4382
4484
  *
4383
4485
  * Error codes follow a hierarchical numbering scheme where the first digit
4384
4486
  * indicates the error category (1 = INPUT) and subsequent digits provide
@@ -4395,9 +4497,10 @@ class KitError extends Error {
4395
4497
  * message: 'Source and destination networks must be different'
4396
4498
  * })
4397
4499
  *
4398
- * // Access code and name individually if needed
4500
+ * // Access code, name, and type individually if needed
4399
4501
  * console.log(InputError.NETWORK_MISMATCH.code) // 1001
4400
4502
  * console.log(InputError.NETWORK_MISMATCH.name) // 'INPUT_NETWORK_MISMATCH'
4503
+ * console.log(InputError.NETWORK_MISMATCH.type) // 'INPUT'
4401
4504
  * ```
4402
4505
  */
4403
4506
  const InputError = {
@@ -4405,16 +4508,19 @@ const InputError = {
4405
4508
  NETWORK_MISMATCH: {
4406
4509
  code: 1001,
4407
4510
  name: 'INPUT_NETWORK_MISMATCH',
4511
+ type: 'INPUT',
4408
4512
  },
4409
4513
  /** Unsupported or invalid bridge route configuration */
4410
4514
  UNSUPPORTED_ROUTE: {
4411
4515
  code: 1003,
4412
4516
  name: 'INPUT_UNSUPPORTED_ROUTE',
4517
+ type: 'INPUT',
4413
4518
  },
4414
4519
  /** General validation failure for complex validation rules */
4415
4520
  VALIDATION_FAILED: {
4416
4521
  code: 1098,
4417
4522
  name: 'INPUT_VALIDATION_FAILED',
4523
+ type: 'INPUT',
4418
4524
  },
4419
4525
  };
4420
4526
 
@@ -4529,6 +4635,36 @@ function createValidationFailedError(field, value, reason) {
4529
4635
  return new KitError(errorDetails);
4530
4636
  }
4531
4637
 
4638
+ /**
4639
+ * Type guard to check if an error is a KitError instance.
4640
+ *
4641
+ * This guard enables TypeScript to narrow the type from `unknown` to
4642
+ * `KitError`, providing access to structured error properties like
4643
+ * code, name, and recoverability.
4644
+ *
4645
+ * @param error - Unknown error to check
4646
+ * @returns True if error is KitError with proper type narrowing
4647
+ *
4648
+ * @example
4649
+ * ```typescript
4650
+ * import { isKitError } from '@core/errors'
4651
+ *
4652
+ * try {
4653
+ * await kit.bridge(params)
4654
+ * } catch (error) {
4655
+ * if (isKitError(error)) {
4656
+ * // TypeScript knows this is KitError
4657
+ * console.log(`Structured error: ${error.name} (${error.code})`)
4658
+ * } else {
4659
+ * console.log('Regular error:', error)
4660
+ * }
4661
+ * }
4662
+ * ```
4663
+ */
4664
+ function isKitError(error) {
4665
+ return error instanceof KitError;
4666
+ }
4667
+
4532
4668
  const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
4533
4669
  /**
4534
4670
  * Asserts that the provided parameters match the CCTPv2 bridge parameters interface.
@@ -4623,6 +4759,11 @@ function assertCCTPv2BridgeParams(params) {
4623
4759
  // Validate CCTP v2 specific requirements for both wallets
4624
4760
  assertCCTPv2WalletContext(bridgeParams.source);
4625
4761
  assertCCTPv2WalletContext(bridgeParams.destination);
4762
+ // Validate that adapters support their respective chains (defense-in-depth)
4763
+ // This provides additional validation for users calling the provider directly
4764
+ // without going through the kit layer, ensuring early detection of unsupported chains
4765
+ bridgeParams.source.adapter.validateChainSupport(bridgeParams.source.chain);
4766
+ bridgeParams.destination.adapter.validateChainSupport(bridgeParams.destination.chain);
4626
4767
  }
4627
4768
  /**
4628
4769
  * Validate CCTP v2 support on both chains
@@ -5205,7 +5346,11 @@ async function bridge(params, provider) {
5205
5346
  try {
5206
5347
  const step = await executor(params, provider, context);
5207
5348
  if (step.state === 'error') {
5208
- // Include error details from the step in the thrown error. Fall back to step.error.
5349
+ // If step.error is a KitError, preserve it by re-throwing directly
5350
+ if (isKitError(step.error)) {
5351
+ throw step.error;
5352
+ }
5353
+ // For non-KitError errors, wrap in descriptive Error
5209
5354
  let fallbackErrorMessage = 'Unknown error';
5210
5355
  if (step.error instanceof Error) {
5211
5356
  fallbackErrorMessage = step.error.message;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circle-fin/provider-cctp-v2",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Circle's official Cross-Chain Transfer Protocol v2 provider for native USDC bridging",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.mjs",