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