@circle-fin/bridge-kit 1.4.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
  /**
@@ -120,6 +121,8 @@ const ERROR_TYPES = {
120
121
  RPC: 'RPC',
121
122
  /** Internet connectivity, DNS resolution, connection issues */
122
123
  NETWORK: 'NETWORK',
124
+ /** Catch-all for unrecognized errors (code 0) */
125
+ UNKNOWN: 'UNKNOWN',
123
126
  };
124
127
  /**
125
128
  * Array of valid error type values for validation.
@@ -133,6 +136,8 @@ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
133
136
  /**
134
137
  * Error code ranges for validation.
135
138
  * Single source of truth for valid error code ranges.
139
+ *
140
+ * Note: Code 0 is special - it's the UNKNOWN catch-all error.
136
141
  */
137
142
  const ERROR_CODE_RANGES = [
138
143
  { min: 1000, max: 1999, type: 'INPUT' },
@@ -141,6 +146,8 @@ const ERROR_CODE_RANGES = [
141
146
  { min: 5000, max: 5999, type: 'ONCHAIN' },
142
147
  { min: 9000, max: 9999, type: 'BALANCE' },
143
148
  ];
149
+ /** Special code for UNKNOWN errors */
150
+ const UNKNOWN_ERROR_CODE = 0;
144
151
  /**
145
152
  * Zod schema for validating ErrorDetails objects.
146
153
  *
@@ -179,6 +186,7 @@ const ERROR_CODE_RANGES = [
179
186
  const errorDetailsSchema = z.object({
180
187
  /**
181
188
  * Numeric identifier following standardized ranges:
189
+ * - 0: UNKNOWN - Catch-all for unrecognized errors
182
190
  * - 1000-1999: INPUT errors - Parameter validation
183
191
  * - 3000-3999: NETWORK errors - Connectivity issues
184
192
  * - 4000-4999: RPC errors - Provider issues, gas estimation
@@ -188,8 +196,9 @@ const errorDetailsSchema = z.object({
188
196
  code: z
189
197
  .number()
190
198
  .int('Error code must be an integer')
191
- .refine((code) => ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
192
- message: 'Error code must be in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
199
+ .refine((code) => code === UNKNOWN_ERROR_CODE ||
200
+ ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
201
+ message: 'Error code must be 0 (UNKNOWN) or in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
193
202
  }),
194
203
  /** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
195
204
  name: z
@@ -199,7 +208,7 @@ const errorDetailsSchema = z.object({
199
208
  /** Error category indicating where the error originated */
200
209
  type: z.enum(ERROR_TYPE_ARRAY, {
201
210
  errorMap: () => ({
202
- message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
211
+ message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK, UNKNOWN',
203
212
  }),
204
213
  }),
205
214
  /** Error handling strategy */
@@ -416,6 +425,7 @@ class KitError extends Error {
416
425
  /**
417
426
  * Standardized error code ranges for consistent categorization:
418
427
  *
428
+ * - 0: UNKNOWN - Catch-all for unrecognized errors
419
429
  * - 1000-1999: INPUT errors - Parameter validation, input format errors
420
430
  * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
421
431
  * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
@@ -644,6 +654,18 @@ const NetworkError = {
644
654
  name: 'NETWORK_TIMEOUT',
645
655
  type: 'NETWORK',
646
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
+ },
647
669
  };
648
670
 
649
671
  /**
@@ -964,6 +986,8 @@ var Blockchain;
964
986
  Blockchain["Ink_Testnet"] = "Ink_Testnet";
965
987
  Blockchain["Linea"] = "Linea";
966
988
  Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
989
+ Blockchain["Monad"] = "Monad";
990
+ Blockchain["Monad_Testnet"] = "Monad_Testnet";
967
991
  Blockchain["NEAR"] = "NEAR";
968
992
  Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
969
993
  Blockchain["Noble"] = "Noble";
@@ -1055,6 +1079,7 @@ var BridgeChain;
1055
1079
  BridgeChain["HyperEVM"] = "HyperEVM";
1056
1080
  BridgeChain["Ink"] = "Ink";
1057
1081
  BridgeChain["Linea"] = "Linea";
1082
+ BridgeChain["Monad"] = "Monad";
1058
1083
  BridgeChain["Optimism"] = "Optimism";
1059
1084
  BridgeChain["Plume"] = "Plume";
1060
1085
  BridgeChain["Polygon"] = "Polygon";
@@ -1074,6 +1099,7 @@ var BridgeChain;
1074
1099
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
1075
1100
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
1076
1101
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
1102
+ BridgeChain["Monad_Testnet"] = "Monad_Testnet";
1077
1103
  BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
1078
1104
  BridgeChain["Plume_Testnet"] = "Plume_Testnet";
1079
1105
  BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
@@ -1200,6 +1226,10 @@ const Aptos = defineChain({
1200
1226
  confirmations: 1,
1201
1227
  },
1202
1228
  },
1229
+ forwarderSupported: {
1230
+ source: false,
1231
+ destination: false,
1232
+ },
1203
1233
  },
1204
1234
  });
1205
1235
 
@@ -1233,6 +1263,10 @@ const AptosTestnet = defineChain({
1233
1263
  confirmations: 1,
1234
1264
  },
1235
1265
  },
1266
+ forwarderSupported: {
1267
+ source: false,
1268
+ destination: false,
1269
+ },
1236
1270
  },
1237
1271
  });
1238
1272
 
@@ -1292,6 +1326,10 @@ const ArcTestnet = defineChain({
1292
1326
  fastConfirmations: 1,
1293
1327
  },
1294
1328
  },
1329
+ forwarderSupported: {
1330
+ source: true,
1331
+ destination: true,
1332
+ },
1295
1333
  },
1296
1334
  kitContracts: {
1297
1335
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1336,6 +1374,10 @@ const Arbitrum = defineChain({
1336
1374
  fastConfirmations: 1,
1337
1375
  },
1338
1376
  },
1377
+ forwarderSupported: {
1378
+ source: true,
1379
+ destination: true,
1380
+ },
1339
1381
  },
1340
1382
  kitContracts: {
1341
1383
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1380,6 +1422,10 @@ const ArbitrumSepolia = defineChain({
1380
1422
  fastConfirmations: 1,
1381
1423
  },
1382
1424
  },
1425
+ forwarderSupported: {
1426
+ source: true,
1427
+ destination: true,
1428
+ },
1383
1429
  },
1384
1430
  kitContracts: {
1385
1431
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1424,6 +1470,10 @@ const Avalanche = defineChain({
1424
1470
  fastConfirmations: 1,
1425
1471
  },
1426
1472
  },
1473
+ forwarderSupported: {
1474
+ source: true,
1475
+ destination: true,
1476
+ },
1427
1477
  },
1428
1478
  kitContracts: {
1429
1479
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1467,6 +1517,10 @@ const AvalancheFuji = defineChain({
1467
1517
  fastConfirmations: 1,
1468
1518
  },
1469
1519
  },
1520
+ forwarderSupported: {
1521
+ source: true,
1522
+ destination: true,
1523
+ },
1470
1524
  },
1471
1525
  rpcEndpoints: ['https://api.avax-test.network/ext/bc/C/rpc'],
1472
1526
  kitContracts: {
@@ -1512,6 +1566,10 @@ const Base = defineChain({
1512
1566
  fastConfirmations: 1,
1513
1567
  },
1514
1568
  },
1569
+ forwarderSupported: {
1570
+ source: true,
1571
+ destination: true,
1572
+ },
1515
1573
  },
1516
1574
  kitContracts: {
1517
1575
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1556,6 +1614,10 @@ const BaseSepolia = defineChain({
1556
1614
  fastConfirmations: 1,
1557
1615
  },
1558
1616
  },
1617
+ forwarderSupported: {
1618
+ source: true,
1619
+ destination: true,
1620
+ },
1559
1621
  },
1560
1622
  kitContracts: {
1561
1623
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1642,6 +1704,10 @@ const Codex = defineChain({
1642
1704
  fastConfirmations: 1,
1643
1705
  },
1644
1706
  },
1707
+ forwarderSupported: {
1708
+ source: true,
1709
+ destination: false,
1710
+ },
1645
1711
  },
1646
1712
  kitContracts: {
1647
1713
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1680,6 +1746,10 @@ const CodexTestnet = defineChain({
1680
1746
  fastConfirmations: 1,
1681
1747
  },
1682
1748
  },
1749
+ forwarderSupported: {
1750
+ source: true,
1751
+ destination: false,
1752
+ },
1683
1753
  },
1684
1754
  kitContracts: {
1685
1755
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1724,6 +1794,10 @@ const Ethereum = defineChain({
1724
1794
  fastConfirmations: 2,
1725
1795
  },
1726
1796
  },
1797
+ forwarderSupported: {
1798
+ source: true,
1799
+ destination: true,
1800
+ },
1727
1801
  },
1728
1802
  kitContracts: {
1729
1803
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1768,6 +1842,10 @@ const EthereumSepolia = defineChain({
1768
1842
  fastConfirmations: 2,
1769
1843
  },
1770
1844
  },
1845
+ forwarderSupported: {
1846
+ source: true,
1847
+ destination: true,
1848
+ },
1771
1849
  },
1772
1850
  kitContracts: {
1773
1851
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1854,6 +1932,10 @@ const HyperEVM = defineChain({
1854
1932
  fastConfirmations: 1,
1855
1933
  },
1856
1934
  },
1935
+ forwarderSupported: {
1936
+ source: true,
1937
+ destination: true,
1938
+ },
1857
1939
  },
1858
1940
  kitContracts: {
1859
1941
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1893,6 +1975,10 @@ const HyperEVMTestnet = defineChain({
1893
1975
  fastConfirmations: 1,
1894
1976
  },
1895
1977
  },
1978
+ forwarderSupported: {
1979
+ source: true,
1980
+ destination: true,
1981
+ },
1896
1982
  },
1897
1983
  kitContracts: {
1898
1984
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1936,6 +2022,10 @@ const Ink = defineChain({
1936
2022
  fastConfirmations: 1,
1937
2023
  },
1938
2024
  },
2025
+ forwarderSupported: {
2026
+ source: true,
2027
+ destination: true,
2028
+ },
1939
2029
  },
1940
2030
  kitContracts: {
1941
2031
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1978,6 +2068,10 @@ const InkTestnet = defineChain({
1978
2068
  fastConfirmations: 1,
1979
2069
  },
1980
2070
  },
2071
+ forwarderSupported: {
2072
+ source: true,
2073
+ destination: true,
2074
+ },
1981
2075
  },
1982
2076
  kitContracts: {
1983
2077
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2016,6 +2110,10 @@ const Linea = defineChain({
2016
2110
  fastConfirmations: 1,
2017
2111
  },
2018
2112
  },
2113
+ forwarderSupported: {
2114
+ source: true,
2115
+ destination: true,
2116
+ },
2019
2117
  },
2020
2118
  kitContracts: {
2021
2119
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2054,6 +2152,98 @@ const LineaSepolia = defineChain({
2054
2152
  fastConfirmations: 1,
2055
2153
  },
2056
2154
  },
2155
+ forwarderSupported: {
2156
+ source: true,
2157
+ destination: true,
2158
+ },
2159
+ },
2160
+ kitContracts: {
2161
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2162
+ },
2163
+ });
2164
+
2165
+ /**
2166
+ * Monad Mainnet chain definition
2167
+ * @remarks
2168
+ * This represents the official production network for the Monad blockchain.
2169
+ * Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
2170
+ * over 10,000 TPS, sub-second finality, and near-zero gas fees.
2171
+ */
2172
+ const Monad = defineChain({
2173
+ type: 'evm',
2174
+ chain: Blockchain.Monad,
2175
+ name: 'Monad',
2176
+ title: 'Monad Mainnet',
2177
+ nativeCurrency: {
2178
+ name: 'Monad',
2179
+ symbol: 'MON',
2180
+ decimals: 18,
2181
+ },
2182
+ chainId: 143,
2183
+ isTestnet: false,
2184
+ explorerUrl: 'https://monadscan.com/tx/{hash}',
2185
+ rpcEndpoints: ['https://rpc.monad.xyz'],
2186
+ eurcAddress: null,
2187
+ usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
2188
+ cctp: {
2189
+ domain: 15,
2190
+ contracts: {
2191
+ v2: {
2192
+ type: 'split',
2193
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
2194
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
2195
+ confirmations: 1,
2196
+ fastConfirmations: 1,
2197
+ },
2198
+ },
2199
+ forwarderSupported: {
2200
+ source: true,
2201
+ destination: true,
2202
+ },
2203
+ },
2204
+ kitContracts: {
2205
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2206
+ },
2207
+ });
2208
+
2209
+ /**
2210
+ * Monad Testnet chain definition
2211
+ * @remarks
2212
+ * This represents the official test network for the Monad blockchain.
2213
+ * Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
2214
+ * over 10,000 TPS, sub-second finality, and near-zero gas fees.
2215
+ */
2216
+ const MonadTestnet = defineChain({
2217
+ type: 'evm',
2218
+ chain: Blockchain.Monad_Testnet,
2219
+ name: 'Monad Testnet',
2220
+ title: 'Monad Testnet',
2221
+ nativeCurrency: {
2222
+ name: 'Monad',
2223
+ symbol: 'MON',
2224
+ decimals: 18,
2225
+ },
2226
+ chainId: 10143,
2227
+ isTestnet: true,
2228
+ explorerUrl: 'https://testnet.monadscan.com/tx/{hash}',
2229
+ rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
2230
+ eurcAddress: null,
2231
+ usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
2232
+ cctp: {
2233
+ domain: 15,
2234
+ contracts: {
2235
+ v2: {
2236
+ type: 'split',
2237
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
2238
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
2239
+ confirmations: 1,
2240
+ fastConfirmations: 1,
2241
+ },
2242
+ },
2243
+ forwarderSupported: {
2244
+ source: true,
2245
+ destination: true,
2246
+ },
2057
2247
  },
2058
2248
  kitContracts: {
2059
2249
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2135,6 +2325,10 @@ const Noble = defineChain({
2135
2325
  confirmations: 1,
2136
2326
  },
2137
2327
  },
2328
+ forwarderSupported: {
2329
+ source: false,
2330
+ destination: false,
2331
+ },
2138
2332
  },
2139
2333
  });
2140
2334
 
@@ -2167,6 +2361,10 @@ const NobleTestnet = defineChain({
2167
2361
  confirmations: 1,
2168
2362
  },
2169
2363
  },
2364
+ forwarderSupported: {
2365
+ source: false,
2366
+ destination: false,
2367
+ },
2170
2368
  },
2171
2369
  });
