@circle-fin/bridge-kit 1.5.0 → 1.6.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
@@ -23,8 +23,13 @@ require('@ethersproject/bytes');
23
23
  require('@ethersproject/address');
24
24
  require('bs58');
25
25
  var units = require('@ethersproject/units');
26
+ var pino = require('pino');
26
27
  var providerCctpV2 = require('@circle-fin/provider-cctp-v2');
27
28
 
29
+ function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
30
+
31
+ var pino__default = /*#__PURE__*/_interopDefault(pino);
32
+
28
33
  /**
29
34
  * Detect the runtime environment and return a shortened identifier.
30
35
  *
@@ -655,6 +660,18 @@ const NetworkError = {
655
660
  name: 'NETWORK_TIMEOUT',
656
661
  type: 'NETWORK',
657
662
  },
663
+ /** Circle relayer failed to process the forwarding/mint transaction */
664
+ RELAYER_FORWARD_FAILED: {
665
+ code: 3003,
666
+ name: 'NETWORK_RELAYER_FORWARD_FAILED',
667
+ type: 'NETWORK',
668
+ },
669
+ /** Relayer mint is pending - waiting for confirmation */
670
+ RELAYER_PENDING: {
671
+ code: 3004,
672
+ name: 'NETWORK_RELAYER_PENDING',
673
+ type: 'NETWORK',
674
+ },
658
675
  };
659
676
 
660
677
  /**
@@ -1215,6 +1232,10 @@ const Aptos = defineChain({
1215
1232
  confirmations: 1,
1216
1233
  },
1217
1234
  },
1235
+ forwarderSupported: {
1236
+ source: false,
1237
+ destination: false,
1238
+ },
1218
1239
  },
1219
1240
  });
1220
1241
 
@@ -1248,6 +1269,10 @@ const AptosTestnet = defineChain({
1248
1269
  confirmations: 1,
1249
1270
  },
1250
1271
  },
1272
+ forwarderSupported: {
1273
+ source: false,
1274
+ destination: false,
1275
+ },
1251
1276
  },
1252
1277
  });
1253
1278
 
@@ -1307,6 +1332,10 @@ const ArcTestnet = defineChain({
1307
1332
  fastConfirmations: 1,
1308
1333
  },
1309
1334
  },
1335
+ forwarderSupported: {
1336
+ source: true,
1337
+ destination: true,
1338
+ },
1310
1339
  },
1311
1340
  kitContracts: {
1312
1341
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1351,6 +1380,10 @@ const Arbitrum = defineChain({
1351
1380
  fastConfirmations: 1,
1352
1381
  },
1353
1382
  },
1383
+ forwarderSupported: {
1384
+ source: true,
1385
+ destination: true,
1386
+ },
1354
1387
  },
1355
1388
  kitContracts: {
1356
1389
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1395,6 +1428,10 @@ const ArbitrumSepolia = defineChain({
1395
1428
  fastConfirmations: 1,
1396
1429
  },
1397
1430
  },
1431
+ forwarderSupported: {
1432
+ source: true,
1433
+ destination: true,
1434
+ },
1398
1435
  },
1399
1436
  kitContracts: {
1400
1437
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1439,6 +1476,10 @@ const Avalanche = defineChain({
1439
1476
  fastConfirmations: 1,
1440
1477
  },
1441
1478
  },
1479
+ forwarderSupported: {
1480
+ source: true,
1481
+ destination: true,
1482
+ },
1442
1483
  },
1443
1484
  kitContracts: {
1444
1485
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1482,6 +1523,10 @@ const AvalancheFuji = defineChain({
1482
1523
  fastConfirmations: 1,
1483
1524
  },
1484
1525
  },
1526
+ forwarderSupported: {
1527
+ source: true,
1528
+ destination: true,
1529
+ },
1485
1530
  },
1486
1531
  rpcEndpoints: ['https://api.avax-test.network/ext/bc/C/rpc'],
1487
1532
  kitContracts: {
@@ -1527,6 +1572,10 @@ const Base = defineChain({
1527
1572
  fastConfirmations: 1,
1528
1573
  },
1529
1574
  },
1575
+ forwarderSupported: {
1576
+ source: true,
1577
+ destination: true,
1578
+ },
1530
1579
  },
1531
1580
  kitContracts: {
1532
1581
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1571,6 +1620,10 @@ const BaseSepolia = defineChain({
1571
1620
  fastConfirmations: 1,
1572
1621
  },
1573
1622
  },
1623
+ forwarderSupported: {
1624
+ source: true,
1625
+ destination: true,
1626
+ },
1574
1627
  },
1575
1628
  kitContracts: {
1576
1629
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1657,6 +1710,10 @@ const Codex = defineChain({
1657
1710
  fastConfirmations: 1,
1658
1711
  },
1659
1712
  },
1713
+ forwarderSupported: {
1714
+ source: true,
1715
+ destination: false,
1716
+ },
1660
1717
  },
1661
1718
  kitContracts: {
1662
1719
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1695,6 +1752,10 @@ const CodexTestnet = defineChain({
1695
1752
  fastConfirmations: 1,
1696
1753
  },
1697
1754
  },
1755
+ forwarderSupported: {
1756
+ source: true,
1757
+ destination: false,
1758
+ },
1698
1759
  },
1699
1760
  kitContracts: {
1700
1761
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1739,6 +1800,10 @@ const Ethereum = defineChain({
1739
1800
  fastConfirmations: 2,
1740
1801
  },
1741
1802
  },
1803
+ forwarderSupported: {
1804
+ source: true,
1805
+ destination: true,
1806
+ },
1742
1807
  },
1743
1808
  kitContracts: {
1744
1809
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1783,6 +1848,10 @@ const EthereumSepolia = defineChain({
1783
1848
  fastConfirmations: 2,
1784
1849
  },
1785
1850
  },
1851
+ forwarderSupported: {
1852
+ source: true,
1853
+ destination: true,
1854
+ },
1786
1855
  },
1787
1856
  kitContracts: {
1788
1857
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1869,6 +1938,10 @@ const HyperEVM = defineChain({
1869
1938
  fastConfirmations: 1,
1870
1939
  },
1871
1940
  },
1941
+ forwarderSupported: {
1942
+ source: true,
1943
+ destination: true,
1944
+ },
1872
1945
  },
1873
1946
  kitContracts: {
1874
1947
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1908,6 +1981,10 @@ const HyperEVMTestnet = defineChain({
1908
1981
  fastConfirmations: 1,
1909
1982
  },
1910
1983
  },
1984
+ forwarderSupported: {
1985
+ source: true,
1986
+ destination: true,
1987
+ },
1911
1988
  },
1912
1989
  kitContracts: {
1913
1990
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1951,6 +2028,10 @@ const Ink = defineChain({
1951
2028
  fastConfirmations: 1,
1952
2029
  },
1953
2030
  },
2031
+ forwarderSupported: {
2032
+ source: true,
2033
+ destination: true,
2034
+ },
1954
2035
  },
1955
2036
  kitContracts: {
1956
2037
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1993,6 +2074,10 @@ const InkTestnet = defineChain({
1993
2074
  fastConfirmations: 1,
1994
2075
  },
1995
2076
  },
2077
+ forwarderSupported: {
2078
+ source: true,
2079
+ destination: true,
2080
+ },
1996
2081
  },
1997
2082
  kitContracts: {
1998
2083
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2031,6 +2116,10 @@ const Linea = defineChain({
2031
2116
  fastConfirmations: 1,
2032
2117
  },
2033
2118
  },
2119
+ forwarderSupported: {
2120
+ source: true,
2121
+ destination: true,
2122
+ },
2034
2123
  },
2035
2124
  kitContracts: {
2036
2125
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2069,6 +2158,10 @@ const LineaSepolia = defineChain({
2069
2158
  fastConfirmations: 1,
2070
2159
  },
2071
2160
  },
2161
+ forwarderSupported: {
2162
+ source: true,
2163
+ destination: true,
2164
+ },
2072
2165
  },
2073
2166
  kitContracts: {
2074
2167
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2109,6 +2202,10 @@ const Monad = defineChain({
2109
2202
  fastConfirmations: 1,
2110
2203
  },
2111
2204
  },
2205
+ forwarderSupported: {
2206
+ source: true,
2207
+ destination: true,
2208
+ },
2112
2209
  },
2113
2210
  kitContracts: {
2114
2211
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2149,6 +2246,10 @@ const MonadTestnet = defineChain({
2149
2246
  fastConfirmations: 1,
2150
2247
  },
2151
2248
  },
2249
+ forwarderSupported: {
2250
+ source: true,
2251
+ destination: true,
2252
+ },
2152
2253
  },
2153
2254
  kitContracts: {
2154
2255
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2230,6 +2331,10 @@ const Noble = defineChain({
2230
2331
  confirmations: 1,
2231
2332
  },
2232
2333
  },
2334
+ forwarderSupported: {
2335
+ source: false,
2336
+ destination: false,
2337
+ },
2233
2338
  },
2234
2339
  });
2235
2340
 
@@ -2262,6 +2367,10 @@ const NobleTestnet = defineChain({
2262
2367
  confirmations: 1,
2263
2368
  },
2264
2369
  },
2370
+ forwarderSupported: {
2371
+ source: false,
2372
+ destination: false,
2373
+ },
2265
2374
  },
2266
2375
  });
2267
2376
 
@@ -2303,6 +2412,10 @@ const Optimism = defineChain({
2303
2412
  fastConfirmations: 1,
2304
2413
  },
2305
2414
  },
2415
+ forwarderSupported: {
2416
+ source: true,
2417
+ destination: true,
2418
+ },
2306
2419
  },
2307
2420
  kitContracts: {
2308
2421
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2347,6 +2460,10 @@ const OptimismSepolia = defineChain({
2347
2460
  fastConfirmations: 1,
2348
2461
  },
2349
2462
  },
2463
+ forwarderSupported: {
2464
+ source: true,
2465
+ destination: true,
2466
+ },
2350
2467
  },
2351
2468
  kitContracts: {
2352
2469
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2387,6 +2504,10 @@ const Plume = defineChain({
2387
2504
  fastConfirmations: 1,
2388
2505
  },
2389
2506
  },
2507
+ forwarderSupported: {
2508
+ source: true,
2509
+ destination: false,
2510
+ },
2390
2511
  },
2391
2512
  kitContracts: {
2392
2513
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2426,6 +2547,10 @@ const PlumeTestnet = defineChain({
2426
2547
  fastConfirmations: 1,
2427
2548
  },
2428
2549
  },
2550
+ forwarderSupported: {
2551
+ source: true,
2552
+ destination: false,
2553
+ },
2429
2554
  },
2430
2555
  kitContracts: {
2431
2556
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2516,6 +2641,10 @@ const Polygon = defineChain({
2516
2641
  fastConfirmations: 13,
2517
2642
  },
2518
2643
  },
2644
+ forwarderSupported: {
2645
+ source: true,
2646
+ destination: true,
2647
+ },
2519
2648
  },
2520
2649
  kitContracts: {
2521
2650
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2560,6 +2689,10 @@ const PolygonAmoy = defineChain({
2560
2689
  fastConfirmations: 13,
2561
2690
  },
2562
2691
  },
2692
+ forwarderSupported: {
2693
+ source: true,
2694
+ destination: true,
2695
+ },
2563
2696
  },
2564
2697
  kitContracts: {
2565
2698
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2600,6 +2733,10 @@ const Sei = defineChain({
2600
2733
  fastConfirmations: 1,
2601
2734
  },
2602
2735
  },
2736
+ forwarderSupported: {
2737
+ source: true,
2738
+ destination: true,
2739
+ },
2603
2740
  },
2604
2741
  kitContracts: {
2605
2742
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2639,6 +2776,10 @@ const SeiTestnet = defineChain({
2639
2776
  fastConfirmations: 1,
2640
2777
  },
2641
2778
  },
2779
+ forwarderSupported: {
2780
+ source: true,
2781
+ destination: true,
2782
+ },
2642
2783
  },
2643
2784
  kitContracts: {
2644
2785
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2677,6 +2818,10 @@ const Sonic = defineChain({
2677
2818
  fastConfirmations: 1,
2678
2819
  },
2679
2820
  },
2821
+ forwarderSupported: {
2822
+ source: true,
2823
+ destination: true,
2824
+ },
2680
2825
  },
2681
2826
  kitContracts: {
2682
2827
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2715,6 +2860,10 @@ const SonicTestnet = defineChain({
2715
2860
  fastConfirmations: 1,
2716
2861
  },
2717
2862
  },
2863
+ forwarderSupported: {
2864
+ source: true,
2865
+ destination: true,
2866
+ },
2718
2867
  },
2719
2868
  kitContracts: {
2720
2869
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2758,6 +2907,10 @@ const Solana = defineChain({
2758
2907
  fastConfirmations: 3,
2759
2908
  },
2760
2909
  },
2910
+ forwarderSupported: {
2911
+ source: true,
2912
+ destination: false,
2913
+ },
2761
2914
  },
2762
2915
  kitContracts: {
2763
2916
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
@@ -2800,6 +2953,10 @@ const SolanaDevnet = defineChain({
2800
2953
  fastConfirmations: 3,
2801
2954
  },
2802
2955
  },
2956
+ forwarderSupported: {
2957
+ source: true,
2958
+ destination: false,
2959
+ },
2803
2960
  },
2804
2961
  kitContracts: {
2805
2962
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
@@ -2883,6 +3040,10 @@ const Sui = defineChain({
2883
3040
  confirmations: 1,
2884
3041
  },
2885
3042
  },
3043
+ forwarderSupported: {
3044
+ source: false,
3045
+ destination: false,
3046
+ },
2886
3047
  },
2887
3048
  });
2888
3049
 
@@ -2916,6 +3077,10 @@ const SuiTestnet = defineChain({
2916
3077
  confirmations: 1,
2917
3078
  },
2918
3079
  },
3080
+ forwarderSupported: {
3081
+ source: false,
3082
+ destination: false,
3083
+ },
2919
3084
  },
2920
3085
  });
2921
3086
 
@@ -2937,7 +3102,7 @@ const Unichain = defineChain({
2937
3102
  chainId: 130,
2938
3103
  isTestnet: false,
2939
3104
  explorerUrl: 'https://unichain.blockscout.com/tx/{hash}',
2940
- rpcEndpoints: ['https://rpc.unichain.org', 'https://mainnet.unichain.org'],
3105
+ rpcEndpoints: ['https://mainnet.unichain.org'],
2941
3106
  eurcAddress: null,
2942
3107
  usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
2943
3108
  cctp: {
@@ -2957,6 +3122,10 @@ const Unichain = defineChain({
2957
3122
  fastConfirmations: 1,
2958
3123
  },
2959
3124
  },
3125
+ forwarderSupported: {
3126
+ source: true,
3127
+ destination: true,
3128
+ },
2960
3129
  },
2961
3130
  kitContracts: {
2962
3131
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -3001,6 +3170,10 @@ const UnichainSepolia = defineChain({
3001
3170
  fastConfirmations: 1,
3002
3171
  },
3003
3172
  },
3173
+ forwarderSupported: {
3174
+ source: true,
3175
+ destination: true,
3176
+ },
3004
3177
  },
3005
3178
  kitContracts: {
3006
3179
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3027,7 +3200,7 @@ const WorldChain = defineChain({
3027
3200
  explorerUrl: 'https://worldscan.org/tx/{hash}',
3028
3201
  rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
3029
3202
  eurcAddress: null,
3030
- usdcAddress: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
3203
+ usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
3031
3204
  cctp: {
3032
3205
  domain: 14,
3033
3206
  contracts: {
@@ -3039,6 +3212,10 @@ const WorldChain = defineChain({
3039
3212
  fastConfirmations: 1,
3040
3213
  },
3041
3214
  },
3215
+ forwarderSupported: {
3216
+ source: true,
3217
+ destination: true,
3218
+ },
3042
3219
  },
3043
3220
  kitContracts: {
3044
3221
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -3080,6 +3257,10 @@ const WorldChainSepolia = defineChain({
3080
3257
  fastConfirmations: 1,
3081
3258
  },
3082
3259
  },
3260
+ forwarderSupported: {
3261
+ source: true,
3262
+ destination: true,
3263
+ },
3083
3264
  },
3084
3265
  kitContracts: {
3085
3266
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3106,7 +3287,7 @@ const XDC = defineChain({
3106
3287
  chainId: 50,
3107
3288
  isTestnet: false,
3108
3289
  explorerUrl: 'https://xdcscan.io/tx/{hash}',
3109
- rpcEndpoints: ['https://erpc.xinfin.network'],
3290
+ rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
3110
3291
  eurcAddress: null,
3111
3292
  usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
3112
3293
  cctp: {
@@ -3120,6 +3301,10 @@ const XDC = defineChain({
3120
3301
  fastConfirmations: 3,
3121
3302
  },
3122
3303
  },
3304
+ forwarderSupported: {
3305
+ source: true,
3306
+ destination: false,
3307
+ },
3123
3308
  },
3124
3309
  kitContracts: {
3125
3310
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -3158,6 +3343,10 @@ const XDCApothem = defineChain({
3158
3343
  fastConfirmations: 1,
3159
3344
  },
3160
3345
  },
3346
+ forwarderSupported: {
3347
+ source: true,
3348
+ destination: false,
3349
+ },
3161
3350
  },
3162
3351
  kitContracts: {
3163
3352
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3394,7 +3583,7 @@ const nonEvmChainDefinitionSchema = baseChainDefinitionSchema
3394
3583
  * })
3395
3584
  * ```
3396
3585
  */
