@circle-fin/bridge-kit 1.0.0 → 1.1.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
@@ -23,6 +23,47 @@ import 'bs58';
23
23
  import { parseUnits as parseUnits$1 } from '@ethersproject/units';
24
24
  import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2';
25
25
 
26
+ /**
27
+ * Detect the runtime environment and return a shortened identifier.
28
+ *
29
+ * @returns Runtime string, e.g., "node/18", "browser/Chrome", or "unknown"
30
+ */
31
+ /**
32
+ * Set an application-level identifier prefix for all HTTP requests.
33
+ *
34
+ * This allows applications to identify themselves in the user agent string,
35
+ * which is useful for tracking and analytics at the application level.
36
+ *
37
+ * @param prefix - Application identifier with version, e.g., "my-app/1.0.0"
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { setExternalPrefix } from '\@circle-fin/bridge-kit'
42
+ *
43
+ * setExternalPrefix('my-dapp/2.1.0')
44
+ * // All subsequent HTTP requests will include this prefix
45
+ * ```
46
+ */
47
+ const setExternalPrefix = (prefix) => {
48
+ if (typeof globalThis !== 'undefined') {
49
+ globalThis.__STABLECOIN_KITS_EXTERNAL_PREFIX__ = prefix;
50
+ }
51
+ };
52
+ /**
53
+ * Register a kit identifier globally (internal use only).
54
+ *
55
+ * This is called automatically when a kit module loads, using build-time
56
+ * injected constants. For now, only one kit can be registered at a time.
57
+ *
58
+ * @param kitIdentifier - Kit identifier, e.g., "\@circle-fin/bridge-kit/1.2.3"
59
+ * @internal
60
+ */
61
+ const registerKit = (kitIdentifier) => {
62
+ if (typeof globalThis !== 'undefined') {
63
+ globalThis.__STABLECOIN_KITS_CURRENT_KIT__ = kitIdentifier;
64
+ }
65
+ };
66
+
26
67
  /**
27
68
  * Custom error class for validation errors.
28
69
  * Provides structured error information while hiding implementation details.
@@ -326,6 +367,12 @@ const parseUnits = (value, decimals) => {
326
367
  return parseUnits$1(value, decimals).toBigInt();
327
368
  };
328
369
 
370
+ var name = "@circle-fin/bridge-kit";
371
+ var version = "1.1.0";
372
+ var pkg = {
373
+ name: name,
374
+ version: version};
375
+
329
376
  /**
330
377
  * Valid recoverability values for error handling strategies.
331
378
  *
@@ -523,6 +570,16 @@ class KitError extends Error {
523
570
  }
524
571
  }
525
572
 
573
+ /**
574
+ * Minimum error code for INPUT type errors.
575
+ * INPUT errors represent validation failures and invalid parameters.
576
+ */
577
+ const INPUT_ERROR_CODE_MIN = 1000;
578
+ /**
579
+ * Maximum error code for INPUT type errors (exclusive).
580
+ * INPUT error codes range from 1000 to 1099 inclusive.
581
+ */
582
+ const INPUT_ERROR_CODE_MAX = 1100;
526
583
  /**
527
584
  * Standardized error definitions for INPUT type errors.
528
585
  *
@@ -740,6 +797,185 @@ function createInvalidChainError(chain, reason) {
740
797
  return new KitError(errorDetails);
741
798
  }
742
799
 
800
+ /**
801
+ * Type guard to check if an error is a KitError instance.
802
+ *
803
+ * This guard enables TypeScript to narrow the type from `unknown` to
804
+ * `KitError`, providing access to structured error properties like
805
+ * code, name, and recoverability.
806
+ *
807
+ * @param error - Unknown error to check
808
+ * @returns True if error is KitError with proper type narrowing
809
+ *
810
+ * @example
811
+ * ```typescript
812
+ * import { isKitError } from '@core/errors'
813
+ *
814
+ * try {
815
+ * await kit.bridge(params)
816
+ * } catch (error) {
817
+ * if (isKitError(error)) {
818
+ * // TypeScript knows this is KitError
819
+ * console.log(`Structured error: ${error.name} (${error.code})`)
820
+ * } else {
821
+ * console.log('Regular error:', error)
822
+ * }
823
+ * }
824
+ * ```
825
+ */
826
+ function isKitError(error) {
827
+ return error instanceof KitError;
828
+ }
829
+ /**
830
+ * Checks if an error is a KitError with FATAL recoverability.
831
+ *
832
+ * FATAL errors indicate issues that cannot be resolved through retries,
833
+ * such as invalid inputs, configuration problems, or business rule
834
+ * violations. These errors require user intervention to fix.
835
+ *
836
+ * @param error - Unknown error to check
837
+ * @returns True if error is a KitError with FATAL recoverability
838
+ *
839
+ * @example
840
+ * ```typescript
841
+ * import { isFatalError } from '@core/errors'
842
+ *
843
+ * try {
844
+ * await kit.bridge(params)
845
+ * } catch (error) {
846
+ * if (isFatalError(error)) {
847
+ * // Show user-friendly error message - don't retry
848
+ * showUserError(error.message)
849
+ * }
850
+ * }
851
+ * ```
852
+ */
853
+ function isFatalError(error) {
854
+ return isKitError(error) && error.recoverability === 'FATAL';
855
+ }
856
+ /**
857
+ * Checks if an error is a KitError with RETRYABLE recoverability.
858
+ *
859
+ * RETRYABLE errors indicate transient failures that may succeed on
860
+ * subsequent attempts, such as network timeouts or temporary service
861
+ * unavailability. These errors are safe to retry after a delay.
862
+ *
863
+ * @param error - Unknown error to check
864
+ * @returns True if error is a KitError with RETRYABLE recoverability
865
+ *
866
+ * @example
867
+ * ```typescript
868
+ * import { isRetryableError } from '@core/errors'
869
+ *
870
+ * try {
871
+ * await kit.bridge(params)
872
+ * } catch (error) {
873
+ * if (isRetryableError(error)) {
874
+ * // Implement retry logic with exponential backoff
875
+ * setTimeout(() => retryOperation(), 5000)
876
+ * }
877
+ * }
878
+ * ```
879
+ */
880
+ function isRetryableError(error) {
881
+ return isKitError(error) && error.recoverability === 'RETRYABLE';
882
+ }
883
+ /**
884
+ * Combined type guard to check if error is KitError with INPUT type.
885
+ *
886
+ * INPUT errors have error codes in the 1000-1099 range and represent
887
+ * validation failures, invalid parameters, or user input problems.
888
+ * These errors are always FATAL and require the user to correct their
889
+ * input before retrying.
890
+ *
891
+ * @param error - Unknown error to check
892
+ * @returns True if error is KitError with INPUT error code range
893
+ *
894
+ * @example
895
+ * ```typescript
896
+ * import { isInputError } from '@core/errors'
897
+ * import { createBridgeKit } from '@core/bridge'
898
+ *
899
+ * async function handleBridge() {
900
+ * const kit = createBridgeKit(config)
901
+ * const params = { amount: '100', from: 'ethereum', to: 'polygon' }
902
+ *
903
+ * try {
904
+ * await kit.bridge(params)
905
+ * } catch (error) {
906
+ * if (isInputError(error)) {
907
+ * console.log(`Input error: ${error.message} (code: ${error.code})`)
908
+ * }
909
+ * }
910
+ * }
911
+ * ```
912
+ */
913
+ function isInputError(error) {
914
+ return (isKitError(error) &&
915
+ error.code >= INPUT_ERROR_CODE_MIN &&
916
+ error.code < INPUT_ERROR_CODE_MAX);
917
+ }
918
+ /**
919
+ * Safely extracts error message from any error type.
920
+ *
921
+ * This utility handles different error types gracefully, extracting
922
+ * meaningful messages from Error instances, string errors, or providing
923
+ * a fallback for unknown error types. Never throws.
924
+ *
925
+ * @param error - Unknown error to extract message from
926
+ * @returns Error message string, or fallback message
927
+ *
928
+ * @example
929
+ * ```typescript
930
+ * import { getErrorMessage } from '@core/errors'
931
+ *
932
+ * try {
933
+ * await riskyOperation()
934
+ * } catch (error) {
935
+ * const message = getErrorMessage(error)
936
+ * console.log('Error occurred:', message)
937
+ * // Works with Error, KitError, string, or any other type
938
+ * }
939
+ * ```
940
+ */
941
+ function getErrorMessage(error) {
942
+ if (error instanceof Error) {
943
+ return error.message;
944
+ }
945
+ if (typeof error === 'string') {
946
+ return error;
947
+ }
948
+ return 'An unknown error occurred';
949
+ }
950
+ /**
951
+ * Gets the error code from a KitError, or null if not applicable.
952
+ *
953
+ * This utility safely extracts the numeric error code from KitError
954
+ * instances, returning null for non-KitError types. Useful for
955
+ * programmatic error handling based on specific error codes.
956
+ *
957
+ * @param error - Unknown error to extract code from
958
+ * @returns Error code number, or null if not a KitError
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * import { getErrorCode, InputError } from '@core/errors'
963
+ *
964
+ * try {
965
+ * await kit.bridge(params)
966
+ * } catch (error) {
967
+ * const code = getErrorCode(error)
968
+ * if (code === InputError.NETWORK_MISMATCH.code) {
969
+ * // Handle network mismatch specifically
970
+ * showNetworkMismatchHelp()
971
+ * }
972
+ * }
973
+ * ```
974
+ */
975
+ function getErrorCode(error) {
976
+ return isKitError(error) ? error.code : null;
977
+ }
978
+
743
979
  /**
744
980
  * Extracts chain information including name, display name, and expected address format.
745
981
  *
@@ -1399,6 +1635,7 @@ var Blockchain;
1399
1635
  Blockchain["Algorand_Testnet"] = "Algorand_Testnet";
1400
1636
  Blockchain["Aptos"] = "Aptos";
1401
1637
  Blockchain["Aptos_Testnet"] = "Aptos_Testnet";
1638
+ Blockchain["Arc_Testnet"] = "Arc_Testnet";
1402
1639
  Blockchain["Arbitrum"] = "Arbitrum";
1403
1640
  Blockchain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
1404
1641
  Blockchain["Avalanche"] = "Avalanche";
@@ -1619,6 +1856,48 @@ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d'
1619
1856
  */