2172
2370
 
@@ -2208,6 +2406,10 @@ const Optimism = defineChain({
2208
2406
  fastConfirmations: 1,
2209
2407
  },
2210
2408
  },
2409
+ forwarderSupported: {
2410
+ source: true,
2411
+ destination: true,
2412
+ },
2211
2413
  },
2212
2414
  kitContracts: {
2213
2415
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2252,6 +2454,10 @@ const OptimismSepolia = defineChain({
2252
2454
  fastConfirmations: 1,
2253
2455
  },
2254
2456
  },
2457
+ forwarderSupported: {
2458
+ source: true,
2459
+ destination: true,
2460
+ },
2255
2461
  },
2256
2462
  kitContracts: {
2257
2463
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2292,6 +2498,10 @@ const Plume = defineChain({
2292
2498
  fastConfirmations: 1,
2293
2499
  },
2294
2500
  },
2501
+ forwarderSupported: {
2502
+ source: true,
2503
+ destination: false,
2504
+ },
2295
2505
  },
2296
2506
  kitContracts: {
2297
2507
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2331,6 +2541,10 @@ const PlumeTestnet = defineChain({
2331
2541
  fastConfirmations: 1,
2332
2542
  },
2333
2543
  },
2544
+ forwarderSupported: {
2545
+ source: true,
2546
+ destination: false,
2547
+ },
2334
2548
  },
