@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.mjs CHANGED
@@ -21,6 +21,7 @@ import '@ethersproject/bytes';
21
21
  import '@ethersproject/address';
22
22
  import 'bs58';
23
23
  import { formatUnits as formatUnits$1, parseUnits as parseUnits$1 } from '@ethersproject/units';
24
+ import pino from 'pino';
24
25
  import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2';
25
26
 
26
27
  /**
@@ -653,6 +654,18 @@ const NetworkError = {
653
654
  name: 'NETWORK_TIMEOUT',
654
655
  type: 'NETWORK',
655
656
  },
657
+ /** Circle relayer failed to process the forwarding/mint transaction */
658
+ RELAYER_FORWARD_FAILED: {
659
+ code: 3003,
660
+ name: 'NETWORK_RELAYER_FORWARD_FAILED',
661
+ type: 'NETWORK',
662
+ },
663
+ /** Relayer mint is pending - waiting for confirmation */
664
+ RELAYER_PENDING: {
665
+ code: 3004,
666
+ name: 'NETWORK_RELAYER_PENDING',
667
+ type: 'NETWORK',
668
+ },
656
669
  };
657
670
 
658
671
  /**
@@ -1213,6 +1226,10 @@ const Aptos = defineChain({
1213
1226
  confirmations: 1,
1214
1227
  },
1215
1228
  },
1229
+ forwarderSupported: {
1230
+ source: false,
1231
+ destination: false,
1232
+ },
1216
1233
  },
1217
1234
  });
1218
1235
 
@@ -1246,6 +1263,10 @@ const AptosTestnet = defineChain({
1246
1263
  confirmations: 1,
1247
1264
  },
1248
1265
  },
1266
+ forwarderSupported: {
1267
+ source: false,
1268
+ destination: false,
1269
+ },
1249
1270
  },
1250
1271
  });
1251
1272
 
@@ -1305,6 +1326,10 @@ const ArcTestnet = defineChain({
1305
1326
  fastConfirmations: 1,
1306
1327
  },
1307
1328
  },
1329
+ forwarderSupported: {
1330
+ source: true,
1331
+ destination: true,
1332
+ },
1308
1333
  },
1309
1334
  kitContracts: {
1310
1335
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1349,6 +1374,10 @@ const Arbitrum = defineChain({
1349
1374
  fastConfirmations: 1,
1350
1375
  },
1351
1376
  },
1377
+ forwarderSupported: {
1378
+ source: true,
1379
+ destination: true,
1380
+ },
1352
1381
  },
1353
1382
  kitContracts: {
1354
1383
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1393,6 +1422,10 @@ const ArbitrumSepolia = defineChain({
1393
1422
  fastConfirmations: 1,
1394
1423
  },
1395
1424
  },
1425
+ forwarderSupported: {
1426
+ source: true,
1427
+ destination: true,
1428
+ },
1396
1429
  },
1397
1430
  kitContracts: {
1398
1431
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1437,6 +1470,10 @@ const Avalanche = defineChain({
1437
1470
  fastConfirmations: 1,
1438
1471
  },
1439
1472
  },
1473
+ forwarderSupported: {
1474
+ source: true,
1475
+ destination: true,
1476
+ },
1440
1477
  },
1441
1478
  kitContracts: {
1442
1479
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1480,6 +1517,10 @@ const AvalancheFuji = defineChain({
1480
1517
  fastConfirmations: 1,
1481
1518
  },
1482
1519
  },
1520
+ forwarderSupported: {
1521
+ source: true,
1522
+ destination: true,
1523
+ },
1483
1524
  },
1484
1525
  rpcEndpoints: ['https://api.avax-test.network/ext/bc/C/rpc'],
1485
1526
  kitContracts: {
@@ -1525,6 +1566,10 @@ const Base = defineChain({
1525
1566
  fastConfirmations: 1,
1526
1567
  },
1527
1568
  },
1569
+ forwarderSupported: {
1570
+ source: true,
1571
+ destination: true,
1572
+ },
1528
1573
  },
1529
1574
  kitContracts: {
1530
1575
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1569,6 +1614,10 @@ const BaseSepolia = defineChain({
1569
1614
  fastConfirmations: 1,
1570
1615
  },
1571
1616
  },
1617
+ forwarderSupported: {
1618
+ source: true,
1619
+ destination: true,
1620
+ },
1572
1621
  },
1573
1622
  kitContracts: {
1574
1623
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1655,6 +1704,10 @@ const Codex = defineChain({
1655
1704
  fastConfirmations: 1,
1656
1705
  },
1657
1706
  },
1707
+ forwarderSupported: {
1708
+ source: true,
1709
+ destination: false,
1710
+ },
1658
1711
  },
1659
1712
  kitContracts: {
1660
1713
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1693,6 +1746,10 @@ const CodexTestnet = defineChain({
1693
1746
  fastConfirmations: 1,
1694
1747
  },
1695
1748
  },
1749
+ forwarderSupported: {
1750
+ source: true,
1751
+ destination: false,
1752
+ },
1696
1753
  },
1697
1754
  kitContracts: {
1698
1755
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1737,6 +1794,10 @@ const Ethereum = defineChain({
1737
1794
  fastConfirmations: 2,
1738
1795
  },
1739
1796
  },
1797
+ forwarderSupported: {
1798
+ source: true,
1799
+ destination: true,
1800
+ },
1740
1801
  },
1741
1802
  kitContracts: {
1742
1803
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1781,6 +1842,10 @@ const EthereumSepolia = defineChain({
1781
1842
  fastConfirmations: 2,
1782
1843
  },
1783
1844
  },
1845
+ forwarderSupported: {
1846
+ source: true,
1847
+ destination: true,
1848
+ },
1784
1849
  },
1785
1850
  kitContracts: {
1786
1851
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1867,6 +1932,10 @@ const HyperEVM = defineChain({
1867
1932
  fastConfirmations: 1,
1868
1933
  },
1869
1934
  },
1935
+ forwarderSupported: {
1936
+ source: true,
1937
+ destination: true,
1938
+ },
1870
1939
  },
1871
1940
  kitContracts: {
1872
1941
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1906,6 +1975,10 @@ const HyperEVMTestnet = defineChain({
1906
1975
  fastConfirmations: 1,
1907
1976
  },
1908
1977
  },
1978
+ forwarderSupported: {
1979
+ source: true,
1980
+ destination: true,
1981
+ },
1909
1982
  },
1910
1983
  kitContracts: {
1911
1984
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1949,6 +2022,10 @@ const Ink = defineChain({
1949
2022
  fastConfirmations: 1,
1950
2023
  },
1951
2024
  },
2025
+ forwarderSupported: {
2026
+ source: true,
2027
+ destination: true,
2028
+ },
1952
2029
  },
1953
2030
  kitContracts: {
1954
2031
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1991,6 +2068,10 @@ const InkTestnet = defineChain({
1991
2068
  fastConfirmations: 1,
1992
2069
  },
1993
2070
  },
2071
+ forwarderSupported: {
2072
+ source: true,
2073
+ destination: true,
2074
+ },
1994
2075
  },
1995
2076
  kitContracts: {
1996
2077
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2029,6 +2110,10 @@ const Linea = defineChain({
2029
2110
  fastConfirmations: 1,
2030
2111
  },
2031
2112
  },
2113
+ forwarderSupported: {
2114
+ source: true,
2115
+ destination: true,
2116
+ },
2032
2117
  },
2033
2118
  kitContracts: {
2034
2119
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2067,6 +2152,10 @@ const LineaSepolia = defineChain({
2067
2152
  fastConfirmations: 1,
2068
2153
  },
2069
2154
  },
2155
+ forwarderSupported: {
2156
+ source: true,
2157
+ destination: true,
2158
+ },
2070
2159
  },
2071
2160
  kitContracts: {
2072
2161
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2107,6 +2196,10 @@ const Monad = defineChain({
2107
2196
  fastConfirmations: 1,
2108
2197
  },
2109
2198
  },
2199
+ forwarderSupported: {
2200
+ source: true,
2201
+ destination: true,
2202
+ },
2110
2203
  },
2111
2204
  kitContracts: {
2112
2205
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2147,6 +2240,10 @@ const MonadTestnet = defineChain({
2147
2240
  fastConfirmations: 1,
2148
2241
  },
2149
2242
  },
2243
+ forwarderSupported: {
2244
+ source: true,
2245
+ destination: true,
2246
+ },
2150
2247
  },
2151
2248
  kitContracts: {
2152
2249
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2228,6 +2325,10 @@ const Noble = defineChain({
2228
2325
  confirmations: 1,
2229
2326
  },
2230
2327
  },
2328
+ forwarderSupported: {
2329
+ source: false,
2330
+ destination: false,
2331
+ },
2231
2332
  },
2232
2333
  });
2233
2334
 
@@ -2260,6 +2361,10 @@ const NobleTestnet = defineChain({
2260
2361
  confirmations: 1,
2261
2362
  },
2262
2363
  },
2364
+ forwarderSupported: {
2365
+ source: false,
2366
+ destination: false,
2367
+ },
2263
2368
  },
2264
2369
  });
2265
2370
 
@@ -2301,6 +2406,10 @@ const Optimism = defineChain({
2301
2406
  fastConfirmations: 1,
2302
2407
  },
2303
2408
  },
2409
+ forwarderSupported: {
2410
+ source: true,
2411
+ destination: true,
2412
+ },
2304
2413
  },
2305
2414
  kitContracts: {
2306
2415
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2345,6 +2454,10 @@ const OptimismSepolia = defineChain({
2345
2454
  fastConfirmations: 1,
2346
2455
  },
2347
2456
  },
2457
+ forwarderSupported: {
2458
+ source: true,
2459
+ destination: true,
2460
+ },
2348
2461
  },
2349
2462
  kitContracts: {
2350
2463
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2385,6 +2498,10 @@ const Plume = defineChain({
2385
2498
  fastConfirmations: 1,
2386
2499
  },
2387
2500
  },
2501
+ forwarderSupported: {
2502
+ source: true,
2503
+ destination: false,
2504
+ },
2388
2505
  },
2389
2506
  kitContracts: {
2390
2507
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2424,6 +2541,10 @@ const PlumeTestnet = defineChain({
2424
2541
  fastConfirmations: 1,
2425
2542
  },
2426
2543
  },
2544
+ forwarderSupported: {
2545
+ source: true,
2546
+ destination: false,
2547
+ },
2427
2548
  },
2428
2549
  kitContracts: {
2429
2550
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2514,6 +2635,10 @@ const Polygon = defineChain({
2514
2635
  fastConfirmations: 13,
2515
2636
  },
2516
2637
  },
2638
+ forwarderSupported: {
2639
+ source: true,
2640
+ destination: true,
2641
+ },
2517
2642
  },
2518
2643
  kitContracts: {
2519
2644
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2558,6 +2683,10 @@ const PolygonAmoy = defineChain({
2558
2683
  fastConfirmations: 13,
2559
2684
  },
2560
2685
  },
2686
+ forwarderSupported: {
2687
+ source: true,
2688
+ destination: true,
2689
+ },
2561
2690
  },
2562
2691
  kitContracts: {
2563
2692
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2598,6 +2727,10 @@ const Sei = defineChain({
2598
2727
  fastConfirmations: 1,
2599
2728
  },
2600
2729
  },
2730
+ forwarderSupported: {
2731
+ source: true,
2732
+ destination: true,
2733
+ },
2601
2734
  },
2602
2735
  kitContracts: {
2603
2736
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2637,6 +2770,10 @@ const SeiTestnet = defineChain({
2637
2770
  fastConfirmations: 1,
2638
2771
  },
2639
2772
  },
2773
+ forwarderSupported: {
2774
+ source: true,
2775
+ destination: true,
2776
+ },
2640
2777
  },
2641
2778
  kitContracts: {
2642
2779
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2675,6 +2812,10 @@ const Sonic = defineChain({
2675
2812
  fastConfirmations: 1,
2676
2813
  },
2677
2814
  },
2815
+ forwarderSupported: {
2816
+ source: true,
2817
+ destination: true,
2818
+ },
2678
2819
  },
2679
2820
  kitContracts: {
2680
2821
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2713,6 +2854,10 @@ const SonicTestnet = defineChain({
2713
2854
  fastConfirmations: 1,
2714
2855
  },
2715
2856
  },
2857
+ forwarderSupported: {
2858
+ source: true,
2859
+ destination: true,
2860
+ },
2716
2861
  },
2717
2862
  kitContracts: {
2718
2863
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2756,6 +2901,10 @@ const Solana = defineChain({
2756
2901
  fastConfirmations: 3,
2757
2902
  },
2758
2903
  },
2904
+ forwarderSupported: {
2905
+ source: true,
2906
+ destination: false,
2907
+ },
2759
2908
  },
2760
2909
  kitContracts: {
2761
2910
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
@@ -2798,6 +2947,10 @@ const SolanaDevnet = defineChain({
2798
2947
  fastConfirmations: 3,
2799
2948
  },
2800
2949
  },
2950
+ forwarderSupported: {
2951
+ source: true,
2952
+ destination: false,
2953
+ },
2801
2954
  },
2802
2955
  kitContracts: {
2803
2956
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
@@ -2881,6 +3034,10 @@ const Sui = defineChain({
2881
3034
  confirmations: 1,
2882
3035
  },
2883
3036
  },
3037
+ forwarderSupported: {
3038
+ source: false,
3039
+ destination: false,
3040
+ },
2884
3041
  },
2885
3042
  });
2886
3043
 
@@ -2914,6 +3071,10 @@ const SuiTestnet = defineChain({
2914
3071
  confirmations: 1,
2915
3072
  },
2916
3073
  },
3074
+ forwarderSupported: {
3075
+ source: false,
3076
+ destination: false,
3077
+ },
2917
3078
  },
2918
3079
  });
2919
3080
 
@@ -2935,7 +3096,7 @@ const Unichain = defineChain({
2935
3096
  chainId: 130,
2936
3097
  isTestnet: false,
2937
3098
  explorerUrl: 'https://unichain.blockscout.com/tx/{hash}',
2938
- rpcEndpoints: ['https://rpc.unichain.org', 'https://mainnet.unichain.org'],
3099
+ rpcEndpoints: ['https://mainnet.unichain.org'],
2939
3100
  eurcAddress: null,
2940
3101
  usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
2941
3102
  cctp: {
@@ -2955,6 +3116,10 @@ const Unichain = defineChain({
2955
3116
  fastConfirmations: 1,
2956
3117
  },
2957
3118
  },
3119
+ forwarderSupported: {
3120
+ source: true,
3121
+ destination: true,
3122
+ },
2958
3123
  },
2959
3124
  kitContracts: {
2960
3125
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2999,6 +3164,10 @@ const UnichainSepolia = defineChain({
2999
3164
  fastConfirmations: 1,
3000
3165
  },
3001
3166
  },
3167
+ forwarderSupported: {
3168
+ source: true,
3169
+ destination: true,
3170
+ },
3002
3171
  },
3003
3172
  kitContracts: {
3004
3173
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3025,7 +3194,7 @@ const WorldChain = defineChain({
3025
3194
  explorerUrl: 'https://worldscan.org/tx/{hash}',
3026
3195
  rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
3027
3196
  eurcAddress: null,
3028
- usdcAddress: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
3197
+ usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
3029
3198
  cctp: {
3030
3199
  domain: 14,
3031
3200
  contracts: {
@@ -3037,6 +3206,10 @@ const WorldChain = defineChain({
3037
3206
  fastConfirmations: 1,
3038
3207
  },
3039
3208
  },
3209
+ forwarderSupported: {
3210
+ source: true,
3211
+ destination: true,
3212
+ },
3040
3213
  },
3041
3214
  kitContracts: {
3042
3215
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -3078,6 +3251,10 @@ const WorldChainSepolia = defineChain({
3078
3251
  fastConfirmations: 1,
3079
3252
  },
3080
3253
  },
3254
+ forwarderSupported: {
3255
+ source: true,
3256
+ destination: true,
3257
+ },
3081
3258
  },
3082
3259
  kitContracts: {
3083
3260
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3104,7 +3281,7 @@ const XDC = defineChain({
3104
3281
  chainId: 50,
3105
3282
  isTestnet: false,
3106
3283
  explorerUrl: 'https://xdcscan.io/tx/{hash}',
3107
- rpcEndpoints: ['https://erpc.xinfin.network'],
3284
+ rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
3108
3285
  eurcAddress: null,
3109
3286
  usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
3110
3287
  cctp: {
@@ -3118,6 +3295,10 @@ const XDC = defineChain({
3118
3295
  fastConfirmations: 3,
3119
3296
  },
3120
3297
  },
3298
+ forwarderSupported: {
3299
+ source: true,
3300
+ destination: false,
3301
+ },
3121
3302
  },
3122
3303
  kitContracts: {
3123
3304
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -3156,6 +3337,10 @@ const XDCApothem = defineChain({
3156
3337
  fastConfirmations: 1,
3157
3338
  },
3158
3339
  },
3340
+ forwarderSupported: {
3341
+ source: true,
3342
+ destination: false,
3343
+ },
3159
3344
  },
3160
3345
  kitContracts: {
3161
3346
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3392,7 +3577,7 @@ const nonEvmChainDefinitionSchema = baseChainDefinitionSchema
3392
3577
  * })
3393
3578
  * ```
3394
3579
  */