3397
- const chainDefinitionSchema$1 = zod.z.discriminatedUnion('type', [
3586
+ const chainDefinitionSchema$2 = zod.z.discriminatedUnion('type', [
3398
3587
  evmChainDefinitionSchema,
3399
3588
  nonEvmChainDefinitionSchema,
3400
3589
  ]);
@@ -3419,7 +3608,7 @@ zod.z.union([
3419
3608
  .string()
3420
3609
  .refine((val) => val in exports.Blockchain, 'Must be a valid Blockchain enum value as string'),
3421
3610
  zod.z.nativeEnum(exports.Blockchain),
3422
- chainDefinitionSchema$1,
3611
+ chainDefinitionSchema$2,
3423
3612
  ]);
3424
3613
  /**
3425
3614
  * Zod schema for validating bridge chain identifiers.
@@ -3455,7 +3644,7 @@ const bridgeChainIdentifierSchema = zod.z.union([
3455
3644
  zod.z.string().refine((val) => val in exports.BridgeChain, (val) => ({
3456
3645
  message: `Chain "${val}" is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3457
3646
  })),
3458
- chainDefinitionSchema$1.refine((chainDef) => chainDef.chain in exports.BridgeChain, (chainDef) => ({
3647
+ chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in exports.BridgeChain, (chainDef) => ({
3459
3648
  message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3460
3649
  })),
3461
3650
  ]);
@@ -3942,6 +4131,62 @@ function getErrorMessage(error) {
3942
4131
  function getErrorCode(error) {
3943
4132
  return isKitError(error) ? error.code : null;
3944
4133
  }
4134
+ /**
4135
+ * Extract structured error information for logging and events.
4136
+ *
4137
+ * @remarks
4138
+ * Safely extracts error information from any thrown value, handling:
4139
+ * - KitError objects (message preserved - safe to log)
4140
+ * - Standard Error objects (message redacted for security)
4141
+ * - Plain objects with name/message/code (message redacted)
4142
+ * - Primitive values (logged as-is since they contain no structured data)
4143
+ *
4144
+ * For security, only KitError messages are included. Other error messages are
4145
+ * redacted to prevent logging sensitive data like tokens or PII.
4146
+ *
4147
+ * @param error - The error to extract info from.
4148
+ * @returns Structured error information.
4149
+ *
4150
+ * @example
4151
+ * ```typescript
4152
+ * import { extractErrorInfo } from '@core/errors'
4153
+ *
4154
+ * // Standard Error - message is redacted for security
4155
+ * const info1 = extractErrorInfo(new Error('Something went wrong'))
4156
+ * // { name: 'Error', message: 'An error occurred. See error name and code for details.' }
4157
+ *
4158
+ * // KitError - message is preserved (safe type)
4159
+ * const error = createNetworkConnectionError('Ethereum')
4160
+ * const info2 = extractErrorInfo(error)
4161
+ * // { name: 'NETWORK_CONNECTION_FAILED', message: 'Network connection failed for Ethereum', code: 3001 }
4162
+ *
4163
+ * // Primitive value - logged as-is
4164
+ * const info3 = extractErrorInfo('string error')
4165
+ * // { name: 'UnknownError', message: 'string error' }
4166
+ * ```
4167
+ */
4168
+ function extractErrorInfo(error) {
4169
+ if (error === null || typeof error !== 'object') {
4170
+ return {
4171
+ name: 'UnknownError',
4172
+ message: String(error),
4173
+ };
4174
+ }
4175
+ const err = error;
4176
+ // Only preserve messages for KitError instances (safe type)
4177
+ // For other errors, redact message for security
4178
+ const errorMessage = isKitError(error)
4179
+ ? getErrorMessage(error)
4180
+ : 'An error occurred. See error name and code for details.';
4181
+ const info = {
4182
+ name: err.name ?? 'UnknownError',
4183
+ message: errorMessage,
4184
+ };
4185
+ if (typeof err.code === 'number') {
4186
+ info.code = err.code;
4187
+ }
4188
+ return info;
4189
+ }
3945
4190
 
3946
4191
  /**
3947
4192
  * Validates if an address format is correct for the specified chain.
@@ -4486,7 +4731,7 @@ function validateWithStateTracking(value, schema, context, validatorName) {
4486
4731
  * Zod schema for validating chain definition objects used in buildExplorerUrl.
4487
4732
  * This schema ensures the chain definition has the required properties for URL generation.
4488
4733
  */
4489
- const chainDefinitionSchema = zod.z.object({
4734
+ const chainDefinitionSchema$1 = zod.z.object({
4490
4735
  name: zod.z
4491
4736
  .string({
4492
4737
  required_error: 'Chain name is required',
@@ -4510,15 +4755,14 @@ const transactionHashSchema = zod.z
4510
4755
  required_error: 'Transaction hash is required',
4511
4756
  invalid_type_error: 'Transaction hash must be a string',
4512
4757
  })
4513
- .min(1, 'Transaction hash cannot be empty')
4514
- .transform((hash) => hash.trim()) // Automatically trim whitespace
4515
- .refine((hash) => hash.length > 0, 'Transaction hash must not be empty or whitespace-only');
4758
+ .transform((hash) => hash.trim())
4759
+ .refine((hash) => hash.length > 0, 'Transaction hash cannot be empty');
4516
4760
  /**
4517
4761
  * Zod schema for validating buildExplorerUrl function parameters.
4518
4762
  * This schema validates both the chain definition and transaction hash together.
4519
4763
  */
4520
4764
  zod.z.object({
4521
- chainDef: chainDefinitionSchema,
4765
+ chainDef: chainDefinitionSchema$1,
4522
4766
  txHash: transactionHashSchema,
4523
4767
  });
4524
4768
  /**
@@ -4529,6 +4773,69 @@ zod.z
4529
4773
  .string()
4530
4774
  .url('Generated explorer URL is invalid');
4531
4775
 
4776
+ /**
4777
+ * Parses and validates data against a Zod schema, returning the transformed result.
4778
+ *
4779
+ * Unlike `validate` and `validateOrThrow` which use type assertions (`asserts value is T`),
4780
+ * this function **returns the parsed data** from Zod. This is important when your schema
4781
+ * includes transformations like defaults, coercion, or custom transforms.
4782
+ *
4783
+ * @typeParam T - The expected output type (caller must specify).
4784
+ * @param value - The value to parse and validate.
4785
+ * @param schema - The Zod schema to validate against.
4786
+ * @param context - Context string to include in error messages (e.g., 'user input', 'config').
4787
+ * @returns The parsed and transformed data of type T.
4788
+ * @throws KitError with INPUT_VALIDATION_FAILED code (1098) if validation fails.
4789
+ *
4790
+ * @remarks
4791
+ * This function uses `ZodTypeAny` for the schema parameter to avoid TypeScript's
4792
+ * "excessively deep type instantiation" error in generic contexts. The caller
4793
+ * must provide the expected type `T` explicitly.
4794
+ *
4795
+ * Use this instead of `validate`/`validateOrThrow` when:
4796
+ * - Your schema has `.default()` values
4797
+ * - Your schema uses `.coerce` (e.g., `z.coerce.number()`)
4798
+ * - Your schema has `.transform()` functions
4799
+ * - You need the actual parsed output, not just type narrowing
4800
+ *
4801
+ * @example
4802
+ * ```typescript
4803
+ * import { parseOrThrow } from '@core/utils'
4804
+ * import { z } from 'zod'
4805
+ *
4806
+ * const userSchema = z.object({
4807
+ * name: z.string().default('Anonymous'),
4808
+ * age: z.coerce.number(), // Transforms "25" → 25
4809
+ * })
4810
+ *
4811
+ * type User = z.infer<typeof userSchema>
4812
+ *
4813
+ * // Explicit type parameter
4814
+ * const user = parseOrThrow<User>({ age: '25' }, userSchema, 'user data')
4815
+ * console.log(user) // { name: 'Anonymous', age: 25 }
4816
+ * ```
4817
+ *
4818
+ * @example
4819
+ * ```typescript
4820
+ * // Usage in generic adapter functions (no deep type instantiation)
4821
+ * function validateInput<TInput>(
4822
+ * input: unknown,
4823
+ * schema: z.ZodTypeAny,
4824
+ * name: string,
4825
+ * ): TInput {
4826
+ * return parseOrThrow<TInput>(input, schema, `${name} input`)
4827
+ * }
4828
+ * ```
4829
+ */
4830
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Intentional: T is caller-provided to avoid deep type instantiation from schema inference
4831
+ function parseOrThrow(value, schema, context) {
4832
+ const result = schema.safeParse(value);
4833
+ if (!result.success) {
4834
+ throw createValidationErrorFromZod(result.error, context);
4835
+ }
4836
+ return result.data;
4837
+ }
4838
+
4532
4839
  /**
4533
4840
  * A type-safe event emitter for managing action-based event subscriptions.
4534
4841
  *
@@ -4803,7 +5110,7 @@ const parseAmount = (params) => {
4803
5110
  };
4804
5111
 
4805
5112
  var name = "@circle-fin/bridge-kit";
4806
- var version = "1.5.0";
5113
+ var version = "1.6.0";
4807
5114
  var pkg = {
4808
5115
  name: name,
4809
5116
  version: version};
@@ -5229,7 +5536,7 @@ const createDecimalStringValidator = (options) => (schema) => {
5229
5536
  * console.log(result.success) // true
5230
5537
  * ```
5231
5538
  */
5232
- zod.z.object({
5539
+ const chainDefinitionSchema = zod.z.object({
5233
5540
  name: zod.z.string().min(1, 'Chain name is required'),
5234
5541
  type: zod.z.string().min(1, 'Chain type is required'),
5235
5542
  });
@@ -5276,6 +5583,53 @@ const walletContextSchema = zod.z.object({
5276
5583
  isTestnet: zod.z.boolean(),
5277
5584
  }),
5278
5585
  });
5586
+ /**
5587
+ * Schema for validating destination wallet contexts.
5588
+ * Extends walletContextSchema with optional useForwarder flag.
5589
+ *
5590
+ * @throws KitError if validation fails
5591
+ */
5592
+ const destinationContextSchema = walletContextSchema.extend({
5593
+ useForwarder: zod.z.boolean().optional(),
5594
+ recipientAddress: zod.z.string().optional(),
5595
+ });
5596
+ /**
5597
+ * Schema for validating forwarder-only destination contexts.
5598
+ * Used when useForwarder is true and no adapter is provided.
5599
+ * Requires chain definition and recipientAddress.
5600
+ *
5601
+ * Validates that recipientAddress format is correct for the destination chain
5602
+ * (EVM hex address or Solana base58 address).
5603
+ *
5604
+ * @throws KitError if validation fails
5605
+ */
5606
+ const forwarderOnlyDestinationSchema = zod.z
5607
+ .object({
5608
+ chain: chainDefinitionSchema.extend({
5609
+ isTestnet: zod.z.boolean(),
5610
+ }),
5611
+ recipientAddress: zod.z
5612
+ .string()
5613
+ .min(1, 'Recipient address is required for forwarder-only destination'),
5614
+ useForwarder: zod.z.literal(true),
5615
+ })
5616
+ .superRefine((data, ctx) => {
5617
+ // Pass chain name as string - isValidAddressForChain will use name-based matching
5618
+ if (!isValidAddressForChain(data.recipientAddress, data.chain.name)) {
5619
+ ctx.addIssue({
5620
+ code: zod.z.ZodIssueCode.custom,
5621
+ message: `Invalid recipient address format for chain ${data.chain.name}`,
5622
+ path: ['recipientAddress'],
5623
+ });
5624
+ }
5625
+ });
5626
+ /**
5627
+ * Schema for validating any destination - either with adapter or forwarder-only.
5628
+ */
5629
+ const bridgeDestinationSchema$1 = zod.z.union([
5630
+ destinationContextSchema,
5631
+ forwarderOnlyDestinationSchema,
5632
+ ]);
5279
5633
  /**
5280
5634
  * Schema for validating a custom fee configuration.
5281
5635
  * Validates the simplified CustomFee interface which includes:
@@ -5373,7 +5727,7 @@ zod.z.object({
5373
5727
  maxDecimals: 6,
5374
5728
  })(zod.z.string())),
5375
5729
  source: walletContextSchema,
5376
- destination: walletContextSchema,
5730
+ destination: bridgeDestinationSchema$1,
5377
5731
  token: zod.z.literal('USDC'),
5378
5732
  config: zod.z.object({
5379
5733
  transferSpeed: zod.z.nativeEnum(exports.TransferSpeed).optional(),
@@ -5390,6 +5744,28 @@ zod.z.object({
5390
5744
  }),
5391
5745
  });
5392
5746
 
5747
+ /**
5748
+ * Creates a Zod superRefine validator for recipient address format validation.
5749
+ * Validates that the address format matches the expected format for the chain type.
5750
+ *
5751
+ * @returns A superRefine function that validates recipientAddress against chain type
5752
+ */
5753
+ function createRecipientAddressValidator() {
5754
+ return (data, ctx) => {
5755
+ const chain = data.chain;
5756
+ if (chain === null) {
5757
+ return;
5758
+ }
5759
+ if (!isValidAddressForChain(data.recipientAddress, chain)) {
5760
+ const chainInfo = extractChainInfo(chain);
5761
+ ctx.addIssue({
5762
+ code: zod.z.ZodIssueCode.custom,
5763
+ path: ['recipientAddress'],
5764
+ message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
5765
+ });
5766
+ }
5767
+ };
5768
+ }
5393
5769
  /**
5394
5770
  * Schema for validating AdapterContext for bridge operations.
5395
5771
  * Must always contain both adapter and chain explicitly.
@@ -5409,32 +5785,52 @@ const adapterContextSchema = zod.z.object({
5409
5785
  const bridgeDestinationWithAddressSchema = adapterContextSchema
5410
5786
  .extend({
5411
5787
  recipientAddress: zod.z.string().min(1, 'Recipient address is required'),
5788
+ useForwarder: zod.z.boolean().optional(),
5412
5789
  })
5413
- .superRefine((data, ctx) => {
5414
- const chain = data.chain;
5415
- if (chain === null) {
5416
- return;
5417
- }
5418
- if (!isValidAddressForChain(data.recipientAddress, chain)) {
5419
- const chainInfo = extractChainInfo(chain);
5420
- ctx.addIssue({
5421
- code: zod.z.ZodIssueCode.custom,
5422
- path: ['recipientAddress'],
5423
- message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
5424
- });
5425
- }
5790
+ .superRefine(createRecipientAddressValidator());
5791
+ /**
5792
+ * Schema for validating AdapterContext with optional useForwarder.
5793
+ * Extends adapterContextSchema with the useForwarder flag.
5794
+ */
5795
+ const adapterContextWithForwarderSchema = adapterContextSchema.extend({
5796
+ useForwarder: zod.z.boolean().optional(),
5426
5797
  });
5798
+ /**
5799
+ * Schema for validating ForwarderDestination objects.
5800
+ * Used when useForwarder is true and no adapter is provided.
5801
+ * Requires chain, recipientAddress, and useForwarder: true.
5802
+ *
5803
+ * When using this destination type:
5804
+ * - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
5805
+ * - No on-chain transaction confirmation is performed (no adapter available)
5806
+ * - The mint step's data field will be undefined (no transaction receipt)
5807
+ */
5808
+ const forwarderDestinationSchema = zod.z
5809
+ .object({
5810
+ chain: bridgeChainIdentifierSchema,
5811
+ recipientAddress: zod.z.string().min(1, 'Recipient address is required'),
5812
+ useForwarder: zod.z.literal(true),
5813
+ })
5814
+ .strict()
5815
+ .superRefine(createRecipientAddressValidator());
5427
5816
  /**
5428
5817
  * Schema for validating BridgeDestination union type.
5429
- * Can be an AdapterContext or BridgeDestinationWithAddress.
5818
+ * Supports three destination configurations:
5819
+ * - BridgeDestinationWithAddress (adapter with explicit recipient)
5820
+ * - ForwarderDestination (no adapter, requires useForwarder: true and recipientAddress)
5821
+ * - AdapterContext with optional useForwarder (adapter for default recipient)
5822
+ *
5823
+ * When using ForwarderDestination (no adapter):
5824
+ * - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
5825
+ * - No on-chain transaction confirmation is performed
5430
5826
  *
5431
- * The order matters: we check the more specific schema (with recipientAddress) first.
5432
- * This ensures that objects with an empty recipientAddress field are rejected rather
5433
- * than silently treated as AdapterContext with the field ignored.
5827
+ * The order matters: we check the more specific schemas first.
5828
+ * This ensures that objects with specific fields are matched correctly.
5434
5829
  */
5435
5830
  const bridgeDestinationSchema = zod.z.union([
5436
5831
  bridgeDestinationWithAddressSchema,
5437
- adapterContextSchema.strict(),
5832
+ forwarderDestinationSchema,
5833
+ adapterContextWithForwarderSchema.strict(),
5438
5834
  ]);
5439
5835
  /**
5440
5836
  * Schema for validating bridge parameters with chain identifiers.
@@ -5508,119 +5904,1411 @@ const bridgeParamsWithChainIdentifierSchema = zod.z.object({
5508
5904
  });
5509
5905
 
5510
5906
  /**
5511
- * Resolves a chain identifier to a chain definition.
5512
- *
5513
- * Both AdapterContext and BridgeDestinationWithAddress have the chain property
5514
- * at the top level, so we can directly access it from either type.
5907
+ * Default clock implementation using `Date.now()`.
5515
5908
  *
5516
- * @param ctx - The bridge destination containing the chain identifier
5517
- * @returns The resolved chain definition
5518
- * @throws If the chain definition cannot be resolved
5909
+ * @remarks
5910
+ * Use this in production code. For testing, inject a mock clock
5911
+ * that returns controlled timestamps.
5519
5912
  *
5520
5913
  * @example
5521
5914
  * ```typescript
5522
- * import { Blockchain } from '@core/chains'
5523
- *
5524
- * // AdapterContext
5525
- * const chain1 = resolveChainDefinition({
5526
- * adapter: mockAdapter,
5527
- * chain: 'Ethereum'
5528
- * })
5915
+ * import { defaultClock } from '@core/runtime'
5529
5916
  *
5530
- * // BridgeDestinationWithAddress
5531
- * const chain2 = resolveChainDefinition({
5532
- * adapter: mockAdapter,
5533
- * chain: 'Base',
5534
- * recipientAddress: '0x123...'
5535
- * })
5917
+ * const start = defaultClock.now()
5918
+ * // ... do work ...
5919
+ * const elapsed = defaultClock.since(start)
5536
5920
  * ```
5537
5921
  */
5538
- function resolveChainDefinition(ctx) {
5539
- return resolveChainIdentifier(ctx.chain);
5540
- }
5922
+ const defaultClock = {
5923
+ now: () => Date.now(),
5924
+ since: (start) => Date.now() - start,
5925
+ };
5926
+
5541
5927
  /**
5542
- * Resolves the signer's address from a bridge destination.
5928
+ * ID generation utilities for distributed tracing.
5543
5929
  *
5544
- * This function resolves the address that will be used for transaction signing,
5545
- * ignoring any `recipientAddress` field which is handled separately.
5930
+ * @remarks
5931
+ * | Function | Format | Use Case |
5932
+ * |----------|--------|----------|
5933
+ * | {@link createTraceId} | 32-char hex | Standard traceId (OpenTelemetry) |
5934
+ * | {@link createShortOpId} | 8-char hex | Standard opId |
5935
+ * | {@link createOpId} | `op-{ts}-{rand}` | Timestamped operation IDs |
5936
+ * | {@link createUuidV4} | RFC 4122 UUID | External system compatibility |
5937
+ * | {@link createId} | `{prefix}-{ts}-{rand}` | Custom prefixed IDs |
5546
5938
  *
5547
- * It handles two cases:
5548
- * - Developer-controlled adapters - returns the explicit address from context
5549
- * - User-controlled adapters - calls getAddress() on the adapter
5939
+ * @packageDocumentation
5940
+ */
5941
+ // ============================================================================
5942
+ // Crypto Utilities (Internal)
5943
+ // ============================================================================
5944
+ /** @internal */
5945
+ function hasGetRandomValues() {
5946
+ return (typeof crypto !== 'undefined' &&
5947
+ typeof crypto.getRandomValues === 'function');
5948
+ }
5949
+ /**
5950
+ * Create a W3C/OpenTelemetry-compatible trace ID.
5550
5951
  *
5551
- * @param ctx - The bridge destination to resolve the address from
5552
- * @returns The resolved signer address string
5952
+ * @returns 32-character lowercase hex string (128-bit).
5953
+ *
5954
+ * @remarks
5955
+ * **Standard function for generating `traceId` values.** Compatible with
5956
+ * OpenTelemetry, Jaeger, Zipkin, and AWS X-Ray.
5553
5957
  *
5554
5958
  * @example
5555
5959
  * ```typescript
5556
- * // Developer-controlled adapter
5557
- * const addr1 = await resolveAddress({
5558
- * adapter: devAdapter,
5559
- * chain: 'Ethereum',
5560
- * address: '0x1234567890123456789012345678901234567890'
5561
- * }) // Returns: '0x1234567890123456789012345678901234567890'
5562
- *
5563
- * // User-controlled adapter
5564
- * const addr2 = await resolveAddress({
5565
- * adapter: userAdapter,
5566
- * chain: 'Ethereum'
5567
- * }) // Returns adapter's connected address
5960
+ * const traceId = createTraceId() // "a1b2c3d4e5f6789012345678abcdef00"
5568
5961
  * ```
5569
5962
  */
5570
- async function resolveAddress(ctx) {
5571
- // Handle based on adapter's addressContext
5572
- if (ctx.adapter.capabilities?.addressContext === 'developer-controlled') {
5573
- // Developer-controlled: address must be provided explicitly
5574
- if ('address' in ctx && ctx.address) {
5575
- return ctx.address;
5576
- }
5577
- throw new Error('Address is required in context for developer-controlled adapters. ' +
5578
- 'Please provide: { adapter, chain, address: "0x..." }');
5963
+ function createTraceId() {
5964
+ const bytes = new Uint8Array(16);
5965
+ if (hasGetRandomValues()) {
5966
+ crypto.getRandomValues(bytes);
5579
5967
  }
5580
5968
  else {
5581
- // User-controlled: address should not be provided (auto-resolved from adapter)
5582
- if ('address' in ctx && ctx.address) {
5583
- throw new Error('Address should not be provided for user-controlled adapters. ' +
5584
- 'The address is automatically resolved from the connected wallet.');
5969
+ for (let i = 0; i < 16; i++) {
5970
+ bytes[i] = Math.floor(Math.random() * 256); // NOSONAR:
5585
5971
  }
5586
- // Derive address from adapter
5587
- const chain = resolveChainDefinition(ctx);
5588
- return await ctx.adapter.getAddress(chain);
5589
5972
  }
5973
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
5590
5974
  }
5975
+
5591
5976
  /**
5592
- * Resolves the amount of a transfer by formatting it according to the token's decimal places.
5977
+ * Clean tags by removing keys with undefined values.
5593
5978
  *
5594
- * This function takes the raw amount from the transfer parameters and formats it
5595
- * using the appropriate decimal places for the specified token. Currently supports
5596
- * USDC (6 decimals) and falls back to the raw amount for other tokens.
5979
+ * @param tags - Tags object, may contain undefined values. Accepts `undefined` or `null`.
5980
+ * @returns A new object with undefined values removed, or empty object if input is nullish.
5597
5981
  *
5598
- * @param params - The bridge parameters containing the amount, token type, and from context
5599
- * @returns The formatted amount string with proper decimal places
5982
+ * @remarks
5983
+ * This helper ensures tags are safe for serialization and logging.
5984
+ * Similar to the internal `cleanUndefined` in the logger module, but
5985
+ * typed specifically for {@link Tags}.
5600
5986
  *
5601
5987
  * @example
5602
5988
  * ```typescript
5603
- * import { Adapter } from '@core/adapter'
5989
+ * import { cleanTags } from '@core/runtime'
5604
5990
  *
5605
- * const params = {
5606
- * amount: '1000000',
5607
- * token: 'USDC',
5608
- * from: { adapter: mockAdapter, chain: Ethereum },
5609
- * to: { adapter: mockAdapter, chain: Base }
5610
- * }
5611
- * const formattedAmount = resolveAmount(params) // Returns '1000000000000'
5991
+ * const tags = { chain: 'Ethereum', amount: 100, extra: undefined }
5992
+ * const cleaned = cleanTags(tags)
5993
+ * // { chain: 'Ethereum', amount: 100 }
5994
+ *
5995
+ * const empty = cleanTags(undefined)
5996
+ * // {}
5612
5997
  * ```
5613
5998
  */
5614
- function resolveAmount(params) {
5615
- if (params.token === 'USDC') {
5616
- return parseUnits(params.amount, 6).toString();
5999
+ function cleanTags(tags) {
6000
+ if (tags == null)
6001
+ return {};
6002
+ return Object.fromEntries(Object.entries(tags).filter(([, value]) => value !== undefined));
6003
+ }
6004
+
6005
+ /**
6006
+ * Check if a pattern is valid.
6007
+ *
6008
+ * @remarks
6009
+ * Invalid patterns:
6010
+ * - Empty segments (e.g., `a..b`, `.a`, `a.`)
6011
+ * - `**` not at end (e.g., `a.**.b`)
6012
+ *
6013
+ * @param pattern - The pattern to validate.
6014
+ * @returns True if valid, false otherwise.
6015
+ */
6016
+ function isValidPattern(pattern) {
6017
+ // Empty pattern is valid (matches empty topic)
6018
+ if (pattern === '')
6019
+ return true;
6020
+ const segments = pattern.split('.');
6021
+ // Check for empty segments
6022
+ if (segments.includes(''))
6023
+ return false;
6024
+ // Check that ** only appears at the end
6025
+ const doubleStarIndex = segments.indexOf('**');
6026
+ if (doubleStarIndex !== -1 && doubleStarIndex !== segments.length - 1) {
6027
+ return false;
5617
6028
  }
5618
- return params.amount;
6029
+ return true;
5619
6030
  }
5620
6031
  /**
5621
- * Resolves and normalizes bridge configuration for the provider.
6032
+ * Convert a pattern to a regular expression.
6033
+ *
6034
+ * @param pattern - The pattern to convert.
6035
+ * @returns A RegExp that matches topics according to the pattern.
6036
+ */
6037
+ function patternToRegex(pattern) {
6038
+ const segments = pattern.split('.');
6039
+ // Handle ** at end specially to match zero or more segments
6040
+ const lastSegment = segments.at(-1);
6041
+ if (lastSegment === '**') {
6042
+ // Everything before ** must match, then optionally more segments
6043
+ const prefix = segments
6044
+ .slice(0, -1)
6045
+ .map((seg) => {
6046
+ if (seg === '*')
6047
+ return '[^.]+';
6048
+ return seg.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
6049
+ })
6050
+ .join(String.raw `\.`);
6051
+ // ** matches zero or more segments:
6052
+ // - If prefix is empty (**), match anything
6053
+ // - Otherwise, match prefix, then optionally (dot + more content)
6054
+ if (prefix === '') {
6055
+ return /^.*$/;
6056
+ }
6057
+ return new RegExp(String.raw `^${prefix}(?:\..*)?$`);
6058
+ }
6059
+ // No ** - just convert segments normally
6060
+ const escaped = segments
6061
+ .map((segment) => {
6062
+ if (segment === '*')
6063
+ return '[^.]+';
6064
+ return segment.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
6065
+ })
6066
+ .join(String.raw `\.`);
6067
+ return new RegExp(String.raw `^${escaped}$`);
6068
+ }
6069
+ /**
6070
+ * Execute an event handler with error isolation.
5622
6071
  *
5623
- * This function takes the optional configuration from bridge parameters and returns
6072
+ * @param handler - The handler function to execute.
6073
+ * @param event - The event to pass to the handler.
6074
+ * @param logger - Optional logger for error reporting.
6075
+ *
6076
+ * @remarks
6077
+ * - Synchronous errors are caught and logged
6078
+ * - Promise rejections are caught without awaiting
6079
+ * - Handler errors never propagate to caller
6080
+ */
6081
+ function executeHandler(handler, event, logger) {
6082
+ try {
6083
+ const result = handler(event);
6084
+ // Handle promise rejection without awaiting
6085
+ if (result instanceof Promise) {
6086
+ result.catch((err) => {
6087
+ logger?.error('Event handler promise rejected', {
6088
+ event: event.name,
6089
+ error: extractErrorInfo(err),
6090
+ });
6091
+ });
6092
+ }
6093
+ }
6094
+ catch (err) {
6095
+ // Isolate handler errors
6096
+ logger?.error('Event handler threw', {
6097
+ event: event.name,
6098
+ error: extractErrorInfo(err),
6099
+ });
6100
+ }
6101
+ }
6102
+ /**
6103
+ * Create an event bus.
6104
+ *
6105
+ * @param opts - Options for the event bus.
6106
+ * @returns An EventBus instance.
6107
+ *
6108
+ * @example
6109
+ * ```typescript
6110
+ * import { createEventBus } from '@core/runtime'
6111
+ *
6112
+ * const bus = createEventBus({ logger })
6113
+ *
6114
+ * // Subscribe to events
6115
+ * const unsubscribe = bus.on('tx.*', (event) => {
6116
+ * console.log('Transaction event:', event.name)
6117
+ * })
6118
+ *
6119
+ * // Emit events
6120
+ * bus.emit({ name: 'tx.sent', data: { txHash: '0x123' } })
6121
+ *
6122
+ * // Unsubscribe
6123
+ * unsubscribe()
6124
+ * ```
6125
+ */
6126
+ function createEventBus(opts) {
6127
+ const logger = opts?.logger;
6128
+ const subscriptions = new Set();
6129
+ function createBus(baseTags) {
6130
+ return {
6131
+ emit(event) {
6132
+ // Invalid topic names silently don't match any subscriptions
6133
+ if (!isValidPattern(event.name))
6134
+ return;
6135
+ const mergedTags = Object.keys(baseTags).length > 0 || event.tags
6136
+ ? { ...baseTags, ...cleanTags(event.tags) }
6137
+ : undefined;
6138
+ const finalEvent = mergedTags === undefined ? event : { ...event, tags: mergedTags };
6139
+ // Notify all matching subscribers
6140
+ for (const sub of subscriptions) {
6141
+ // Match-all subscription
6142
+ if (sub.pattern === null) {
6143
+ executeHandler(sub.handler, finalEvent, logger);
6144
+ continue;
6145
+ }
6146
+ // Patterned subscription: invalid pattern => regex=null => never match
6147
+ if (sub.regex === null)
6148
+ continue;
6149
+ if (!sub.regex.test(event.name))
6150
+ continue;
6151
+ executeHandler(sub.handler, finalEvent, logger);
6152
+ }
6153
+ },
6154
+ child(tags) {
6155
+ // Merge and clean tags, don't mutate parent
6156
+ const childTags = { ...baseTags, ...cleanTags(tags) };
6157
+ return createBus(childTags);
6158
+ },
6159
+ on(patternOrHandler, handler) {
6160
+ let sub;
6161
+ if (typeof patternOrHandler === 'function') {
6162
+ // on(handler) - subscribe to all
6163
+ sub = { pattern: null, handler: patternOrHandler, regex: null };
6164
+ }
6165
+ else {
6166
+ // on(pattern, handler)
6167
+ if (typeof handler !== 'function') {
6168
+ throw new TypeError(`EventBus.on("${patternOrHandler}") expected a function handler`);
6169
+ }
6170
+ const regex = isValidPattern(patternOrHandler)
6171
+ ? patternToRegex(patternOrHandler)
6172
+ : null;
6173
+ sub = {
6174
+ pattern: patternOrHandler,
6175
+ handler,
6176
+ regex,
6177
+ };
6178
+ }
6179
+ subscriptions.add(sub);
6180
+ // Return unsubscribe function (idempotent)
6181
+ let unsubscribed = false;
6182
+ return () => {
6183
+ if (!unsubscribed) {
6184
+ subscriptions.delete(sub);
6185
+ unsubscribed = true;
6186
+ }
6187
+ };
6188
+ },
6189
+ };
6190
+ }
6191
+ return createBus({});
6192
+ }
6193
+
6194
+ /** No-op counter that discards all increments. */
6195
+ const noopCounter = {
6196
+ inc: () => {
6197
+ // Intentionally empty - discards increment
6198
+ },
6199
+ };
6200
+ /** No-op histogram that discards all observations. */
6201
+ const noopHistogram = {
6202
+ observe: () => {
6203
+ // Intentionally empty - discards observation
6204
+ },
6205
+ };
6206
+ /** No-op timer that returns an empty stop function. */
6207
+ const noopTimer = {
6208
+ start: () => () => {
6209
+ // Intentionally empty - discards timing
6210
+ },
6211
+ };
6212
+ /**
6213
+ * Create a no-op metrics instance.
6214
+ *
6215
+ * @returns A Metrics instance that discards all operations.
6216
+ */
6217
+ function createNoopMetrics() {
6218
+ // Self-referential instance - child() returns same object to avoid allocations
6219
+ const instance = {
6220
+ counter: () => noopCounter,
6221
+ histogram: () => noopHistogram,
6222
+ timer: () => noopTimer,
6223
+ child: () => instance,
6224
+ };
6225
+ return instance;
6226
+ }
6227
+ /**
6228
+ * A no-op metrics implementation that discards all operations.
6229
+ *
6230
+ * @remarks
6231
+ * Use this when metrics collection is disabled or not configured.
6232
+ * All metric operations are safe to call and return immediately.
6233
+ * The `child()` method returns the same instance to avoid allocations.
6234
+ *
6235
+ * @example
6236
+ * ```typescript
6237
+ * import { noopMetrics } from '@core/runtime'
6238
+ *
6239
+ * // Use when metrics are disabled
6240
+ * const metrics = config.metricsEnabled
6241
+ * ? createDatadogMetrics(config)
6242
+ * : noopMetrics
6243
+ *
6244
+ * // All operations are safe to call
6245
+ * metrics.counter('requests').inc()
6246
+ * const stop = metrics.timer('query').start()
6247
+ * stop()
6248
+ * ```
6249
+ */
6250
+ const noopMetrics = createNoopMetrics();
6251
+
6252
+ /**
6253
+ * Define a schema for runtime logger interfaces.
6254
+ *
6255
+ * @remarks
6256
+ * Validate that a runtime logger provides the minimal methods expected by the SDK.
6257
+ *
6258
+ * @example
6259
+ * ```typescript
6260
+ * import { loggerSchema } from '@core/runtime'
6261
+ *
6262
+ * const logger = {
6263
+ * debug: () => undefined,
6264
+ * info: () => undefined,
6265
+ * warn: () => undefined,
6266
+ * error: () => undefined,
6267
+ * child: () => logger,
6268
+ * }
6269
+ *
6270
+ * loggerSchema.parse(logger)
6271
+ * ```
6272
+ */
6273
+ const loggerSchema = zod.z.custom((value) => {
6274
+ if (value === null || typeof value !== 'object') {
6275
+ return false;
6276
+ }
6277
+ const record = value;
6278
+ return (typeof record['debug'] === 'function' &&
6279
+ typeof record['info'] === 'function' &&
6280
+ typeof record['warn'] === 'function' &&
6281
+ typeof record['error'] === 'function' &&
6282
+ typeof record['child'] === 'function');
6283
+ }, {
6284
+ message: 'Invalid logger',
6285
+ });
6286
+
6287
+ /**
6288
+ * Define a schema for runtime metrics interfaces.
6289
+ *
6290
+ * @remarks
6291
+ * Validate that a metrics implementation exposes the minimal API expected by the runtime.
6292
+ *
6293
+ * @example
6294
+ * ```typescript
6295
+ * import { metricsSchema } from '@core/runtime'
6296
+ *
6297
+ * const metrics = {
6298
+ * counter: () => ({ inc: () => undefined }),
6299
+ * histogram: () => ({ observe: () => undefined }),
6300
+ * timer: () => ({ start: () => () => undefined }),
6301
+ * child: () => metrics,
6302
+ * }
6303
+ *
6304
+ * metricsSchema.parse(metrics)
6305
+ * ```
6306
+ */
6307
+ const metricsSchema = zod.z.custom((value) => {
6308
+ if (value === null || typeof value !== 'object') {
6309
+ return false;
6310
+ }
6311
+ const record = value;
6312
+ return (typeof record['counter'] === 'function' &&
6313
+ typeof record['histogram'] === 'function' &&
6314
+ typeof record['timer'] === 'function' &&
6315
+ typeof record['child'] === 'function');
6316
+ }, {
6317
+ message: 'Invalid metrics',
6318
+ });
6319
+
6320
+ /**
6321
+ * Omit undefined values from an object.
6322
+ *
6323
+ * @param obj - The object to process.
6324
+ * @returns A new object with undefined values removed.
6325
+ *
6326
+ * @internal
6327
+ * @remarks
6328
+ * Used by both production and mock loggers to ensure consistent behavior.
6329
+ * This prevents undefined values from being serialized in log output,
6330
+ * which can cause issues with some log transports.
6331
+ */
6332
+ function omitUndefined(obj) {
6333
+ const result = {};
6334
+ for (const [key, value] of Object.entries(obj)) {
6335
+ if (value !== undefined) {
6336
+ result[key] = value;
6337
+ }
6338
+ }
6339
+ return result;
6340
+ }
6341
+
6342
+ /**
6343
+ * Default redaction paths for web3/blockchain SDKs.
6344
+ *
6345
+ * @remarks
6346
+ * These paths target common sensitive fields in blockchain applications.
6347
+ * All user fields are nested under `context`, so paths start with `context.`.
6348
+ * Wildcard `*` matches any key at that level.
6349
+ */
6350
+ const DEFAULT_REDACT_PATHS = [
6351
+ // Generic Credentials
6352
+ 'context.password',
6353
+ 'context.passphrase',
6354
+ 'context.secret',
6355
+ 'context.token',
6356
+ 'context.*.password',
6357
+ 'context.*.passphrase',
6358
+ 'context.*.secret',
6359
+ 'context.*.token',
6360
+ // API Keys & Auth Tokens
6361
+ 'context.apiKey',
6362
+ 'context.apiSecret',
6363
+ 'context.accessToken',
6364
+ 'context.refreshToken',
6365
+ 'context.jwt',
6366
+ 'context.bearerToken',
6367
+ 'context.sessionId',
6368
+ 'context.authorization',
6369
+ 'context.cookie',
6370
+ 'context.*.apiKey',
6371
+ 'context.*.apiSecret',
6372
+ 'context.*.accessToken',
6373
+ 'context.*.refreshToken',
6374
+ 'context.*.jwt',
6375
+ 'context.*.bearerToken',
6376
+ 'context.*.sessionId',
6377
+ 'context.*.authorization',
6378
+ 'context.*.cookie',
6379
+ // Web3 / Crypto Keys
6380
+ 'context.privateKey',
6381
+ 'context.secretKey',
6382
+ 'context.signingKey',
6383
+ 'context.encryptionKey',
6384
+ 'context.*.privateKey',
6385
+ 'context.*.secretKey',
6386
+ 'context.*.signingKey',
6387
+ 'context.*.encryptionKey',
6388
+ // Web3 / Crypto Mnemonics and Seeds
6389
+ 'context.mnemonic',
6390
+ 'context.seed',
6391
+ 'context.seedPhrase',
6392
+ 'context.*.mnemonic',
6393
+ 'context.*.seed',
6394
+ 'context.*.seedPhrase',
6395
+ // OTP / Verification Codes
6396
+ 'context.otp',
6397
+ 'context.verificationCode',
6398
+ 'context.*.otp',
6399
+ 'context.*.verificationCode',
6400
+ // Payment Information
6401
+ 'context.cardNumber',
6402
+ 'context.cvv',
6403
+ 'context.accountNumber',
6404
+ 'context.*.cardNumber',
6405
+ 'context.*.cvv',
6406
+ 'context.*.accountNumber',
6407
+ ];
6408
+ /**
6409
+ * Wrap user fields under `context` to prevent collision with pino internals.
6410
+ *
6411
+ * @param fields - User-provided log fields.
6412
+ * @returns Object with fields nested under `context`, or undefined if empty.
6413
+ *
6414
+ * @remarks
6415
+ * This function handles edge cases by returning undefined for null, undefined,
6416
+ * or empty objects to avoid unnecessary wrapping in log output.
6417
+ * Undefined values are cleaned before wrapping.
6418
+ */
6419
+ function wrapInContext(fields) {
6420
+ if (!fields)
6421
+ return undefined;
6422
+ // Clean undefined values for consistency and transport compatibility
6423
+ const cleaned = omitUndefined(fields);
6424
+ // Handle edge case: all values were undefined, resulting in empty object
6425
+ const keys = Object.keys(cleaned);
6426
+ if (keys.length === 0)
6427
+ return undefined;
6428
+ return { context: cleaned };
6429
+ }
6430
+ /**
6431
+ * Wrap a pino instance to conform to our Logger interface.
6432
+ *
6433
+ * @param pinoInstance - The pino logger instance to wrap.
6434
+ * @returns A Logger instance conforming to our stable interface.
6435
+ */
6436
+ function wrapPino(pinoInstance) {
6437
+ return {
6438
+ debug(message, fields) {
6439
+ const wrapped = wrapInContext(fields);
6440
+ if (wrapped) {
6441
+ pinoInstance.debug(wrapped, message);
6442
+ }
6443
+ else {
6444
+ pinoInstance.debug(message);
6445
+ }
6446
+ },
6447
+ info(message, fields) {
6448
+ const wrapped = wrapInContext(fields);
6449
+ if (wrapped) {
6450
+ pinoInstance.info(wrapped, message);
6451
+ }
6452
+ else {
6453
+ pinoInstance.info(message);
6454
+ }
6455
+ },
6456
+ warn(message, fields) {
6457
+ const wrapped = wrapInContext(fields);
6458
+ if (wrapped) {
6459
+ pinoInstance.warn(wrapped, message);
6460
+ }
6461
+ else {
6462
+ pinoInstance.warn(message);
6463
+ }
6464
+ },
6465
+ error(message, fields) {
6466
+ const wrapped = wrapInContext(fields);
6467
+ if (wrapped) {
6468
+ pinoInstance.error(wrapped, message);
6469
+ }
6470
+ else {
6471
+ pinoInstance.error(message);
6472
+ }
6473
+ },
6474
+ child(tags) {
6475
+ // Child bindings stay flat (not wrapped) - they're part of logger's base context
6476
+ const cleaned = omitUndefined(tags);
6477
+ return wrapPino(pinoInstance.child(cleaned));
6478
+ },
6479
+ };
6480
+ }
6481
+ /**
6482
+ * Build pino redact configuration from our simplified options.
6483
+ *
6484
+ * @param redact - The redact configuration option.
6485
+ * @returns Pino-compatible redact configuration or undefined.
6486
+ */
6487
+ function buildRedactConfig(redact) {
6488
+ // Explicitly disabled
6489
+ if (redact === false) {
6490
+ return undefined;
6491
+ }
6492
+ // Custom paths provided
6493
+ if (Array.isArray(redact)) {
6494
+ return redact.length > 0
6495
+ ? { paths: redact, censor: '[REDACTED]' }
6496
+ : undefined;
6497
+ }
6498
+ // Default: use web3 sensible defaults
6499
+ return {
6500
+ paths: [...DEFAULT_REDACT_PATHS],
6501
+ censor: '[REDACTED]',
6502
+ };
6503
+ }
6504
+ /**
6505
+ * Create a logger backed by pino.
6506
+ *
6507
+ * @param options - Logger options (optional).
6508
+ * @param stream - Destination stream (optional).
6509
+ * @returns A Logger instance.
6510
+ * @throws Error if invalid pino options are provided.
6511
+ *
6512
+ * @remarks
6513
+ * This is a thin wrapper around pino that exposes our stable Logger interface.
6514
+ * Pino handles all transport concerns: JSON, pretty printing, file, remote, browser, etc.
6515
+ *
6516
+ * **Security**: By default, sensitive web3 fields (privateKey, mnemonic, apiKey, etc.)
6517
+ * are automatically redacted from log output. Use `redact: false` to disable.
6518
+ *
6519
+ * @example
6520
+ * ```typescript
6521
+ * import { createLogger } from '@core/runtime'
6522
+ *
6523
+ * // Default: web3 sensitive fields are redacted
6524
+ * const logger = createLogger({ level: 'info' })
6525
+ * logger.info('Signing', { privateKey: '0x123...' })
6526
+ * // Output: { context: { privateKey: '[REDACTED]' }, msg: 'Signing' }
6527
+ *
6528
+ * // Disable redaction (use with caution)
6529
+ * const unsafeLogger = createLogger({ level: 'debug', redact: false })
6530
+ *
6531
+ * // Custom redaction paths
6532
+ * const customLogger = createLogger({
6533
+ * level: 'info',
6534
+ * redact: ['context.mySecret', 'context.*.credentials']
6535
+ * })
6536
+ *
6537
+ * // Pretty output for development
6538
+ * const devLogger = createLogger({
6539
+ * level: 'debug',
6540
+ * transport: { target: 'pino-pretty' }
6541
+ * })
6542
+ *
6543
+ * // Browser logger
6544
+ * const browserLogger = createLogger({
6545
+ * browser: { asObject: true }
6546
+ * })
6547
+ * ```
6548
+ */
6549
+ function createLogger(options, stream) {
6550
+ const { redact, ...pinoOptions } = {};
6551
+ // Build redaction config
6552
+ const redactConfig = buildRedactConfig(redact);
6553
+ // Build final pino options, only include redact if defined
6554
+ const finalOptions = redactConfig
6555
+ ? { ...pinoOptions, redact: redactConfig }
6556
+ : pinoOptions;
6557
+ const pinoInstance = pino__default(finalOptions);
6558
+ return wrapPino(pinoInstance);
6559
+ }
6560
+
6561
+ /**
6562
+ * Factory for creating Runtime instances with defaults.
6563
+ *
6564
+ * @packageDocumentation
6565
+ */
6566
+ // ============================================================================
6567
+ // Validation Schema
6568
+ // ============================================================================
6569
+ /**
6570
+ * Schema for validating {@link RuntimeOptions}.
6571
+ *
6572
+ * @remarks
6573
+ * Used internally by {@link createRuntime} to validate options from JS consumers.
6574
+ * Exported for advanced use cases where manual validation is needed.
6575
+ */
6576
+ const runtimeOptionsSchema = zod.z
6577
+ .object({
6578
+ logger: loggerSchema.optional(),
6579
+ metrics: metricsSchema.optional(),
6580
+ })
6581
+ .passthrough();
6582
+ // ============================================================================
6583
+ // Factory
6584
+ // ============================================================================
6585
+ /**
6586
+ * Create a complete Runtime with sensible defaults.
6587
+ *
6588
+ * @param options - Optional configuration to override logger and metrics.
6589
+ * @returns A frozen, immutable Runtime with all services guaranteed present.
6590
+ * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
6591
+ *
6592
+ * @remarks
6593
+ * Creates a fully-configured runtime by merging provided options with defaults.
6594
+ * The returned runtime is frozen to enforce immutability.
6595
+ *
6596
+ * | Service | Default | Configurable |
6597
+ * |---------|---------|--------------|
6598
+ * | `logger` | pino logger (info level) | Yes |
6599
+ * | `metrics` | No-op metrics | Yes |
6600
+ * | `events` | Internal event bus | No |
6601
+ * | `clock` | `Date.now()` | No |
6602
+ *
6603
+ * **Why only logger and metrics?**
6604
+ *
6605
+ * - **Logger/Metrics**: Integration points with your infrastructure
6606
+ * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
6607
+ * - **Clock**: Testing concern - use mock factories for tests
6608
+ *
6609
+ * @example
6610
+ * ```typescript
6611
+ * import { createRuntime, createLogger } from '@core/runtime'
6612
+ *
6613
+ * // Use all defaults
6614
+ * const runtime = createRuntime()
6615
+ *
6616
+ * // Custom logger
6617
+ * const runtime = createRuntime({
6618
+ * logger: createLogger({ level: 'debug' }),
6619
+ * })
6620
+ *
6621
+ * // Custom metrics (e.g., Prometheus)
6622
+ * const runtime = createRuntime({
6623
+ * metrics: myPrometheusMetrics,
6624
+ * })
6625
+ *
6626
+ * // Subscribe to events (don't replace the bus)
6627
+ * runtime.events.on('operation.*', (event) => {
6628
+ * console.log('Event:', event.name)
6629
+ * })
6630
+ * ```
6631
+ */
6632
+ function createRuntime(options) {
6633
+ // Validate options for JS consumers
6634
+ if (options != null) {
6635
+ parseOrThrow(options, runtimeOptionsSchema, 'runtime options');
6636
+ }
6637
+ // Resolve logger first (events may need it)
6638
+ const logger = options?.logger ?? createLogger();
6639
+ // Internal services - not configurable
6640
+ const events = createEventBus({ logger });
6641
+ const clock = defaultClock;
6642
+ // Resolve metrics
6643
+ const metrics = options?.metrics ?? noopMetrics;
6644
+ return Object.freeze({ logger, events, metrics, clock });
6645
+ }
6646
+
6647
+ /**
6648
+ * Invocation context resolution - resolves the **WHO/HOW** of an operation.
6649
+ *
6650
+ * @packageDocumentation
6651
+ */
6652
+ // ============================================================================
6653
+ // Validation Schemas
6654
+ // ============================================================================
6655
+ /**
6656
+ * Schema for validating Caller.
6657
+ */
6658
+ const callerSchema = zod.z.object({
6659
+ type: zod.z.string(),
6660
+ name: zod.z.string(),
6661
+ version: zod.z.string().optional(),
6662
+ });
6663
+ /**
6664
+ * Schema for validating InvocationMeta input.
6665
+ */
6666
+ const invocationMetaSchema = zod.z
6667
+ .object({
6668
+ traceId: zod.z.string().optional(),
6669
+ runtime: zod.z.object({}).passthrough().optional(),
6670
+ tokens: zod.z.object({}).passthrough().optional(),
6671
+ callers: zod.z.array(callerSchema).optional(),
6672
+ })
6673
+ .strict();
6674
+ // ============================================================================
6675
+ // Invocation Context Resolution
6676
+ // ============================================================================
6677
+ /**
6678
+ * Resolve invocation metadata to invocation context.
6679
+ *
6680
+ * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
6681
+ * @param defaults - Default runtime and tokens to use if not overridden.
6682
+ * @returns Frozen, immutable invocation context with guaranteed values.
6683
+ * @throws KitError when meta contains invalid properties.
6684
+ *
6685
+ * @remarks
6686
+ * Resolves the **WHO** called and **HOW** to observe:
6687
+ * - TraceId: Uses provided value or generates new one
6688
+ * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
6689
+ * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
6690
+ * - Callers: Uses provided array or empty array
6691
+ *
6692
+ * The returned context is frozen to enforce immutability.
6693
+ *
6694
+ * @example
6695
+ * ```typescript
6696
+ * import { resolveInvocationContext, createRuntime } from '@core/runtime'
6697
+ * import { createTokenRegistry } from '@core/tokens'
6698
+ *
6699
+ * const defaults = {
6700
+ * runtime: createRuntime(),
6701
+ * tokens: createTokenRegistry(),
6702
+ * }
6703
+ *
6704
+ * // Minimal - just using defaults
6705
+ * const ctx = resolveInvocationContext(undefined, defaults)
6706
+ *
6707
+ * // With trace ID and caller info
6708
+ * const ctx = resolveInvocationContext(
6709
+ * {
6710
+ * traceId: 'abc-123',
6711
+ * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
6712
+ * },
6713
+ * defaults
6714
+ * )
6715
+ *
6716
+ * // With runtime override (complete replacement)
6717
+ * const ctx = resolveInvocationContext(
6718
+ * { runtime: createRuntime({ logger: myLogger }) },
6719
+ * defaults
6720
+ * )
6721
+ * ```
6722
+ */
6723
+ function resolveInvocationContext(meta, defaults) {
6724
+ // Validate meta input if provided
6725
+ if (meta !== undefined) {
6726
+ const result = invocationMetaSchema.safeParse(meta);
6727
+ if (!result.success) {
6728
+ throw createValidationFailedError$1('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
6729
+ }
6730
+ }
6731
+ // Generate trace ID if not provided
6732
+ const traceId = meta?.traceId ?? createTraceId();
6733
+ // Use meta overrides or fall back to defaults
6734
+ const runtime = meta?.runtime ?? defaults.runtime;
6735
+ const tokens = meta?.tokens ?? defaults.tokens;
6736
+ const callers = meta?.callers ?? [];
6737
+ return Object.freeze({ traceId, runtime, tokens, callers });
6738
+ }
6739
+
6740
+ /**
6741
+ * Extend an invocation context by appending a caller to its call chain.
6742
+ *
6743
+ * @param context - The existing invocation context to extend.
6744
+ * @param caller - The caller to append to the call chain.
6745
+ * @returns A new frozen invocation context with the caller appended.
6746
+ *
6747
+ * @remarks
6748
+ * This function creates a new immutable context with the caller appended
6749
+ * to the `callers` array while preserving all other context properties
6750
+ * (traceId, runtime, tokens).
6751
+ *
6752
+ * The returned context is frozen to enforce immutability.
6753
+ *
6754
+ * @example
6755
+ * ```typescript
6756
+ * import { extendInvocationContext } from '@core/runtime'
6757
+ *
6758
+ * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
6759
+ * const extended = extendInvocationContext(existingContext, caller)
6760
+ * // extended.callers === [...existingContext.callers, caller]
6761
+ * ```
6762
+ */
6763
+ function extendInvocationContext(context, caller) {
6764
+ return Object.freeze({
6765
+ traceId: context.traceId,
6766
+ runtime: context.runtime,
6767
+ tokens: context.tokens,
6768
+ callers: [...context.callers, caller],
6769
+ });
6770
+ }
6771
+
6772
+ // Clock - expose defaultClock for backward compatibility
6773
+ /** Clock validation schema (backward compatibility). */
6774
+ zod.z.custom((val) => val !== null &&
6775
+ typeof val === 'object' &&
6776
+ 'now' in val &&
6777
+ typeof val['now'] === 'function');
6778
+ /** EventBus validation schema (backward compatibility). */
6779
+ zod.z.custom((val) => val !== null &&
6780
+ typeof val === 'object' &&
6781
+ 'emit' in val &&
6782
+ typeof val['emit'] === 'function');
6783
+ /** Runtime validation schema (backward compatibility). */
6784
+ zod.z
6785
+ .object({
6786
+ logger: zod.z.any().optional(),
6787
+ events: zod.z.any().optional(),
6788
+ metrics: zod.z.any().optional(),
6789
+ clock: zod.z.any().optional(),
6790
+ })
6791
+ .passthrough();
6792
+
6793
+ /**
6794
+ * Create a structured error for token resolution failures.
6795
+ *
6796
+ * @remarks
6797
+ * Returns a `KitError` with `INPUT_INVALID_TOKEN` code (1006).
6798
+ * The error trace contains the selector and chain context for debugging.
6799
+ *
6800
+ * @param message - Human-readable error description.
6801
+ * @param selector - The token selector that failed to resolve.
6802
+ * @param chainId - The chain being resolved for (optional).
6803
+ * @param cause - The underlying error, if any (optional).
6804
+ * @returns A KitError with INPUT type and FATAL recoverability.
6805
+ *
6806
+ * @example
6807
+ * ```typescript
6808
+ * throw createTokenResolutionError(
6809
+ * 'Unknown token symbol: FAKE',
6810
+ * 'FAKE',
6811
+ * 'Ethereum'
6812
+ * )
6813
+ * ```
6814
+ */
6815
+ function createTokenResolutionError(message, selector, chainId, cause) {
6816
+ const trace = {
6817
+ selector,
6818
+ ...(chainId === undefined ? {} : { chainId }),
6819
+ ...({} ),
6820
+ };
6821
+ return new KitError({
6822
+ ...InputError.INVALID_TOKEN,
6823
+ recoverability: 'FATAL',
6824
+ message,
6825
+ cause: { trace },
6826
+ });
6827
+ }
6828
+
6829
+ /**
6830
+ * USDC token definition with addresses and metadata.
6831
+ *
6832
+ * @remarks
6833
+ * This is the built-in USDC definition used by the TokenRegistry.
6834
+ * Includes all known USDC addresses across supported chains.
6835
+ *
6836
+ * Keys use the `Blockchain` enum for type safety. Both enum values
6837
+ * and string literals are supported:
6838
+ * - `Blockchain.Ethereum` or `'Ethereum'`
6839
+ *
6840
+ * @example
6841
+ * ```typescript
6842
+ * import { USDC } from '@core/tokens'
6843
+ * import { Blockchain } from '@core/chains'
6844
+ *
6845
+ * console.log(USDC.symbol) // 'USDC'
6846
+ * console.log(USDC.decimals) // 6
6847
+ * console.log(USDC.locators[Blockchain.Ethereum])
6848
+ * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
6849
+ * ```
6850
+ */
6851
+ const USDC = {
6852
+ symbol: 'USDC',
6853
+ decimals: 6,
6854
+ locators: {
6855
+ // =========================================================================
6856
+ // Mainnets (alphabetically sorted)
6857
+ // =========================================================================
6858
+ [exports.Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
6859
+ [exports.Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
6860
+ [exports.Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
6861
+ [exports.Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
6862
+ [exports.Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
6863
+ [exports.Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
6864
+ [exports.Blockchain.Hedera]: '0.0.456858',
6865
+ [exports.Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
6866
+ [exports.Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
6867
+ [exports.Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
6868
+ [exports.Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
6869
+ [exports.Blockchain.Noble]: 'uusdc',
6870
+ [exports.Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
6871
+ [exports.Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
6872
+ [exports.Blockchain.Polkadot_Asset_Hub]: '1337',
6873
+ [exports.Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
6874
+ [exports.Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
6875
+ [exports.Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
6876
+ [exports.Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
6877
+ [exports.Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
6878
+ [exports.Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
6879
+ [exports.Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
6880
+ [exports.Blockchain.World_Chain]: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
6881
+ [exports.Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
6882
+ [exports.Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
6883
+ // =========================================================================
6884
+ // Testnets (alphabetically sorted)
6885
+ // =========================================================================
6886
+ [exports.Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
6887
+ [exports.Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
6888
+ [exports.Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
6889
+ [exports.Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
6890
+ [exports.Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
6891
+ [exports.Blockchain.Hedera_Testnet]: '0.0.429274',
6892
+ [exports.Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
6893
+ [exports.Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
6894
+ [exports.Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
6895
+ [exports.Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
6896
+ [exports.Blockchain.Noble_Testnet]: 'uusdc',
6897
+ [exports.Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
6898
+ [exports.Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
6899
+ [exports.Blockchain.Polkadot_Westmint]: '31337',
6900
+ [exports.Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
6901
+ [exports.Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
6902
+ [exports.Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
6903
+ [exports.Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
6904
+ [exports.Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
6905
+ [exports.Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
6906
+ [exports.Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
6907
+ [exports.Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
6908
+ [exports.Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
6909
+ [exports.Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
6910
+ },
6911
+ };
6912
+
6913
+ // Re-export for consumers
6914
+ /**
6915
+ * All default token definitions.
6916
+ *
6917
+ * @remarks
6918
+ * These tokens are automatically included in the TokenRegistry when created
6919
+ * without explicit defaults. Extensions can override these definitions.
6920
+ *
6921
+ * @example
6922
+ * ```typescript
6923
+ * import { createTokenRegistry } from '@core/tokens'
6924
+ *
6925
+ * // Registry uses these by default
6926
+ * const registry = createTokenRegistry()
6927
+ *
6928
+ * // Add custom tokens (built-ins are still included)
6929
+ * const customRegistry = createTokenRegistry({
6930
+ * tokens: [myCustomToken],
6931
+ * })
6932
+ * ```
6933
+ */
6934
+ const DEFAULT_TOKENS = [USDC];
6935
+
6936
+ /**
6937
+ * Check if a selector is a raw token selector (object form).
6938
+ *
6939
+ * @param selector - The token selector to check.
6940
+ * @returns True if the selector is a raw token selector.
6941
+ */
6942
+ function isRawSelector(selector) {
6943
+ return typeof selector === 'object' && 'locator' in selector;
6944
+ }
6945
+ /**
6946
+ * Normalize a symbol to uppercase for case-insensitive lookup.
6947
+ *
6948
+ * @param symbol - The symbol to normalize.
6949
+ * @returns The normalized (uppercase) symbol.
6950
+ */
6951
+ function normalizeSymbol(symbol) {
6952
+ return symbol.toUpperCase();
6953
+ }
6954
+ /**
6955
+ * Create a token registry with built-in tokens and optional extensions.
6956
+ *
6957
+ * @remarks
6958
+ * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
6959
+ * Custom tokens are merged on top - use this to add new tokens or override
6960
+ * built-in definitions.
6961
+ *
6962
+ * @param options - Configuration options for the registry.
6963
+ * @returns A token registry instance.
6964
+ *
6965
+ * @example
6966
+ * ```typescript
6967
+ * import { createTokenRegistry } from '@core/tokens'
6968
+ *
6969
+ * // Create registry with built-in tokens (USDC, etc.)
6970
+ * const registry = createTokenRegistry()
6971
+ *
6972
+ * // Resolve USDC on Ethereum
6973
+ * const usdc = registry.resolve('USDC', 'Ethereum')
6974
+ * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
6975
+ * console.log(usdc.decimals) // 6
6976
+ * ```
6977
+ *
6978
+ * @example
6979
+ * ```typescript
6980
+ * // Add custom tokens (built-ins are still included)
6981
+ * const myToken: TokenDefinition = {
6982
+ * symbol: 'MY',
6983
+ * decimals: 18,
6984
+ * locators: { Ethereum: '0x...' },
6985
+ * }
6986
+ *
6987
+ * const registry = createTokenRegistry({ tokens: [myToken] })
6988
+ * registry.resolve('USDC', 'Ethereum') // Still works!
6989
+ * registry.resolve('MY', 'Ethereum') // Also works
6990
+ * ```
6991
+ *
6992
+ * @example
6993
+ * ```typescript
6994
+ * // Override a built-in token
6995
+ * const customUsdc: TokenDefinition = {
6996
+ * symbol: 'USDC',
6997
+ * decimals: 6,
6998
+ * locators: { MyChain: '0xCustomAddress' },
6999
+ * }
7000
+ *
7001
+ * const registry = createTokenRegistry({ tokens: [customUsdc] })
7002
+ * // Now USDC resolves to customUsdc definition
7003
+ * ```
7004
+ *
7005
+ * @example
7006
+ * ```typescript
7007
+ * // Resolve arbitrary tokens by raw locator
7008
+ * const registry = createTokenRegistry()
7009
+ * const token = registry.resolve(
7010
+ * { locator: '0x1234...', decimals: 18 },
7011
+ * 'Ethereum'
7012
+ * )
7013
+ * ```
7014
+ */
7015
+ function createTokenRegistry(options = {}) {
7016
+ const { tokens = [], requireDecimals = false } = options;
7017
+ // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
7018
+ const tokenMap = new Map();
7019
+ // Add built-in tokens first
7020
+ for (const def of DEFAULT_TOKENS) {
7021
+ tokenMap.set(normalizeSymbol(def.symbol), def);
7022
+ }
7023
+ // Custom tokens override built-ins
7024
+ for (const def of tokens) {
7025
+ tokenMap.set(normalizeSymbol(def.symbol), def);
7026
+ }
7027
+ /**
7028
+ * Resolve a symbol selector to token information.
7029
+ */
7030
+ function resolveSymbol(symbol, chainId) {
7031
+ const normalizedSymbol = normalizeSymbol(symbol);
7032
+ const definition = tokenMap.get(normalizedSymbol);
7033
+ if (definition === undefined) {
7034
+ throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
7035
+ }
7036
+ const locator = definition.locators[chainId];
7037
+ if (locator === undefined || locator.trim() === '') {
7038
+ throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
7039
+ }
7040
+ return {
7041
+ symbol: definition.symbol,
7042
+ decimals: definition.decimals,
7043
+ locator,
7044
+ };
7045
+ }
7046
+ /**
7047
+ * Resolve a raw selector to token information.
7048
+ */
7049
+ function resolveRaw(selector, chainId) {
7050
+ const { locator, decimals } = selector;
7051
+ // Validate locator
7052
+ if (!locator || typeof locator !== 'string') {
7053
+ throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
7054
+ }
7055
+ // Decimals are always required for raw selectors
7056
+ if (decimals === undefined) {
7057
+ const message = requireDecimals
7058
+ ? 'Decimals required for raw token selector (requireDecimals: true)'
7059
+ : 'Decimals required for raw token selector. Provide { locator, decimals }.';
7060
+ throw createTokenResolutionError(message, selector, chainId);
7061
+ }
7062
+ // Validate decimals
7063
+ if (typeof decimals !== 'number' ||
7064
+ decimals < 0 ||
7065
+ !Number.isInteger(decimals)) {
7066
+ throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
7067
+ }
7068
+ return {
7069
+ decimals,
7070
+ locator,
7071
+ };
7072
+ }
7073
+ return {
7074
+ resolve(selector, chainId) {
7075
+ // Runtime validation of inputs - these checks are for JS consumers
7076
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
7077
+ if (selector === null || selector === undefined) {
7078
+ throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
7079
+ }
7080
+ if (chainId === '' || typeof chainId !== 'string') {
7081
+ throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
7082
+ }
7083
+ // Dispatch based on selector type
7084
+ if (isRawSelector(selector)) {
7085
+ return resolveRaw(selector, chainId);
7086
+ }
7087
+ if (typeof selector === 'string') {
7088
+ return resolveSymbol(selector, chainId);
7089
+ }
7090
+ throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
7091
+ },
7092
+ get(symbol) {
7093
+ if (!symbol || typeof symbol !== 'string') {
7094
+ return undefined;
7095
+ }
7096
+ return tokenMap.get(normalizeSymbol(symbol));
7097
+ },
7098
+ has(symbol) {
7099
+ if (!symbol || typeof symbol !== 'string') {
7100
+ return false;
7101
+ }
7102
+ return tokenMap.has(normalizeSymbol(symbol));
7103
+ },
7104
+ symbols() {
7105
+ return Array.from(tokenMap.values()).map((def) => def.symbol);
7106
+ },
7107
+ entries() {
7108
+ return Array.from(tokenMap.values());
7109
+ },
7110
+ };
7111
+ }
7112
+
7113
+ /**
7114
+ * Define a schema for token registry interfaces.
7115
+ *
7116
+ * @remarks
7117
+ * Validate that a registry exposes the `resolve()` API used by adapters.
7118
+ *
7119
+ * @example
7120
+ * ```typescript
7121
+ * import { tokenRegistrySchema } from '@core/tokens'
7122
+ *
7123
+ * const registry = {
7124
+ * resolve: () => ({ locator: '0x0', decimals: 6 }),
7125
+ * }
7126
+ *
7127
+ * tokenRegistrySchema.parse(registry)
7128
+ * ```
7129
+ */
7130
+ zod.z.custom((value) => {
7131
+ if (value === null || typeof value !== 'object') {
7132
+ return false;
7133
+ }
7134
+ const record = value;
7135
+ return (typeof record['resolve'] === 'function' &&
7136
+ typeof record['get'] === 'function' &&
7137
+ typeof record['has'] === 'function' &&
7138
+ typeof record['symbols'] === 'function' &&
7139
+ typeof record['entries'] === 'function');
7140
+ }, {
7141
+ message: 'Invalid token registry',
7142
+ });
7143
+
7144
+ /**
7145
+ * Type guard to check if the destination is a forwarder-only destination.
7146
+ *
7147
+ * Forwarder-only destinations have `useForwarder: true` and no adapter.
7148
+ * They require a `recipientAddress` to be specified.
7149
+ *
7150
+ * @param dest - The bridge destination to check
7151
+ * @returns True if this is a forwarder-only destination without adapter
7152
+ */
7153
+ function isForwarderOnlyDestination(dest) {
7154
+ return (dest.useForwarder === true &&
7155
+ !('adapter' in dest) &&
7156
+ 'recipientAddress' in dest);
7157
+ }
7158
+ /**
7159
+ * Resolves a chain identifier to a chain definition.
7160
+ *
7161
+ * Both AdapterContext and BridgeDestinationWithAddress have the chain property
7162
+ * at the top level, so we can directly access it from either type.
7163
+ *
7164
+ * @param ctx - The bridge destination containing the chain identifier
7165
+ * @returns The resolved chain definition
7166
+ * @throws If the chain definition cannot be resolved
7167
+ *
7168
+ * @example
7169
+ * ```typescript
7170
+ * import { Blockchain } from '@core/chains'
7171
+ *
7172
+ * // AdapterContext
7173
+ * const chain1 = resolveChainDefinition({
7174
+ * adapter: mockAdapter,
7175
+ * chain: 'Ethereum'
7176
+ * })
7177
+ *
7178
+ * // BridgeDestinationWithAddress
7179
+ * const chain2 = resolveChainDefinition({
7180
+ * adapter: mockAdapter,
7181
+ * chain: 'Base',
7182
+ * recipientAddress: '0x123...'
7183
+ * })
7184
+ * ```
7185
+ */
7186
+ function resolveChainDefinition(ctx) {
7187
+ return resolveChainIdentifier(ctx.chain);
7188
+ }
7189
+ /**
7190
+ * Resolves the signer's address from a bridge destination.
7191
+ *
7192
+ * This function resolves the address that will be used for transaction signing,
7193
+ * ignoring any `recipientAddress` field which is handled separately.
7194
+ *
7195
+ * It handles two cases:
7196
+ * - Developer-controlled adapters - returns the explicit address from context
7197
+ * - User-controlled adapters - calls getAddress() on the adapter
7198
+ *
7199
+ * @param ctx - The bridge destination to resolve the address from
7200
+ * @returns The resolved signer address string
7201
+ *
7202
+ * @example
7203
+ * ```typescript
7204
+ * // Developer-controlled adapter
7205
+ * const addr1 = await resolveAddress({
7206
+ * adapter: devAdapter,
7207
+ * chain: 'Ethereum',
7208
+ * address: '0x1234567890123456789012345678901234567890'
7209
+ * }) // Returns: '0x1234567890123456789012345678901234567890'
7210
+ *
7211
+ * // User-controlled adapter
7212
+ * const addr2 = await resolveAddress({
7213
+ * adapter: userAdapter,
7214
+ * chain: 'Ethereum'
7215
+ * }) // Returns adapter's connected address
7216
+ * ```
7217
+ */
7218
+ async function resolveAddress(ctx) {
7219
+ // Handle based on adapter's addressContext
7220
+ if (ctx.adapter.capabilities?.addressContext === 'developer-controlled') {
7221
+ // Developer-controlled: address must be provided explicitly
7222
+ if ('address' in ctx && ctx.address) {
7223
+ return ctx.address;
7224
+ }
7225
+ throw new Error('Address is required in context for developer-controlled adapters. ' +
7226
+ 'Please provide: { adapter, chain, address: "0x..." }');
7227
+ }
7228
+ else {
7229
+ // User-controlled: address should not be provided (auto-resolved from adapter)
7230
+ if ('address' in ctx && ctx.address) {
7231
+ throw new Error('Address should not be provided for user-controlled adapters. ' +
7232
+ 'The address is automatically resolved from the connected wallet.');
7233
+ }
7234
+ // Derive address from adapter
7235
+ const chain = resolveChainDefinition(ctx);
7236
+ return await ctx.adapter.getAddress(chain);
7237
+ }
7238
+ }
7239
+ /**
7240
+ * Resolves the amount of a transfer by formatting it according to the token's decimal places.
7241
+ *
7242
+ * This function takes the raw amount from the transfer parameters and formats it
7243
+ * using the appropriate decimal places for the specified token. Currently supports
7244
+ * USDC (6 decimals) and falls back to the raw amount for other tokens.
7245
+ *
7246
+ * @param params - The bridge parameters containing the amount, token type, and from context
7247
+ * @returns The formatted amount string with proper decimal places
7248
+ *
7249
+ * @example
7250
+ * ```typescript
7251
+ * import { Adapter } from '@core/adapter'
7252
+ *
7253
+ * const params = {
7254
+ * amount: '1000000',
7255
+ * token: 'USDC',
7256
+ * from: { adapter: mockAdapter, chain: Ethereum },
7257
+ * to: { adapter: mockAdapter, chain: Base }
7258
+ * }
7259
+ * const formattedAmount = resolveAmount(params) // Returns '1000000000000'
7260
+ * ```
7261
+ */
7262
+ function resolveAmount(params) {
7263
+ if (params.token === 'USDC') {
7264
+ return parseUnits(params.amount, 6).toString();
7265
+ }
7266
+ return params.amount;
7267
+ }
7268
+ /**
7269
+ * Resolves the invocation context for a bridge operation.
7270
+ *
7271
+ * Takes optional invocation metadata and resolves it into a full
7272
+ * InvocationContext with BridgeKit caller information appended.
7273
+ *
7274
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation
7275
+ * @returns An InvocationContext with traceId, runtime, tokens, and caller chain
7276
+ *
7277
+ * @example
7278
+ * ```typescript
7279
+ * // With user-provided invocation metadata
7280
+ * const invocation = resolveBridgeInvocation({
7281
+ * traceId: 'my-custom-trace-id',
7282
+ * callers: [{ type: 'app', name: 'MyDApp' }],
7283
+ * })
7284
+ * // invocation.traceId === 'my-custom-trace-id'
7285
+ * // invocation.callers === [{ type: 'app', name: 'MyDApp' }, { type: 'kit', name: 'BridgeKit' }]
7286
+ *
7287
+ * // Without invocation metadata (auto-generated traceId)
7288
+ * const invocation2 = resolveBridgeInvocation()
7289
+ * // invocation2.traceId === <auto-generated OpenTelemetry-compatible trace ID>
7290
+ * // invocation2.callers === [{ type: 'kit', name: 'BridgeKit' }]
7291
+ * ```
7292
+ */
7293
+ function resolveBridgeInvocation(invocationMeta) {
7294
+ const bridgeKitCaller = {
7295
+ type: 'kit',
7296
+ name: 'BridgeKit',
7297
+ version: pkg.version,
7298
+ };
7299
+ // Create default runtime and tokens for invocation context resolution
7300
+ const defaults = {
7301
+ runtime: createRuntime(),
7302
+ tokens: createTokenRegistry(),
7303
+ };
7304
+ // Resolve invocation metadata to full context, then extend with BridgeKit caller
7305
+ const baseContext = resolveInvocationContext(invocationMeta, defaults);
7306
+ return extendInvocationContext(baseContext, bridgeKitCaller);
7307
+ }
7308
+ /**
7309
+ * Resolves and normalizes bridge configuration for the provider.
7310
+ *
7311
+ * This function takes the optional configuration from bridge parameters and returns
5624
7312
  * a normalized BridgeConfig with:
5625
7313
  * - Default transfer speed set to FAST if not provided
5626
7314
  * - Max fee values converted from human-readable to smallest units (6 decimals for USDC)
@@ -5685,6 +7373,7 @@ function resolveConfig(params) {
5685
7373
  * - Amount formatting
5686
7374
  *
5687
7375
  * @param params - The bridge parameters containing source/destination contexts, amount, and token
7376
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation
5688
7377
  * @returns Promise resolving to normalized bridge parameters for provider consumption
5689
7378
  * @throws \{Error\} If parameters cannot be resolved (invalid chains, etc.)
5690
7379
  *
@@ -5704,22 +7393,32 @@ function resolveConfig(params) {
5704
7393
  * ```
5705
7394
  */
5706
7395
  async function resolveBridgeParams(params) {
5707
- const fromChain = resolveChainDefinition(params.from);
5708
- const toChain = resolveChainDefinition(params.to);
7396
+ // Resolve chains
7397
+ const fromChain = resolveChainIdentifier(params.from.chain);
7398
+ const toChain = resolveChainIdentifier(params.to.chain);
7399
+ // Check if this is a forwarder-only destination (no adapter)
7400
+ const isForwarderOnly = isForwarderOnlyDestination(params.to);
5709
7401
  // Validate adapter chain support after resolution
5710
- // This ensures adapters support the resolved chains before proceeding
5711
7402
  params.from.adapter.validateChainSupport(fromChain);
5712
- params.to.adapter.validateChainSupport(toChain);
7403
+ // Only validate destination adapter if it exists
7404
+ if (!isForwarderOnly && 'adapter' in params.to) {
7405
+ params.to.adapter.validateChainSupport(toChain);
7406
+ }
7407
+ // For forwarder-only destinations, use recipientAddress directly
7408
+ // For other destinations, resolve address from adapter
5713
7409
  const [fromAddress, toAddress] = await Promise.all([
5714
7410
  resolveAddress(params.from),
5715
- resolveAddress(params.to),
7411
+ isForwarderOnly
7412
+ ? Promise.resolve(params.to.recipientAddress)
7413
+ : resolveAddress(params.to),
5716
7414
  ]);
5717
7415
  const token = params.token ?? 'USDC';
5718
- // Extract adapters - now always from explicit contexts
5719
- const fromAdapter = params.from.adapter;
5720
- const toAdapter = params.to.adapter;
5721
7416
  // Extract recipientAddress from params.to if it exists
5722
7417
  const recipientAddress = 'recipientAddress' in params.to ? params.to.recipientAddress : undefined;
7418
+ // Extract useForwarder from params.to if it exists
7419
+ const useForwarder = 'useForwarder' in params.to ? params.to.useForwarder : undefined;
7420
+ // Resolve invocation metadata to full InvocationContext with BridgeKit caller
7421
+ const resolvedInvocation = resolveBridgeInvocation(params.invocationMeta);
5723
7422
  return {
5724
7423
  amount: resolveAmount({
5725
7424
  ...params,
@@ -5729,16 +7428,21 @@ async function resolveBridgeParams(params) {
5729
7428
  config: resolveConfig({
5730
7429
  ...params}),
5731
7430
  source: {
5732
- adapter: fromAdapter,
7431
+ adapter: params.from.adapter,
5733
7432
  chain: fromChain,
5734
7433
  address: fromAddress,
5735
7434
  },
5736
7435
  destination: {
5737
- adapter: toAdapter,
7436
+ // Only include adapter if it exists (not forwarder-only)
7437
+ ...(!isForwarderOnly &&
7438
+ 'adapter' in params.to && { adapter: params.to.adapter }),
5738
7439
  chain: toChain,
5739
7440
  address: toAddress,
5740
7441
  ...(recipientAddress !== undefined && { recipientAddress }),
7442
+ ...(useForwarder !== undefined && { useForwarder }),
5741
7443
  },
7444
+ // Pass resolved InvocationContext as invocationMeta (superset is compatible)
7445
+ invocationMeta: resolvedInvocation,
5742
7446
  };
5743
7447
  }
5744
7448
 
@@ -5816,6 +7520,36 @@ const formatBridgeResult = (result, formatDirection) => {
5816
7520
  };
5817
7521
  };
5818
7522
 
7523
+ /**
7524
+ * BridgeKit caller component for retry and estimate operations.
7525
+ */
7526
+ const BRIDGE_KIT_CALLER = {
7527
+ type: 'kit',
7528
+ name: 'BridgeKit',
7529
+ version: pkg.version,
7530
+ };
7531
+ /**
7532
+ * Merge BridgeKit's caller into the invocation metadata for retry operations.
7533
+ *
7534
+ * Prepends the BridgeKit caller to the callers array.
7535
+ *
7536
+ * @param invocationMeta - Optional invocation metadata provided by caller.
7537
+ * @returns Merged invocation metadata with BridgeKit caller prepended.
7538
+ *
7539
+ * @internal
7540
+ */
7541
+ function mergeRetryInvocationMeta(invocationMeta) {
7542
+ // Prepend BridgeKit caller to existing callers array
7543
+ const existingCallers = invocationMeta?.callers ?? [];
7544
+ return invocationMeta
7545
+ ? {
7546
+ ...invocationMeta,
7547
+ callers: [BRIDGE_KIT_CALLER, ...existingCallers],
7548
+ }
7549
+ : {
7550
+ callers: [BRIDGE_KIT_CALLER, ...existingCallers],
7551
+ };
7552
+ }
5819
7553
  /**
5820
7554
  * Route cross-chain USDC bridging through Circle's Cross-Chain Transfer Protocol v2 (CCTPv2).
5821
7555
  *
@@ -5912,7 +7646,7 @@ class BridgeKit {
5912
7646
  * - CCTPv2 support for the chain pair
5913
7647
  * - Transfer configuration options
5914
7648
  *
5915
- * @param params - The transfer parameters containing source, destination, amount, and token
7649
+ * @param params - The transfer parameters containing source, destination, amount, token, and optional invocation metadata
5916
7650
  * @returns Promise resolving to the transfer result with transaction details and steps
5917
7651
  * @throws {KitError} When any parameter validation fails.
5918
7652
  * @throws {Error} When CCTPv2 does not support the specified route.
@@ -5929,18 +7663,24 @@ class BridgeKit {
5929
7663
  * privateKey: process.env.PRIVATE_KEY,
5930
7664
  * })
5931
7665
  *
7666
+ * // Basic usage
5932
7667
  * const result = await kit.bridge({
5933
- * from: {
5934
- * adapter,
5935
- * chain: 'Ethereum'
5936
- * },
5937
- * to: {
5938
- * adapter,
5939
- * chain: 'Base'
5940
- * },
7668
+ * from: { adapter, chain: 'Ethereum' },
7669
+ * to: { adapter, chain: 'Base' },
5941
7670
  * amount: '100.50'
5942
7671
  * })
5943
7672
  *
7673
+ * // With custom invocation metadata
7674
+ * const result = await kit.bridge({
7675
+ * from: { adapter, chain: 'Ethereum' },
7676
+ * to: { adapter, chain: 'Base' },
7677
+ * amount: '100.50',
7678
+ * invocationMeta: {
7679
+ * traceId: 'custom-trace-id',
7680
+ * callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
7681
+ * },
7682
+ * })
7683
+ *
5944
7684
  * // Handle result
5945
7685
  * if (result.state === 'success') {
5946
7686
  * console.log('Bridge completed!')
@@ -5986,6 +7726,8 @@ class BridgeKit {
5986
7726
  * @param context - The retry context containing fresh adapter instances for both
5987
7727
  * source and destination chains. These adapters should be properly
5988
7728
  * configured with current network connections and signing capabilities.
7729
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation.
7730
+ * If not provided, uses the traceId from the original result.
5989
7731
  * @returns A promise that resolves to the updated bridge result after retry execution.
5990
7732
  * The result will contain the complete step history including both original
5991
7733
  * and retry attempts.
@@ -6017,31 +7759,35 @@ class BridgeKit {
6017
7759
  * // ... other properties
6018
7760
  * }
6019
7761
  *
7762
+ * // Basic retry (uses traceId from original result)
7763
+ * const retryResult = await kit.retry(failedResult, {
7764
+ * from: sourceAdapter,
7765
+ * to: destAdapter
7766
+ * })
6020
7767
  *
6021
- * try {
6022
- * const retryResult = await kit.retry(failedResult, {
6023
- * from: sourceAdapter,
6024
- * to: destAdapter
6025
- * })
6026
- *
6027
- * console.log('Retry completed successfully:', retryResult.state)
6028
- * console.log('Total steps executed:', retryResult.steps.length)
6029
- * } catch (error) {
6030
- * console.error('Retry failed:', error.message)
6031
- * // Handle retry failure (may require manual intervention)
6032
- * }
7768
+ * // Retry with custom invocation metadata
7769
+ * const retryResult = await kit.retry(
7770
+ * failedResult,
7771
+ * { from: sourceAdapter, to: destAdapter },
7772
+ * {
7773
+ * traceId: 'custom-trace-id',
7774
+ * callers: [{ type: 'app', name: 'MyApp' }],
7775
+ * }
7776
+ * )
6033
7777
  * ```
6034
7778
  */
6035
- async retry(result, context) {
7779
+ async retry(result, context, invocationMeta) {
6036
7780
  const provider = this.providers.find((p) => p.name === result.provider);
6037
7781
  if (!provider) {
6038
7782
  throw new Error(`Provider ${result.provider} not found`);
6039
7783
  }
7784
+ // Merge BridgeKit caller into invocation metadata for retry operation
7785
+ const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
6040
7786
  // Format the bridge result into bigint string values for internal use
6041
7787
  const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
6042
7788
  // Execute the retry using the provider
6043
7789
  // Format the bridge result into human-readable string values for the user
6044
- return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context), 'to-human-readable');
7790
+ return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
6045
7791
  }
6046
7792
  /**
6047
7793
  * Estimate the cost and fees for a cross-chain USDC bridge operation.
@@ -6049,13 +7795,15 @@ class BridgeKit {
6049
7795
  * This method calculates the expected gas fees and protocol costs for bridging
6050
7796
  * without actually executing the transaction. It performs the same validation
6051
7797
  * as the bridge method but stops before execution.
6052
- * @param params - The bridge parameters for cost estimation
7798
+ *
7799
+ * @param params - The bridge parameters for cost estimation, including optional invocation metadata
6053
7800
  * @returns Promise resolving to detailed cost breakdown including gas estimates
6054
7801
  * @throws {KitError} When the parameters are invalid.
6055
7802
  * @throws {UnsupportedRouteError} When the route is not supported.
6056
7803
  *
6057
7804
  * @example
6058
7805
  * ```typescript
7806
+ * // Basic usage
6059
7807
  * const estimate = await kit.estimate({
6060
7808
  * from: { adapter: adapter, chain: 'Ethereum' },
6061
7809
  * to: { adapter: adapter, chain: 'Base' },
@@ -6063,6 +7811,18 @@ class BridgeKit {
6063
7811
  * token: 'USDC'
6064
7812
  * })
6065
7813
  * console.log('Estimated cost:', estimate.totalCost)
7814
+ *
7815
+ * // With custom invocation metadata
7816
+ * const estimate = await kit.estimate({
7817
+ * from: { adapter: adapter, chain: 'Ethereum' },
7818
+ * to: { adapter: adapter, chain: 'Base' },
7819
+ * amount: '10.50',
7820
+ * token: 'USDC',
7821
+ * invocationMeta: {
7822
+ * traceId: 'custom-trace-id',
7823
+ * callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
7824
+ * },
7825
+ * })
6066
7826
  * ```
6067
7827
  */
6068
7828
  async estimate(params) {
@@ -6114,6 +7874,9 @@ class BridgeKit {
6114
7874
  * // Get only EVM mainnet chains
6115
7875
  * const evmMainnets = kit.getSupportedChains({ chainType: 'evm', isTestnet: false })
6116
7876
  *
7877
+ * // Get only chains that support forwarding
7878
+ * const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
7879
+ *
6117
7880
  * console.log('Supported chains:')
6118
7881
  * allChains.forEach(chain => {
6119
7882
  * console.log(`- ${chain.name} (${chain.type})`)
@@ -6147,6 +7910,18 @@ class BridgeKit {
6147
7910
  if (options?.isTestnet !== undefined) {
6148
7911
  chains = chains.filter((chain) => chain.isTestnet === options.isTestnet);
6149
7912
  }
7913
+ // Apply forwarder support filter if provided
7914
+ if (options?.forwarderSupported !== undefined) {
7915
+ chains = chains.filter((chain) => {
7916
+ const fs = chain.cctp?.forwarderSupported;
7917
+ if (!fs) {
7918
+ return !options.forwarderSupported;
7919
+ }
7920
+ return options.forwarderSupported
7921
+ ? fs.source || fs.destination
7922
+ : !fs.source && !fs.destination;
7923
+ });
7924
+ }
6150
7925
  return chains;
6151
7926
  }
6152
7927
  /**
@@ -6342,6 +8117,10 @@ exports.NetworkError = NetworkError;
6342
8117
  exports.OnchainError = OnchainError;
6343
8118
  exports.RpcError = RpcError;
6344
8119
  exports.bridgeParamsWithChainIdentifierSchema = bridgeParamsWithChainIdentifierSchema;
8120
+ exports.createRuntime = createRuntime;
8121
+ exports.createTokenRegistry = createTokenRegistry;
8122
+ exports.createTraceId = createTraceId;
8123
+ exports.extendInvocationContext = extendInvocationContext;
6345
8124
  exports.getErrorCode = getErrorCode;
6346
8125
  exports.getErrorMessage = getErrorMessage;
6347
8126
  exports.isBalanceError = isBalanceError;
@@ -6353,5 +8132,6 @@ exports.isOnchainError = isOnchainError;
6353
8132
  exports.isRetryableError = isRetryableError;
6354
8133
  exports.isRpcError = isRpcError;
6355
8134
  exports.resolveChainIdentifier = resolveChainIdentifier;
8135
+ exports.resolveInvocationContext = resolveInvocationContext;
6356
8136
  exports.setExternalPrefix = setExternalPrefix;
6357
8137
  //# sourceMappingURL=index.cjs.map