2335
2549
  kitContracts: {
2336
2550
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2421,6 +2635,10 @@ const Polygon = defineChain({
2421
2635
  fastConfirmations: 13,
2422
2636
  },
2423
2637
  },
2638
+ forwarderSupported: {
2639
+ source: true,
2640
+ destination: true,
2641
+ },
2424
2642
  },
2425
2643
  kitContracts: {
2426
2644
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2465,6 +2683,10 @@ const PolygonAmoy = defineChain({
2465
2683
  fastConfirmations: 13,
2466
2684
  },
2467
2685
  },
2686
+ forwarderSupported: {
2687
+ source: true,
2688
+ destination: true,
2689
+ },
2468
2690
  },
2469
2691
  kitContracts: {
2470
2692
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2505,6 +2727,10 @@ const Sei = defineChain({
2505
2727
  fastConfirmations: 1,
2506
2728
  },
2507
2729
  },
2730
+ forwarderSupported: {
2731
+ source: true,
2732
+ destination: true,
2733
+ },
2508
2734
  },
2509
2735
  kitContracts: {
2510
2736
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2544,6 +2770,10 @@ const SeiTestnet = defineChain({
2544
2770
  fastConfirmations: 1,
2545
2771
  },
2546
2772
  },
2773
+ forwarderSupported: {
2774
+ source: true,
2775
+ destination: true,
2776
+ },
2547
2777
  },
2548
2778
  kitContracts: {
2549
2779
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2582,6 +2812,10 @@ const Sonic = defineChain({
2582
2812
  fastConfirmations: 1,
2583
2813
  },
2584
2814
  },
2815
+ forwarderSupported: {
2816
+ source: true,
2817
+ destination: true,
2818
+ },
2585
2819
  },