3395
- const chainDefinitionSchema$1 = z.discriminatedUnion('type', [
3580
+ const chainDefinitionSchema$2 = z.discriminatedUnion('type', [
3396
3581
  evmChainDefinitionSchema,
3397
3582
  nonEvmChainDefinitionSchema,
3398
3583
  ]);
@@ -3417,7 +3602,7 @@ z.union([
3417
3602
  .string()
3418
3603
  .refine((val) => val in Blockchain, 'Must be a valid Blockchain enum value as string'),
3419
3604
  z.nativeEnum(Blockchain),
3420
- chainDefinitionSchema$1,
3605
+ chainDefinitionSchema$2,
3421
3606
  ]);
3422
3607
  /**
3423
3608
  * Zod schema for validating bridge chain identifiers.
@@ -3453,7 +3638,7 @@ const bridgeChainIdentifierSchema = z.union([
3453
3638
  z.string().refine((val) => val in BridgeChain, (val) => ({
3454
3639
  message: `Chain "${val}" is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3455
3640
  })),
3456
- chainDefinitionSchema$1.refine((chainDef) => chainDef.chain in BridgeChain, (chainDef) => ({
3641
+ chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in BridgeChain, (chainDef) => ({
3457
3642
  message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3458
3643
  })),
3459
3644
  ]);
@@ -3940,6 +4125,62 @@ function getErrorMessage(error) {
3940
4125
  function getErrorCode(error) {
3941
4126
  return isKitError(error) ? error.code : null;
3942
4127
  }
4128
+ /**
4129
+ * Extract structured error information for logging and events.
4130
+ *
4131
+ * @remarks
4132
+ * Safely extracts error information from any thrown value, handling:
4133
+ * - KitError objects (message preserved - safe to log)
4134
+ * - Standard Error objects (message redacted for security)
4135
+ * - Plain objects with name/message/code (message redacted)
4136
+ * - Primitive values (logged as-is since they contain no structured data)
4137
+ *
4138
+ * For security, only KitError messages are included. Other error messages are
4139
+ * redacted to prevent logging sensitive data like tokens or PII.
4140
+ *
4141
+ * @param error - The error to extract info from.
4142
+ * @returns Structured error information.
4143
+ *
4144
+ * @example
4145
+ * ```typescript
4146
+ * import { extractErrorInfo } from '@core/errors'
4147
+ *
4148
+ * // Standard Error - message is redacted for security
4149
+ * const info1 = extractErrorInfo(new Error('Something went wrong'))
4150
+ * // { name: 'Error', message: 'An error occurred. See error name and code for details.' }
4151
+ *
4152
+ * // KitError - message is preserved (safe type)
4153
+ * const error = createNetworkConnectionError('Ethereum')
4154
+ * const info2 = extractErrorInfo(error)
4155
+ * // { name: 'NETWORK_CONNECTION_FAILED', message: 'Network connection failed for Ethereum', code: 3001 }
4156
+ *
4157
+ * // Primitive value - logged as-is
4158
+ * const info3 = extractErrorInfo('string error')
4159
+ * // { name: 'UnknownError', message: 'string error' }
4160
+ * ```
4161
+ */
4162
+ function extractErrorInfo(error) {
4163
+ if (error === null || typeof error !== 'object') {
4164
+ return {
4165
+ name: 'UnknownError',
4166
+ message: String(error),
4167
+ };
4168
+ }
4169
+ const err = error;
4170
+ // Only preserve messages for KitError instances (safe type)
4171
+ // For other errors, redact message for security
4172
+ const errorMessage = isKitError(error)
4173
+ ? getErrorMessage(error)
4174
+ : 'An error occurred. See error name and code for details.';
4175
+ const info = {
4176
+ name: err.name ?? 'UnknownError',
4177
+ message: errorMessage,
4178
+ };
4179
+ if (typeof err.code === 'number') {
4180
+ info.code = err.code;
4181
+ }
4182
+ return info;
4183
+ }
3943
4184
 
3944
4185
  /**
3945
4186
  * Validates if an address format is correct for the specified chain.
@@ -4484,7 +4725,7 @@ function validateWithStateTracking(value, schema, context, validatorName) {
4484
4725
  * Zod schema for validating chain definition objects used in buildExplorerUrl.
4485
4726
  * This schema ensures the chain definition has the required properties for URL generation.
4486
4727
  */
4487
- const chainDefinitionSchema = z.object({
4728
+ const chainDefinitionSchema$1 = z.object({
4488
4729
  name: z
4489
4730
  .string({
4490
4731
  required_error: 'Chain name is required',
@@ -4508,15 +4749,14 @@ const transactionHashSchema = z
4508
4749
  required_error: 'Transaction hash is required',
4509
4750
  invalid_type_error: 'Transaction hash must be a string',
4510
4751
  })
4511
- .min(1, 'Transaction hash cannot be empty')
4512
- .transform((hash) => hash.trim()) // Automatically trim whitespace
4513
- .refine((hash) => hash.length > 0, 'Transaction hash must not be empty or whitespace-only');
4752
+ .transform((hash) => hash.trim())
4753
+ .refine((hash) => hash.length > 0, 'Transaction hash cannot be empty');
4514
4754
  /**
4515
4755
  * Zod schema for validating buildExplorerUrl function parameters.
4516
4756
  * This schema validates both the chain definition and transaction hash together.
4517
4757
  */
4518
4758
  z.object({
4519
- chainDef: chainDefinitionSchema,
4759
+ chainDef: chainDefinitionSchema$1,
4520
4760
  txHash: transactionHashSchema,
4521
4761
  });
4522
4762
  /**
@@ -4527,6 +4767,69 @@ z
4527
4767
  .string()
4528
4768
  .url('Generated explorer URL is invalid');
4529
4769
 
4770
+ /**
4771
+ * Parses and validates data against a Zod schema, returning the transformed result.
4772
+ *
4773
+ * Unlike `validate` and `validateOrThrow` which use type assertions (`asserts value is T`),
4774
+ * this function **returns the parsed data** from Zod. This is important when your schema
4775
+ * includes transformations like defaults, coercion, or custom transforms.
4776
+ *
4777
+ * @typeParam T - The expected output type (caller must specify).
4778
+ * @param value - The value to parse and validate.
4779
+ * @param schema - The Zod schema to validate against.
4780
+ * @param context - Context string to include in error messages (e.g., 'user input', 'config').
4781
+ * @returns The parsed and transformed data of type T.
4782
+ * @throws KitError with INPUT_VALIDATION_FAILED code (1098) if validation fails.
4783
+ *
4784
+ * @remarks
4785
+ * This function uses `ZodTypeAny` for the schema parameter to avoid TypeScript's
4786
+ * "excessively deep type instantiation" error in generic contexts. The caller
4787
+ * must provide the expected type `T` explicitly.
4788
+ *
4789
+ * Use this instead of `validate`/`validateOrThrow` when:
4790
+ * - Your schema has `.default()` values
4791
+ * - Your schema uses `.coerce` (e.g., `z.coerce.number()`)
4792
+ * - Your schema has `.transform()` functions
4793
+ * - You need the actual parsed output, not just type narrowing
4794
+ *
4795
+ * @example
4796
+ * ```typescript
4797
+ * import { parseOrThrow } from '@core/utils'
4798
+ * import { z } from 'zod'
4799
+ *
4800
+ * const userSchema = z.object({
4801
+ * name: z.string().default('Anonymous'),
4802
+ * age: z.coerce.number(), // Transforms "25" → 25
4803
+ * })
4804
+ *
4805
+ * type User = z.infer<typeof userSchema>
4806
+ *
4807
+ * // Explicit type parameter
4808
+ * const user = parseOrThrow<User>({ age: '25' }, userSchema, 'user data')
4809
+ * console.log(user) // { name: 'Anonymous', age: 25 }
4810
+ * ```
4811
+ *
4812
+ * @example
4813
+ * ```typescript
4814
+ * // Usage in generic adapter functions (no deep type instantiation)
4815
+ * function validateInput<TInput>(
4816
+ * input: unknown,
4817
+ * schema: z.ZodTypeAny,
4818
+ * name: string,
4819
+ * ): TInput {
4820
+ * return parseOrThrow<TInput>(input, schema, `${name} input`)
4821
+ * }
4822
+ * ```
4823
+ */
4824
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Intentional: T is caller-provided to avoid deep type instantiation from schema inference
4825
+ function parseOrThrow(value, schema, context) {
4826
+ const result = schema.safeParse(value);
4827
+ if (!result.success) {
4828
+ throw createValidationErrorFromZod(result.error, context);
4829
+ }
4830
+ return result.data;
4831
+ }
4832
+
4530
4833
  /**
4531
4834
  * A type-safe event emitter for managing action-based event subscriptions.
4532
4835
  *
@@ -4801,7 +5104,7 @@ const parseAmount = (params) => {
4801
5104
  };
4802
5105
 
4803
5106
  var name = "@circle-fin/bridge-kit";
4804
- var version = "1.5.0";
5107
+ var version = "1.6.0";
4805
5108
  var pkg = {
4806
5109
  name: name,
4807
5110
  version: version};
@@ -5227,7 +5530,7 @@ const createDecimalStringValidator = (options) => (schema) => {
5227
5530
  * console.log(result.success) // true
5228
5531
  * ```
5229
5532
  */
5230
- z.object({
5533
+ const chainDefinitionSchema = z.object({
5231
5534
  name: z.string().min(1, 'Chain name is required'),
5232
5535
  type: z.string().min(1, 'Chain type is required'),
5233
5536
  });
@@ -5274,6 +5577,53 @@ const walletContextSchema = z.object({
5274
5577
  isTestnet: z.boolean(),
5275
5578
  }),
5276
5579
  });
5580
+ /**
5581
+ * Schema for validating destination wallet contexts.
5582
+ * Extends walletContextSchema with optional useForwarder flag.
5583
+ *
5584
+ * @throws KitError if validation fails
5585
+ */
5586
+ const destinationContextSchema = walletContextSchema.extend({
5587
+ useForwarder: z.boolean().optional(),
5588
+ recipientAddress: z.string().optional(),
5589
+ });
5590
+ /**
5591
+ * Schema for validating forwarder-only destination contexts.
5592
+ * Used when useForwarder is true and no adapter is provided.
5593
+ * Requires chain definition and recipientAddress.
5594
+ *
5595
+ * Validates that recipientAddress format is correct for the destination chain
5596
+ * (EVM hex address or Solana base58 address).
5597
+ *
5598
+ * @throws KitError if validation fails
5599
+ */
5600
+ const forwarderOnlyDestinationSchema = z
5601
+ .object({
5602
+ chain: chainDefinitionSchema.extend({
5603
+ isTestnet: z.boolean(),
5604
+ }),
5605
+ recipientAddress: z
5606
+ .string()
5607
+ .min(1, 'Recipient address is required for forwarder-only destination'),
5608
+ useForwarder: z.literal(true),
5609
+ })
5610
+ .superRefine((data, ctx) => {
5611
+ // Pass chain name as string - isValidAddressForChain will use name-based matching
5612
+ if (!isValidAddressForChain(data.recipientAddress, data.chain.name)) {
5613
+ ctx.addIssue({
5614
+ code: z.ZodIssueCode.custom,
5615
+ message: `Invalid recipient address format for chain ${data.chain.name}`,
5616
+ path: ['recipientAddress'],
5617
+ });
5618
+ }
5619
+ });
5620
+ /**
5621
+ * Schema for validating any destination - either with adapter or forwarder-only.
5622
+ */
5623
+ const bridgeDestinationSchema$1 = z.union([
5624
+ destinationContextSchema,
5625
+ forwarderOnlyDestinationSchema,
5626
+ ]);
5277
5627
  /**
5278
5628
  * Schema for validating a custom fee configuration.
5279
5629
  * Validates the simplified CustomFee interface which includes:
@@ -5371,7 +5721,7 @@ z.object({
5371
5721
  maxDecimals: 6,
5372
5722
  })(z.string())),
5373
5723
  source: walletContextSchema,
5374
- destination: walletContextSchema,
5724
+ destination: bridgeDestinationSchema$1,
5375
5725
  token: z.literal('USDC'),
5376
5726
  config: z.object({
5377
5727
  transferSpeed: z.nativeEnum(TransferSpeed).optional(),
@@ -5388,6 +5738,28 @@ z.object({
5388
5738
  }),
5389
5739
  });
5390
5740
 
5741
+ /**
5742
+ * Creates a Zod superRefine validator for recipient address format validation.
5743
+ * Validates that the address format matches the expected format for the chain type.
5744
+ *
5745
+ * @returns A superRefine function that validates recipientAddress against chain type
5746
+ */
5747
+ function createRecipientAddressValidator() {
5748
+ return (data, ctx) => {
5749
+ const chain = data.chain;
5750
+ if (chain === null) {
5751
+ return;
5752
+ }
5753
+ if (!isValidAddressForChain(data.recipientAddress, chain)) {
5754
+ const chainInfo = extractChainInfo(chain);
5755
+ ctx.addIssue({
5756
+ code: z.ZodIssueCode.custom,
5757
+ path: ['recipientAddress'],
5758
+ message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
5759
+ });
5760
+ }
5761
+ };
5762
+ }
5391
5763
  /**
5392
5764
  * Schema for validating AdapterContext for bridge operations.
5393
5765
  * Must always contain both adapter and chain explicitly.
@@ -5407,32 +5779,52 @@ const adapterContextSchema = z.object({
5407
5779
  const bridgeDestinationWithAddressSchema = adapterContextSchema
5408
5780
  .extend({
5409
5781
  recipientAddress: z.string().min(1, 'Recipient address is required'),
5782
+ useForwarder: z.boolean().optional(),
5410
5783
  })
5411
- .superRefine((data, ctx) => {
5412
- const chain = data.chain;
5413
- if (chain === null) {
5414
- return;
5415
- }
5416
- if (!isValidAddressForChain(data.recipientAddress, chain)) {
5417
- const chainInfo = extractChainInfo(chain);
5418
- ctx.addIssue({
5419
- code: z.ZodIssueCode.custom,
5420
- path: ['recipientAddress'],
5421
- message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
5422
- });
5423
- }
5784
+ .superRefine(createRecipientAddressValidator());
5785
+ /**
5786
+ * Schema for validating AdapterContext with optional useForwarder.
5787
+ * Extends adapterContextSchema with the useForwarder flag.
5788
+ */
5789
+ const adapterContextWithForwarderSchema = adapterContextSchema.extend({
5790
+ useForwarder: z.boolean().optional(),
5424
5791
  });
5792
+ /**
5793
+ * Schema for validating ForwarderDestination objects.
5794
+ * Used when useForwarder is true and no adapter is provided.
5795
+ * Requires chain, recipientAddress, and useForwarder: true.
5796
+ *
5797
+ * When using this destination type:
5798
+ * - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
5799
+ * - No on-chain transaction confirmation is performed (no adapter available)
5800
+ * - The mint step's data field will be undefined (no transaction receipt)
5801
+ */
5802
+ const forwarderDestinationSchema = z
5803
+ .object({
5804
+ chain: bridgeChainIdentifierSchema,
5805
+ recipientAddress: z.string().min(1, 'Recipient address is required'),
5806
+ useForwarder: z.literal(true),
5807
+ })
5808
+ .strict()
5809
+ .superRefine(createRecipientAddressValidator());
5425
5810
  /**
5426
5811
  * Schema for validating BridgeDestination union type.
5427
- * Can be an AdapterContext or BridgeDestinationWithAddress.
5812
+ * Supports three destination configurations:
5813
+ * - BridgeDestinationWithAddress (adapter with explicit recipient)
5814
+ * - ForwarderDestination (no adapter, requires useForwarder: true and recipientAddress)
5815
+ * - AdapterContext with optional useForwarder (adapter for default recipient)
5816
+ *
5817
+ * When using ForwarderDestination (no adapter):
5818
+ * - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
5819
+ * - No on-chain transaction confirmation is performed
5428
5820
  *
5429
- * The order matters: we check the more specific schema (with recipientAddress) first.
5430
- * This ensures that objects with an empty recipientAddress field are rejected rather
5431
- * than silently treated as AdapterContext with the field ignored.
5821
+ * The order matters: we check the more specific schemas first.
5822
+ * This ensures that objects with specific fields are matched correctly.
5432
5823
  */
5433
5824
  const bridgeDestinationSchema = z.union([
5434
5825
  bridgeDestinationWithAddressSchema,
5435
- adapterContextSchema.strict(),
5826
+ forwarderDestinationSchema,
5827
+ adapterContextWithForwarderSchema.strict(),
5436
5828
  ]);
5437
5829
  /**
5438
5830
  * Schema for validating bridge parameters with chain identifiers.
@@ -5506,121 +5898,1413 @@ const bridgeParamsWithChainIdentifierSchema = z.object({
5506
5898
  });
5507
5899
 
5508
5900
  /**
5509
- * Resolves a chain identifier to a chain definition.
5510
- *
5511
- * Both AdapterContext and BridgeDestinationWithAddress have the chain property
5512
- * at the top level, so we can directly access it from either type.
5901
+ * Default clock implementation using `Date.now()`.
5513
5902
  *
5514
- * @param ctx - The bridge destination containing the chain identifier
5515
- * @returns The resolved chain definition
5516
- * @throws If the chain definition cannot be resolved
5903
+ * @remarks
5904
+ * Use this in production code. For testing, inject a mock clock
5905
+ * that returns controlled timestamps.
5517
5906
  *
5518
5907
  * @example
5519
5908
  * ```typescript
5520
- * import { Blockchain } from '@core/chains'
5521
- *
5522
- * // AdapterContext
5523
- * const chain1 = resolveChainDefinition({
5524
- * adapter: mockAdapter,
5525
- * chain: 'Ethereum'
5526
- * })
5909
+ * import { defaultClock } from '@core/runtime'
5527
5910
  *
5528
- * // BridgeDestinationWithAddress
5529
- * const chain2 = resolveChainDefinition({
5530
- * adapter: mockAdapter,
5531
- * chain: 'Base',
5532
- * recipientAddress: '0x123...'
5533
- * })
5911
+ * const start = defaultClock.now()
5912
+ * // ... do work ...
5913
+ * const elapsed = defaultClock.since(start)
5534
5914
  * ```
5535
5915
  */
5536
- function resolveChainDefinition(ctx) {
5537
- return resolveChainIdentifier(ctx.chain);
5538
- }
5916
+ const defaultClock = {
5917
+ now: () => Date.now(),
5918
+ since: (start) => Date.now() - start,
5919
+ };
5920
+
5539
5921
  /**
5540
- * Resolves the signer's address from a bridge destination.
5922
+ * ID generation utilities for distributed tracing.
5541
5923
  *
5542
- * This function resolves the address that will be used for transaction signing,
5543
- * ignoring any `recipientAddress` field which is handled separately.
5924
+ * @remarks
5925
+ * | Function | Format | Use Case |
5926
+ * |----------|--------|----------|
5927
+ * | {@link createTraceId} | 32-char hex | Standard traceId (OpenTelemetry) |
5928
+ * | {@link createShortOpId} | 8-char hex | Standard opId |
5929
+ * | {@link createOpId} | `op-{ts}-{rand}` | Timestamped operation IDs |
5930
+ * | {@link createUuidV4} | RFC 4122 UUID | External system compatibility |
5931
+ * | {@link createId} | `{prefix}-{ts}-{rand}` | Custom prefixed IDs |
5544
5932
  *
5545
- * It handles two cases:
5546
- * - Developer-controlled adapters - returns the explicit address from context
5547
- * - User-controlled adapters - calls getAddress() on the adapter
5933
+ * @packageDocumentation
5934
+ */
5935
+ // ============================================================================
5936
+ // Crypto Utilities (Internal)
5937
+ // ============================================================================
5938
+ /** @internal */
5939
+ function hasGetRandomValues() {
5940
+ return (typeof crypto !== 'undefined' &&
5941
+ typeof crypto.getRandomValues === 'function');
5942
+ }
5943
+ /**
5944
+ * Create a W3C/OpenTelemetry-compatible trace ID.
5548
5945
  *
5549
- * @param ctx - The bridge destination to resolve the address from
5550
- * @returns The resolved signer address string
5946
+ * @returns 32-character lowercase hex string (128-bit).
5947
+ *
5948
+ * @remarks
5949
+ * **Standard function for generating `traceId` values.** Compatible with
5950
+ * OpenTelemetry, Jaeger, Zipkin, and AWS X-Ray.
5551
5951
  *
5552
5952
  * @example
5553
5953
  * ```typescript
5554
- * // Developer-controlled adapter
5555
- * const addr1 = await resolveAddress({
5556
- * adapter: devAdapter,
5557
- * chain: 'Ethereum',
5558
- * address: '0x1234567890123456789012345678901234567890'
5559
- * }) // Returns: '0x1234567890123456789012345678901234567890'
5560
- *
5561
- * // User-controlled adapter
5562
- * const addr2 = await resolveAddress({
5563
- * adapter: userAdapter,
5564
- * chain: 'Ethereum'
5565
- * }) // Returns adapter's connected address
5954
+ * const traceId = createTraceId() // "a1b2c3d4e5f6789012345678abcdef00"
5566
5955
  * ```
5567
5956
  */
5568
- async function resolveAddress(ctx) {
5569
- // Handle based on adapter's addressContext
5570
- if (ctx.adapter.capabilities?.addressContext === 'developer-controlled') {
5571
- // Developer-controlled: address must be provided explicitly
5572
- if ('address' in ctx && ctx.address) {
5573
- return ctx.address;
5574
- }
5575
- throw new Error('Address is required in context for developer-controlled adapters. ' +
5576
- 'Please provide: { adapter, chain, address: "0x..." }');
5957
+ function createTraceId() {
5958
+ const bytes = new Uint8Array(16);
5959
+ if (hasGetRandomValues()) {
5960
+ crypto.getRandomValues(bytes);
5577
5961
  }
5578
5962
  else {
5579
- // User-controlled: address should not be provided (auto-resolved from adapter)
5580
- if ('address' in ctx && ctx.address) {
5581
- throw new Error('Address should not be provided for user-controlled adapters. ' +
5582
- 'The address is automatically resolved from the connected wallet.');
5963
+ for (let i = 0; i < 16; i++) {
5964
+ bytes[i] = Math.floor(Math.random() * 256); // NOSONAR:
5583
5965
  }
5584
- // Derive address from adapter
5585
- const chain = resolveChainDefinition(ctx);
5586
- return await ctx.adapter.getAddress(chain);
5587
5966
  }
5967
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
5588
5968
  }
5969
+
5589
5970
  /**
5590
- * Resolves the amount of a transfer by formatting it according to the token's decimal places.
5971
+ * Clean tags by removing keys with undefined values.
5591
5972
  *
5592
- * This function takes the raw amount from the transfer parameters and formats it
5593
- * using the appropriate decimal places for the specified token. Currently supports
5594
- * USDC (6 decimals) and falls back to the raw amount for other tokens.
5973
+ * @param tags - Tags object, may contain undefined values. Accepts `undefined` or `null`.
5974
+ * @returns A new object with undefined values removed, or empty object if input is nullish.
5595
5975
  *
5596
- * @param params - The bridge parameters containing the amount, token type, and from context
5597
- * @returns The formatted amount string with proper decimal places
5976
+ * @remarks
5977
+ * This helper ensures tags are safe for serialization and logging.
5978
+ * Similar to the internal `cleanUndefined` in the logger module, but
5979
+ * typed specifically for {@link Tags}.
5598
5980
  *
5599
5981
  * @example
5600
5982
  * ```typescript
5601
- * import { Adapter } from '@core/adapter'
5983
+ * import { cleanTags } from '@core/runtime'
5602
5984
  *
5603
- * const params = {
5604
- * amount: '1000000',
5605
- * token: 'USDC',
5606
- * from: { adapter: mockAdapter, chain: Ethereum },
5607
- * to: { adapter: mockAdapter, chain: Base }
5608
- * }
5609
- * const formattedAmount = resolveAmount(params) // Returns '1000000000000'
5985
+ * const tags = { chain: 'Ethereum', amount: 100, extra: undefined }
5986
+ * const cleaned = cleanTags(tags)
5987
+ * // { chain: 'Ethereum', amount: 100 }
5988
+ *
5989
+ * const empty = cleanTags(undefined)
5990
+ * // {}
5610
5991
  * ```
5611
5992
  */
5612
- function resolveAmount(params) {
5613
- if (params.token === 'USDC') {
5614
- return parseUnits(params.amount, 6).toString();
5993
+ function cleanTags(tags) {
5994
+ if (tags == null)
5995
+ return {};
5996
+ return Object.fromEntries(Object.entries(tags).filter(([, value]) => value !== undefined));
5997
+ }
5998
+
5999
+ /**
6000
+ * Check if a pattern is valid.
6001
+ *
6002
+ * @remarks
6003
+ * Invalid patterns:
6004
+ * - Empty segments (e.g., `a..b`, `.a`, `a.`)
6005
+ * - `**` not at end (e.g., `a.**.b`)
6006
+ *
6007
+ * @param pattern - The pattern to validate.
6008
+ * @returns True if valid, false otherwise.
6009
+ */
6010
+ function isValidPattern(pattern) {
6011
+ // Empty pattern is valid (matches empty topic)
6012
+ if (pattern === '')
6013
+ return true;
6014
+ const segments = pattern.split('.');
6015
+ // Check for empty segments
6016
+ if (segments.includes(''))
6017
+ return false;
6018
+ // Check that ** only appears at the end
6019
+ const doubleStarIndex = segments.indexOf('**');
6020
+ if (doubleStarIndex !== -1 && doubleStarIndex !== segments.length - 1) {
6021
+ return false;
5615
6022
  }
5616
- return params.amount;
6023
+ return true;
5617
6024
  }
5618
6025
  /**
5619
- * Resolves and normalizes bridge configuration for the provider.
6026
+ * Convert a pattern to a regular expression.
6027
+ *
6028
+ * @param pattern - The pattern to convert.
6029
+ * @returns A RegExp that matches topics according to the pattern.
6030
+ */
6031
+ function patternToRegex(pattern) {
6032
+ const segments = pattern.split('.');
6033
+ // Handle ** at end specially to match zero or more segments
6034
+ const lastSegment = segments.at(-1);
6035
+ if (lastSegment === '**') {
6036
+ // Everything before ** must match, then optionally more segments
6037
+ const prefix = segments
6038
+ .slice(0, -1)
6039
+ .map((seg) => {
6040
+ if (seg === '*')
6041
+ return '[^.]+';
6042
+ return seg.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
6043
+ })
6044
+ .join(String.raw `\.`);
6045
+ // ** matches zero or more segments:
6046
+ // - If prefix is empty (**), match anything
6047
+ // - Otherwise, match prefix, then optionally (dot + more content)
6048
+ if (prefix === '') {
6049
+ return /^.*$/;
6050
+ }
6051
+ return new RegExp(String.raw `^${prefix}(?:\..*)?$`);
6052
+ }
6053
+ // No ** - just convert segments normally
6054
+ const escaped = segments
6055
+ .map((segment) => {
6056
+ if (segment === '*')
6057
+ return '[^.]+';
6058
+ return segment.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
6059
+ })
6060
+ .join(String.raw `\.`);
6061
+ return new RegExp(String.raw `^${escaped}$`);
6062
+ }
6063
+ /**
6064
+ * Execute an event handler with error isolation.
5620
6065
  *
5621
- * This function takes the optional configuration from bridge parameters and returns
5622
- * a normalized BridgeConfig with:
5623
- * - Default transfer speed set to FAST if not provided
6066
+ * @param handler - The handler function to execute.
6067
+ * @param event - The event to pass to the handler.
6068
+ * @param logger - Optional logger for error reporting.
6069
+ *
6070
+ * @remarks
6071
+ * - Synchronous errors are caught and logged
6072
+ * - Promise rejections are caught without awaiting
6073
+ * - Handler errors never propagate to caller
6074
+ */
6075
+ function executeHandler(handler, event, logger) {
6076
+ try {
6077
+ const result = handler(event);
6078
+ // Handle promise rejection without awaiting
6079
+ if (result instanceof Promise) {
6080
+ result.catch((err) => {
6081
+ logger?.error('Event handler promise rejected', {
6082
+ event: event.name,
6083
+ error: extractErrorInfo(err),
6084
+ });
6085
+ });
6086
+ }
6087
+ }
6088
+ catch (err) {
6089
+ // Isolate handler errors
6090
+ logger?.error('Event handler threw', {
6091
+ event: event.name,
6092
+ error: extractErrorInfo(err),
6093
+ });
6094
+ }
6095
+ }
6096
+ /**
6097
+ * Create an event bus.
6098
+ *
6099
+ * @param opts - Options for the event bus.
6100
+ * @returns An EventBus instance.
6101
+ *
6102
+ * @example
6103
+ * ```typescript
6104
+ * import { createEventBus } from '@core/runtime'
6105
+ *
6106
+ * const bus = createEventBus({ logger })
6107
+ *
6108
+ * // Subscribe to events
6109
+ * const unsubscribe = bus.on('tx.*', (event) => {
6110
+ * console.log('Transaction event:', event.name)
6111
+ * })
6112
+ *
6113
+ * // Emit events
6114
+ * bus.emit({ name: 'tx.sent', data: { txHash: '0x123' } })
6115
+ *
6116
+ * // Unsubscribe
6117
+ * unsubscribe()
6118
+ * ```
6119
+ */
6120
+ function createEventBus(opts) {
6121
+ const logger = opts?.logger;
6122
+ const subscriptions = new Set();
6123
+ function createBus(baseTags) {
6124
+ return {
6125
+ emit(event) {
6126
+ // Invalid topic names silently don't match any subscriptions
6127
+ if (!isValidPattern(event.name))
6128
+ return;
6129
+ const mergedTags = Object.keys(baseTags).length > 0 || event.tags
6130
+ ? { ...baseTags, ...cleanTags(event.tags) }
6131
+ : undefined;
6132
+ const finalEvent = mergedTags === undefined ? event : { ...event, tags: mergedTags };
6133
+ // Notify all matching subscribers
6134
+ for (const sub of subscriptions) {
6135
+ // Match-all subscription
6136
+ if (sub.pattern === null) {
6137
+ executeHandler(sub.handler, finalEvent, logger);
6138
+ continue;
6139
+ }
6140
+ // Patterned subscription: invalid pattern => regex=null => never match
6141
+ if (sub.regex === null)
6142
+ continue;
6143
+ if (!sub.regex.test(event.name))
6144
+ continue;
6145
+ executeHandler(sub.handler, finalEvent, logger);
6146
+ }
6147
+ },
6148
+ child(tags) {
6149
+ // Merge and clean tags, don't mutate parent
6150
+ const childTags = { ...baseTags, ...cleanTags(tags) };
6151
+ return createBus(childTags);
6152
+ },
6153
+ on(patternOrHandler, handler) {
6154
+ let sub;
6155
+ if (typeof patternOrHandler === 'function') {
6156
+ // on(handler) - subscribe to all
6157
+ sub = { pattern: null, handler: patternOrHandler, regex: null };
6158
+ }
6159
+ else {
6160
+ // on(pattern, handler)
6161
+ if (typeof handler !== 'function') {
6162
+ throw new TypeError(`EventBus.on("${patternOrHandler}") expected a function handler`);
6163
+ }
6164
+ const regex = isValidPattern(patternOrHandler)
6165
+ ? patternToRegex(patternOrHandler)
6166
+ : null;
6167
+ sub = {
6168
+ pattern: patternOrHandler,
6169
+ handler,
6170
+ regex,
6171
+ };
6172
+ }
6173
+ subscriptions.add(sub);
6174
+ // Return unsubscribe function (idempotent)
6175
+ let unsubscribed = false;
6176
+ return () => {
6177
+ if (!unsubscribed) {
6178
+ subscriptions.delete(sub);
6179
+ unsubscribed = true;
6180
+ }
6181
+ };
6182
+ },
6183
+ };
6184
+ }
6185
+ return createBus({});
6186
+ }
6187
+
6188
+ /** No-op counter that discards all increments. */
6189
+ const noopCounter = {
6190
+ inc: () => {
6191
+ // Intentionally empty - discards increment
6192
+ },
6193
+ };
6194
+ /** No-op histogram that discards all observations. */
6195
+ const noopHistogram = {
6196
+ observe: () => {
6197
+ // Intentionally empty - discards observation
6198
+ },
6199
+ };
6200
+ /** No-op timer that returns an empty stop function. */
6201
+ const noopTimer = {
6202
+ start: () => () => {
6203
+ // Intentionally empty - discards timing
6204
+ },
6205
+ };
6206
+ /**
6207
+ * Create a no-op metrics instance.
6208
+ *
6209
+ * @returns A Metrics instance that discards all operations.
6210
+ */
6211
+ function createNoopMetrics() {
6212
+ // Self-referential instance - child() returns same object to avoid allocations
6213
+ const instance = {
6214
+ counter: () => noopCounter,
6215
+ histogram: () => noopHistogram,
6216
+ timer: () => noopTimer,
6217
+ child: () => instance,
6218
+ };
6219
+ return instance;
6220
+ }
6221
+ /**
6222
+ * A no-op metrics implementation that discards all operations.
6223
+ *
6224
+ * @remarks
6225
+ * Use this when metrics collection is disabled or not configured.
6226
+ * All metric operations are safe to call and return immediately.
6227
+ * The `child()` method returns the same instance to avoid allocations.
6228
+ *
6229
+ * @example
6230
+ * ```typescript
6231
+ * import { noopMetrics } from '@core/runtime'
6232
+ *
6233
+ * // Use when metrics are disabled
6234
+ * const metrics = config.metricsEnabled
6235
+ * ? createDatadogMetrics(config)
6236
+ * : noopMetrics
6237
+ *
6238
+ * // All operations are safe to call
6239
+ * metrics.counter('requests').inc()
6240
+ * const stop = metrics.timer('query').start()
6241
+ * stop()
6242
+ * ```
6243
+ */
6244
+ const noopMetrics = createNoopMetrics();
6245
+
6246
+ /**
6247
+ * Define a schema for runtime logger interfaces.
6248
+ *
6249
+ * @remarks
6250
+ * Validate that a runtime logger provides the minimal methods expected by the SDK.
6251
+ *
6252
+ * @example
6253
+ * ```typescript
6254
+ * import { loggerSchema } from '@core/runtime'
6255
+ *
6256
+ * const logger = {
6257
+ * debug: () => undefined,
6258
+ * info: () => undefined,
6259
+ * warn: () => undefined,
6260
+ * error: () => undefined,
6261
+ * child: () => logger,
6262
+ * }
6263
+ *
6264
+ * loggerSchema.parse(logger)
6265
+ * ```
6266
+ */
6267
+ const loggerSchema = z.custom((value) => {
6268
+ if (value === null || typeof value !== 'object') {
6269
+ return false;
6270
+ }
6271
+ const record = value;
6272
+ return (typeof record['debug'] === 'function' &&
6273
+ typeof record['info'] === 'function' &&
6274
+ typeof record['warn'] === 'function' &&
6275
+ typeof record['error'] === 'function' &&
6276
+ typeof record['child'] === 'function');
6277
+ }, {
6278
+ message: 'Invalid logger',
6279
+ });
6280
+
6281
+ /**
6282
+ * Define a schema for runtime metrics interfaces.
6283
+ *
6284
+ * @remarks
6285
+ * Validate that a metrics implementation exposes the minimal API expected by the runtime.
6286
+ *
6287
+ * @example
6288
+ * ```typescript
6289
+ * import { metricsSchema } from '@core/runtime'
6290
+ *
6291
+ * const metrics = {
6292
+ * counter: () => ({ inc: () => undefined }),
6293
+ * histogram: () => ({ observe: () => undefined }),
6294
+ * timer: () => ({ start: () => () => undefined }),
6295
+ * child: () => metrics,
6296
+ * }
6297
+ *
6298
+ * metricsSchema.parse(metrics)
6299
+ * ```
6300
+ */
6301
+ const metricsSchema = z.custom((value) => {
6302
+ if (value === null || typeof value !== 'object') {
6303
+ return false;
6304
+ }
6305
+ const record = value;
6306
+ return (typeof record['counter'] === 'function' &&
6307
+ typeof record['histogram'] === 'function' &&
6308
+ typeof record['timer'] === 'function' &&
6309
+ typeof record['child'] === 'function');
6310
+ }, {
6311
+ message: 'Invalid metrics',
6312
+ });
6313
+
6314
+ /**
6315
+ * Omit undefined values from an object.
6316
+ *
6317
+ * @param obj - The object to process.
6318
+ * @returns A new object with undefined values removed.
6319
+ *
6320
+ * @internal
6321
+ * @remarks
6322
+ * Used by both production and mock loggers to ensure consistent behavior.
6323
+ * This prevents undefined values from being serialized in log output,
6324
+ * which can cause issues with some log transports.
6325
+ */
6326
+ function omitUndefined(obj) {
6327
+ const result = {};
6328
+ for (const [key, value] of Object.entries(obj)) {
6329
+ if (value !== undefined) {
6330
+ result[key] = value;
6331
+ }
6332
+ }
6333
+ return result;
6334
+ }
6335
+
6336
+ /**
6337
+ * Default redaction paths for web3/blockchain SDKs.
6338
+ *
6339
+ * @remarks
6340
+ * These paths target common sensitive fields in blockchain applications.
6341
+ * All user fields are nested under `context`, so paths start with `context.`.
6342
+ * Wildcard `*` matches any key at that level.
6343
+ */
6344
+ const DEFAULT_REDACT_PATHS = [
6345
+ // Generic Credentials
6346
+ 'context.password',
6347
+ 'context.passphrase',
6348
+ 'context.secret',
6349
+ 'context.token',
6350
+ 'context.*.password',
6351
+ 'context.*.passphrase',
6352
+ 'context.*.secret',
6353
+ 'context.*.token',
6354
+ // API Keys & Auth Tokens
6355
+ 'context.apiKey',
6356
+ 'context.apiSecret',
6357
+ 'context.accessToken',
6358
+ 'context.refreshToken',
6359
+ 'context.jwt',
6360
+ 'context.bearerToken',
6361
+ 'context.sessionId',
6362
+ 'context.authorization',
6363
+ 'context.cookie',
6364
+ 'context.*.apiKey',
6365
+ 'context.*.apiSecret',
6366
+ 'context.*.accessToken',
6367
+ 'context.*.refreshToken',
6368
+ 'context.*.jwt',
6369
+ 'context.*.bearerToken',
6370
+ 'context.*.sessionId',
6371
+ 'context.*.authorization',
6372
+ 'context.*.cookie',
6373
+ // Web3 / Crypto Keys
6374
+ 'context.privateKey',
6375
+ 'context.secretKey',
6376
+ 'context.signingKey',
6377
+ 'context.encryptionKey',
6378
+ 'context.*.privateKey',
6379
+ 'context.*.secretKey',
6380
+ 'context.*.signingKey',
6381
+ 'context.*.encryptionKey',
6382
+ // Web3 / Crypto Mnemonics and Seeds
6383
+ 'context.mnemonic',
6384
+ 'context.seed',
6385
+ 'context.seedPhrase',
6386
+ 'context.*.mnemonic',
6387
+ 'context.*.seed',
6388
+ 'context.*.seedPhrase',
6389
+ // OTP / Verification Codes
6390
+ 'context.otp',
6391
+ 'context.verificationCode',
6392
+ 'context.*.otp',
6393
+ 'context.*.verificationCode',
6394
+ // Payment Information
6395
+ 'context.cardNumber',
6396
+ 'context.cvv',
6397
+ 'context.accountNumber',
6398
+ 'context.*.cardNumber',
6399
+ 'context.*.cvv',
6400
+ 'context.*.accountNumber',
6401
+ ];
6402
+ /**
6403
+ * Wrap user fields under `context` to prevent collision with pino internals.
6404
+ *
6405
+ * @param fields - User-provided log fields.
6406
+ * @returns Object with fields nested under `context`, or undefined if empty.
6407
+ *
6408
+ * @remarks
6409
+ * This function handles edge cases by returning undefined for null, undefined,
6410
+ * or empty objects to avoid unnecessary wrapping in log output.
6411
+ * Undefined values are cleaned before wrapping.
6412
+ */
6413
+ function wrapInContext(fields) {
6414
+ if (!fields)
6415
+ return undefined;
6416
+ // Clean undefined values for consistency and transport compatibility
6417
+ const cleaned = omitUndefined(fields);
6418
+ // Handle edge case: all values were undefined, resulting in empty object
6419
+ const keys = Object.keys(cleaned);
6420
+ if (keys.length === 0)
6421
+ return undefined;
6422
+ return { context: cleaned };
6423
+ }
6424
+ /**
6425
+ * Wrap a pino instance to conform to our Logger interface.
6426
+ *
6427
+ * @param pinoInstance - The pino logger instance to wrap.
6428
+ * @returns A Logger instance conforming to our stable interface.
6429
+ */
6430
+ function wrapPino(pinoInstance) {
6431
+ return {
6432
+ debug(message, fields) {
6433
+ const wrapped = wrapInContext(fields);
6434
+ if (wrapped) {
6435
+ pinoInstance.debug(wrapped, message);
6436
+ }
6437
+ else {
6438
+ pinoInstance.debug(message);
6439
+ }
6440
+ },
6441
+ info(message, fields) {
6442
+ const wrapped = wrapInContext(fields);
6443
+ if (wrapped) {
6444
+ pinoInstance.info(wrapped, message);
6445
+ }
6446
+ else {
6447
+ pinoInstance.info(message);
6448
+ }
6449
+ },
6450
+ warn(message, fields) {
6451
+ const wrapped = wrapInContext(fields);
6452
+ if (wrapped) {
6453
+ pinoInstance.warn(wrapped, message);
6454
+ }
6455
+ else {
6456
+ pinoInstance.warn(message);
6457
+ }
6458
+ },
6459
+ error(message, fields) {
6460
+ const wrapped = wrapInContext(fields);
6461
+ if (wrapped) {
6462
+ pinoInstance.error(wrapped, message);
6463
+ }
6464
+ else {
6465
+ pinoInstance.error(message);
6466
+ }
6467
+ },
6468
+ child(tags) {
6469
+ // Child bindings stay flat (not wrapped) - they're part of logger's base context
6470
+ const cleaned = omitUndefined(tags);
6471
+ return wrapPino(pinoInstance.child(cleaned));
6472
+ },
6473
+ };
6474
+ }
6475
+ /**
6476
+ * Build pino redact configuration from our simplified options.
6477
+ *
6478
+ * @param redact - The redact configuration option.
6479
+ * @returns Pino-compatible redact configuration or undefined.
6480
+ */
6481
+ function buildRedactConfig(redact) {
6482
+ // Explicitly disabled
6483
+ if (redact === false) {
6484
+ return undefined;
6485
+ }
6486
+ // Custom paths provided
6487
+ if (Array.isArray(redact)) {
6488
+ return redact.length > 0
6489
+ ? { paths: redact, censor: '[REDACTED]' }
6490
+ : undefined;
6491
+ }
6492
+ // Default: use web3 sensible defaults
6493
+ return {
6494
+ paths: [...DEFAULT_REDACT_PATHS],
6495
+ censor: '[REDACTED]',
6496
+ };
6497
+ }
6498
+ /**
6499
+ * Create a logger backed by pino.
6500
+ *
6501
+ * @param options - Logger options (optional).
6502
+ * @param stream - Destination stream (optional).
6503
+ * @returns A Logger instance.
6504
+ * @throws Error if invalid pino options are provided.
6505
+ *
6506
+ * @remarks
6507
+ * This is a thin wrapper around pino that exposes our stable Logger interface.
6508
+ * Pino handles all transport concerns: JSON, pretty printing, file, remote, browser, etc.
6509
+ *
6510
+ * **Security**: By default, sensitive web3 fields (privateKey, mnemonic, apiKey, etc.)
6511
+ * are automatically redacted from log output. Use `redact: false` to disable.
6512
+ *
6513
+ * @example
6514
+ * ```typescript
6515
+ * import { createLogger } from '@core/runtime'
6516
+ *
6517
+ * // Default: web3 sensitive fields are redacted
6518
+ * const logger = createLogger({ level: 'info' })
6519
+ * logger.info('Signing', { privateKey: '0x123...' })
6520
+ * // Output: { context: { privateKey: '[REDACTED]' }, msg: 'Signing' }
6521
+ *
6522
+ * // Disable redaction (use with caution)
6523
+ * const unsafeLogger = createLogger({ level: 'debug', redact: false })
6524
+ *
6525
+ * // Custom redaction paths
6526
+ * const customLogger = createLogger({
6527
+ * level: 'info',
6528
+ * redact: ['context.mySecret', 'context.*.credentials']
6529
+ * })
6530
+ *
6531
+ * // Pretty output for development
6532
+ * const devLogger = createLogger({
6533
+ * level: 'debug',
6534
+ * transport: { target: 'pino-pretty' }
6535
+ * })
6536
+ *
6537
+ * // Browser logger
6538
+ * const browserLogger = createLogger({
6539
+ * browser: { asObject: true }
6540
+ * })
6541
+ * ```
6542
+ */
6543
+ function createLogger(options, stream) {
6544
+ const { redact, ...pinoOptions } = {};
6545
+ // Build redaction config
6546
+ const redactConfig = buildRedactConfig(redact);
6547
+ // Build final pino options, only include redact if defined
6548
+ const finalOptions = redactConfig
6549
+ ? { ...pinoOptions, redact: redactConfig }
6550
+ : pinoOptions;
6551
+ const pinoInstance = pino(finalOptions);
6552
+ return wrapPino(pinoInstance);
6553
+ }
6554
+
6555
+ /**
6556
+ * Factory for creating Runtime instances with defaults.
6557
+ *
6558
+ * @packageDocumentation
6559
+ */
6560
+ // ============================================================================
6561
+ // Validation Schema
6562
+ // ============================================================================
6563
+ /**
6564
+ * Schema for validating {@link RuntimeOptions}.
6565
+ *
6566
+ * @remarks
6567
+ * Used internally by {@link createRuntime} to validate options from JS consumers.
6568
+ * Exported for advanced use cases where manual validation is needed.
6569
+ */
6570
+ const runtimeOptionsSchema = z
6571
+ .object({
6572
+ logger: loggerSchema.optional(),
6573
+ metrics: metricsSchema.optional(),
6574
+ })
6575
+ .passthrough();
6576
+ // ============================================================================
6577
+ // Factory
6578
+ // ============================================================================
6579
+ /**
6580
+ * Create a complete Runtime with sensible defaults.
6581
+ *
6582
+ * @param options - Optional configuration to override logger and metrics.
6583
+ * @returns A frozen, immutable Runtime with all services guaranteed present.
6584
+ * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
6585
+ *
6586
+ * @remarks
6587
+ * Creates a fully-configured runtime by merging provided options with defaults.
6588
+ * The returned runtime is frozen to enforce immutability.
6589
+ *
6590
+ * | Service | Default | Configurable |
6591
+ * |---------|---------|--------------|
6592
+ * | `logger` | pino logger (info level) | Yes |
6593
+ * | `metrics` | No-op metrics | Yes |
6594
+ * | `events` | Internal event bus | No |
6595
+ * | `clock` | `Date.now()` | No |
6596
+ *
6597
+ * **Why only logger and metrics?**
6598
+ *
6599
+ * - **Logger/Metrics**: Integration points with your infrastructure
6600
+ * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
6601
+ * - **Clock**: Testing concern - use mock factories for tests
6602
+ *
6603
+ * @example
6604
+ * ```typescript
6605
+ * import { createRuntime, createLogger } from '@core/runtime'
6606
+ *
6607
+ * // Use all defaults
6608
+ * const runtime = createRuntime()
6609
+ *
6610
+ * // Custom logger
6611
+ * const runtime = createRuntime({
6612
+ * logger: createLogger({ level: 'debug' }),
6613
+ * })
6614
+ *
6615
+ * // Custom metrics (e.g., Prometheus)
6616
+ * const runtime = createRuntime({
6617
+ * metrics: myPrometheusMetrics,
6618
+ * })
6619
+ *
6620
+ * // Subscribe to events (don't replace the bus)
6621
+ * runtime.events.on('operation.*', (event) => {
6622
+ * console.log('Event:', event.name)
6623
+ * })
6624
+ * ```
6625
+ */
6626
+ function createRuntime(options) {
6627
+ // Validate options for JS consumers
6628
+ if (options != null) {
6629
+ parseOrThrow(options, runtimeOptionsSchema, 'runtime options');
6630
+ }
6631
+ // Resolve logger first (events may need it)
6632
+ const logger = options?.logger ?? createLogger();
6633
+ // Internal services - not configurable
6634
+ const events = createEventBus({ logger });
6635
+ const clock = defaultClock;
6636
+ // Resolve metrics
6637
+ const metrics = options?.metrics ?? noopMetrics;
6638
+ return Object.freeze({ logger, events, metrics, clock });
6639
+ }
6640
+
6641
+ /**
6642
+ * Invocation context resolution - resolves the **WHO/HOW** of an operation.
6643
+ *
6644
+ * @packageDocumentation
6645
+ */
6646
+ // ============================================================================
6647
+ // Validation Schemas
6648
+ // ============================================================================
6649
+ /**
6650
+ * Schema for validating Caller.
6651
+ */
6652
+ const callerSchema = z.object({
6653
+ type: z.string(),
6654
+ name: z.string(),
6655
+ version: z.string().optional(),
6656
+ });
6657
+ /**
6658
+ * Schema for validating InvocationMeta input.
6659
+ */
6660
+ const invocationMetaSchema = z
6661
+ .object({
6662
+ traceId: z.string().optional(),
6663
+ runtime: z.object({}).passthrough().optional(),
6664
+ tokens: z.object({}).passthrough().optional(),
6665
+ callers: z.array(callerSchema).optional(),
6666
+ })
6667
+ .strict();
6668
+ // ============================================================================
6669
+ // Invocation Context Resolution
6670
+ // ============================================================================
6671
+ /**
6672
+ * Resolve invocation metadata to invocation context.
6673
+ *
6674
+ * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
6675
+ * @param defaults - Default runtime and tokens to use if not overridden.
6676
+ * @returns Frozen, immutable invocation context with guaranteed values.
6677
+ * @throws KitError when meta contains invalid properties.
6678
+ *
6679
+ * @remarks
6680
+ * Resolves the **WHO** called and **HOW** to observe:
6681
+ * - TraceId: Uses provided value or generates new one
6682
+ * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
6683
+ * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
6684
+ * - Callers: Uses provided array or empty array
6685
+ *
6686
+ * The returned context is frozen to enforce immutability.
6687
+ *
6688
+ * @example
6689
+ * ```typescript
6690
+ * import { resolveInvocationContext, createRuntime } from '@core/runtime'
6691
+ * import { createTokenRegistry } from '@core/tokens'
6692
+ *
6693
+ * const defaults = {
6694
+ * runtime: createRuntime(),
6695
+ * tokens: createTokenRegistry(),
6696
+ * }
6697
+ *
6698
+ * // Minimal - just using defaults
6699
+ * const ctx = resolveInvocationContext(undefined, defaults)
6700
+ *
6701
+ * // With trace ID and caller info
6702
+ * const ctx = resolveInvocationContext(
6703
+ * {
6704
+ * traceId: 'abc-123',
6705
+ * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
6706
+ * },
6707
+ * defaults
6708
+ * )
6709
+ *
6710
+ * // With runtime override (complete replacement)
6711
+ * const ctx = resolveInvocationContext(
6712
+ * { runtime: createRuntime({ logger: myLogger }) },
6713
+ * defaults
6714
+ * )
6715
+ * ```
6716
+ */
6717
+ function resolveInvocationContext(meta, defaults) {
6718
+ // Validate meta input if provided
6719
+ if (meta !== undefined) {
6720
+ const result = invocationMetaSchema.safeParse(meta);
6721
+ if (!result.success) {
6722
+ throw createValidationFailedError$1('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
6723
+ }
6724
+ }
6725
+ // Generate trace ID if not provided
6726
+ const traceId = meta?.traceId ?? createTraceId();
6727
+ // Use meta overrides or fall back to defaults
6728
+ const runtime = meta?.runtime ?? defaults.runtime;
6729
+ const tokens = meta?.tokens ?? defaults.tokens;
6730
+ const callers = meta?.callers ?? [];
6731
+ return Object.freeze({ traceId, runtime, tokens, callers });
6732
+ }
6733
+
6734
+ /**
6735
+ * Extend an invocation context by appending a caller to its call chain.
6736
+ *
6737
+ * @param context - The existing invocation context to extend.
6738
+ * @param caller - The caller to append to the call chain.
6739
+ * @returns A new frozen invocation context with the caller appended.
6740
+ *
6741
+ * @remarks
6742
+ * This function creates a new immutable context with the caller appended
6743
+ * to the `callers` array while preserving all other context properties
6744
+ * (traceId, runtime, tokens).
6745
+ *
6746
+ * The returned context is frozen to enforce immutability.
6747
+ *
6748
+ * @example
6749
+ * ```typescript
6750
+ * import { extendInvocationContext } from '@core/runtime'
6751
+ *
6752
+ * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
6753
+ * const extended = extendInvocationContext(existingContext, caller)
6754
+ * // extended.callers === [...existingContext.callers, caller]
6755
+ * ```
6756
+ */
6757
+ function extendInvocationContext(context, caller) {
6758
+ return Object.freeze({
6759
+ traceId: context.traceId,
6760
+ runtime: context.runtime,
6761
+ tokens: context.tokens,
6762
+ callers: [...context.callers, caller],
6763
+ });
6764
+ }
6765
+
6766
+ // Clock - expose defaultClock for backward compatibility
6767
+ /** Clock validation schema (backward compatibility). */
6768
+ z.custom((val) => val !== null &&
6769
+ typeof val === 'object' &&
6770
+ 'now' in val &&
6771
+ typeof val['now'] === 'function');
6772
+ /** EventBus validation schema (backward compatibility). */
6773
+ z.custom((val) => val !== null &&
6774
+ typeof val === 'object' &&
6775
+ 'emit' in val &&
6776
+ typeof val['emit'] === 'function');
6777
+ /** Runtime validation schema (backward compatibility). */
6778
+ z
6779
+ .object({
6780
+ logger: z.any().optional(),
6781
+ events: z.any().optional(),
6782
+ metrics: z.any().optional(),
6783
+ clock: z.any().optional(),
6784
+ })
6785
+ .passthrough();
6786
+
6787
+ /**
6788
+ * Create a structured error for token resolution failures.
6789
+ *
6790
+ * @remarks
6791
+ * Returns a `KitError` with `INPUT_INVALID_TOKEN` code (1006).
6792
+ * The error trace contains the selector and chain context for debugging.
6793
+ *
6794
+ * @param message - Human-readable error description.
6795
+ * @param selector - The token selector that failed to resolve.
6796
+ * @param chainId - The chain being resolved for (optional).
6797
+ * @param cause - The underlying error, if any (optional).
6798
+ * @returns A KitError with INPUT type and FATAL recoverability.
6799
+ *
6800
+ * @example
6801
+ * ```typescript
6802
+ * throw createTokenResolutionError(
6803
+ * 'Unknown token symbol: FAKE',
6804
+ * 'FAKE',
6805
+ * 'Ethereum'
6806
+ * )
6807
+ * ```
6808
+ */
6809
+ function createTokenResolutionError(message, selector, chainId, cause) {
6810
+ const trace = {
6811
+ selector,
6812
+ ...(chainId === undefined ? {} : { chainId }),
6813
+ ...({} ),
6814
+ };
6815
+ return new KitError({
6816
+ ...InputError.INVALID_TOKEN,
6817
+ recoverability: 'FATAL',
6818
+ message,
6819
+ cause: { trace },
6820
+ });
6821
+ }
6822
+
6823
+ /**
6824
+ * USDC token definition with addresses and metadata.
6825
+ *
6826
+ * @remarks
6827
+ * This is the built-in USDC definition used by the TokenRegistry.
6828
+ * Includes all known USDC addresses across supported chains.
6829
+ *
6830
+ * Keys use the `Blockchain` enum for type safety. Both enum values
6831
+ * and string literals are supported:
6832
+ * - `Blockchain.Ethereum` or `'Ethereum'`
6833
+ *
6834
+ * @example
6835
+ * ```typescript
6836
+ * import { USDC } from '@core/tokens'
6837
+ * import { Blockchain } from '@core/chains'
6838
+ *
6839
+ * console.log(USDC.symbol) // 'USDC'
6840
+ * console.log(USDC.decimals) // 6
6841
+ * console.log(USDC.locators[Blockchain.Ethereum])
6842
+ * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
6843
+ * ```
6844
+ */
6845
+ const USDC = {
6846
+ symbol: 'USDC',
6847
+ decimals: 6,
6848
+ locators: {
6849
+ // =========================================================================
6850
+ // Mainnets (alphabetically sorted)
6851
+ // =========================================================================
6852
+ [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
6853
+ [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
6854
+ [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
6855
+ [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
6856
+ [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
6857
+ [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
6858
+ [Blockchain.Hedera]: '0.0.456858',
6859
+ [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
6860
+ [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
6861
+ [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
6862
+ [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
6863
+ [Blockchain.Noble]: 'uusdc',
6864
+ [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
6865
+ [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
6866
+ [Blockchain.Polkadot_Asset_Hub]: '1337',
6867
+ [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
6868
+ [Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
6869
+ [Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
6870
+ [Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
6871
+ [Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
6872
+ [Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
6873
+ [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
6874
+ [Blockchain.World_Chain]: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
6875
+ [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
6876
+ [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
6877
+ // =========================================================================
6878
+ // Testnets (alphabetically sorted)
6879
+ // =========================================================================
6880
+ [Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
6881
+ [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
6882
+ [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
6883
+ [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
6884
+ [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
6885
+ [Blockchain.Hedera_Testnet]: '0.0.429274',
6886
+ [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
6887
+ [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
6888
+ [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
6889
+ [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
6890
+ [Blockchain.Noble_Testnet]: 'uusdc',
6891
+ [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
6892
+ [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
6893
+ [Blockchain.Polkadot_Westmint]: '31337',
6894
+ [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
6895
+ [Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
6896
+ [Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
6897
+ [Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
6898
+ [Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
6899
+ [Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
6900
+ [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
6901
+ [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
6902
+ [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
6903
+ [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
6904
+ },
6905
+ };
6906
+
6907
+ // Re-export for consumers
6908
+ /**
6909
+ * All default token definitions.
6910
+ *
6911
+ * @remarks
6912
+ * These tokens are automatically included in the TokenRegistry when created
6913
+ * without explicit defaults. Extensions can override these definitions.
6914
+ *
6915
+ * @example
6916
+ * ```typescript
6917
+ * import { createTokenRegistry } from '@core/tokens'
6918
+ *
6919
+ * // Registry uses these by default
6920
+ * const registry = createTokenRegistry()
6921
+ *
6922
+ * // Add custom tokens (built-ins are still included)
6923
+ * const customRegistry = createTokenRegistry({
6924
+ * tokens: [myCustomToken],
6925
+ * })
6926
+ * ```
6927
+ */
6928
+ const DEFAULT_TOKENS = [USDC];
6929
+
6930
+ /**
6931
+ * Check if a selector is a raw token selector (object form).
6932
+ *
6933
+ * @param selector - The token selector to check.
6934
+ * @returns True if the selector is a raw token selector.
6935
+ */
6936
+ function isRawSelector(selector) {
6937
+ return typeof selector === 'object' && 'locator' in selector;
6938
+ }
6939
+ /**
6940
+ * Normalize a symbol to uppercase for case-insensitive lookup.
6941
+ *
6942
+ * @param symbol - The symbol to normalize.
6943
+ * @returns The normalized (uppercase) symbol.
6944
+ */
6945
+ function normalizeSymbol(symbol) {
6946
+ return symbol.toUpperCase();
6947
+ }
6948
+ /**
6949
+ * Create a token registry with built-in tokens and optional extensions.
6950
+ *
6951
+ * @remarks
6952
+ * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
6953
+ * Custom tokens are merged on top - use this to add new tokens or override
6954
+ * built-in definitions.
6955
+ *
6956
+ * @param options - Configuration options for the registry.
6957
+ * @returns A token registry instance.
6958
+ *
6959
+ * @example
6960
+ * ```typescript
6961
+ * import { createTokenRegistry } from '@core/tokens'
6962
+ *
6963
+ * // Create registry with built-in tokens (USDC, etc.)
6964
+ * const registry = createTokenRegistry()
6965
+ *
6966
+ * // Resolve USDC on Ethereum
6967
+ * const usdc = registry.resolve('USDC', 'Ethereum')
6968
+ * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
6969
+ * console.log(usdc.decimals) // 6
6970
+ * ```
6971
+ *
6972
+ * @example
6973
+ * ```typescript
6974
+ * // Add custom tokens (built-ins are still included)
6975
+ * const myToken: TokenDefinition = {
6976
+ * symbol: 'MY',
6977
+ * decimals: 18,
6978
+ * locators: { Ethereum: '0x...' },
6979
+ * }
6980
+ *
6981
+ * const registry = createTokenRegistry({ tokens: [myToken] })
6982
+ * registry.resolve('USDC', 'Ethereum') // Still works!
6983
+ * registry.resolve('MY', 'Ethereum') // Also works
6984
+ * ```
6985
+ *
6986
+ * @example
6987
+ * ```typescript
6988
+ * // Override a built-in token
6989
+ * const customUsdc: TokenDefinition = {
6990
+ * symbol: 'USDC',
6991
+ * decimals: 6,
6992
+ * locators: { MyChain: '0xCustomAddress' },
6993
+ * }
6994
+ *
6995
+ * const registry = createTokenRegistry({ tokens: [customUsdc] })
6996
+ * // Now USDC resolves to customUsdc definition
6997
+ * ```
6998
+ *
6999
+ * @example
7000
+ * ```typescript
7001
+ * // Resolve arbitrary tokens by raw locator
7002
+ * const registry = createTokenRegistry()
7003
+ * const token = registry.resolve(
7004
+ * { locator: '0x1234...', decimals: 18 },
7005
+ * 'Ethereum'
7006
+ * )
7007
+ * ```
7008
+ */
7009
+ function createTokenRegistry(options = {}) {
7010
+ const { tokens = [], requireDecimals = false } = options;
7011
+ // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
7012
+ const tokenMap = new Map();
7013
+ // Add built-in tokens first
7014
+ for (const def of DEFAULT_TOKENS) {
7015
+ tokenMap.set(normalizeSymbol(def.symbol), def);
7016
+ }
7017
+ // Custom tokens override built-ins
7018
+ for (const def of tokens) {
7019
+ tokenMap.set(normalizeSymbol(def.symbol), def);
7020
+ }
7021
+ /**
7022
+ * Resolve a symbol selector to token information.
7023
+ */
7024
+ function resolveSymbol(symbol, chainId) {
7025
+ const normalizedSymbol = normalizeSymbol(symbol);
7026
+ const definition = tokenMap.get(normalizedSymbol);
7027
+ if (definition === undefined) {
7028
+ throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
7029
+ }
7030
+ const locator = definition.locators[chainId];
7031
+ if (locator === undefined || locator.trim() === '') {
7032
+ throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
7033
+ }
7034
+ return {
7035
+ symbol: definition.symbol,
7036
+ decimals: definition.decimals,
7037
+ locator,
7038
+ };
7039
+ }
7040
+ /**
7041
+ * Resolve a raw selector to token information.
7042
+ */
7043
+ function resolveRaw(selector, chainId) {
7044
+ const { locator, decimals } = selector;
7045
+ // Validate locator
7046
+ if (!locator || typeof locator !== 'string') {
7047
+ throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
7048
+ }
7049
+ // Decimals are always required for raw selectors
7050
+ if (decimals === undefined) {
7051
+ const message = requireDecimals
7052
+ ? 'Decimals required for raw token selector (requireDecimals: true)'
7053
+ : 'Decimals required for raw token selector. Provide { locator, decimals }.';
7054
+ throw createTokenResolutionError(message, selector, chainId);
7055
+ }
7056
+ // Validate decimals
7057
+ if (typeof decimals !== 'number' ||
7058
+ decimals < 0 ||
7059
+ !Number.isInteger(decimals)) {
7060
+ throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
7061
+ }
7062
+ return {
7063
+ decimals,
7064
+ locator,
7065
+ };
7066
+ }
7067
+ return {
7068
+ resolve(selector, chainId) {
7069
+ // Runtime validation of inputs - these checks are for JS consumers
7070
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
7071
+ if (selector === null || selector === undefined) {
7072
+ throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
7073
+ }
7074
+ if (chainId === '' || typeof chainId !== 'string') {
7075
+ throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
7076
+ }
7077
+ // Dispatch based on selector type
7078
+ if (isRawSelector(selector)) {
7079
+ return resolveRaw(selector, chainId);
7080
+ }
7081
+ if (typeof selector === 'string') {
7082
+ return resolveSymbol(selector, chainId);
7083
+ }
7084
+ throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
7085
+ },
7086
+ get(symbol) {
7087
+ if (!symbol || typeof symbol !== 'string') {
7088
+ return undefined;
7089
+ }
7090
+ return tokenMap.get(normalizeSymbol(symbol));
7091
+ },
7092
+ has(symbol) {
7093
+ if (!symbol || typeof symbol !== 'string') {
7094
+ return false;
7095
+ }
7096
+ return tokenMap.has(normalizeSymbol(symbol));
7097
+ },
7098
+ symbols() {
7099
+ return Array.from(tokenMap.values()).map((def) => def.symbol);
7100
+ },
7101
+ entries() {
7102
+ return Array.from(tokenMap.values());
7103
+ },
7104
+ };
7105
+ }
7106
+
7107
+ /**
7108
+ * Define a schema for token registry interfaces.
7109
+ *
7110
+ * @remarks
7111
+ * Validate that a registry exposes the `resolve()` API used by adapters.
7112
+ *
7113
+ * @example
7114
+ * ```typescript
7115
+ * import { tokenRegistrySchema } from '@core/tokens'
7116
+ *
7117
+ * const registry = {
7118
+ * resolve: () => ({ locator: '0x0', decimals: 6 }),
7119
+ * }
7120
+ *
7121
+ * tokenRegistrySchema.parse(registry)
7122
+ * ```
7123
+ */
7124
+ z.custom((value) => {
7125
+ if (value === null || typeof value !== 'object') {
7126
+ return false;
7127
+ }
7128
+ const record = value;
7129
+ return (typeof record['resolve'] === 'function' &&
7130
+ typeof record['get'] === 'function' &&
7131
+ typeof record['has'] === 'function' &&
7132
+ typeof record['symbols'] === 'function' &&
7133
+ typeof record['entries'] === 'function');
7134
+ }, {
7135
+ message: 'Invalid token registry',
7136
+ });
7137
+
7138
+ /**
7139
+ * Type guard to check if the destination is a forwarder-only destination.
7140
+ *
7141
+ * Forwarder-only destinations have `useForwarder: true` and no adapter.
7142
+ * They require a `recipientAddress` to be specified.
7143
+ *
7144
+ * @param dest - The bridge destination to check
7145
+ * @returns True if this is a forwarder-only destination without adapter
7146
+ */
7147
+ function isForwarderOnlyDestination(dest) {
7148
+ return (dest.useForwarder === true &&
7149
+ !('adapter' in dest) &&
7150
+ 'recipientAddress' in dest);
7151
+ }
7152
+ /**
7153
+ * Resolves a chain identifier to a chain definition.
7154
+ *
7155
+ * Both AdapterContext and BridgeDestinationWithAddress have the chain property
7156
+ * at the top level, so we can directly access it from either type.
7157
+ *
7158
+ * @param ctx - The bridge destination containing the chain identifier
7159
+ * @returns The resolved chain definition
7160
+ * @throws If the chain definition cannot be resolved
7161
+ *
7162
+ * @example
7163
+ * ```typescript
7164
+ * import { Blockchain } from '@core/chains'
7165
+ *
7166
+ * // AdapterContext
7167
+ * const chain1 = resolveChainDefinition({
7168
+ * adapter: mockAdapter,
7169
+ * chain: 'Ethereum'
7170
+ * })
7171
+ *
7172
+ * // BridgeDestinationWithAddress
7173
+ * const chain2 = resolveChainDefinition({
7174
+ * adapter: mockAdapter,
7175
+ * chain: 'Base',
7176
+ * recipientAddress: '0x123...'
7177
+ * })
7178
+ * ```
7179
+ */
7180
+ function resolveChainDefinition(ctx) {
7181
+ return resolveChainIdentifier(ctx.chain);
7182
+ }
7183
+ /**
7184
+ * Resolves the signer's address from a bridge destination.
7185
+ *
7186
+ * This function resolves the address that will be used for transaction signing,
7187
+ * ignoring any `recipientAddress` field which is handled separately.
7188
+ *
7189
+ * It handles two cases:
7190
+ * - Developer-controlled adapters - returns the explicit address from context
7191
+ * - User-controlled adapters - calls getAddress() on the adapter
7192
+ *
7193
+ * @param ctx - The bridge destination to resolve the address from
7194
+ * @returns The resolved signer address string
7195
+ *
7196
+ * @example
7197
+ * ```typescript
7198
+ * // Developer-controlled adapter
7199
+ * const addr1 = await resolveAddress({
7200
+ * adapter: devAdapter,
7201
+ * chain: 'Ethereum',
7202
+ * address: '0x1234567890123456789012345678901234567890'
7203
+ * }) // Returns: '0x1234567890123456789012345678901234567890'
7204
+ *
7205
+ * // User-controlled adapter
7206
+ * const addr2 = await resolveAddress({
7207
+ * adapter: userAdapter,
7208
+ * chain: 'Ethereum'
7209
+ * }) // Returns adapter's connected address
7210
+ * ```
7211
+ */
7212
+ async function resolveAddress(ctx) {
7213
+ // Handle based on adapter's addressContext
7214
+ if (ctx.adapter.capabilities?.addressContext === 'developer-controlled') {
7215
+ // Developer-controlled: address must be provided explicitly
7216
+ if ('address' in ctx && ctx.address) {
7217
+ return ctx.address;
7218
+ }
7219
+ throw new Error('Address is required in context for developer-controlled adapters. ' +
7220
+ 'Please provide: { adapter, chain, address: "0x..." }');
7221
+ }
7222
+ else {
7223
+ // User-controlled: address should not be provided (auto-resolved from adapter)
7224
+ if ('address' in ctx && ctx.address) {
7225
+ throw new Error('Address should not be provided for user-controlled adapters. ' +
7226
+ 'The address is automatically resolved from the connected wallet.');
7227
+ }
7228
+ // Derive address from adapter
7229
+ const chain = resolveChainDefinition(ctx);
7230
+ return await ctx.adapter.getAddress(chain);
7231
+ }
7232
+ }
7233
+ /**
7234
+ * Resolves the amount of a transfer by formatting it according to the token's decimal places.
7235
+ *
7236
+ * This function takes the raw amount from the transfer parameters and formats it
7237
+ * using the appropriate decimal places for the specified token. Currently supports
7238
+ * USDC (6 decimals) and falls back to the raw amount for other tokens.
7239
+ *
7240
+ * @param params - The bridge parameters containing the amount, token type, and from context
7241
+ * @returns The formatted amount string with proper decimal places
7242
+ *
7243
+ * @example
7244
+ * ```typescript
7245
+ * import { Adapter } from '@core/adapter'
7246
+ *
7247
+ * const params = {
7248
+ * amount: '1000000',
7249
+ * token: 'USDC',
7250
+ * from: { adapter: mockAdapter, chain: Ethereum },
7251
+ * to: { adapter: mockAdapter, chain: Base }
7252
+ * }
7253
+ * const formattedAmount = resolveAmount(params) // Returns '1000000000000'
7254
+ * ```
7255
+ */
7256
+ function resolveAmount(params) {
7257
+ if (params.token === 'USDC') {
7258
+ return parseUnits(params.amount, 6).toString();
7259
+ }
7260
+ return params.amount;
7261
+ }
7262
+ /**
7263
+ * Resolves the invocation context for a bridge operation.
7264
+ *
7265
+ * Takes optional invocation metadata and resolves it into a full
7266
+ * InvocationContext with BridgeKit caller information appended.
7267
+ *
7268
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation
7269
+ * @returns An InvocationContext with traceId, runtime, tokens, and caller chain
7270
+ *
7271
+ * @example
7272
+ * ```typescript
7273
+ * // With user-provided invocation metadata
7274
+ * const invocation = resolveBridgeInvocation({
7275
+ * traceId: 'my-custom-trace-id',
7276
+ * callers: [{ type: 'app', name: 'MyDApp' }],
7277
+ * })
7278
+ * // invocation.traceId === 'my-custom-trace-id'
7279
+ * // invocation.callers === [{ type: 'app', name: 'MyDApp' }, { type: 'kit', name: 'BridgeKit' }]
7280
+ *
7281
+ * // Without invocation metadata (auto-generated traceId)
7282
+ * const invocation2 = resolveBridgeInvocation()
7283
+ * // invocation2.traceId === <auto-generated OpenTelemetry-compatible trace ID>
7284
+ * // invocation2.callers === [{ type: 'kit', name: 'BridgeKit' }]
7285
+ * ```
7286
+ */
7287
+ function resolveBridgeInvocation(invocationMeta) {
7288
+ const bridgeKitCaller = {
7289
+ type: 'kit',
7290
+ name: 'BridgeKit',
7291
+ version: pkg.version,
7292
+ };
7293
+ // Create default runtime and tokens for invocation context resolution
7294
+ const defaults = {
7295
+ runtime: createRuntime(),
7296
+ tokens: createTokenRegistry(),
7297
+ };
7298
+ // Resolve invocation metadata to full context, then extend with BridgeKit caller
7299
+ const baseContext = resolveInvocationContext(invocationMeta, defaults);
7300
+ return extendInvocationContext(baseContext, bridgeKitCaller);
7301
+ }
7302
+ /**
7303
+ * Resolves and normalizes bridge configuration for the provider.
7304
+ *
7305
+ * This function takes the optional configuration from bridge parameters and returns
7306
+ * a normalized BridgeConfig with:
7307
+ * - Default transfer speed set to FAST if not provided
5624
7308
  * - Max fee values converted from human-readable to smallest units (6 decimals for USDC)
5625
7309
  * - Custom fee values converted from human-readable to smallest units (6 decimals for USDC)
5626
7310
  *
@@ -5683,6 +7367,7 @@ function resolveConfig(params) {
5683
7367
  * - Amount formatting
5684
7368
  *
5685
7369
  * @param params - The bridge parameters containing source/destination contexts, amount, and token
7370
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation
5686
7371
  * @returns Promise resolving to normalized bridge parameters for provider consumption
5687
7372
  * @throws \{Error\} If parameters cannot be resolved (invalid chains, etc.)
5688
7373
  *
@@ -5702,22 +7387,32 @@ function resolveConfig(params) {
5702
7387
  * ```
5703
7388
  */
5704
7389
  async function resolveBridgeParams(params) {
5705
- const fromChain = resolveChainDefinition(params.from);
5706
- const toChain = resolveChainDefinition(params.to);
7390
+ // Resolve chains
7391
+ const fromChain = resolveChainIdentifier(params.from.chain);
7392
+ const toChain = resolveChainIdentifier(params.to.chain);
7393
+ // Check if this is a forwarder-only destination (no adapter)
7394
+ const isForwarderOnly = isForwarderOnlyDestination(params.to);
5707
7395
  // Validate adapter chain support after resolution
5708
- // This ensures adapters support the resolved chains before proceeding
5709
7396
  params.from.adapter.validateChainSupport(fromChain);
5710
- params.to.adapter.validateChainSupport(toChain);
7397
+ // Only validate destination adapter if it exists
7398
+ if (!isForwarderOnly && 'adapter' in params.to) {
7399
+ params.to.adapter.validateChainSupport(toChain);
7400
+ }
7401
+ // For forwarder-only destinations, use recipientAddress directly
7402
+ // For other destinations, resolve address from adapter
5711
7403
  const [fromAddress, toAddress] = await Promise.all([
5712
7404
  resolveAddress(params.from),
5713
- resolveAddress(params.to),
7405
+ isForwarderOnly
7406
+ ? Promise.resolve(params.to.recipientAddress)
7407
+ : resolveAddress(params.to),
5714
7408
  ]);
5715
7409
  const token = params.token ?? 'USDC';
5716
- // Extract adapters - now always from explicit contexts
5717
- const fromAdapter = params.from.adapter;
5718
- const toAdapter = params.to.adapter;
5719
7410
  // Extract recipientAddress from params.to if it exists
5720
7411
  const recipientAddress = 'recipientAddress' in params.to ? params.to.recipientAddress : undefined;
7412
+ // Extract useForwarder from params.to if it exists
7413
+ const useForwarder = 'useForwarder' in params.to ? params.to.useForwarder : undefined;
7414
+ // Resolve invocation metadata to full InvocationContext with BridgeKit caller
7415
+ const resolvedInvocation = resolveBridgeInvocation(params.invocationMeta);
5721
7416
  return {
5722
7417
  amount: resolveAmount({
5723
7418
  ...params,
@@ -5727,16 +7422,21 @@ async function resolveBridgeParams(params) {
5727
7422
  config: resolveConfig({
5728
7423
  ...params}),
5729
7424
  source: {
5730
- adapter: fromAdapter,
7425
+ adapter: params.from.adapter,
5731
7426
  chain: fromChain,
5732
7427
  address: fromAddress,
5733
7428
  },
5734
7429
  destination: {
5735
- adapter: toAdapter,
7430
+ // Only include adapter if it exists (not forwarder-only)
7431
+ ...(!isForwarderOnly &&
7432
+ 'adapter' in params.to && { adapter: params.to.adapter }),
5736
7433
  chain: toChain,
5737
7434
  address: toAddress,
5738
7435
  ...(recipientAddress !== undefined && { recipientAddress }),
7436
+ ...(useForwarder !== undefined && { useForwarder }),
5739
7437
  },
7438
+ // Pass resolved InvocationContext as invocationMeta (superset is compatible)
7439
+ invocationMeta: resolvedInvocation,
5740
7440
  };
5741
7441
  }
5742
7442
 
@@ -5814,6 +7514,36 @@ const formatBridgeResult = (result, formatDirection) => {
5814
7514
  };
5815
7515
  };
5816
7516
 
7517
+ /**
7518
+ * BridgeKit caller component for retry and estimate operations.
7519
+ */
7520
+ const BRIDGE_KIT_CALLER = {
7521
+ type: 'kit',
7522
+ name: 'BridgeKit',
7523
+ version: pkg.version,
7524
+ };
7525
+ /**
7526
+ * Merge BridgeKit's caller into the invocation metadata for retry operations.
7527
+ *
7528
+ * Prepends the BridgeKit caller to the callers array.
7529
+ *
7530
+ * @param invocationMeta - Optional invocation metadata provided by caller.
7531
+ * @returns Merged invocation metadata with BridgeKit caller prepended.
7532
+ *
7533
+ * @internal
7534
+ */
7535
+ function mergeRetryInvocationMeta(invocationMeta) {
7536
+ // Prepend BridgeKit caller to existing callers array
7537
+ const existingCallers = invocationMeta?.callers ?? [];
7538
+ return invocationMeta
7539
+ ? {
7540
+ ...invocationMeta,
7541
+ callers: [BRIDGE_KIT_CALLER, ...existingCallers],
7542
+ }
7543
+ : {
7544
+ callers: [BRIDGE_KIT_CALLER, ...existingCallers],
7545
+ };
7546
+ }
5817
7547
  /**
5818
7548
  * Route cross-chain USDC bridging through Circle's Cross-Chain Transfer Protocol v2 (CCTPv2).
5819
7549
  *
@@ -5910,7 +7640,7 @@ class BridgeKit {
5910
7640
  * - CCTPv2 support for the chain pair
5911
7641
  * - Transfer configuration options
5912
7642
  *
5913
- * @param params - The transfer parameters containing source, destination, amount, and token
7643
+ * @param params - The transfer parameters containing source, destination, amount, token, and optional invocation metadata
5914
7644
  * @returns Promise resolving to the transfer result with transaction details and steps
5915
7645
  * @throws {KitError} When any parameter validation fails.
5916
7646
  * @throws {Error} When CCTPv2 does not support the specified route.
@@ -5927,18 +7657,24 @@ class BridgeKit {
5927
7657
  * privateKey: process.env.PRIVATE_KEY,
5928
7658
  * })
5929
7659
  *
7660
+ * // Basic usage
5930
7661
  * const result = await kit.bridge({
5931
- * from: {
5932
- * adapter,
5933
- * chain: 'Ethereum'
5934
- * },
5935
- * to: {
5936
- * adapter,
5937
- * chain: 'Base'
5938
- * },
7662
+ * from: { adapter, chain: 'Ethereum' },
7663
+ * to: { adapter, chain: 'Base' },
5939
7664
  * amount: '100.50'
5940
7665
  * })
5941
7666
  *
7667
+ * // With custom invocation metadata
7668
+ * const result = await kit.bridge({
7669
+ * from: { adapter, chain: 'Ethereum' },
7670
+ * to: { adapter, chain: 'Base' },
7671
+ * amount: '100.50',
7672
+ * invocationMeta: {
7673
+ * traceId: 'custom-trace-id',
7674
+ * callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
7675
+ * },
7676
+ * })
7677
+ *
5942
7678
  * // Handle result
5943
7679
  * if (result.state === 'success') {
5944
7680
  * console.log('Bridge completed!')
@@ -5984,6 +7720,8 @@ class BridgeKit {
5984
7720
  * @param context - The retry context containing fresh adapter instances for both
5985
7721
  * source and destination chains. These adapters should be properly
5986
7722
  * configured with current network connections and signing capabilities.
7723
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation.
7724
+ * If not provided, uses the traceId from the original result.
5987
7725
  * @returns A promise that resolves to the updated bridge result after retry execution.
5988
7726
  * The result will contain the complete step history including both original
5989
7727
  * and retry attempts.
@@ -6015,31 +7753,35 @@ class BridgeKit {
6015
7753
  * // ... other properties
6016
7754
  * }
6017
7755
  *
7756
+ * // Basic retry (uses traceId from original result)
7757
+ * const retryResult = await kit.retry(failedResult, {
7758
+ * from: sourceAdapter,
7759
+ * to: destAdapter
7760
+ * })
6018
7761
  *
6019
- * try {
6020
- * const retryResult = await kit.retry(failedResult, {
6021
- * from: sourceAdapter,
6022
- * to: destAdapter
6023
- * })
6024
- *
6025
- * console.log('Retry completed successfully:', retryResult.state)
6026
- * console.log('Total steps executed:', retryResult.steps.length)
6027
- * } catch (error) {
6028
- * console.error('Retry failed:', error.message)
6029
- * // Handle retry failure (may require manual intervention)
6030
- * }
7762
+ * // Retry with custom invocation metadata
7763
+ * const retryResult = await kit.retry(
7764
+ * failedResult,
7765
+ * { from: sourceAdapter, to: destAdapter },
7766
+ * {
7767
+ * traceId: 'custom-trace-id',
7768
+ * callers: [{ type: 'app', name: 'MyApp' }],
7769
+ * }
7770
+ * )
6031
7771
  * ```
6032
7772
  */
6033
- async retry(result, context) {
7773
+ async retry(result, context, invocationMeta) {
6034
7774
  const provider = this.providers.find((p) => p.name === result.provider);
6035
7775
  if (!provider) {
6036
7776
  throw new Error(`Provider ${result.provider} not found`);
6037
7777
  }
7778
+ // Merge BridgeKit caller into invocation metadata for retry operation
7779
+ const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
6038
7780
  // Format the bridge result into bigint string values for internal use
6039
7781
  const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
6040
7782
  // Execute the retry using the provider
6041
7783
  // Format the bridge result into human-readable string values for the user
6042
- return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context), 'to-human-readable');
7784
+ return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
6043
7785
  }
6044
7786
  /**
6045
7787
  * Estimate the cost and fees for a cross-chain USDC bridge operation.
@@ -6047,13 +7789,15 @@ class BridgeKit {
6047
7789
  * This method calculates the expected gas fees and protocol costs for bridging
6048
7790
  * without actually executing the transaction. It performs the same validation
6049
7791
  * as the bridge method but stops before execution.
6050
- * @param params - The bridge parameters for cost estimation
7792
+ *
7793
+ * @param params - The bridge parameters for cost estimation, including optional invocation metadata
6051
7794
  * @returns Promise resolving to detailed cost breakdown including gas estimates
6052
7795
  * @throws {KitError} When the parameters are invalid.
6053
7796
  * @throws {UnsupportedRouteError} When the route is not supported.
6054
7797
  *
6055
7798
  * @example
6056
7799
  * ```typescript
7800
+ * // Basic usage
6057
7801
  * const estimate = await kit.estimate({
6058
7802
  * from: { adapter: adapter, chain: 'Ethereum' },
6059
7803
  * to: { adapter: adapter, chain: 'Base' },
@@ -6061,6 +7805,18 @@ class BridgeKit {
6061
7805
  * token: 'USDC'
6062
7806
  * })
6063
7807
  * console.log('Estimated cost:', estimate.totalCost)
7808
+ *
7809
+ * // With custom invocation metadata
7810
+ * const estimate = await kit.estimate({
7811
+ * from: { adapter: adapter, chain: 'Ethereum' },
7812
+ * to: { adapter: adapter, chain: 'Base' },
7813
+ * amount: '10.50',
7814
+ * token: 'USDC',
7815
+ * invocationMeta: {
7816
+ * traceId: 'custom-trace-id',
7817
+ * callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
7818
+ * },
7819
+ * })
6064
7820
  * ```
6065
7821
  */
6066
7822
  async estimate(params) {
@@ -6112,6 +7868,9 @@ class BridgeKit {
6112
7868
  * // Get only EVM mainnet chains
6113
7869
  * const evmMainnets = kit.getSupportedChains({ chainType: 'evm', isTestnet: false })
6114
7870
  *
7871
+ * // Get only chains that support forwarding
7872
+ * const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
7873
+ *
6115
7874
  * console.log('Supported chains:')
6116
7875
  * allChains.forEach(chain => {
6117
7876
  * console.log(`- ${chain.name} (${chain.type})`)
@@ -6145,6 +7904,18 @@ class BridgeKit {
6145
7904
  if (options?.isTestnet !== undefined) {
6146
7905
  chains = chains.filter((chain) => chain.isTestnet === options.isTestnet);
6147
7906
  }
7907
+ // Apply forwarder support filter if provided
7908
+ if (options?.forwarderSupported !== undefined) {
7909
+ chains = chains.filter((chain) => {
7910
+ const fs = chain.cctp?.forwarderSupported;
7911
+ if (!fs) {
7912
+ return !options.forwarderSupported;
7913
+ }
7914
+ return options.forwarderSupported
7915
+ ? fs.source || fs.destination
7916
+ : !fs.source && !fs.destination;
7917
+ });
7918
+ }
6148
7919
  return chains;
6149
7920
  }
6150
7921
  /**
@@ -6332,5 +8103,5 @@ class BridgeKit {
6332
8103
  // Auto-register this kit for user agent tracking
6333
8104
  registerKit(`${pkg.name}/${pkg.version}`);
6334
8105
 
6335
- export { BalanceError, Blockchain, BridgeChain, BridgeKit, InputError, KitError, NetworkError, OnchainError, RpcError, TransferSpeed, bridgeParamsWithChainIdentifierSchema, getErrorCode, getErrorMessage, isBalanceError, isFatalError, isInputError, isKitError, isNetworkError, isOnchainError, isRetryableError, isRpcError, resolveChainIdentifier, setExternalPrefix };
8106
+ export { BalanceError, Blockchain, BridgeChain, BridgeKit, InputError, KitError, NetworkError, OnchainError, RpcError, TransferSpeed, bridgeParamsWithChainIdentifierSchema, createRuntime, createTokenRegistry, createTraceId, extendInvocationContext, getErrorCode, getErrorMessage, isBalanceError, isFatalError, isInputError, isKitError, isNetworkError, isOnchainError, isRetryableError, isRpcError, resolveChainIdentifier, resolveInvocationContext, setExternalPrefix };
6336
8107
  //# sourceMappingURL=index.mjs.map