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