@circle-fin/bridge-kit 1.3.0 → 1.5.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
@@ -122,6 +122,8 @@ const ERROR_TYPES = {
122
122
  RPC: 'RPC',
123
123
  /** Internet connectivity, DNS resolution, connection issues */
124
124
  NETWORK: 'NETWORK',
125
+ /** Catch-all for unrecognized errors (code 0) */
126
+ UNKNOWN: 'UNKNOWN',
125
127
  };
126
128
  /**
127
129
  * Array of valid error type values for validation.
@@ -135,6 +137,8 @@ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
135
137
  /**
136
138
  * Error code ranges for validation.
137
139
  * Single source of truth for valid error code ranges.
140
+ *
141
+ * Note: Code 0 is special - it's the UNKNOWN catch-all error.
138
142
  */
139
143
  const ERROR_CODE_RANGES = [
140
144
  { min: 1000, max: 1999, type: 'INPUT' },
@@ -143,6 +147,8 @@ const ERROR_CODE_RANGES = [
143
147
  { min: 5000, max: 5999, type: 'ONCHAIN' },
144
148
  { min: 9000, max: 9999, type: 'BALANCE' },
145
149
  ];
150
+ /** Special code for UNKNOWN errors */
151
+ const UNKNOWN_ERROR_CODE = 0;
146
152
  /**
147
153
  * Zod schema for validating ErrorDetails objects.
148
154
  *
@@ -181,6 +187,7 @@ const ERROR_CODE_RANGES = [
181
187
  const errorDetailsSchema = zod.z.object({
182
188
  /**
183
189
  * Numeric identifier following standardized ranges:
190
+ * - 0: UNKNOWN - Catch-all for unrecognized errors
184
191
  * - 1000-1999: INPUT errors - Parameter validation
185
192
  * - 3000-3999: NETWORK errors - Connectivity issues
186
193
  * - 4000-4999: RPC errors - Provider issues, gas estimation
@@ -190,8 +197,9 @@ const errorDetailsSchema = zod.z.object({
190
197
  code: zod.z
191
198
  .number()
192
199
  .int('Error code must be an integer')
193
- .refine((code) => ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
194
- message: 'Error code must be in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
200
+ .refine((code) => code === UNKNOWN_ERROR_CODE ||
201
+ ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
202
+ message: 'Error code must be 0 (UNKNOWN) or in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
195
203
  }),
196
204
  /** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
197
205
  name: zod.z
@@ -201,7 +209,7 @@ const errorDetailsSchema = zod.z.object({
201
209
  /** Error category indicating where the error originated */
202
210
  type: zod.z.enum(ERROR_TYPE_ARRAY, {
203
211
  errorMap: () => ({
204
- message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
212
+ message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK, UNKNOWN',
205
213
  }),
206
214
  }),
207
215
  /** Error handling strategy */
@@ -418,6 +426,7 @@ class KitError extends Error {
418
426
  /**
419
427
  * Standardized error code ranges for consistent categorization:
420
428
  *
429
+ * - 0: UNKNOWN - Catch-all for unrecognized errors
421
430
  * - 1000-1999: INPUT errors - Parameter validation, input format errors
422
431
  * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
423
432
  * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
@@ -482,6 +491,12 @@ const InputError = {
482
491
  name: 'INPUT_INVALID_CHAIN',
483
492
  type: 'INPUT',
484
493
  },
494
+ /** Invalid or unknown token (symbol not found, missing decimals, etc.) */
495
+ INVALID_TOKEN: {
496
+ code: 1006,
497
+ name: 'INPUT_INVALID_TOKEN',
498
+ type: 'INPUT',
499
+ },
485
500
  /** General validation failure for complex validation rules */
486
501
  VALIDATION_FAILED: {
487
502
  code: 1098,
@@ -960,6 +975,8 @@ exports.Blockchain = void 0;
960
975
  Blockchain["Ink_Testnet"] = "Ink_Testnet";
961
976
  Blockchain["Linea"] = "Linea";
962
977
  Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
978
+ Blockchain["Monad"] = "Monad";
979
+ Blockchain["Monad_Testnet"] = "Monad_Testnet";
963
980
  Blockchain["NEAR"] = "NEAR";
964
981
  Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
965
982
  Blockchain["Noble"] = "Noble";
@@ -1051,6 +1068,7 @@ exports.BridgeChain = void 0;
1051
1068
  BridgeChain["HyperEVM"] = "HyperEVM";
1052
1069
  BridgeChain["Ink"] = "Ink";
1053
1070
  BridgeChain["Linea"] = "Linea";
1071
+ BridgeChain["Monad"] = "Monad";
1054
1072
  BridgeChain["Optimism"] = "Optimism";
1055
1073
  BridgeChain["Plume"] = "Plume";
1056
1074
  BridgeChain["Polygon"] = "Polygon";
@@ -1070,6 +1088,7 @@ exports.BridgeChain = void 0;
1070
1088
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
1071
1089
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
1072
1090
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
1091
+ BridgeChain["Monad_Testnet"] = "Monad_Testnet";
1073
1092
  BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
1074
1093
  BridgeChain["Plume_Testnet"] = "Plume_Testnet";
1075
1094
  BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
@@ -1264,8 +1283,11 @@ const ArcTestnet = defineChain({
1264
1283
  name: 'Arc Testnet',
1265
1284
  title: 'ArcTestnet',
1266
1285
  nativeCurrency: {
1267
- name: 'Arc',
1268
- symbol: 'Arc',
1286
+ name: 'USDC',
1287
+ symbol: 'USDC',
1288
+ // Arc uses native USDC with 18 decimals for gas payments (EVM standard).
1289
+ // Note: The ERC-20 USDC contract at usdcAddress uses 6 decimals.
1290
+ // See: https://docs.arc.network/arc/references/contract-addresses
1269
1291
  decimals: 18,
1270
1292
  },
1271
1293
  chainId: 5042002,
@@ -2053,6 +2075,86 @@ const LineaSepolia = defineChain({
2053
2075
  },
2054
2076
  });
2055
2077
 
2078
+ /**
2079
+ * Monad Mainnet chain definition
2080
+ * @remarks
2081
+ * This represents the official production network for the Monad blockchain.
2082
+ * Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
2083
+ * over 10,000 TPS, sub-second finality, and near-zero gas fees.
2084
+ */
2085
+ const Monad = defineChain({
2086
+ type: 'evm',
2087
+ chain: exports.Blockchain.Monad,
2088
+ name: 'Monad',
2089
+ title: 'Monad Mainnet',
2090
+ nativeCurrency: {
2091
+ name: 'Monad',
2092
+ symbol: 'MON',
2093
+ decimals: 18,
2094
+ },
2095
+ chainId: 143,
2096
+ isTestnet: false,
2097
+ explorerUrl: 'https://monadscan.com/tx/{hash}',
2098
+ rpcEndpoints: ['https://rpc.monad.xyz'],
2099
+ eurcAddress: null,
2100
+ usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
2101
+ cctp: {
2102
+ domain: 15,
2103
+ contracts: {
2104
+ v2: {
2105
+ type: 'split',
2106
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
2107
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
2108
+ confirmations: 1,
2109
+ fastConfirmations: 1,
2110
+ },
2111
+ },
2112
+ },
2113
+ kitContracts: {
2114
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2115
+ },
2116
+ });
2117
+
2118
+ /**
2119
+ * Monad Testnet chain definition
2120
+ * @remarks
2121
+ * This represents the official test network for the Monad blockchain.
2122
+ * Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
2123
+ * over 10,000 TPS, sub-second finality, and near-zero gas fees.
2124
+ */
2125
+ const MonadTestnet = defineChain({
2126
+ type: 'evm',
2127
+ chain: exports.Blockchain.Monad_Testnet,
2128
+ name: 'Monad Testnet',
2129
+ title: 'Monad Testnet',
2130
+ nativeCurrency: {
2131
+ name: 'Monad',
2132
+ symbol: 'MON',
2133
+ decimals: 18,
2134
+ },
2135
+ chainId: 10143,
2136
+ isTestnet: true,
2137
+ explorerUrl: 'https://testnet.monadscan.com/tx/{hash}',
2138
+ rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
2139
+ eurcAddress: null,
2140
+ usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
2141
+ cctp: {
2142
+ domain: 15,
2143
+ contracts: {
2144
+ v2: {
2145
+ type: 'split',
2146
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
2147
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
2148
+ confirmations: 1,
2149
+ fastConfirmations: 1,
2150
+ },
2151
+ },
2152
+ },
2153
+ kitContracts: {
2154
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2155
+ },
2156
+ });
2157
+
2056
2158
  /**
2057
2159
  * NEAR Protocol Mainnet chain definition
2058
2160
  * @remarks
@@ -3137,6 +3239,8 @@ var Blockchains = {
3137
3239
  InkTestnet: InkTestnet,
3138
3240
  Linea: Linea,
3139
3241
  LineaSepolia: LineaSepolia,
3242
+ Monad: Monad,
3243
+ MonadTestnet: MonadTestnet,
3140
3244
  NEAR: NEAR,
3141
3245
  NEARTestnet: NEARTestnet,
3142
3246
  Noble: Noble,
@@ -3548,14 +3652,41 @@ function isFatalError(error) {
3548
3652
  return isKitError(error) && error.recoverability === 'FATAL';
3549
3653
  }
3550
3654
  /**
3551
- * Checks if an error is a KitError with RETRYABLE recoverability.
3655
+ * Error codes that are considered retryable by default.
3656
+ *
3657
+ * @remarks
3658
+ * These are typically transient errors that may succeed on retry:
3659
+ * - Network connectivity issues (3001, 3002)
3660
+ * - Provider unavailability (4001, 4002)
3661
+ * - RPC nonce errors (4003)
3662
+ */
3663
+ const DEFAULT_RETRYABLE_ERROR_CODES = [
3664
+ // Network errors
3665
+ 3001, // NETWORK_CONNECTION_FAILED
3666
+ 3002, // NETWORK_TIMEOUT
3667
+ // Provider errors
3668
+ 4001, // PROVIDER_UNAVAILABLE
3669
+ 4002, // PROVIDER_TIMEOUT
3670
+ 4003, // RPC_NONCE_ERROR
3671
+ ];
3672
+ /**
3673
+ * Checks if an error is retryable.
3674
+ *
3675
+ * @remarks
3676
+ * Check order for KitError instances:
3677
+ * 1. If `recoverability === 'RETRYABLE'`, return `true` immediately (priority check).
3678
+ * 2. Otherwise, check if `error.code` is in `DEFAULT_RETRYABLE_ERROR_CODES` (fallback check).
3679
+ * 3. Non-KitError instances always return `false`.
3680
+ *
3681
+ * This two-tier approach allows both explicit recoverability control and
3682
+ * backward-compatible code-based retry logic.
3552
3683
  *
3553
3684
  * RETRYABLE errors indicate transient failures that may succeed on
3554
3685
  * subsequent attempts, such as network timeouts or temporary service
3555
3686
  * unavailability. These errors are safe to retry after a delay.
3556
3687
  *
3557
3688
  * @param error - Unknown error to check
3558
- * @returns True if error is a KitError with RETRYABLE recoverability
3689
+ * @returns True if error is retryable
3559
3690
  *
3560
3691
  * @example
3561
3692
  * ```typescript
@@ -3570,9 +3701,51 @@ function isFatalError(error) {
3570
3701
  * }
3571
3702
  * }
3572
3703
  * ```
3704
+ *
3705
+ * @example
3706
+ * ```typescript
3707
+ * import { isRetryableError, createNetworkConnectionError, KitError } from '@core/errors'
3708
+ *
3709
+ * // KitError with RETRYABLE recoverability (priority check)
3710
+ * const error1 = createNetworkConnectionError('Ethereum')
3711
+ * isRetryableError(error1) // true
3712
+ *
3713
+ * // KitError with default retryable code (fallback check)
3714
+ * const error2 = new KitError({
3715
+ * code: 3002, // NETWORK_TIMEOUT - in DEFAULT_RETRYABLE_ERROR_CODES
3716
+ * name: 'NETWORK_TIMEOUT',
3717
+ * type: 'NETWORK',
3718
+ * recoverability: 'FATAL', // Not RETRYABLE
3719
+ * message: 'Timeout',
3720
+ * })
3721
+ * isRetryableError(error2) // true (code 3002 is in default list)
3722
+ *
3723
+ * // KitError with non-retryable code and FATAL recoverability
3724
+ * const error3 = new KitError({
3725
+ * code: 1001,
3726
+ * name: 'INVALID_INPUT',
3727
+ * type: 'INPUT',
3728
+ * recoverability: 'FATAL',
3729
+ * message: 'Invalid input',
3730
+ * })
3731
+ * isRetryableError(error3) // false
3732
+ *
3733
+ * // Non-KitError
3734
+ * const error4 = new Error('Standard error')
3735
+ * isRetryableError(error4) // false
3736
+ * ```
3573
3737
  */
3574
3738
  function isRetryableError(error) {
3575
- return isKitError(error) && error.recoverability === 'RETRYABLE';
3739
+ // Use proper type guard to check if it's a KitError
3740
+ if (isKitError(error)) {
3741
+ // Priority check: explicit recoverability
3742
+ if (error.recoverability === 'RETRYABLE') {
3743
+ return true;
3744
+ }
3745
+ // Fallback check: error code against default retryable codes
3746
+ return DEFAULT_RETRYABLE_ERROR_CODES.includes(error.code);
3747
+ }
3748
+ return false;
3576
3749
  }
3577
3750
  /**
3578
3751
  * Type guard to check if error is KitError with INPUT type.
@@ -4630,7 +4803,7 @@ const parseAmount = (params) => {
4630
4803
  };
4631
4804
 
4632
4805
  var name = "@circle-fin/bridge-kit";
4633
- var version = "1.3.0";
4806
+ var version = "1.5.0";
4634
4807
  var pkg = {
4635
4808
  name: name,
4636
4809
  version: version};
@@ -5907,7 +6080,7 @@ class BridgeKit {
5907
6080
  return formatBridgeResult(await provider.estimate(finalResolvedParams), 'to-human-readable');
5908
6081
  }
5909
6082
  /**
5910
- * Get all chains supported by any provider in the kit.
6083
+ * Get all chains supported by any provider in the kit, with optional filtering.
5911
6084
  *
5912
6085
  * Aggregate and deduplicate the supported chains from all registered providers.
5913
6086
  * This provides a comprehensive list of chains that can be used as either source
@@ -5917,6 +6090,7 @@ class BridgeKit {
5917
6090
  * ensuring each chain appears only once in the result regardless of how many
5918
6091
  * providers support it.
5919
6092
  *
6093
+ * @param options - Optional filtering options to narrow down the returned chains
5920
6094
  * @returns Array of unique chain definitions supported by the registered providers
5921
6095
  *
5922
6096
  * @example
@@ -5924,19 +6098,56 @@ class BridgeKit {
5924
6098
  * import { BridgeKit } from '@circle-fin/bridge-kit'
5925
6099
  *
5926
6100
  * const kit = new BridgeKit()
5927
- * const chains = kit.getSupportedChains()
6101
+ *
6102
+ * // Get all supported chains (no filtering)
6103
+ * const allChains = kit.getSupportedChains()
6104
+ *
6105
+ * // Get only EVM chains
6106
+ * const evmChains = kit.getSupportedChains({ chainType: 'evm' })
6107
+ *
6108
+ * // Get EVM and Solana chains
6109
+ * const evmAndSolana = kit.getSupportedChains({ chainType: ['evm', 'solana'] })
6110
+ *
6111
+ * // Get only mainnet chains
6112
+ * const mainnets = kit.getSupportedChains({ isTestnet: false })
6113
+ *
6114
+ * // Get only EVM mainnet chains
6115
+ * const evmMainnets = kit.getSupportedChains({ chainType: 'evm', isTestnet: false })
5928
6116
  *
5929
6117
  * console.log('Supported chains:')
5930
- * chains.forEach(chain => {
6118
+ * allChains.forEach(chain => {
5931
6119
  * console.log(`- ${chain.name} (${chain.type})`)
5932
6120
  * })
5933
6121
  * ```
5934
6122
  */
5935
- getSupportedChains() {
6123
+ getSupportedChains(options) {
5936
6124
  const supportedChains = this.providers.flatMap((p) => p.supportedChains);
5937
6125
  // Deduplicate chains by using chain identifiers as object keys
5938
6126
  // Later duplicates will override earlier ones, keeping only the last occurrence
5939
- return Object.values(Object.fromEntries(supportedChains.map((chain) => [chain.chain, chain])));
6127
+ let chains = Object.values(Object.fromEntries(supportedChains.map((chain) => [chain.chain, chain])));
6128
+ // Apply chain type filter if provided
6129
+ if (options?.chainType !== undefined) {
6130
+ // Validate at runtime since JS consumers can bypass TypeScript's narrow type.
6131
+ const supportedChainTypes = ['evm', 'solana'];
6132
+ const chainTypeInput = options.chainType;
6133
+ const chainTypeValues = Array.isArray(chainTypeInput)
6134
+ ? chainTypeInput
6135
+ : [chainTypeInput];
6136
+ if (!chainTypeValues.every((chainType) => supportedChainTypes.includes(chainType))) {
6137
+ const listFormatter = new Intl.ListFormat('en', {
6138
+ style: 'long',
6139
+ type: 'conjunction',
6140
+ });
6141
+ throw createValidationFailedError$1('options.chainType', options.chainType, `Supported chain types include: ${listFormatter.format(supportedChainTypes)}`);
6142
+ }
6143
+ const chainTypes = new Set(chainTypeValues);
6144
+ chains = chains.filter((chain) => chainTypes.has(chain.type));
6145
+ }
6146
+ // Apply testnet filter if provided
6147
+ if (options?.isTestnet !== undefined) {
6148
+ chains = chains.filter((chain) => chain.isTestnet === options.isTestnet);
6149
+ }
6150
+ return chains;
5940
6151
  }
5941
6152
  /**
5942
6153
  * Validate that source and destination chains are on the same network type.