1620
1857
  const BRIDGE_CONTRACT_EVM_MAINNET = '0xB3FA262d0fB521cc93bE83d87b322b8A23DAf3F0';
1621
1858
 
1859
+ /**
1860
+ * Arc Testnet chain definition
1861
+ * @remarks
1862
+ * This represents the test network for the Arc blockchain,
1863
+ * Circle's EVM-compatible Layer-1 designed for stablecoin finance
1864
+ * and asset tokenization. Arc uses USDC as the native gas token and
1865
+ * features the Malachite Byzantine Fault Tolerant (BFT) consensus
1866
+ * engine for sub-second finality.
1867
+ */
1868
+ const ArcTestnet = defineChain({
1869
+ type: 'evm',
1870
+ chain: Blockchain.Arc_Testnet,
1871
+ name: 'Arc Testnet',
1872
+ title: 'ArcTestnet',
1873
+ nativeCurrency: {
1874
+ name: 'Arc',
1875
+ symbol: 'Arc',
1876
+ decimals: 18,
1877
+ },
1878
+ chainId: 5042002,
1879
+ isTestnet: true,
1880
+ explorerUrl: 'https://testnet.arcscan.app/tx/{hash}',
1881
+ rpcEndpoints: ['https://rpc.testnet.arc.network/'],
1882
+ eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
1883
+ usdcAddress: '0x3600000000000000000000000000000000000000',
1884
+ cctp: {
1885
+ domain: 26,
1886
+ contracts: {
1887
+ v2: {
1888
+ type: 'split',
1889
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
1890
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
1891
+ confirmations: 1,
1892
+ fastConfirmations: 1,
1893
+ },
1894
+ },
1895
+ },
1896
+ kitContracts: {
1897
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1898
+ },
1899
+ });
1900
+
1622
1901
  /**
1623
1902
  * Arbitrum Mainnet chain definition
1624
1903
  * @remarks
@@ -2910,26 +3189,26 @@ const Sonic = defineChain({
2910
3189
  });
2911
3190
 
2912
3191
  /**
2913
- * Sonic Blaze Testnet chain definition
3192
+ * Sonic Testnet chain definition
2914
3193
  * @remarks
2915
3194
  * This represents the official test network for the Sonic blockchain.
2916
3195
  */