2586
2820
  kitContracts: {
2587
2821
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2620,6 +2854,10 @@ const SonicTestnet = defineChain({
2620
2854
  fastConfirmations: 1,
2621
2855
  },
2622
2856
  },
2857
+ forwarderSupported: {
2858
+ source: true,
2859
+ destination: true,
2860
+ },
2623
2861
  },
2624
2862
  kitContracts: {
2625
2863
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2663,6 +2901,10 @@ const Solana = defineChain({
2663
2901
  fastConfirmations: 3,
2664
2902
  },
2665
2903
  },
2904
+ forwarderSupported: {
2905
+ source: true,
2906
+ destination: false,
2907
+ },
2666
2908
  },
2667
2909
  kitContracts: {
2668
2910
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
@@ -2705,6 +2947,10 @@ const SolanaDevnet = defineChain({
2705
2947
  fastConfirmations: 3,
2706
2948
  },
2707
2949
  },
2950
+ forwarderSupported: {
2951
+ source: true,
2952
+ destination: false,
2953
+ },
2708
2954
  },
2709
2955
  kitContracts: {
2710
2956
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
@@ -2788,6 +3034,10 @@ const Sui = defineChain({
2788
3034
  confirmations: 1,
2789
3035
  },
2790
3036
  },
3037
+ forwarderSupported: {
3038
+ source: false,
3039
+ destination: false,
3040
+ },
2791
3041
  },
2792
3042
  });
2793
3043
 
@@ -2821,6 +3071,10 @@ const SuiTestnet = defineChain({
2821
3071
  confirmations: 1,
2822
3072
  },
2823
3073
  },
3074
+ forwarderSupported: {
3075
+ source: false,
3076
+ destination: false,
3077
+ },
2824
3078
  },
2825
3079
  });
2826
3080
 
