@circle-fin/bridge-kit 1.2.0 → 1.4.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.
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) 2025, Circle Internet Group, Inc. All rights reserved.
2
+ * Copyright (c) 2026, Circle Internet Group, Inc. All rights reserved.
3
3
  *
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  *
@@ -270,6 +270,40 @@ function validateErrorDetails(details) {
270
270
  * stays within KitError's constraints.
271
271
  */
272
272
  const MAX_MESSAGE_LENGTH = 950;
273
+ /**
274
+ * Standard error message for invalid amount format.
275
+ *
276
+ * The SDK enforces strict dot-decimal notation for amount values. This constant
277
+ * provides a consistent error message when users provide amounts with:
278
+ * - Comma decimals (e.g., "1,5")
279
+ * - Thousand separators (e.g., "1,000.50")
280
+ * - Non-numeric characters
281
+ * - Invalid format
282
+ */
283
+ const AMOUNT_FORMAT_ERROR_MESSAGE = 'Amount must be a numeric string with dot (.) as decimal separator (e.g., "10.5", "100"), with no thousand separators or comma decimals';
284
+ /**
285
+ * Error message for amounts that must be greater than zero.
286
+ */
287
+ const AMOUNT_GREATER_THAN_ZERO_MESSAGE = 'Amount must be greater than 0';
288
+ /**
289
+ * Error message for amounts exceeding maximum decimal places.
290
+ *
291
+ * USDC uses 6 decimal places, so amounts with more precision are invalid.
292
+ */
293
+ const AMOUNT_MAX_DECIMAL_PLACES_MESSAGE = 'Maximum supported decimal places: 6';
294
+ /**
295
+ * Error message for amounts that must be non-negative.
296
+ *
297
+ * Used when validating amounts that can be zero or positive but not negative.
298
+ */
299
+ const AMOUNT_NON_NEGATIVE_MESSAGE = 'Amount must be non-negative';
300
+ /**
301
+ * Error message for invalid maxFee format.
302
+ *
303
+ * Used when validating the maxFee configuration parameter. The maxFee can be zero
304
+ * or positive and must follow strict dot-decimal notation.
305
+ */
306
+ const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (.) as decimal separator (e.g., "1", "0.5", ".5", "1.5"), with no thousand separators or comma decimals';
273
307
 
274
308
  /**
275
309
  * Structured error class for Stablecoin Kit operations.
@@ -446,6 +480,12 @@ const InputError = {
446
480
  name: 'INPUT_INVALID_CHAIN',
447
481
  type: 'INPUT',
448
482
  },
483
+ /** Invalid or unknown token (symbol not found, missing decimals, etc.) */
484
+ INVALID_TOKEN: {
485
+ code: 1006,
486
+ name: 'INPUT_INVALID_TOKEN',
487
+ type: 'INPUT',
488
+ },
449
489
  /** General validation failure for complex validation rules */
450
490
  VALIDATION_FAILED: {
451
491
  code: 1098,
@@ -453,6 +493,158 @@ const InputError = {
453
493
  type: 'INPUT',
454
494
  },
455
495
  };
496
+ /**
497
+ * Standardized error definitions for BALANCE type errors.
498
+ *
499
+ * BALANCE errors indicate insufficient funds or allowance issues
500
+ * that prevent transaction execution.
501
+ *
502
+ * @example
503
+ * ```typescript
504
+ * import { BalanceError } from '@core/errors'
505
+ *
506
+ * const error = new KitError({
507
+ * ...BalanceError.INSUFFICIENT_TOKEN,
508
+ * recoverability: 'FATAL',
509
+ * message: 'Insufficient USDC balance on Ethereum',
510
+ * cause: { trace: { required: '100', available: '50' } }
511
+ * })
512
+ * ```
513
+ */
514
+ const BalanceError = {
515
+ /** Insufficient token balance for transaction */
516
+ INSUFFICIENT_TOKEN: {
517
+ code: 9001,
518
+ name: 'BALANCE_INSUFFICIENT_TOKEN',
519
+ type: 'BALANCE',
520
+ },
521
+ /** Insufficient native token (ETH/SOL/etc) for gas fees */
522
+ INSUFFICIENT_GAS: {
523
+ code: 9002,
524
+ name: 'BALANCE_INSUFFICIENT_GAS',
525
+ type: 'BALANCE',
526
+ },
527
+ /** Insufficient allowance for token transfer */
528
+ INSUFFICIENT_ALLOWANCE: {
529
+ code: 9003,
530
+ name: 'BALANCE_INSUFFICIENT_ALLOWANCE',
531
+ type: 'BALANCE',
532
+ },
533
+ };
534
+ /**
535
+ * Standardized error definitions for ONCHAIN type errors.
536
+ *
537
+ * ONCHAIN errors occur during transaction execution, simulation,
538
+ * or interaction with smart contracts on the blockchain.
539
+ *
540
+ * @example
541
+ * ```typescript
542
+ * import { OnchainError } from '@core/errors'
543
+ *
544
+ * const error = new KitError({
545
+ * ...OnchainError.SIMULATION_FAILED,
546
+ * recoverability: 'FATAL',
547
+ * message: 'Simulation failed: ERC20 transfer amount exceeds balance',
548
+ * cause: { trace: { reason: 'ERC20: transfer amount exceeds balance' } }
549
+ * })
550
+ * ```
551
+ */
552
+ const OnchainError = {
553
+ /** Transaction reverted on-chain after execution */
554
+ TRANSACTION_REVERTED: {
555
+ code: 5001,
556
+ name: 'ONCHAIN_TRANSACTION_REVERTED',
557
+ type: 'ONCHAIN',
558
+ },
559
+ /** Pre-flight transaction simulation failed */
560
+ SIMULATION_FAILED: {
561
+ code: 5002,
562
+ name: 'ONCHAIN_SIMULATION_FAILED',
563
+ type: 'ONCHAIN',
564
+ },
565
+ /** Transaction ran out of gas during execution */
566
+ OUT_OF_GAS: {
567
+ code: 5003,
568
+ name: 'ONCHAIN_OUT_OF_GAS',
569
+ type: 'ONCHAIN',
570
+ },
571
+ /** Transaction exceeds block gas limit */
572
+ GAS_LIMIT_EXCEEDED: {
573
+ code: 5004,
574
+ name: 'ONCHAIN_GAS_LIMIT_EXCEEDED',
575
+ type: 'ONCHAIN',
576
+ },
577
+ };
578
+ /**
579
+ * Standardized error definitions for RPC type errors.
580
+ *
581
+ * RPC errors occur when communicating with blockchain RPC providers,
582
+ * including endpoint failures, invalid responses, and provider-specific issues.
583
+ *
584
+ * @example
585
+ * ```typescript
586
+ * import { RpcError } from '@core/errors'
587
+ *
588
+ * const error = new KitError({
589
+ * ...RpcError.ENDPOINT_ERROR,
590
+ * recoverability: 'RETRYABLE',
591
+ * message: 'RPC endpoint unavailable on Ethereum',
592
+ * cause: { trace: { endpoint: 'https://mainnet.infura.io' } }
593
+ * })
594
+ * ```
595
+ */
596
+ const RpcError = {
597
+ /** RPC endpoint returned error or is unavailable */
598
+ ENDPOINT_ERROR: {
599
+ code: 4001,
600
+ name: 'RPC_ENDPOINT_ERROR',
601
+ type: 'RPC',
602
+ },
603
+ /** Invalid or unexpected RPC response format */
604
+ INVALID_RESPONSE: {
605
+ code: 4002,
606
+ name: 'RPC_INVALID_RESPONSE',
607
+ type: 'RPC',
608
+ },
609
+ /** Nonce-related errors from RPC provider */
610
+ NONCE_ERROR: {
611
+ code: 4003,
612
+ name: 'RPC_NONCE_ERROR',
613
+ type: 'RPC',
614
+ },
615
+ };
616
+ /**
617
+ * Standardized error definitions for NETWORK type errors.
618
+ *
619
+ * NETWORK errors indicate connectivity issues at the network layer,
620
+ * including DNS failures, connection timeouts, and unreachable endpoints.
621
+ *
622
+ * @example
623
+ * ```typescript
624
+ * import { NetworkError } from '@core/errors'
625
+ *
626
+ * const error = new KitError({
627
+ * ...NetworkError.CONNECTION_FAILED,
628
+ * recoverability: 'RETRYABLE',
629
+ * message: 'Failed to connect to Ethereum network',
630
+ * cause: { trace: { error: 'ECONNREFUSED' } }
631
+ * })
632
+ * ```
633
+ */
634
+ const NetworkError = {
635
+ /** Network connection failed or unreachable */
636
+ CONNECTION_FAILED: {
637
+ code: 3001,
638
+ name: 'NETWORK_CONNECTION_FAILED',
639
+ type: 'NETWORK',
640
+ },
641
+ /** Network request timeout */
642
+ TIMEOUT: {
643
+ code: 3002,
644
+ name: 'NETWORK_TIMEOUT',
645
+ type: 'NETWORK',
646
+ },
647
+ };
456
648
 
457
649
  /**
458
650
  * Creates error for network type mismatch between source and destination.
@@ -1076,8 +1268,11 @@ const ArcTestnet = defineChain({
1076
1268
  name: 'Arc Testnet',
1077
1269
  title: 'ArcTestnet',
1078
1270
  nativeCurrency: {
1079
- name: 'Arc',
1080
- symbol: 'Arc',
1271
+ name: 'USDC',
1272
+ symbol: 'USDC',
1273
+ // Arc uses native USDC with 18 decimals for gas payments (EVM standard).
1274
+ // Note: The ERC-20 USDC contract at usdcAddress uses 6 decimals.
1275
+ // See: https://docs.arc.network/arc/references/contract-addresses
1081
1276
  decimals: 18,
1082
1277
  },
1083
1278
  chainId: 5042002,
@@ -3413,6 +3608,114 @@ function isRetryableError(error) {
3413
3608
  function isInputError(error) {
3414
3609
  return isKitError(error) && error.type === ERROR_TYPES.INPUT;
3415
3610
  }
3611
+ /**
3612
+ * Type guard to check if error is KitError with BALANCE type.
3613
+ *
3614
+ * BALANCE errors indicate insufficient funds or allowance issues
3615
+ * that prevent transaction execution. These errors are always FATAL
3616
+ * and require the user to add funds or approve more tokens.
3617
+ *
3618
+ * @param error - Unknown error to check
3619
+ * @returns True if error is KitError with BALANCE type
3620
+ *
3621
+ * @example
3622
+ * ```typescript
3623
+ * import { isBalanceError } from '@core/errors'
3624
+ *
3625
+ * try {
3626
+ * await kit.bridge(params)
3627
+ * } catch (error) {
3628
+ * if (isBalanceError(error)) {
3629
+ * console.log('Insufficient funds:', error.message)
3630
+ * showAddFundsUI()
3631
+ * }
3632
+ * }
3633
+ * ```
3634
+ */
3635
+ function isBalanceError(error) {
3636
+ return isKitError(error) && error.type === ERROR_TYPES.BALANCE;
3637
+ }
3638
+ /**
3639
+ * Type guard to check if error is KitError with ONCHAIN type.
3640
+ *
3641
+ * ONCHAIN errors occur during transaction execution or simulation,
3642
+ * including reverts, gas issues, and smart contract failures.
3643
+ * These errors are typically FATAL.
3644
+ *
3645
+ * @param error - Unknown error to check
3646
+ * @returns True if error is KitError with ONCHAIN type
3647
+ *
3648
+ * @example
3649
+ * ```typescript
3650
+ * import { isOnchainError } from '@core/errors'
3651
+ *
3652
+ * try {
3653
+ * await kit.bridge(params)
3654
+ * } catch (error) {
3655
+ * if (isOnchainError(error)) {
3656
+ * console.log('Transaction failed:', error.message)
3657
+ * showTransactionErrorUI()
3658
+ * }
3659
+ * }
3660
+ * ```
3661
+ */
3662
+ function isOnchainError(error) {
3663
+ return isKitError(error) && error.type === ERROR_TYPES.ONCHAIN;
3664
+ }
3665
+ /**
3666
+ * Type guard to check if error is KitError with RPC type.
3667
+ *
3668
+ * RPC errors occur when communicating with blockchain RPC providers.
3669
+ * These errors are typically RETRYABLE as they often indicate
3670
+ * temporary provider issues.
3671
+ *
3672
+ * @param error - Unknown error to check
3673
+ * @returns True if error is KitError with RPC type
3674
+ *
3675
+ * @example
3676
+ * ```typescript
3677
+ * import { isRpcError } from '@core/errors'
3678
+ *
3679
+ * try {
3680
+ * await kit.bridge(params)
3681
+ * } catch (error) {
3682
+ * if (isRpcError(error)) {
3683
+ * console.log('RPC error:', error.message)
3684
+ * retryWithBackoff()
3685
+ * }
3686
+ * }
3687
+ * ```
3688
+ */
3689
+ function isRpcError(error) {
3690
+ return isKitError(error) && error.type === ERROR_TYPES.RPC;
3691
+ }
3692
+ /**
3693
+ * Type guard to check if error is KitError with NETWORK type.
3694
+ *
3695
+ * NETWORK errors indicate connectivity issues at the network layer.
3696
+ * These errors are typically RETRYABLE as they often indicate
3697
+ * temporary network problems.
3698
+ *
3699
+ * @param error - Unknown error to check
3700
+ * @returns True if error is KitError with NETWORK type
3701
+ *
3702
+ * @example
3703
+ * ```typescript
3704
+ * import { isNetworkError } from '@core/errors'
3705
+ *
3706
+ * try {
3707
+ * await kit.bridge(params)
3708
+ * } catch (error) {
3709
+ * if (isNetworkError(error)) {
3710
+ * console.log('Network issue:', error.message)
3711
+ * retryWithBackoff()
3712
+ * }
3713
+ * }
3714
+ * ```
3715
+ */
3716
+ function isNetworkError(error) {
3717
+ return isKitError(error) && error.type === ERROR_TYPES.NETWORK;
3718
+ }
3416
3719
  /**
3417
3720
  * Safely extracts error message from any error type.
3418
3721
  *
@@ -3702,7 +4005,6 @@ function convertZodErrorToStructured(zodError, params) {
3702
4005
  function createValidationFailedError(zodError) {
3703
4006
  return createValidationErrorFromZod(zodError, 'parameters');
3704
4007
  }
3705
- const AMOUNT_FORMAT_ERROR_MESSAGE = 'Amount must be a numeric string with dot (.) as decimal separator, with no thousand separators or comma decimals';
3706
4008
  /**
3707
4009
  * Handles amount-related validation errors from Zod.
3708
4010
  *
@@ -3752,7 +4054,7 @@ function handleAmountError(path, code, message, paramsObj) {
3752
4054
  */
3753
4055
  function handleNegativeAmountError(code, message, amount) {
3754
4056
  if (code === 'too_small' || message.includes('greater than')) {
3755
- return createInvalidAmountError(amount, 'Amount must be greater than 0');
4057
+ return createInvalidAmountError(amount, AMOUNT_GREATER_THAN_ZERO_MESSAGE);
3756
4058
  }
3757
4059
  return null;
3758
4060
  }
@@ -3774,10 +4076,10 @@ function handleCustomAmountError(code, message, amount) {
3774
4076
  if (code !== 'custom')
3775
4077
  return null;
3776
4078
  if (message.includes('non-negative')) {
3777
- return createInvalidAmountError(amount, 'Amount must be non-negative');
4079
+ return createInvalidAmountError(amount, AMOUNT_NON_NEGATIVE_MESSAGE);
3778
4080
  }
3779
4081
  if (message.includes('greater than 0')) {
3780
- return createInvalidAmountError(amount, 'Amount must be greater than 0');
4082
+ return createInvalidAmountError(amount, AMOUNT_GREATER_THAN_ZERO_MESSAGE);
3781
4083
  }
3782
4084
  if (message.includes('decimal places')) {
3783
4085
  return createInvalidAmountError(amount, message);
@@ -3810,7 +4112,7 @@ function handleInvalidStringAmountError(code, message, amount) {
3810
4112
  return null;
3811
4113
  // Check for decimal places validation
3812
4114
  if (isDecimalPlacesError(message)) {
3813
- return createInvalidAmountError(amount, 'Maximum supported decimal places: 6');
4115
+ return createInvalidAmountError(amount, AMOUNT_MAX_DECIMAL_PLACES_MESSAGE);
3814
4116
  }
3815
4117
  // Check for numeric format validation
3816
4118
  if (isNumericFormatError(message)) {
@@ -4335,7 +4637,7 @@ const parseAmount = (params) => {
4335
4637
  };
4336
4638
 
4337
4639
  var name = "@circle-fin/bridge-kit";
4338
- var version = "1.2.0";
4640
+ var version = "1.4.0";
4339
4641
  var pkg = {
4340
4642
  name: name,
4341
4643
  version: version};
@@ -4701,41 +5003,46 @@ var TransferSpeed;
4701
5003
  * - regexMessage: error message when the basic numeric format fails.
4702
5004
  * - maxDecimals: maximum number of decimal places allowed (e.g., 6 for USDC).
4703
5005
  */
4704
- const createDecimalStringValidator = (options) => (schema) => schema
4705
- .regex(/^-?(?:\d+(?:\.\d+)?|\.\d+)$/, options.regexMessage)
4706
- .superRefine((val, ctx) => {
4707
- const amount = Number.parseFloat(val);
4708
- if (Number.isNaN(amount)) {
4709
- ctx.addIssue({
4710
- code: z.ZodIssueCode.custom,
4711
- message: options.regexMessage,
4712
- });
4713
- return;
4714
- }
4715
- // Check decimal precision if maxDecimals is specified
4716
- if (options.maxDecimals !== undefined) {
4717
- const decimalPart = val.split('.')[1];
4718
- if (decimalPart && decimalPart.length > options.maxDecimals) {
5006
+ const createDecimalStringValidator = (options) => (schema) => {
5007
+ // Capitalize first letter of attribute name for error messages
5008
+ const capitalizedAttributeName = options.attributeName.charAt(0).toUpperCase() +
5009
+ options.attributeName.slice(1);
5010
+ return schema
5011
+ .regex(/^-?(?:\d+(?:\.\d+)?|\.\d+)$/, options.regexMessage)
5012
+ .superRefine((val, ctx) => {
5013
+ const amount = Number.parseFloat(val);
5014
+ if (Number.isNaN(amount)) {
4719
5015
  ctx.addIssue({
4720
5016
  code: z.ZodIssueCode.custom,
4721
- message: `Maximum supported decimal places: ${options.maxDecimals.toString()}`,
5017
+ message: options.regexMessage,
4722
5018
  });
4723
5019
  return;
4724
5020
  }
4725
- }
4726
- if (options.allowZero && amount < 0) {
4727
- ctx.addIssue({
4728
- code: z.ZodIssueCode.custom,
4729
- message: `${options.attributeName} must be non-negative`,
4730
- });
4731
- }
4732
- else if (!options.allowZero && amount <= 0) {
4733
- ctx.addIssue({
4734
- code: z.ZodIssueCode.custom,
4735
- message: `${options.attributeName} must be greater than 0`,
4736
- });
4737
- }
4738
- });
5021
+ // Check decimal precision if maxDecimals is specified
5022
+ if (options.maxDecimals !== undefined) {
5023
+ const decimalPart = val.split('.')[1];
5024
+ if (decimalPart && decimalPart.length > options.maxDecimals) {
5025
+ ctx.addIssue({
5026
+ code: z.ZodIssueCode.custom,
5027
+ message: `Maximum supported decimal places: ${options.maxDecimals.toString()}`,
5028
+ });
5029
+ return;
5030
+ }
5031
+ }
5032
+ if (options.allowZero && amount < 0) {
5033
+ ctx.addIssue({
5034
+ code: z.ZodIssueCode.custom,
5035
+ message: `${capitalizedAttributeName} must be non-negative`,
5036
+ });
5037
+ }
5038
+ else if (!options.allowZero && amount <= 0) {
5039
+ ctx.addIssue({
5040
+ code: z.ZodIssueCode.custom,
5041
+ message: `${capitalizedAttributeName} must be greater than 0`,
5042
+ });
5043
+ }
5044
+ });
5045
+ };
4739
5046
  /**
4740
5047
  * Schema for validating chain definitions.
4741
5048
  * This ensures the basic structure of a chain definition is valid.
@@ -4895,7 +5202,7 @@ z.object({
4895
5202
  .min(1, 'Required')
4896
5203
  .pipe(createDecimalStringValidator({
4897
5204
  allowZero: false,
4898
- regexMessage: 'Amount must be a numeric string with dot (.) as decimal separator (e.g., "0.1", ".1", "10.5", "1000.50"), with no thousand separators or comma decimals.',
5205
+ regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
4899
5206
  attributeName: 'amount',
4900
5207
  maxDecimals: 6,
4901
5208
  })(z.string())),
@@ -4908,7 +5215,7 @@ z.object({
4908
5215
  .string()
4909
5216
  .pipe(createDecimalStringValidator({
4910
5217
  allowZero: true,
4911
- regexMessage: 'maxFee must be a numeric string with dot (.) as decimal separator (e.g., "1", "0.5", ".5", "1.5"), with no thousand separators or comma decimals.',
5218
+ regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
4912
5219
  attributeName: 'maxFee',
4913
5220
  maxDecimals: 6,
4914
5221
  })(z.string()))
@@ -4917,11 +5224,6 @@ z.object({
4917
5224
  }),
4918
5225
  });
4919
5226
 
4920
- /**
4921
- * Error message constants for validation
4922
- */
4923
- const AMOUNT_VALIDATION_MESSAGE = 'Amount must be a numeric string with dot (.) as decimal separator (e.g., "0.1", ".1", "10.5", "1000.50"), with no thousand separators or comma decimals.';
4924
- const MAX_FEE_VALIDATION_MESSAGE = 'maxFee must be a numeric string with dot (.) as decimal separator (e.g., "1", "0.5", ".5", "1.5"), with no thousand separators or comma decimals.';
4925
5227
  /**
4926
5228
  * Schema for validating AdapterContext for bridge operations.
4927
5229
  * Must always contain both adapter and chain explicitly.
@@ -5016,7 +5318,7 @@ const bridgeParamsWithChainIdentifierSchema = z.object({
5016
5318
  .min(1, 'Required')
5017
5319
  .pipe(createDecimalStringValidator({
5018
5320
  allowZero: false,
5019
- regexMessage: AMOUNT_VALIDATION_MESSAGE,
5321
+ regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
5020
5322
  attributeName: 'amount',
5021
5323
  maxDecimals: 6,
5022
5324
  })(z.string())),
@@ -5029,7 +5331,7 @@ const bridgeParamsWithChainIdentifierSchema = z.object({
5029
5331
  .min(1, 'Required')
5030
5332
  .pipe(createDecimalStringValidator({
5031
5333
  allowZero: true,
5032
- regexMessage: MAX_FEE_VALIDATION_MESSAGE,
5334
+ regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
5033
5335
  attributeName: 'maxFee',
5034
5336
  maxDecimals: 6,
5035
5337
  })(z.string()))
@@ -5290,6 +5592,8 @@ const getAmountTransformer = (formatDirection) => formatDirection === 'to-human-
5290
5592
  : (params) => parseAmount(params).toString();
5291
5593
  /**
5292
5594
  * Format the bridge result into human-readable string values for the user or bigint string values for internal use.
5595
+ *
5596
+ * @typeParam T - The specific result type (must extend BridgeResult or EstimateResult). Preserves the exact type passed in.
5293
5597
  * @param result - The bridge result to format.
5294
5598
  * @param formatDirection - The direction to format the result in.
5295
5599
  * - If 'to-human-readable', the result will be converted to human-readable string values.
@@ -5319,7 +5623,9 @@ const formatBridgeResult = (result, formatDirection) => {
5319
5623
  return {
5320
5624
  ...result,
5321
5625
  amount: transform({ value: result.amount, token: result.token }),
5322
- ...(result.config && {
5626
+ ...('config' in result &&
5627
+ result.config &&
5628
+ Object.keys(result.config).length > 0 && {
5323
5629
  config: {
5324
5630
  ...result.config,
5325
5631
  ...(result.config.maxFee && {
@@ -5331,12 +5637,12 @@ const formatBridgeResult = (result, formatDirection) => {
5331
5637
  ...(result.config.customFee && {
5332
5638
  customFee: {
5333
5639
  ...result.config.customFee,
5334
- value: result.config.customFee.value
5335
- ? transform({
5640
+ ...(result.config.customFee.value && {
5641
+ value: transform({
5336
5642
  value: result.config.customFee.value,
5337
5643
  token: result.token,
5338
- })
5339
- : undefined,
5644
+ }),
5645
+ }),
5340
5646
  },
5341
5647
  }),
5342
5648
  },
@@ -5604,11 +5910,11 @@ class BridgeKit {
5604
5910
  const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
5605
5911
  // Find a provider that supports this route
5606
5912
  const provider = this.findProviderForRoute(finalResolvedParams);
5607
- // Estimate the transfer using the provider
5608
- return provider.estimate(finalResolvedParams);
5913
+ // Estimate the transfer using the provider and format amounts to human-readable strings
5914
+ return formatBridgeResult(await provider.estimate(finalResolvedParams), 'to-human-readable');
5609
5915
  }
5610
5916
  /**
5611
- * Get all chains supported by any provider in the kit.
5917
+ * Get all chains supported by any provider in the kit, with optional filtering.
5612
5918
  *
5613
5919
  * Aggregate and deduplicate the supported chains from all registered providers.
5614
5920
  * This provides a comprehensive list of chains that can be used as either source
@@ -5618,6 +5924,7 @@ class BridgeKit {
5618
5924
  * ensuring each chain appears only once in the result regardless of how many
5619
5925
  * providers support it.
5620
5926
  *
5927
+ * @param options - Optional filtering options to narrow down the returned chains
5621
5928
  * @returns Array of unique chain definitions supported by the registered providers
5622
5929
  *
5623
5930
  * @example
@@ -5625,19 +5932,56 @@ class BridgeKit {
5625
5932
  * import { BridgeKit } from '@circle-fin/bridge-kit'
5626
5933
  *
5627
5934
  * const kit = new BridgeKit()
5628
- * const chains = kit.getSupportedChains()
5935
+ *
5936
+ * // Get all supported chains (no filtering)
5937
+ * const allChains = kit.getSupportedChains()
5938
+ *
5939
+ * // Get only EVM chains
5940
+ * const evmChains = kit.getSupportedChains({ chainType: 'evm' })
5941
+ *
5942
+ * // Get EVM and Solana chains
5943
+ * const evmAndSolana = kit.getSupportedChains({ chainType: ['evm', 'solana'] })
5944
+ *
5945
+ * // Get only mainnet chains
5946
+ * const mainnets = kit.getSupportedChains({ isTestnet: false })
5947
+ *
5948
+ * // Get only EVM mainnet chains
5949
+ * const evmMainnets = kit.getSupportedChains({ chainType: 'evm', isTestnet: false })
5629
5950
  *
5630
5951
  * console.log('Supported chains:')
5631
- * chains.forEach(chain => {
5952
+ * allChains.forEach(chain => {
5632
5953
  * console.log(`- ${chain.name} (${chain.type})`)
5633
5954
  * })
5634
5955
  * ```
5635
5956
  */
5636
- getSupportedChains() {
5957
+ getSupportedChains(options) {
5637
5958
  const supportedChains = this.providers.flatMap((p) => p.supportedChains);
5638
5959
  // Deduplicate chains by using chain identifiers as object keys
5639
5960
  // Later duplicates will override earlier ones, keeping only the last occurrence
5640
- return Object.values(Object.fromEntries(supportedChains.map((chain) => [chain.chain, chain])));
5961
+ let chains = Object.values(Object.fromEntries(supportedChains.map((chain) => [chain.chain, chain])));
5962
+ // Apply chain type filter if provided
5963
+ if (options?.chainType !== undefined) {
5964
+ // Validate at runtime since JS consumers can bypass TypeScript's narrow type.
5965
+ const supportedChainTypes = ['evm', 'solana'];
5966
+ const chainTypeInput = options.chainType;
5967
+ const chainTypeValues = Array.isArray(chainTypeInput)
5968
+ ? chainTypeInput
5969
+ : [chainTypeInput];
5970
+ if (!chainTypeValues.every((chainType) => supportedChainTypes.includes(chainType))) {
5971
+ const listFormatter = new Intl.ListFormat('en', {
5972
+ style: 'long',
5973
+ type: 'conjunction',
5974
+ });
5975
+ throw createValidationFailedError$1('options.chainType', options.chainType, `Supported chain types include: ${listFormatter.format(supportedChainTypes)}`);
5976
+ }
5977
+ const chainTypes = new Set(chainTypeValues);
5978
+ chains = chains.filter((chain) => chainTypes.has(chain.type));
5979
+ }
5980
+ // Apply testnet filter if provided
5981
+ if (options?.isTestnet !== undefined) {
5982
+ chains = chains.filter((chain) => chain.isTestnet === options.isTestnet);
5983
+ }
5984
+ return chains;
5641
5985
  }
5642
5986
  /**
5643
5987
  * Validate that source and destination chains are on the same network type.
@@ -5824,5 +6168,5 @@ class BridgeKit {
5824
6168
  // Auto-register this kit for user agent tracking
5825
6169
  registerKit(`${pkg.name}/${pkg.version}`);
5826
6170
 
5827
- export { Blockchain, BridgeChain, BridgeKit, KitError, TransferSpeed, bridgeParamsWithChainIdentifierSchema, getErrorCode, getErrorMessage, isFatalError, isInputError, isKitError, isRetryableError, resolveChainIdentifier, setExternalPrefix };
6171
+ export { BalanceError, Blockchain, BridgeChain, BridgeKit, InputError, KitError, NetworkError, OnchainError, RpcError, TransferSpeed, bridgeParamsWithChainIdentifierSchema, getErrorCode, getErrorMessage, isBalanceError, isFatalError, isInputError, isKitError, isNetworkError, isOnchainError, isRetryableError, isRpcError, resolveChainIdentifier, setExternalPrefix };
5828
6172
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -1,7 +1,19 @@
1
1
  {
2
2
  "name": "@circle-fin/bridge-kit",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "SDK for seamless cross-chain stablecoin bridging",
5
+ "keywords": [
6
+ "circle",
7
+ "cctp",
8
+ "usdc",
9
+ "stablecoin",
10
+ "bridge",
11
+ "bridging",
12
+ "cross-chain",
13
+ "sdk",
14
+ "typescript",
15
+ "bridge-kit"
16
+ ],
5
17
  "engines": {
6
18
  "node": ">=16.0.0"
7
19
  },
@@ -10,7 +22,7 @@
10
22
  "types": "./index.d.ts",
11
23
  "dependencies": {
12
24
  "zod": "3.25.67",
13
- "@circle-fin/provider-cctp-v2": "^1.0.5",
25
+ "@circle-fin/provider-cctp-v2": "^1.2.0",
14
26
  "abitype": "^1.1.0",
15
27
  "@solana/web3.js": "^1.98.4",
16
28
  "@ethersproject/address": "^5.8.0",