2917
3196
  const SonicTestnet = defineChain({
2918
3197
  type: 'evm',
2919
3198
  chain: Blockchain.Sonic_Testnet,
2920
- name: 'Sonic Blaze Testnet',
2921
- title: 'Sonic Blaze Testnet',
3199
+ name: 'Sonic Testnet',
3200
+ title: 'Sonic Testnet',
2922
3201
  nativeCurrency: {
2923
3202
  name: 'Sonic',
2924
3203
  symbol: 'S',
2925
3204
  decimals: 18,
2926
3205
  },
2927
- chainId: 57054,
3206
+ chainId: 14601,
2928
3207
  isTestnet: true,
2929
3208
  explorerUrl: 'https://testnet.sonicscan.org/tx/{hash}',
2930
- rpcEndpoints: ['https://rpc.blaze.soniclabs.com'],
3209
+ rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
2931
3210
  eurcAddress: null,
2932
- usdcAddress: '0xA4879Fed32Ecbef99399e5cbC247E533421C4eC6',
3211
+ usdcAddress: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
2933
3212
  cctp: {
2934
3213
  domain: 13,
2935
3214
  contracts: {
@@ -3446,6 +3725,7 @@ var Blockchains = /*#__PURE__*/Object.freeze({
3446
3725
  AptosTestnet: AptosTestnet,
3447
3726
  Arbitrum: Arbitrum,
3448
3727
  ArbitrumSepolia: ArbitrumSepolia,
3728
+ ArcTestnet: ArcTestnet,
3449
3729
  Avalanche: Avalanche,
3450
3730
  AvalancheFuji: AvalancheFuji,
3451
3731
  Base: Base,
@@ -4079,9 +4359,9 @@ const customFeeSchema = z
4079
4359
  * token: 'USDC',
4080
4360
  * config: {
4081
4361
  * transferSpeed: 'FAST',
4082
- * maxFee: '1000000'
4362
+ * maxFee: '1.5', // Decimal format
4083
4363
  * customFee: {
4084
- * value: '1000000',
4364
+ * value: '0.5', // Decimal format
4085
4365
  * recipientAddress: '0x1234567890123456789012345678901234567890'
4086
4366
  * }
4087
4367
  * }
@@ -4108,7 +4388,12 @@ z.object({
4108
4388
  transferSpeed: z.nativeEnum(TransferSpeed).optional(),
4109
4389
  maxFee: z
4110
4390
  .string()
4111
- .regex(/^\d+$/, 'Max fee must be a numeric string')
4391
+ .pipe(createDecimalStringValidator({
4392
+ allowZero: true,
4393
+ regexMessage: 'maxFee must be a numeric string with optional decimal places (e.g., "1", "0.5", "1.5")',
4394
+ attributeName: 'maxFee',
4395
+ maxDecimals: 6,
4396
+ })(z.string()))
4112
4397
  .optional(),
4113
4398
  customFee: customFeeSchema.optional(),
4114
4399
  }),
@@ -4117,10 +4402,12 @@ z.object({
4117
4402
  /**
4118
4403
  * Schema for validating AdapterContext.
4119
4404
  * Must always contain both adapter and chain explicitly.
4405
+ * Optionally includes address for developer-controlled adapters.
4120
4406
  */
4121
4407
  const adapterContextSchema = z.object({
4122
4408
  adapter: adapterSchema,
4123
4409
  chain: chainIdentifierSchema,
4410
+ address: z.string().optional(),
4124
4411
  });
4125
4412
  /**
4126
4413
  * Schema for validating BridgeDestinationWithAddress objects.
@@ -4198,7 +4485,7 @@ const bridgeDestinationSchema = z.union([
4198
4485
  * ```
4199
4486
  */
4200
4487
  const bridgeParamsWithChainIdentifierSchema = z.object({
4201
- from: adapterContextSchema,
4488
+ from: adapterContextSchema.strict(),
4202
4489
  to: bridgeDestinationSchema,
4203
4490
  amount: z
4204
4491
  .string()
@@ -4215,7 +4502,13 @@ const bridgeParamsWithChainIdentifierSchema = z.object({
4215
4502
  transferSpeed: z.nativeEnum(TransferSpeed).optional(),
4216
4503
  maxFee: z
4217
4504
  .string()
4218
- .regex(/^\d+$/, 'Max fee must be a numeric string')
4505
+ .min(1, 'Required')
4506
+ .pipe(createDecimalStringValidator({
4507
+ allowZero: true,
4508
+ regexMessage: 'Max fee must be a numeric string with optional decimal places (e.g., 1, 0.5, 1.5)',
4509
+ attributeName: 'maxFee',
4510
+ maxDecimals: 6,
4511
+ })(z.string()))
4219
4512
  .optional(),
4220
4513
  customFee: customFeeSchema.optional(),
4221
4514
  })
@@ -4284,10 +4577,25 @@ async function resolveAddress(ctx) {
4284
4577
  if ('recipientAddress' in ctx) {
4285
4578
  return ctx.recipientAddress;
4286
4579
  }
4287
- // Handle AdapterContext (derive from adapter)
4288
- // Pass the chain to support OperationContext pattern
4289
- const chain = resolveChainDefinition(ctx);
4290
- return await ctx.adapter.getAddress(chain);
4580
+ // Handle based on adapter's addressContext
4581
+ if (ctx.adapter.capabilities?.addressContext === 'developer-controlled') {
4582
+ // Developer-controlled: address must be provided explicitly
4583
+ if ('address' in ctx && ctx.address) {
4584
+ return ctx.address;
4585
+ }
4586
+ throw new Error('Address is required in context for developer-controlled adapters. ' +
4587
+ 'Please provide: { adapter, chain, address: "0x..." }');
4588
+ }
4589
+ else {
4590
+ // User-controlled: address should not be provided (auto-resolved from adapter)
4591
+ if ('address' in ctx && ctx.address) {
4592
+ throw new Error('Address should not be provided for user-controlled adapters. ' +
4593
+ 'The address is automatically resolved from the connected wallet.');
4594
+ }
4595
+ // Derive address from adapter
4596
+ const chain = resolveChainDefinition(ctx);
4597
+ return await ctx.adapter.getAddress(chain);
4598
+ }
4291
4599
  }
4292
4600
  /**
4293
4601
  * Resolves the amount of a transfer by formatting it according to the token's decimal places.
@@ -4324,7 +4632,8 @@ function resolveAmount(params) {
4324
4632
  * This function takes the optional configuration from bridge parameters and returns
4325
4633
  * a normalized BridgeConfig with:
4326
4634
  * - Default transfer speed set to FAST if not provided
4327
- * - Custom fee values formatted with proper decimal places (6 decimals for USDC)
4635
+ * - Max fee values converted from human-readable to smallest units (6 decimals for USDC)
4636
+ * - Custom fee values converted from human-readable to smallest units (6 decimals for USDC)
4328
4637
  *
4329
4638
  * @param params - The bridge parameters containing optional configuration
4330
4639
  * @returns A normalized BridgeConfig with defaults applied and values formatted
@@ -4339,35 +4648,45 @@ function resolveAmount(params) {
4339
4648
  * from: { adapter: mockAdapter, chain: Ethereum },
4340
4649
  * to: { adapter: mockAdapter, chain: Base },
4341
4650
  * config: {
4651
+ * maxFee: '1',
4342
4652
  * customFee: { value: '0.5' }
4343
4653
  * }
4344
4654
  * }
4345
4655
  * const config = resolveConfig(params)
4346
4656
  * // Returns: {
4347
4657
  * // transferSpeed: TransferSpeed.FAST,
4658
+ * // maxFee: '1000000',
4348
4659
  * // customFee: { value: '500000' }
4349
4660
  * // }
4350
4661
  * ```
4351
4662
  */
4352
4663
  function resolveConfig(params) {
4664
+ // Convert maxFee from human-readable to minor units if provided
4665
+ const maxFee = params.config?.maxFee
4666
+ ? parseUnits(params.config.maxFee, 6).toString()
4667
+ : undefined;
4668
+ // Convert customFee.value from human-readable to minor units if provided
4669
+ const rawCustomFee = params.config?.customFee;
4670
+ const customFee = rawCustomFee
4671
+ ? {
4672
+ ...rawCustomFee,
4673
+ value: rawCustomFee.value
4674
+ ? parseUnits(rawCustomFee.value, 6).toString()
4675
+ : undefined,
4676
+ }
4677
+ : undefined;
4353
4678
  return {
4354
4679
  ...params.config,
4355
4680
  transferSpeed: params.config?.transferSpeed ?? TransferSpeed.FAST,
4356
- customFee: params.config?.customFee
4357
- ? {
4358
- ...params.config?.customFee,
4359
- value: params.config?.customFee?.value
4360
- ? parseUnits(params.config?.customFee?.value, 6).toString()
4361
- : undefined,
4362
- }
4363
- : undefined,
4681
+ ...(maxFee !== undefined && { maxFee }),
4682
+ ...(customFee !== undefined && { customFee }),
4364
4683
  };
4365
4684
  }
4366
4685
  /**
4367
4686
  * Resolves and normalizes bridge parameters for the BridgeKit.
4368
4687
  *
4369
4688
  * This function takes bridge parameters with explicit adapter contexts and normalizes them
4370
- * into a consistent format that can be used by the bridging providers.
4689
+ * into the format expected by bridging providers.
4371
4690
  *
4372
4691
  * The function performs parallel resolution of:
4373
4692
  * - Source and destination addresses
@@ -4854,5 +5173,15 @@ class BridgeKit {
4854
5173
  }
4855
5174
  }
4856
5175
 
4857
- export { Blockchain, BridgeKit, KitError, bridgeParamsWithChainIdentifierSchema };
5176
+ /**
5177
+ *
5178
+ * Bridge Kit
5179
+ *
5180
+ * A strongly-typed SDK for moving USDC between heterogeneous blockchain networks
5181
+ * @packageDocumentation
5182
+ */
5183
+ // Auto-register this kit for user agent tracking
5184
+ registerKit(`${pkg.name}/${pkg.version}`);
5185
+
5186
+ export { Blockchain, BridgeKit, KitError, bridgeParamsWithChainIdentifierSchema, getErrorCode, getErrorMessage, isFatalError, isInputError, isKitError, isRetryableError, setExternalPrefix };
4858
5187
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@circle-fin/bridge-kit",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
+ "description": "SDK for seamless cross-chain stablecoin bridging",
4
5
  "engines": {
5
6
  "node": ">=16.0.0"
6
7
  },
@@ -41,6 +42,7 @@
41
42
  "LICENSE"
42
43
  ],
43
44
  "publishConfig": {
45
+ "access": "public",
44
46
  "directory": "../../dist/kits/bridge-kit"
45
47
  }
46
48
  }