@@ -2842,7 +3096,7 @@ const Unichain = defineChain({
2842
3096
  chainId: 130,
2843
3097
  isTestnet: false,
2844
3098
  explorerUrl: 'https://unichain.blockscout.com/tx/{hash}',
2845
- rpcEndpoints: ['https://rpc.unichain.org', 'https://mainnet.unichain.org'],
3099
+ rpcEndpoints: ['https://mainnet.unichain.org'],
2846
3100
  eurcAddress: null,
2847
3101
  usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
2848
3102
  cctp: {
@@ -2862,6 +3116,10 @@ const Unichain = defineChain({
2862
3116
  fastConfirmations: 1,
2863
3117
  },
2864
3118
  },
3119
+ forwarderSupported: {
3120
+ source: true,
3121
+ destination: true,
3122
+ },
2865
3123
  },
2866
3124
  kitContracts: {
2867
3125
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2906,6 +3164,10 @@ const UnichainSepolia = defineChain({
2906
3164
  fastConfirmations: 1,
2907
3165
  },
2908
3166
  },
3167
+ forwarderSupported: {
3168
+ source: true,
3169
+ destination: true,
3170
+ },
2909
3171
  },
2910
3172
  kitContracts: {
2911
3173
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2932,7 +3194,7 @@ const WorldChain = defineChain({
2932
3194
  explorerUrl: 'https://worldscan.org/tx/{hash}',
2933
3195
  rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
2934
3196
  eurcAddress: null,
2935
- usdcAddress: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
3197
+ usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
2936
3198
  cctp: {
2937
3199
  domain: 14,
2938
3200
  contracts: {
@@ -2944,6 +3206,10 @@ const WorldChain = defineChain({
2944
3206
  fastConfirmations: 1,
2945
3207
  },
2946
3208
  },
3209
+ forwarderSupported: {
3210
+ source: true,
3211
+ destination: true,
3212
+ },
2947
3213
  },
2948
3214
  kitContracts: {
2949
3215
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2985,6 +3251,10 @@ const WorldChainSepolia = defineChain({
2985
3251
  fastConfirmations: 1,
2986
3252
  },
2987
3253
  },
3254
+ forwarderSupported: {
3255
+ source: true,
3256
+ destination: true,
3257
+ },
2988
3258
  },
2989
3259
  kitContracts: {
2990
3260
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3011,7 +3281,7 @@ const XDC = defineChain({
3011
3281
  chainId: 50,
3012
3282
  isTestnet: false,
3013
3283
  explorerUrl: 'https://xdcscan.io/tx/{hash}',
3014
- rpcEndpoints: ['https://erpc.xinfin.network'],
3284
+ rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
3015
3285
  eurcAddress: null,
3016
3286
  usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
3017
3287
  cctp: {
@@ -3025,6 +3295,10 @@ const XDC = defineChain({
3025
3295
  fastConfirmations: 3,
3026
3296
  },
3027
3297
  },
3298
+ forwarderSupported: {
3299
+ source: true,
3300
+ destination: false,
3301
+ },
3028
3302
  },
3029
3303
  kitContracts: {
3030
3304
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -3063,6 +3337,10 @@ const XDCApothem = defineChain({
3063
3337
  fastConfirmations: 1,
3064
3338
  },
3065
3339
  },
3340
+ forwarderSupported: {
3341
+ source: true,
3342
+ destination: false,
3343
+ },
3066
3344
  },
3067
3345
  kitContracts: {
3068
3346
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3144,6 +3422,8 @@ var Blockchains = /*#__PURE__*/Object.freeze({
3144
3422
  InkTestnet: InkTestnet,
3145
3423
  Linea: Linea,
3146
3424
  LineaSepolia: LineaSepolia,
3425
+ Monad: Monad,
3426
+ MonadTestnet: MonadTestnet,
3147
3427
  NEAR: NEAR,
3148
3428
  NEARTestnet: NEARTestnet,
3149
3429
  Noble: Noble,
@@ -3297,7 +3577,7 @@ const nonEvmChainDefinitionSchema = baseChainDefinitionSchema
3297
3577
  * })
3298
3578
  * ```
3299
3579
  */
3300
- const chainDefinitionSchema$1 = z.discriminatedUnion('type', [
3580
+ const chainDefinitionSchema$2 = z.discriminatedUnion('type', [
3301
3581
  evmChainDefinitionSchema,
3302
3582
  nonEvmChainDefinitionSchema,
3303
3583
  ]);
@@ -3322,7 +3602,7 @@ z.union([
3322
3602
  .string()
3323
3603
  .refine((val) => val in Blockchain, 'Must be a valid Blockchain enum value as string'),
3324
3604
  z.nativeEnum(Blockchain),
3325
- chainDefinitionSchema$1,
3605
+ chainDefinitionSchema$2,
3326
3606
  ]);
3327
3607
  /**
3328
3608
  * Zod schema for validating bridge chain identifiers.
@@ -3358,7 +3638,7 @@ const bridgeChainIdentifierSchema = z.union([
3358
3638
  z.string().refine((val) => val in BridgeChain, (val) => ({
3359
3639
  message: `Chain "${val}" is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3360
3640
  })),
3361
- chainDefinitionSchema$1.refine((chainDef) => chainDef.chain in BridgeChain, (chainDef) => ({
3641
+ chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in BridgeChain, (chainDef) => ({
3362
3642
  message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3363
3643
  })),
3364
3644
  ]);
@@ -3555,14 +3835,41 @@ function isFatalError(error) {
3555
3835
  return isKitError(error) && error.recoverability === 'FATAL';
3556
3836
  }
3557
3837
  /**
3558
- * Checks if an error is a KitError with RETRYABLE recoverability.
3838
+ * Error codes that are considered retryable by default.
3839
+ *
3840
+ * @remarks
3841
+ * These are typically transient errors that may succeed on retry:
3842
+ * - Network connectivity issues (3001, 3002)
3843
+ * - Provider unavailability (4001, 4002)
3844
+ * - RPC nonce errors (4003)
3845
+ */
3846
+ const DEFAULT_RETRYABLE_ERROR_CODES = [
3847
+ // Network errors
3848
+ 3001, // NETWORK_CONNECTION_FAILED
3849
+ 3002, // NETWORK_TIMEOUT
3850
+ // Provider errors
3851
+ 4001, // PROVIDER_UNAVAILABLE
3852
+ 4002, // PROVIDER_TIMEOUT
3853
+ 4003, // RPC_NONCE_ERROR
3854
+ ];
3855
+ /**
3856
+ * Checks if an error is retryable.
3857
+ *
3858
+ * @remarks
3859
+ * Check order for KitError instances:
3860
+ * 1. If `recoverability === 'RETRYABLE'`, return `true` immediately (priority check).
3861
+ * 2. Otherwise, check if `error.code` is in `DEFAULT_RETRYABLE_ERROR_CODES` (fallback check).
3862
+ * 3. Non-KitError instances always return `false`.
3863
+ *
3864
+ * This two-tier approach allows both explicit recoverability control and
3865
+ * backward-compatible code-based retry logic.
3559
3866
  *
3560
3867
  * RETRYABLE errors indicate transient failures that may succeed on
3561
3868
  * subsequent attempts, such as network timeouts or temporary service
3562
3869
  * unavailability. These errors are safe to retry after a delay.
3563
3870
  *
3564
3871
  * @param error - Unknown error to check
3565
- * @returns True if error is a KitError with RETRYABLE recoverability
3872
+ * @returns True if error is retryable
3566
3873
  *
3567
3874
  * @example
3568
3875
  * ```typescript
@@ -3577,9 +3884,51 @@ function isFatalError(error) {
3577
3884
  * }
3578
3885
  * }
3579
3886
  * ```
3887
+ *
3888
+ * @example
3889
+ * ```typescript
3890
+ * import { isRetryableError, createNetworkConnectionError, KitError } from '@core/errors'
3891
+ *
3892
+ * // KitError with RETRYABLE recoverability (priority check)
3893
+ * const error1 = createNetworkConnectionError('Ethereum')
3894
+ * isRetryableError(error1) // true
3895
+ *
3896
+ * // KitError with default retryable code (fallback check)
3897
+ * const error2 = new KitError({
3898
+ * code: 3002, // NETWORK_TIMEOUT - in DEFAULT_RETRYABLE_ERROR_CODES
3899
+ * name: 'NETWORK_TIMEOUT',
3900
+ * type: 'NETWORK',
3901
+ * recoverability: 'FATAL', // Not RETRYABLE
3902
+ * message: 'Timeout',
3903
+ * })
3904
+ * isRetryableError(error2) // true (code 3002 is in default list)
3905
+ *
3906
+ * // KitError with non-retryable code and FATAL recoverability
3907
+ * const error3 = new KitError({
3908
+ * code: 1001,
3909
+ * name: 'INVALID_INPUT',
3910
+ * type: 'INPUT',
3911
+ * recoverability: 'FATAL',
3912
+ * message: 'Invalid input',
3913
+ * })
3914
+ * isRetryableError(error3) // false
3915
+ *
3916
+ * // Non-KitError
3917
+ * const error4 = new Error('Standard error')
3918
+ * isRetryableError(error4) // false
3919
+ * ```
3580
3920
  */
3581
3921
  function isRetryableError(error) {
3582
- return isKitError(error) && error.recoverability === 'RETRYABLE';
3922
+ // Use proper type guard to check if it's a KitError
3923
+ if (isKitError(error)) {
3924
+ // Priority check: explicit recoverability
3925
+ if (error.recoverability === 'RETRYABLE') {
3926
+ return true;
3927
+ }
3928
+ // Fallback check: error code against default retryable codes
3929
+ return DEFAULT_RETRYABLE_ERROR_CODES.includes(error.code);
3930
+ }
3931
+ return false;
3583
3932
  }
3584
3933
  /**
3585
3934
  * Type guard to check if error is KitError with INPUT type.
@@ -3776,6 +4125,62 @@ function getErrorMessage(error) {
3776
4125
  function getErrorCode(error) {
3777
4126
  return isKitError(error) ? error.code : null;
3778
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
+ }
3779
4184
 
3780
4185
  /**
3781
4186
  * Validates if an address format is correct for the specified chain.
@@ -4320,7 +4725,7 @@ function validateWithStateTracking(value, schema, context, validatorName) {
4320
4725
  * Zod schema for validating chain definition objects used in buildExplorerUrl.
4321
4726
  * This schema ensures the chain definition has the required properties for URL generation.
4322
4727
  */
4323
- const chainDefinitionSchema = z.object({
4728
+ const chainDefinitionSchema$1 = z.object({
4324
4729
  name: z
4325
4730
  .string({
4326
4731
  required_error: 'Chain name is required',
@@ -4344,15 +4749,14 @@ const transactionHashSchema = z
4344
4749
  required_error: 'Transaction hash is required',
4345
4750
  invalid_type_error: 'Transaction hash must be a string',
4346
4751
  })
4347
- .min(1, 'Transaction hash cannot be empty')
4348
- .transform((hash) => hash.trim()) // Automatically trim whitespace
4349
- .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');
4350
4754
  /**
4351
4755
  * Zod schema for validating buildExplorerUrl function parameters.
4352
4756
  * This schema validates both the chain definition and transaction hash together.
4353
4757
  */
4354
4758
  z.object({
4355
- chainDef: chainDefinitionSchema,
4759
+ chainDef: chainDefinitionSchema$1,
4356
4760
  txHash: transactionHashSchema,
4357
4761
  });
4358
4762
  /**
@@ -4363,6 +4767,69 @@ z
4363
4767
  .string()
4364
4768
  .url('Generated explorer URL is invalid');
4365
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
+
4366
4833
  /**
4367
4834
  * A type-safe event emitter for managing action-based event subscriptions.
4368
4835
  *
@@ -4637,7 +5104,7 @@ const parseAmount = (params) => {
4637
5104
  };
4638
5105
 
4639
5106
  var name = "@circle-fin/bridge-kit";
4640
- var version = "1.4.0";
5107
+ var version = "1.6.0";
4641
5108
  var pkg = {
4642
5109
  name: name,
4643
5110
  version: version};
@@ -5063,7 +5530,7 @@ const createDecimalStringValidator = (options) => (schema) => {
5063
5530
  * console.log(result.success) // true
5064
5531
  * ```
5065
5532
  */
5066
- z.object({
5533
+ const chainDefinitionSchema = z.object({
5067
5534
  name: z.string().min(1, 'Chain name is required'),
5068
5535
  type: z.string().min(1, 'Chain type is required'),
5069
5536
  });
@@ -5110,6 +5577,53 @@ const walletContextSchema = z.object({
5110
5577
  isTestnet: z.boolean(),
5111
5578
  }),
5112
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
+ ]);
5113
5627
  /**
5114
5628
  * Schema for validating a custom fee configuration.
5115
5629
  * Validates the simplified CustomFee interface which includes:
@@ -5207,7 +5721,7 @@ z.object({
5207
5721
  maxDecimals: 6,
5208
5722
  })(z.string())),
5209
5723
  source: walletContextSchema,
5210
- destination: walletContextSchema,
5724
+ destination: bridgeDestinationSchema$1,
5211
5725
  token: z.literal('USDC'),
5212
5726
  config: z.object({
5213
5727
  transferSpeed: z.nativeEnum(TransferSpeed).optional(),
@@ -5224,6 +5738,28 @@ z.object({
5224
5738
  }),
5225
5739
  });
5226
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
+ }
5227
5763
  /**
5228
5764
  * Schema for validating AdapterContext for bridge operations.
5229
5765
  * Must always contain both adapter and chain explicitly.
@@ -5243,32 +5779,52 @@ const adapterContextSchema = z.object({
5243
5779
  const bridgeDestinationWithAddressSchema = adapterContextSchema
5244
5780
  .extend({
5245
5781
  recipientAddress: z.string().min(1, 'Recipient address is required'),
5782
+ useForwarder: z.boolean().optional(),
5246
5783
  })
5247
- .superRefine((data, ctx) => {
5248
- const chain = data.chain;
5249
- if (chain === null) {
5250
- return;
5251
- }
5252
- if (!isValidAddressForChain(data.recipientAddress, chain)) {
5253
- const chainInfo = extractChainInfo(chain);
5254
- ctx.addIssue({
5255
- code: z.ZodIssueCode.custom,
5256
- path: ['recipientAddress'],
5257
- message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
5258
- });
5259
- }
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(),
5260
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());
5261
5810
  /**
5262
5811
  * Schema for validating BridgeDestination union type.
5263
- * 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)
5264
5816
  *
5265
- * The order matters: we check the more specific schema (with recipientAddress) first.
5266
- * This ensures that objects with an empty recipientAddress field are rejected rather
5267
- * than silently treated as AdapterContext with the field ignored.
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
5820
+ *
5821
+ * The order matters: we check the more specific schemas first.
5822
+ * This ensures that objects with specific fields are matched correctly.
5268
5823
  */
5269
5824
  const bridgeDestinationSchema = z.union([
5270
5825
  bridgeDestinationWithAddressSchema,
5271
- adapterContextSchema.strict(),
5826
+ forwarderDestinationSchema,
5827
+ adapterContextWithForwarderSchema.strict(),
5272
5828
  ]);
5273
5829
  /**
5274
5830
  * Schema for validating bridge parameters with chain identifiers.
@@ -5342,45 +5898,1297 @@ const bridgeParamsWithChainIdentifierSchema = z.object({
5342
5898
  });
5343
5899
 
5344
5900
  /**
5345
- * Resolves a chain identifier to a chain definition.
5901
+ * Default clock implementation using `Date.now()`.
5346
5902
  *
5347
- * Both AdapterContext and BridgeDestinationWithAddress have the chain property
5348
- * at the top level, so we can directly access it from either type.
5349
- *
5350
- * @param ctx - The bridge destination containing the chain identifier
5351
- * @returns The resolved chain definition
5352
- * @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.
5353
5906
  *
5354
5907
  * @example
5355
5908
  * ```typescript
5356
- * import { Blockchain } from '@core/chains'
5909
+ * import { defaultClock } from '@core/runtime'
5357
5910
  *
5358
- * // AdapterContext
5359
- * const chain1 = resolveChainDefinition({
5360
- * adapter: mockAdapter,
5361
- * chain: 'Ethereum'
5362
- * })
5363
- *
5364
- * // BridgeDestinationWithAddress
5365
- * const chain2 = resolveChainDefinition({
5366
- * adapter: mockAdapter,
5367
- * chain: 'Base',
5368
- * recipientAddress: '0x123...'
5369
- * })
5911
+ * const start = defaultClock.now()
5912
+ * // ... do work ...
5913
+ * const elapsed = defaultClock.since(start)
5370
5914
  * ```
5371
5915
  */
5372
- function resolveChainDefinition(ctx) {
5373
- return resolveChainIdentifier(ctx.chain);
5374
- }
5916
+ const defaultClock = {
5917
+ now: () => Date.now(),
5918
+ since: (start) => Date.now() - start,
5919
+ };
5920
+
5375
5921
  /**
5376
- * Resolves the signer's address from a bridge destination.
5922
+ * ID generation utilities for distributed tracing.
5377
5923
  *
5378
- * This function resolves the address that will be used for transaction signing,
5379
- * 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 |
5380
5932
  *
5381
- * It handles two cases:
5382
- * - Developer-controlled adapters - returns the explicit address from context
5383
- * - 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.
5945
+ *
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.
5951
+ *
5952
+ * @example
5953
+ * ```typescript
5954
+ * const traceId = createTraceId() // "a1b2c3d4e5f6789012345678abcdef00"
5955
+ * ```
5956
+ */
5957
+ function createTraceId() {
5958
+ const bytes = new Uint8Array(16);
5959
+ if (hasGetRandomValues()) {
5960
+ crypto.getRandomValues(bytes);
5961
+ }
5962
+ else {
5963
+ for (let i = 0; i < 16; i++) {
5964
+ bytes[i] = Math.floor(Math.random() * 256); // NOSONAR:
5965
+ }
5966
+ }
5967
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
5968
+ }
5969
+
5970
+ /**
5971
+ * Clean tags by removing keys with undefined values.
5972
+ *
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.
5975
+ *
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}.
5980
+ *
5981
+ * @example
5982
+ * ```typescript
5983
+ * import { cleanTags } from '@core/runtime'
5984
+ *
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
+ * // {}
5991
+ * ```
5992
+ */
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;
6022
+ }
6023
+ return true;
6024
+ }
6025
+ /**
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.
6065
+ *
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
5384
7192
  *
5385
7193
  * @param ctx - The bridge destination to resolve the address from
5386
7194
  * @returns The resolved signer address string
@@ -5451,6 +7259,46 @@ function resolveAmount(params) {
5451
7259
  }
5452
7260
  return params.amount;
5453
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
+ }
5454
7302
  /**
5455
7303
  * Resolves and normalizes bridge configuration for the provider.
5456
7304
  *
@@ -5519,6 +7367,7 @@ function resolveConfig(params) {
5519
7367
  * - Amount formatting
5520
7368
  *
5521
7369
  * @param params - The bridge parameters containing source/destination contexts, amount, and token
7370
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation
5522
7371
  * @returns Promise resolving to normalized bridge parameters for provider consumption
5523
7372
  * @throws \{Error\} If parameters cannot be resolved (invalid chains, etc.)
5524
7373
  *
@@ -5538,22 +7387,32 @@ function resolveConfig(params) {
5538
7387
  * ```
5539
7388
  */
5540
7389
  async function resolveBridgeParams(params) {
5541
- const fromChain = resolveChainDefinition(params.from);
5542
- 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);
5543
7395
  // Validate adapter chain support after resolution
5544
- // This ensures adapters support the resolved chains before proceeding
5545
7396
  params.from.adapter.validateChainSupport(fromChain);
5546
- 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
5547
7403
  const [fromAddress, toAddress] = await Promise.all([
5548
7404
  resolveAddress(params.from),
5549
- resolveAddress(params.to),
7405
+ isForwarderOnly
7406
+ ? Promise.resolve(params.to.recipientAddress)
7407
+ : resolveAddress(params.to),
5550
7408
  ]);
5551
7409
  const token = params.token ?? 'USDC';
5552
- // Extract adapters - now always from explicit contexts
5553
- const fromAdapter = params.from.adapter;
5554
- const toAdapter = params.to.adapter;
5555
7410
  // Extract recipientAddress from params.to if it exists
5556
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);
5557
7416
  return {
5558
7417
  amount: resolveAmount({
5559
7418
  ...params,
@@ -5563,16 +7422,21 @@ async function resolveBridgeParams(params) {
5563
7422
  config: resolveConfig({
5564
7423
  ...params}),
5565
7424
  source: {
5566
- adapter: fromAdapter,
7425
+ adapter: params.from.adapter,
5567
7426
  chain: fromChain,
5568
7427
  address: fromAddress,
5569
7428
  },
5570
7429
  destination: {
5571
- adapter: toAdapter,
7430
+ // Only include adapter if it exists (not forwarder-only)
7431
+ ...(!isForwarderOnly &&
7432
+ 'adapter' in params.to && { adapter: params.to.adapter }),
5572
7433
  chain: toChain,
5573
7434
  address: toAddress,
5574
7435
  ...(recipientAddress !== undefined && { recipientAddress }),
7436
+ ...(useForwarder !== undefined && { useForwarder }),
5575
7437
  },
7438
+ // Pass resolved InvocationContext as invocationMeta (superset is compatible)
7439
+ invocationMeta: resolvedInvocation,
5576
7440
  };
5577
7441
  }
5578
7442
 
@@ -5650,6 +7514,36 @@ const formatBridgeResult = (result, formatDirection) => {
5650
7514
  };
5651
7515
  };
5652
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
+ }
5653
7547
  /**
5654
7548
  * Route cross-chain USDC bridging through Circle's Cross-Chain Transfer Protocol v2 (CCTPv2).
5655
7549
  *
@@ -5746,7 +7640,7 @@ class BridgeKit {
5746
7640
  * - CCTPv2 support for the chain pair
5747
7641
  * - Transfer configuration options
5748
7642
  *
5749
- * @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
5750
7644
  * @returns Promise resolving to the transfer result with transaction details and steps
5751
7645
  * @throws {KitError} When any parameter validation fails.
5752
7646
  * @throws {Error} When CCTPv2 does not support the specified route.
@@ -5763,18 +7657,24 @@ class BridgeKit {
5763
7657
  * privateKey: process.env.PRIVATE_KEY,
5764
7658
  * })
5765
7659
  *
7660
+ * // Basic usage
5766
7661
  * const result = await kit.bridge({
5767
- * from: {
5768
- * adapter,
5769
- * chain: 'Ethereum'
5770
- * },
5771
- * to: {
5772
- * adapter,
5773
- * chain: 'Base'
5774
- * },
7662
+ * from: { adapter, chain: 'Ethereum' },
7663
+ * to: { adapter, chain: 'Base' },
5775
7664
  * amount: '100.50'
5776
7665
  * })
5777
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
+ *
5778
7678
  * // Handle result
5779
7679
  * if (result.state === 'success') {
5780
7680
  * console.log('Bridge completed!')
@@ -5820,6 +7720,8 @@ class BridgeKit {
5820
7720
  * @param context - The retry context containing fresh adapter instances for both
5821
7721
  * source and destination chains. These adapters should be properly
5822
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.
5823
7725
  * @returns A promise that resolves to the updated bridge result after retry execution.
5824
7726
  * The result will contain the complete step history including both original
5825
7727
  * and retry attempts.
@@ -5851,31 +7753,35 @@ class BridgeKit {
5851
7753
  * // ... other properties
5852
7754
  * }
5853
7755
  *
7756
+ * // Basic retry (uses traceId from original result)
7757
+ * const retryResult = await kit.retry(failedResult, {
7758
+ * from: sourceAdapter,
7759
+ * to: destAdapter
7760
+ * })
5854
7761
  *
5855
- * try {
5856
- * const retryResult = await kit.retry(failedResult, {
5857
- * from: sourceAdapter,
5858
- * to: destAdapter
5859
- * })
5860
- *
5861
- * console.log('Retry completed successfully:', retryResult.state)
5862
- * console.log('Total steps executed:', retryResult.steps.length)
5863
- * } catch (error) {
5864
- * console.error('Retry failed:', error.message)
5865
- * // Handle retry failure (may require manual intervention)
5866
- * }
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
+ * )
5867
7771
  * ```
5868
7772
  */
5869
- async retry(result, context) {
7773
+ async retry(result, context, invocationMeta) {
5870
7774
  const provider = this.providers.find((p) => p.name === result.provider);
5871
7775
  if (!provider) {
5872
7776
  throw new Error(`Provider ${result.provider} not found`);
5873
7777
  }
7778
+ // Merge BridgeKit caller into invocation metadata for retry operation
7779
+ const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
5874
7780
  // Format the bridge result into bigint string values for internal use
5875
7781
  const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
5876
7782
  // Execute the retry using the provider
5877
7783
  // Format the bridge result into human-readable string values for the user
5878
- return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context), 'to-human-readable');
7784
+ return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
5879
7785
  }
5880
7786
  /**
5881
7787
  * Estimate the cost and fees for a cross-chain USDC bridge operation.
@@ -5883,13 +7789,15 @@ class BridgeKit {
5883
7789
  * This method calculates the expected gas fees and protocol costs for bridging
5884
7790
  * without actually executing the transaction. It performs the same validation
5885
7791
  * as the bridge method but stops before execution.
5886
- * @param params - The bridge parameters for cost estimation
7792
+ *
7793
+ * @param params - The bridge parameters for cost estimation, including optional invocation metadata
5887
7794
  * @returns Promise resolving to detailed cost breakdown including gas estimates
5888
7795
  * @throws {KitError} When the parameters are invalid.
5889
7796
  * @throws {UnsupportedRouteError} When the route is not supported.
5890
7797
  *
5891
7798
  * @example
5892
7799
  * ```typescript
7800
+ * // Basic usage
5893
7801
  * const estimate = await kit.estimate({
5894
7802
  * from: { adapter: adapter, chain: 'Ethereum' },
5895
7803
  * to: { adapter: adapter, chain: 'Base' },
@@ -5897,6 +7805,18 @@ class BridgeKit {
5897
7805
  * token: 'USDC'
5898
7806
  * })
5899
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
+ * })
5900
7820
  * ```
5901
7821
  */
5902
7822
  async estimate(params) {
@@ -5948,6 +7868,9 @@ class BridgeKit {
5948
7868
  * // Get only EVM mainnet chains
5949
7869
  * const evmMainnets = kit.getSupportedChains({ chainType: 'evm', isTestnet: false })
5950
7870
  *
7871
+ * // Get only chains that support forwarding
7872
+ * const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
7873
+ *
5951
7874
  * console.log('Supported chains:')
5952
7875
  * allChains.forEach(chain => {
5953
7876
  * console.log(`- ${chain.name} (${chain.type})`)
@@ -5981,6 +7904,18 @@ class BridgeKit {
5981
7904
  if (options?.isTestnet !== undefined) {
5982
7905
  chains = chains.filter((chain) => chain.isTestnet === options.isTestnet);
5983
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
+ }
5984
7919
  return chains;
5985
7920
  }
5986
7921
  /**
@@ -6168,5 +8103,5 @@ class BridgeKit {
6168
8103
  // Auto-register this kit for user agent tracking
6169
8104
  registerKit(`${pkg.name}/${pkg.version}`);
6170
8105
 
6171
- 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 };
6172
8107
  //# sourceMappingURL=index.mjs.map