@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.cjs CHANGED
@@ -23,8 +23,13 @@ require('@ethersproject/bytes');
23
23
  require('@ethersproject/address');
24
24
  require('bs58');
25
25
  var units = require('@ethersproject/units');
26
+ var pino = require('pino');
26
27
  var providerCctpV2 = require('@circle-fin/provider-cctp-v2');
27
28
 
29
+ function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
30
+
31
+ var pino__default = /*#__PURE__*/_interopDefault(pino);
32
+
28
33
  /**
29
34
  * Detect the runtime environment and return a shortened identifier.
30
35
  *
@@ -122,6 +127,8 @@ const ERROR_TYPES = {
122
127
  RPC: 'RPC',
123
128
  /** Internet connectivity, DNS resolution, connection issues */
124
129
  NETWORK: 'NETWORK',
130
+ /** Catch-all for unrecognized errors (code 0) */
131
+ UNKNOWN: 'UNKNOWN',
125
132
  };
126
133
  /**
127
134
  * Array of valid error type values for validation.
@@ -135,6 +142,8 @@ const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
135
142
  /**
136
143
  * Error code ranges for validation.
137
144
  * Single source of truth for valid error code ranges.
145
+ *
146
+ * Note: Code 0 is special - it's the UNKNOWN catch-all error.
138
147
  */
139
148
  const ERROR_CODE_RANGES = [
140
149
  { min: 1000, max: 1999, type: 'INPUT' },
@@ -143,6 +152,8 @@ const ERROR_CODE_RANGES = [
143
152
  { min: 5000, max: 5999, type: 'ONCHAIN' },
144
153
  { min: 9000, max: 9999, type: 'BALANCE' },
145
154
  ];
155
+ /** Special code for UNKNOWN errors */
156
+ const UNKNOWN_ERROR_CODE = 0;
146
157
  /**
147
158
  * Zod schema for validating ErrorDetails objects.
148
159
  *
@@ -181,6 +192,7 @@ const ERROR_CODE_RANGES = [
181
192
  const errorDetailsSchema = zod.z.object({
182
193
  /**
183
194
  * Numeric identifier following standardized ranges:
195
+ * - 0: UNKNOWN - Catch-all for unrecognized errors
184
196
  * - 1000-1999: INPUT errors - Parameter validation
185
197
  * - 3000-3999: NETWORK errors - Connectivity issues
186
198
  * - 4000-4999: RPC errors - Provider issues, gas estimation
@@ -190,8 +202,9 @@ const errorDetailsSchema = zod.z.object({
190
202
  code: zod.z
191
203
  .number()
192
204
  .int('Error code must be an integer')
193
- .refine((code) => ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
194
- message: 'Error code must be in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
205
+ .refine((code) => code === UNKNOWN_ERROR_CODE ||
206
+ ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
207
+ 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)',
195
208
  }),
196
209
  /** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
197
210
  name: zod.z
@@ -201,7 +214,7 @@ const errorDetailsSchema = zod.z.object({
201
214
  /** Error category indicating where the error originated */
202
215
  type: zod.z.enum(ERROR_TYPE_ARRAY, {
203
216
  errorMap: () => ({
204
- message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
217
+ message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK, UNKNOWN',
205
218
  }),
206
219
  }),
207
220
  /** Error handling strategy */
@@ -418,6 +431,7 @@ class KitError extends Error {
418
431
  /**
419
432
  * Standardized error code ranges for consistent categorization:
420
433
  *
434
+ * - 0: UNKNOWN - Catch-all for unrecognized errors
421
435
  * - 1000-1999: INPUT errors - Parameter validation, input format errors
422
436
  * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
423
437
  * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
@@ -646,6 +660,18 @@ const NetworkError = {
646
660
  name: 'NETWORK_TIMEOUT',
647
661
  type: 'NETWORK',
648
662
  },
663
+ /** Circle relayer failed to process the forwarding/mint transaction */
664
+ RELAYER_FORWARD_FAILED: {
665
+ code: 3003,
666
+ name: 'NETWORK_RELAYER_FORWARD_FAILED',
667
+ type: 'NETWORK',
668
+ },
669
+ /** Relayer mint is pending - waiting for confirmation */
670
+ RELAYER_PENDING: {
671
+ code: 3004,
672
+ name: 'NETWORK_RELAYER_PENDING',
673
+ type: 'NETWORK',
674
+ },
649
675
  };
650
676
 
651
677
  /**
@@ -966,6 +992,8 @@ exports.Blockchain = void 0;
966
992
  Blockchain["Ink_Testnet"] = "Ink_Testnet";
967
993
  Blockchain["Linea"] = "Linea";
968
994
  Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
995
+ Blockchain["Monad"] = "Monad";
996
+ Blockchain["Monad_Testnet"] = "Monad_Testnet";
969
997
  Blockchain["NEAR"] = "NEAR";
970
998
  Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
971
999
  Blockchain["Noble"] = "Noble";
@@ -1057,6 +1085,7 @@ exports.BridgeChain = void 0;
1057
1085
  BridgeChain["HyperEVM"] = "HyperEVM";
1058
1086
  BridgeChain["Ink"] = "Ink";
1059
1087
  BridgeChain["Linea"] = "Linea";
1088
+ BridgeChain["Monad"] = "Monad";
1060
1089
  BridgeChain["Optimism"] = "Optimism";
1061
1090
  BridgeChain["Plume"] = "Plume";
1062
1091
  BridgeChain["Polygon"] = "Polygon";
@@ -1076,6 +1105,7 @@ exports.BridgeChain = void 0;
1076
1105
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
1077
1106
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
1078
1107
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
1108
+ BridgeChain["Monad_Testnet"] = "Monad_Testnet";
1079
1109
  BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
1080
1110
  BridgeChain["Plume_Testnet"] = "Plume_Testnet";
1081
1111
  BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
@@ -1202,6 +1232,10 @@ const Aptos = defineChain({
1202
1232
  confirmations: 1,
1203
1233
  },
1204
1234
  },
1235
+ forwarderSupported: {
1236
+ source: false,
1237
+ destination: false,
1238
+ },
1205
1239
  },
1206
1240
  });
1207
1241
 
@@ -1235,6 +1269,10 @@ const AptosTestnet = defineChain({
1235
1269
  confirmations: 1,
1236
1270
  },
1237
1271
  },
1272
+ forwarderSupported: {
1273
+ source: false,
1274
+ destination: false,
1275
+ },
1238
1276
  },
1239
1277
  });
1240
1278
 
@@ -1294,6 +1332,10 @@ const ArcTestnet = defineChain({
1294
1332
  fastConfirmations: 1,
1295
1333
  },
1296
1334
  },
1335
+ forwarderSupported: {
1336
+ source: true,
1337
+ destination: true,
1338
+ },
1297
1339
  },
1298
1340
  kitContracts: {
1299
1341
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1338,6 +1380,10 @@ const Arbitrum = defineChain({
1338
1380
  fastConfirmations: 1,
1339
1381
  },
1340
1382
  },
1383
+ forwarderSupported: {
1384
+ source: true,
1385
+ destination: true,
1386
+ },
1341
1387
  },
1342
1388
  kitContracts: {
1343
1389
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1382,6 +1428,10 @@ const ArbitrumSepolia = defineChain({
1382
1428
  fastConfirmations: 1,
1383
1429
  },
1384
1430
  },
1431
+ forwarderSupported: {
1432
+ source: true,
1433
+ destination: true,
1434
+ },
1385
1435
  },
1386
1436
  kitContracts: {
1387
1437
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1426,6 +1476,10 @@ const Avalanche = defineChain({
1426
1476
  fastConfirmations: 1,
1427
1477
  },
1428
1478
  },
1479
+ forwarderSupported: {
1480
+ source: true,
1481
+ destination: true,
1482
+ },
1429
1483
  },
1430
1484
  kitContracts: {
1431
1485
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1469,6 +1523,10 @@ const AvalancheFuji = defineChain({
1469
1523
  fastConfirmations: 1,
1470
1524
  },
1471
1525
  },
1526
+ forwarderSupported: {
1527
+ source: true,
1528
+ destination: true,
1529
+ },
1472
1530
  },
1473
1531
  rpcEndpoints: ['https://api.avax-test.network/ext/bc/C/rpc'],
1474
1532
  kitContracts: {
@@ -1514,6 +1572,10 @@ const Base = defineChain({
1514
1572
  fastConfirmations: 1,
1515
1573
  },
1516
1574
  },
1575
+ forwarderSupported: {
1576
+ source: true,
1577
+ destination: true,
1578
+ },
1517
1579
  },
1518
1580
  kitContracts: {
1519
1581
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1558,6 +1620,10 @@ const BaseSepolia = defineChain({
1558
1620
  fastConfirmations: 1,
1559
1621
  },
1560
1622
  },
1623
+ forwarderSupported: {
1624
+ source: true,
1625
+ destination: true,
1626
+ },
1561
1627
  },
1562
1628
  kitContracts: {
1563
1629
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1644,6 +1710,10 @@ const Codex = defineChain({
1644
1710
  fastConfirmations: 1,
1645
1711
  },
1646
1712
  },
1713
+ forwarderSupported: {
1714
+ source: true,
1715
+ destination: false,
1716
+ },
1647
1717
  },
1648
1718
  kitContracts: {
1649
1719
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1682,6 +1752,10 @@ const CodexTestnet = defineChain({
1682
1752
  fastConfirmations: 1,
1683
1753
  },
1684
1754
  },
1755
+ forwarderSupported: {
1756
+ source: true,
1757
+ destination: false,
1758
+ },
1685
1759
  },
1686
1760
  kitContracts: {
1687
1761
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1726,6 +1800,10 @@ const Ethereum = defineChain({
1726
1800
  fastConfirmations: 2,
1727
1801
  },
1728
1802
  },
1803
+ forwarderSupported: {
1804
+ source: true,
1805
+ destination: true,
1806
+ },
1729
1807
  },
1730
1808
  kitContracts: {
1731
1809
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1770,6 +1848,10 @@ const EthereumSepolia = defineChain({
1770
1848
  fastConfirmations: 2,
1771
1849
  },
1772
1850
  },
1851
+ forwarderSupported: {
1852
+ source: true,
1853
+ destination: true,
1854
+ },
1773
1855
  },
1774
1856
  kitContracts: {
1775
1857
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1856,6 +1938,10 @@ const HyperEVM = defineChain({
1856
1938
  fastConfirmations: 1,
1857
1939
  },
1858
1940
  },
1941
+ forwarderSupported: {
1942
+ source: true,
1943
+ destination: true,
1944
+ },
1859
1945
  },
1860
1946
  kitContracts: {
1861
1947
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1895,6 +1981,10 @@ const HyperEVMTestnet = defineChain({
1895
1981
  fastConfirmations: 1,
1896
1982
  },
1897
1983
  },
1984
+ forwarderSupported: {
1985
+ source: true,
1986
+ destination: true,
1987
+ },
1898
1988
  },
1899
1989
  kitContracts: {
1900
1990
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -1938,6 +2028,10 @@ const Ink = defineChain({
1938
2028
  fastConfirmations: 1,
1939
2029
  },
1940
2030
  },
2031
+ forwarderSupported: {
2032
+ source: true,
2033
+ destination: true,
2034
+ },
1941
2035
  },
1942
2036
  kitContracts: {
1943
2037
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -1980,6 +2074,10 @@ const InkTestnet = defineChain({
1980
2074
  fastConfirmations: 1,
1981
2075
  },
1982
2076
  },
2077
+ forwarderSupported: {
2078
+ source: true,
2079
+ destination: true,
2080
+ },
1983
2081
  },
1984
2082
  kitContracts: {
1985
2083
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2018,6 +2116,10 @@ const Linea = defineChain({
2018
2116
  fastConfirmations: 1,
2019
2117
  },
2020
2118
  },
2119
+ forwarderSupported: {
2120
+ source: true,
2121
+ destination: true,
2122
+ },
2021
2123
  },
2022
2124
  kitContracts: {
2023
2125
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2056,6 +2158,98 @@ const LineaSepolia = defineChain({
2056
2158
  fastConfirmations: 1,
2057
2159
  },
2058
2160
  },
2161
+ forwarderSupported: {
2162
+ source: true,
2163
+ destination: true,
2164
+ },
2165
+ },
2166
+ kitContracts: {
2167
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
2168
+ },
2169
+ });
2170
+
2171
+ /**
2172
+ * Monad Mainnet chain definition
2173
+ * @remarks
2174
+ * This represents the official production network for the Monad blockchain.
2175
+ * Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
2176
+ * over 10,000 TPS, sub-second finality, and near-zero gas fees.
2177
+ */
2178
+ const Monad = defineChain({
2179
+ type: 'evm',
2180
+ chain: exports.Blockchain.Monad,
2181
+ name: 'Monad',
2182
+ title: 'Monad Mainnet',
2183
+ nativeCurrency: {
2184
+ name: 'Monad',
2185
+ symbol: 'MON',
2186
+ decimals: 18,
2187
+ },
2188
+ chainId: 143,
2189
+ isTestnet: false,
2190
+ explorerUrl: 'https://monadscan.com/tx/{hash}',
2191
+ rpcEndpoints: ['https://rpc.monad.xyz'],
2192
+ eurcAddress: null,
2193
+ usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
2194
+ cctp: {
2195
+ domain: 15,
2196
+ contracts: {
2197
+ v2: {
2198
+ type: 'split',
2199
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
2200
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
2201
+ confirmations: 1,
2202
+ fastConfirmations: 1,
2203
+ },
2204
+ },
2205
+ forwarderSupported: {
2206
+ source: true,
2207
+ destination: true,
2208
+ },
2209
+ },
2210
+ kitContracts: {
2211
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2212
+ },
2213
+ });
2214
+
2215
+ /**
2216
+ * Monad Testnet chain definition
2217
+ * @remarks
2218
+ * This represents the official test network for the Monad blockchain.
2219
+ * Monad is a high-performance EVM-compatible Layer-1 blockchain featuring
2220
+ * over 10,000 TPS, sub-second finality, and near-zero gas fees.
2221
+ */
2222
+ const MonadTestnet = defineChain({
2223
+ type: 'evm',
2224
+ chain: exports.Blockchain.Monad_Testnet,
2225
+ name: 'Monad Testnet',
2226
+ title: 'Monad Testnet',
2227
+ nativeCurrency: {
2228
+ name: 'Monad',
2229
+ symbol: 'MON',
2230
+ decimals: 18,
2231
+ },
2232
+ chainId: 10143,
2233
+ isTestnet: true,
2234
+ explorerUrl: 'https://testnet.monadscan.com/tx/{hash}',
2235
+ rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
2236
+ eurcAddress: null,
2237
+ usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
2238
+ cctp: {
2239
+ domain: 15,
2240
+ contracts: {
2241
+ v2: {
2242
+ type: 'split',
2243
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
2244
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
2245
+ confirmations: 1,
2246
+ fastConfirmations: 1,
2247
+ },
2248
+ },
2249
+ forwarderSupported: {
2250
+ source: true,
2251
+ destination: true,
2252
+ },
2059
2253
  },
2060
2254
  kitContracts: {
2061
2255
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2137,6 +2331,10 @@ const Noble = defineChain({
2137
2331
  confirmations: 1,
2138
2332
  },
2139
2333
  },
2334
+ forwarderSupported: {
2335
+ source: false,
2336
+ destination: false,
2337
+ },
2140
2338
  },
2141
2339
  });
2142
2340
 
@@ -2169,6 +2367,10 @@ const NobleTestnet = defineChain({
2169
2367
  confirmations: 1,
2170
2368
  },
2171
2369
  },
2370
+ forwarderSupported: {
2371
+ source: false,
2372
+ destination: false,
2373
+ },
2172
2374
  },
2173
2375
  });
2174
2376
 
@@ -2210,6 +2412,10 @@ const Optimism = defineChain({
2210
2412
  fastConfirmations: 1,
2211
2413
  },
2212
2414
  },
2415
+ forwarderSupported: {
2416
+ source: true,
2417
+ destination: true,
2418
+ },
2213
2419
  },
2214
2420
  kitContracts: {
2215
2421
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2254,6 +2460,10 @@ const OptimismSepolia = defineChain({
2254
2460
  fastConfirmations: 1,
2255
2461
  },
2256
2462
  },
2463
+ forwarderSupported: {
2464
+ source: true,
2465
+ destination: true,
2466
+ },
2257
2467
  },
2258
2468
  kitContracts: {
2259
2469
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2294,6 +2504,10 @@ const Plume = defineChain({
2294
2504
  fastConfirmations: 1,
2295
2505
  },
2296
2506
  },
2507
+ forwarderSupported: {
2508
+ source: true,
2509
+ destination: false,
2510
+ },
2297
2511
  },
2298
2512
  kitContracts: {
2299
2513
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2333,6 +2547,10 @@ const PlumeTestnet = defineChain({
2333
2547
  fastConfirmations: 1,
2334
2548
  },
2335
2549
  },
2550
+ forwarderSupported: {
2551
+ source: true,
2552
+ destination: false,
2553
+ },
2336
2554
  },
2337
2555
  kitContracts: {
2338
2556
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2423,6 +2641,10 @@ const Polygon = defineChain({
2423
2641
  fastConfirmations: 13,
2424
2642
  },
2425
2643
  },
2644
+ forwarderSupported: {
2645
+ source: true,
2646
+ destination: true,
2647
+ },
2426
2648
  },
2427
2649
  kitContracts: {
2428
2650
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2467,6 +2689,10 @@ const PolygonAmoy = defineChain({
2467
2689
  fastConfirmations: 13,
2468
2690
  },
2469
2691
  },
2692
+ forwarderSupported: {
2693
+ source: true,
2694
+ destination: true,
2695
+ },
2470
2696
  },
2471
2697
  kitContracts: {
2472
2698
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2507,6 +2733,10 @@ const Sei = defineChain({
2507
2733
  fastConfirmations: 1,
2508
2734
  },
2509
2735
  },
2736
+ forwarderSupported: {
2737
+ source: true,
2738
+ destination: true,
2739
+ },
2510
2740
  },
2511
2741
  kitContracts: {
2512
2742
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2546,6 +2776,10 @@ const SeiTestnet = defineChain({
2546
2776
  fastConfirmations: 1,
2547
2777
  },
2548
2778
  },
2779
+ forwarderSupported: {
2780
+ source: true,
2781
+ destination: true,
2782
+ },
2549
2783
  },
2550
2784
  kitContracts: {
2551
2785
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2584,6 +2818,10 @@ const Sonic = defineChain({
2584
2818
  fastConfirmations: 1,
2585
2819
  },
2586
2820
  },
2821
+ forwarderSupported: {
2822
+ source: true,
2823
+ destination: true,
2824
+ },
2587
2825
  },
2588
2826
  kitContracts: {
2589
2827
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2622,6 +2860,10 @@ const SonicTestnet = defineChain({
2622
2860
  fastConfirmations: 1,
2623
2861
  },
2624
2862
  },
2863
+ forwarderSupported: {
2864
+ source: true,
2865
+ destination: true,
2866
+ },
2625
2867
  },
2626
2868
  kitContracts: {
2627
2869
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2665,6 +2907,10 @@ const Solana = defineChain({
2665
2907
  fastConfirmations: 3,
2666
2908
  },
2667
2909
  },
2910
+ forwarderSupported: {
2911
+ source: true,
2912
+ destination: false,
2913
+ },
2668
2914
  },
2669
2915
  kitContracts: {
2670
2916
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
@@ -2707,6 +2953,10 @@ const SolanaDevnet = defineChain({
2707
2953
  fastConfirmations: 3,
2708
2954
  },
2709
2955
  },
2956
+ forwarderSupported: {
2957
+ source: true,
2958
+ destination: false,
2959
+ },
2710
2960
  },
2711
2961
  kitContracts: {
2712
2962
  bridge: 'DFaauJEjmiHkPs1JG89A4p95hDWi9m9SAEERY1LQJiC3',
@@ -2790,6 +3040,10 @@ const Sui = defineChain({
2790
3040
  confirmations: 1,
2791
3041
  },
2792
3042
  },
3043
+ forwarderSupported: {
3044
+ source: false,
3045
+ destination: false,
3046
+ },
2793
3047
  },
2794
3048
  });
2795
3049
 
@@ -2823,6 +3077,10 @@ const SuiTestnet = defineChain({
2823
3077
  confirmations: 1,
2824
3078
  },
2825
3079
  },
3080
+ forwarderSupported: {
3081
+ source: false,
3082
+ destination: false,
3083
+ },
2826
3084
  },
2827
3085
  });
2828
3086
 
@@ -2844,7 +3102,7 @@ const Unichain = defineChain({
2844
3102
  chainId: 130,
2845
3103
  isTestnet: false,
2846
3104
  explorerUrl: 'https://unichain.blockscout.com/tx/{hash}',
2847
- rpcEndpoints: ['https://rpc.unichain.org', 'https://mainnet.unichain.org'],
3105
+ rpcEndpoints: ['https://mainnet.unichain.org'],
2848
3106
  eurcAddress: null,
2849
3107
  usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
2850
3108
  cctp: {
@@ -2864,6 +3122,10 @@ const Unichain = defineChain({
2864
3122
  fastConfirmations: 1,
2865
3123
  },
2866
3124
  },
3125
+ forwarderSupported: {
3126
+ source: true,
3127
+ destination: true,
3128
+ },
2867
3129
  },
2868
3130
  kitContracts: {
2869
3131
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2908,6 +3170,10 @@ const UnichainSepolia = defineChain({
2908
3170
  fastConfirmations: 1,
2909
3171
  },
2910
3172
  },
3173
+ forwarderSupported: {
3174
+ source: true,
3175
+ destination: true,
3176
+ },
2911
3177
  },
2912
3178
  kitContracts: {
2913
3179
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -2934,7 +3200,7 @@ const WorldChain = defineChain({
2934
3200
  explorerUrl: 'https://worldscan.org/tx/{hash}',
2935
3201
  rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
2936
3202
  eurcAddress: null,
2937
- usdcAddress: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
3203
+ usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
2938
3204
  cctp: {
2939
3205
  domain: 14,
2940
3206
  contracts: {
@@ -2946,6 +3212,10 @@ const WorldChain = defineChain({
2946
3212
  fastConfirmations: 1,
2947
3213
  },
2948
3214
  },
3215
+ forwarderSupported: {
3216
+ source: true,
3217
+ destination: true,
3218
+ },
2949
3219
  },
2950
3220
  kitContracts: {
2951
3221
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -2987,6 +3257,10 @@ const WorldChainSepolia = defineChain({
2987
3257
  fastConfirmations: 1,
2988
3258
  },
2989
3259
  },
3260
+ forwarderSupported: {
3261
+ source: true,
3262
+ destination: true,
3263
+ },
2990
3264
  },
2991
3265
  kitContracts: {
2992
3266
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3013,7 +3287,7 @@ const XDC = defineChain({
3013
3287
  chainId: 50,
3014
3288
  isTestnet: false,
3015
3289
  explorerUrl: 'https://xdcscan.io/tx/{hash}',
3016
- rpcEndpoints: ['https://erpc.xinfin.network'],
3290
+ rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
3017
3291
  eurcAddress: null,
3018
3292
  usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
3019
3293
  cctp: {
@@ -3027,6 +3301,10 @@ const XDC = defineChain({
3027
3301
  fastConfirmations: 3,
3028
3302
  },
3029
3303
  },
3304
+ forwarderSupported: {
3305
+ source: true,
3306
+ destination: false,
3307
+ },
3030
3308
  },
3031
3309
  kitContracts: {
3032
3310
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
@@ -3065,6 +3343,10 @@ const XDCApothem = defineChain({
3065
3343
  fastConfirmations: 1,
3066
3344
  },
3067
3345
  },
3346
+ forwarderSupported: {
3347
+ source: true,
3348
+ destination: false,
3349
+ },
3068
3350
  },
3069
3351
  kitContracts: {
3070
3352
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
@@ -3146,6 +3428,8 @@ var Blockchains = {
3146
3428
  InkTestnet: InkTestnet,
3147
3429
  Linea: Linea,
3148
3430
  LineaSepolia: LineaSepolia,
3431
+ Monad: Monad,
3432
+ MonadTestnet: MonadTestnet,
3149
3433
  NEAR: NEAR,
3150
3434
  NEARTestnet: NEARTestnet,
3151
3435
  Noble: Noble,
@@ -3299,7 +3583,7 @@ const nonEvmChainDefinitionSchema = baseChainDefinitionSchema
3299
3583
  * })
3300
3584
  * ```
3301
3585
  */
3302
- const chainDefinitionSchema$1 = zod.z.discriminatedUnion('type', [
3586
+ const chainDefinitionSchema$2 = zod.z.discriminatedUnion('type', [
3303
3587
  evmChainDefinitionSchema,
3304
3588
  nonEvmChainDefinitionSchema,
3305
3589
  ]);
@@ -3324,7 +3608,7 @@ zod.z.union([
3324
3608
  .string()
3325
3609
  .refine((val) => val in exports.Blockchain, 'Must be a valid Blockchain enum value as string'),
3326
3610
  zod.z.nativeEnum(exports.Blockchain),
3327
- chainDefinitionSchema$1,
3611
+ chainDefinitionSchema$2,
3328
3612
  ]);
3329
3613
  /**
3330
3614
  * Zod schema for validating bridge chain identifiers.
@@ -3360,7 +3644,7 @@ const bridgeChainIdentifierSchema = zod.z.union([
3360
3644
  zod.z.string().refine((val) => val in exports.BridgeChain, (val) => ({
3361
3645
  message: `Chain "${val}" is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3362
3646
  })),
3363
- chainDefinitionSchema$1.refine((chainDef) => chainDef.chain in exports.BridgeChain, (chainDef) => ({
3647
+ chainDefinitionSchema$2.refine((chainDef) => chainDef.chain in exports.BridgeChain, (chainDef) => ({
3364
3648
  message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3365
3649
  })),
3366
3650
  ]);
@@ -3557,14 +3841,41 @@ function isFatalError(error) {
3557
3841
  return isKitError(error) && error.recoverability === 'FATAL';
3558
3842
  }
3559
3843
  /**
3560
- * Checks if an error is a KitError with RETRYABLE recoverability.
3844
+ * Error codes that are considered retryable by default.
3845
+ *
3846
+ * @remarks
3847
+ * These are typically transient errors that may succeed on retry:
3848
+ * - Network connectivity issues (3001, 3002)
3849
+ * - Provider unavailability (4001, 4002)
3850
+ * - RPC nonce errors (4003)
3851
+ */
3852
+ const DEFAULT_RETRYABLE_ERROR_CODES = [
3853
+ // Network errors
3854
+ 3001, // NETWORK_CONNECTION_FAILED
3855
+ 3002, // NETWORK_TIMEOUT
3856
+ // Provider errors
3857
+ 4001, // PROVIDER_UNAVAILABLE
3858
+ 4002, // PROVIDER_TIMEOUT
3859
+ 4003, // RPC_NONCE_ERROR
3860
+ ];
3861
+ /**
3862
+ * Checks if an error is retryable.
3863
+ *
3864
+ * @remarks
3865
+ * Check order for KitError instances:
3866
+ * 1. If `recoverability === 'RETRYABLE'`, return `true` immediately (priority check).
3867
+ * 2. Otherwise, check if `error.code` is in `DEFAULT_RETRYABLE_ERROR_CODES` (fallback check).
3868
+ * 3. Non-KitError instances always return `false`.
3869
+ *
3870
+ * This two-tier approach allows both explicit recoverability control and
3871
+ * backward-compatible code-based retry logic.
3561
3872
  *
3562
3873
  * RETRYABLE errors indicate transient failures that may succeed on
3563
3874
  * subsequent attempts, such as network timeouts or temporary service
3564
3875
  * unavailability. These errors are safe to retry after a delay.
3565
3876
  *
3566
3877
  * @param error - Unknown error to check
3567
- * @returns True if error is a KitError with RETRYABLE recoverability
3878
+ * @returns True if error is retryable
3568
3879
  *
3569
3880
  * @example
3570
3881
  * ```typescript
@@ -3579,9 +3890,51 @@ function isFatalError(error) {
3579
3890
  * }
3580
3891
  * }
3581
3892
  * ```
3893
+ *
3894
+ * @example
3895
+ * ```typescript
3896
+ * import { isRetryableError, createNetworkConnectionError, KitError } from '@core/errors'
3897
+ *
3898
+ * // KitError with RETRYABLE recoverability (priority check)
3899
+ * const error1 = createNetworkConnectionError('Ethereum')
3900
+ * isRetryableError(error1) // true
3901
+ *
3902
+ * // KitError with default retryable code (fallback check)
3903
+ * const error2 = new KitError({
3904
+ * code: 3002, // NETWORK_TIMEOUT - in DEFAULT_RETRYABLE_ERROR_CODES
3905
+ * name: 'NETWORK_TIMEOUT',
3906
+ * type: 'NETWORK',
3907
+ * recoverability: 'FATAL', // Not RETRYABLE
3908
+ * message: 'Timeout',
3909
+ * })
3910
+ * isRetryableError(error2) // true (code 3002 is in default list)
3911
+ *
3912
+ * // KitError with non-retryable code and FATAL recoverability
3913
+ * const error3 = new KitError({
3914
+ * code: 1001,
3915
+ * name: 'INVALID_INPUT',
3916
+ * type: 'INPUT',
3917
+ * recoverability: 'FATAL',
3918
+ * message: 'Invalid input',
3919
+ * })
3920
+ * isRetryableError(error3) // false
3921
+ *
3922
+ * // Non-KitError
3923
+ * const error4 = new Error('Standard error')
3924
+ * isRetryableError(error4) // false
3925
+ * ```
3582
3926
  */
3583
3927
  function isRetryableError(error) {
3584
- return isKitError(error) && error.recoverability === 'RETRYABLE';
3928
+ // Use proper type guard to check if it's a KitError
3929
+ if (isKitError(error)) {
3930
+ // Priority check: explicit recoverability
3931
+ if (error.recoverability === 'RETRYABLE') {
3932
+ return true;
3933
+ }
3934
+ // Fallback check: error code against default retryable codes
3935
+ return DEFAULT_RETRYABLE_ERROR_CODES.includes(error.code);
3936
+ }
3937
+ return false;
3585
3938
  }
3586
3939
  /**
3587
3940
  * Type guard to check if error is KitError with INPUT type.
@@ -3778,6 +4131,62 @@ function getErrorMessage(error) {
3778
4131
  function getErrorCode(error) {
3779
4132
  return isKitError(error) ? error.code : null;
3780
4133
  }
4134
+ /**
4135
+ * Extract structured error information for logging and events.
4136
+ *
4137
+ * @remarks
4138
+ * Safely extracts error information from any thrown value, handling:
4139
+ * - KitError objects (message preserved - safe to log)
4140
+ * - Standard Error objects (message redacted for security)
4141
+ * - Plain objects with name/message/code (message redacted)
4142
+ * - Primitive values (logged as-is since they contain no structured data)
4143
+ *
4144
+ * For security, only KitError messages are included. Other error messages are
4145
+ * redacted to prevent logging sensitive data like tokens or PII.
4146
+ *
4147
+ * @param error - The error to extract info from.
4148
+ * @returns Structured error information.
4149
+ *
4150
+ * @example
4151
+ * ```typescript
4152
+ * import { extractErrorInfo } from '@core/errors'
4153
+ *
4154
+ * // Standard Error - message is redacted for security
4155
+ * const info1 = extractErrorInfo(new Error('Something went wrong'))
4156
+ * // { name: 'Error', message: 'An error occurred. See error name and code for details.' }
4157
+ *
4158
+ * // KitError - message is preserved (safe type)
4159
+ * const error = createNetworkConnectionError('Ethereum')
4160
+ * const info2 = extractErrorInfo(error)
4161
+ * // { name: 'NETWORK_CONNECTION_FAILED', message: 'Network connection failed for Ethereum', code: 3001 }
4162
+ *
4163
+ * // Primitive value - logged as-is
4164
+ * const info3 = extractErrorInfo('string error')
4165
+ * // { name: 'UnknownError', message: 'string error' }
4166
+ * ```
4167
+ */
4168
+ function extractErrorInfo(error) {
4169
+ if (error === null || typeof error !== 'object') {
4170
+ return {
4171
+ name: 'UnknownError',
4172
+ message: String(error),
4173
+ };
4174
+ }
4175
+ const err = error;
4176
+ // Only preserve messages for KitError instances (safe type)
4177
+ // For other errors, redact message for security
4178
+ const errorMessage = isKitError(error)
4179
+ ? getErrorMessage(error)
4180
+ : 'An error occurred. See error name and code for details.';
4181
+ const info = {
4182
+ name: err.name ?? 'UnknownError',
4183
+ message: errorMessage,
4184
+ };
4185
+ if (typeof err.code === 'number') {
4186
+ info.code = err.code;
4187
+ }
4188
+ return info;
4189
+ }
3781
4190
 
3782
4191
  /**
3783
4192
  * Validates if an address format is correct for the specified chain.
@@ -4322,7 +4731,7 @@ function validateWithStateTracking(value, schema, context, validatorName) {
4322
4731
  * Zod schema for validating chain definition objects used in buildExplorerUrl.
4323
4732
  * This schema ensures the chain definition has the required properties for URL generation.
4324
4733
  */
4325
- const chainDefinitionSchema = zod.z.object({
4734
+ const chainDefinitionSchema$1 = zod.z.object({
4326
4735
  name: zod.z
4327
4736
  .string({
4328
4737
  required_error: 'Chain name is required',
@@ -4346,15 +4755,14 @@ const transactionHashSchema = zod.z
4346
4755
  required_error: 'Transaction hash is required',
4347
4756
  invalid_type_error: 'Transaction hash must be a string',
4348
4757
  })
4349
- .min(1, 'Transaction hash cannot be empty')
4350
- .transform((hash) => hash.trim()) // Automatically trim whitespace
4351
- .refine((hash) => hash.length > 0, 'Transaction hash must not be empty or whitespace-only');
4758
+ .transform((hash) => hash.trim())
4759
+ .refine((hash) => hash.length > 0, 'Transaction hash cannot be empty');
4352
4760
  /**
4353
4761
  * Zod schema for validating buildExplorerUrl function parameters.
4354
4762
  * This schema validates both the chain definition and transaction hash together.
4355
4763
  */
4356
4764
  zod.z.object({
4357
- chainDef: chainDefinitionSchema,
4765
+ chainDef: chainDefinitionSchema$1,
4358
4766
  txHash: transactionHashSchema,
4359
4767
  });
4360
4768
  /**
@@ -4365,6 +4773,69 @@ zod.z
4365
4773
  .string()
4366
4774
  .url('Generated explorer URL is invalid');
4367
4775
 
4776
+ /**
4777
+ * Parses and validates data against a Zod schema, returning the transformed result.
4778
+ *
4779
+ * Unlike `validate` and `validateOrThrow` which use type assertions (`asserts value is T`),
4780
+ * this function **returns the parsed data** from Zod. This is important when your schema
4781
+ * includes transformations like defaults, coercion, or custom transforms.
4782
+ *
4783
+ * @typeParam T - The expected output type (caller must specify).
4784
+ * @param value - The value to parse and validate.
4785
+ * @param schema - The Zod schema to validate against.
4786
+ * @param context - Context string to include in error messages (e.g., 'user input', 'config').
4787
+ * @returns The parsed and transformed data of type T.
4788
+ * @throws KitError with INPUT_VALIDATION_FAILED code (1098) if validation fails.
4789
+ *
4790
+ * @remarks
4791
+ * This function uses `ZodTypeAny` for the schema parameter to avoid TypeScript's
4792
+ * "excessively deep type instantiation" error in generic contexts. The caller
4793
+ * must provide the expected type `T` explicitly.
4794
+ *
4795
+ * Use this instead of `validate`/`validateOrThrow` when:
4796
+ * - Your schema has `.default()` values
4797
+ * - Your schema uses `.coerce` (e.g., `z.coerce.number()`)
4798
+ * - Your schema has `.transform()` functions
4799
+ * - You need the actual parsed output, not just type narrowing
4800
+ *
4801
+ * @example
4802
+ * ```typescript
4803
+ * import { parseOrThrow } from '@core/utils'
4804
+ * import { z } from 'zod'
4805
+ *
4806
+ * const userSchema = z.object({
4807
+ * name: z.string().default('Anonymous'),
4808
+ * age: z.coerce.number(), // Transforms "25" → 25
4809
+ * })
4810
+ *
4811
+ * type User = z.infer<typeof userSchema>
4812
+ *
4813
+ * // Explicit type parameter
4814
+ * const user = parseOrThrow<User>({ age: '25' }, userSchema, 'user data')
4815
+ * console.log(user) // { name: 'Anonymous', age: 25 }
4816
+ * ```
4817
+ *
4818
+ * @example
4819
+ * ```typescript
4820
+ * // Usage in generic adapter functions (no deep type instantiation)
4821
+ * function validateInput<TInput>(
4822
+ * input: unknown,
4823
+ * schema: z.ZodTypeAny,
4824
+ * name: string,
4825
+ * ): TInput {
4826
+ * return parseOrThrow<TInput>(input, schema, `${name} input`)
4827
+ * }
4828
+ * ```
4829
+ */
4830
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Intentional: T is caller-provided to avoid deep type instantiation from schema inference
4831
+ function parseOrThrow(value, schema, context) {
4832
+ const result = schema.safeParse(value);
4833
+ if (!result.success) {
4834
+ throw createValidationErrorFromZod(result.error, context);
4835
+ }
4836
+ return result.data;
4837
+ }
4838
+
4368
4839
  /**
4369
4840
  * A type-safe event emitter for managing action-based event subscriptions.
4370
4841
  *
@@ -4639,7 +5110,7 @@ const parseAmount = (params) => {
4639
5110
  };
4640
5111
 
4641
5112
  var name = "@circle-fin/bridge-kit";
4642
- var version = "1.4.0";
5113
+ var version = "1.6.0";
4643
5114
  var pkg = {
4644
5115
  name: name,
4645
5116
  version: version};
@@ -5065,7 +5536,7 @@ const createDecimalStringValidator = (options) => (schema) => {
5065
5536
  * console.log(result.success) // true
5066
5537
  * ```
5067
5538
  */
5068
- zod.z.object({
5539
+ const chainDefinitionSchema = zod.z.object({
5069
5540
  name: zod.z.string().min(1, 'Chain name is required'),
5070
5541
  type: zod.z.string().min(1, 'Chain type is required'),
5071
5542
  });
@@ -5112,6 +5583,53 @@ const walletContextSchema = zod.z.object({
5112
5583
  isTestnet: zod.z.boolean(),
5113
5584
  }),
5114
5585
  });
5586
+ /**
5587
+ * Schema for validating destination wallet contexts.
5588
+ * Extends walletContextSchema with optional useForwarder flag.
5589
+ *
5590
+ * @throws KitError if validation fails
5591
+ */
5592
+ const destinationContextSchema = walletContextSchema.extend({
5593
+ useForwarder: zod.z.boolean().optional(),
5594
+ recipientAddress: zod.z.string().optional(),
5595
+ });
5596
+ /**
5597
+ * Schema for validating forwarder-only destination contexts.
5598
+ * Used when useForwarder is true and no adapter is provided.
5599
+ * Requires chain definition and recipientAddress.
5600
+ *
5601
+ * Validates that recipientAddress format is correct for the destination chain
5602
+ * (EVM hex address or Solana base58 address).
5603
+ *
5604
+ * @throws KitError if validation fails
5605
+ */
5606
+ const forwarderOnlyDestinationSchema = zod.z
5607
+ .object({
5608
+ chain: chainDefinitionSchema.extend({
5609
+ isTestnet: zod.z.boolean(),
5610
+ }),
5611
+ recipientAddress: zod.z
5612
+ .string()
5613
+ .min(1, 'Recipient address is required for forwarder-only destination'),
5614
+ useForwarder: zod.z.literal(true),
5615
+ })
5616
+ .superRefine((data, ctx) => {
5617
+ // Pass chain name as string - isValidAddressForChain will use name-based matching
5618
+ if (!isValidAddressForChain(data.recipientAddress, data.chain.name)) {
5619
+ ctx.addIssue({
5620
+ code: zod.z.ZodIssueCode.custom,
5621
+ message: `Invalid recipient address format for chain ${data.chain.name}`,
5622
+ path: ['recipientAddress'],
5623
+ });
5624
+ }
5625
+ });
5626
+ /**
5627
+ * Schema for validating any destination - either with adapter or forwarder-only.
5628
+ */
5629
+ const bridgeDestinationSchema$1 = zod.z.union([
5630
+ destinationContextSchema,
5631
+ forwarderOnlyDestinationSchema,
5632
+ ]);
5115
5633
  /**
5116
5634
  * Schema for validating a custom fee configuration.
5117
5635
  * Validates the simplified CustomFee interface which includes:
@@ -5209,7 +5727,7 @@ zod.z.object({
5209
5727
  maxDecimals: 6,
5210
5728
  })(zod.z.string())),
5211
5729
  source: walletContextSchema,
5212
- destination: walletContextSchema,
5730
+ destination: bridgeDestinationSchema$1,
5213
5731
  token: zod.z.literal('USDC'),
5214
5732
  config: zod.z.object({
5215
5733
  transferSpeed: zod.z.nativeEnum(exports.TransferSpeed).optional(),
@@ -5226,6 +5744,28 @@ zod.z.object({
5226
5744
  }),
5227
5745
  });
5228
5746
 
5747
+ /**
5748
+ * Creates a Zod superRefine validator for recipient address format validation.
5749
+ * Validates that the address format matches the expected format for the chain type.
5750
+ *
5751
+ * @returns A superRefine function that validates recipientAddress against chain type
5752
+ */
5753
+ function createRecipientAddressValidator() {
5754
+ return (data, ctx) => {
5755
+ const chain = data.chain;
5756
+ if (chain === null) {
5757
+ return;
5758
+ }
5759
+ if (!isValidAddressForChain(data.recipientAddress, chain)) {
5760
+ const chainInfo = extractChainInfo(chain);
5761
+ ctx.addIssue({
5762
+ code: zod.z.ZodIssueCode.custom,
5763
+ path: ['recipientAddress'],
5764
+ message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
5765
+ });
5766
+ }
5767
+ };
5768
+ }
5229
5769
  /**
5230
5770
  * Schema for validating AdapterContext for bridge operations.
5231
5771
  * Must always contain both adapter and chain explicitly.
@@ -5245,32 +5785,52 @@ const adapterContextSchema = zod.z.object({
5245
5785
  const bridgeDestinationWithAddressSchema = adapterContextSchema
5246
5786
  .extend({
5247
5787
  recipientAddress: zod.z.string().min(1, 'Recipient address is required'),
5788
+ useForwarder: zod.z.boolean().optional(),
5248
5789
  })
5249
- .superRefine((data, ctx) => {
5250
- const chain = data.chain;
5251
- if (chain === null) {
5252
- return;
5253
- }
5254
- if (!isValidAddressForChain(data.recipientAddress, chain)) {
5255
- const chainInfo = extractChainInfo(chain);
5256
- ctx.addIssue({
5257
- code: zod.z.ZodIssueCode.custom,
5258
- path: ['recipientAddress'],
5259
- message: `Invalid address format for ${String(chainInfo.name)}. Expected ${chainInfo.expectedAddressFormat}, but received: ${data.recipientAddress}`,
5260
- });
5261
- }
5790
+ .superRefine(createRecipientAddressValidator());
5791
+ /**
5792
+ * Schema for validating AdapterContext with optional useForwarder.
5793
+ * Extends adapterContextSchema with the useForwarder flag.
5794
+ */
5795
+ const adapterContextWithForwarderSchema = adapterContextSchema.extend({
5796
+ useForwarder: zod.z.boolean().optional(),
5262
5797
  });
5798
+ /**
5799
+ * Schema for validating ForwarderDestination objects.
5800
+ * Used when useForwarder is true and no adapter is provided.
5801
+ * Requires chain, recipientAddress, and useForwarder: true.
5802
+ *
5803
+ * When using this destination type:
5804
+ * - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
5805
+ * - No on-chain transaction confirmation is performed (no adapter available)
5806
+ * - The mint step's data field will be undefined (no transaction receipt)
5807
+ */
5808
+ const forwarderDestinationSchema = zod.z
5809
+ .object({
5810
+ chain: bridgeChainIdentifierSchema,
5811
+ recipientAddress: zod.z.string().min(1, 'Recipient address is required'),
5812
+ useForwarder: zod.z.literal(true),
5813
+ })
5814
+ .strict()
5815
+ .superRefine(createRecipientAddressValidator());
5263
5816
  /**
5264
5817
  * Schema for validating BridgeDestination union type.
5265
- * Can be an AdapterContext or BridgeDestinationWithAddress.
5818
+ * Supports three destination configurations:
5819
+ * - BridgeDestinationWithAddress (adapter with explicit recipient)
5820
+ * - ForwarderDestination (no adapter, requires useForwarder: true and recipientAddress)
5821
+ * - AdapterContext with optional useForwarder (adapter for default recipient)
5266
5822
  *
5267
- * The order matters: we check the more specific schema (with recipientAddress) first.
5268
- * This ensures that objects with an empty recipientAddress field are rejected rather
5269
- * than silently treated as AdapterContext with the field ignored.
5823
+ * When using ForwarderDestination (no adapter):
5824
+ * - The mint step completes when the IRIS API confirms forwardState === 'CONFIRMED'
5825
+ * - No on-chain transaction confirmation is performed
5826
+ *
5827
+ * The order matters: we check the more specific schemas first.
5828
+ * This ensures that objects with specific fields are matched correctly.
5270
5829
  */
5271
5830
  const bridgeDestinationSchema = zod.z.union([
5272
5831
  bridgeDestinationWithAddressSchema,
5273
- adapterContextSchema.strict(),
5832
+ forwarderDestinationSchema,
5833
+ adapterContextWithForwarderSchema.strict(),
5274
5834
  ]);
5275
5835
  /**
5276
5836
  * Schema for validating bridge parameters with chain identifiers.
@@ -5344,43 +5904,1295 @@ const bridgeParamsWithChainIdentifierSchema = zod.z.object({
5344
5904
  });
5345
5905
 
5346
5906
  /**
5347
- * Resolves a chain identifier to a chain definition.
5348
- *
5349
- * Both AdapterContext and BridgeDestinationWithAddress have the chain property
5350
- * at the top level, so we can directly access it from either type.
5907
+ * Default clock implementation using `Date.now()`.
5351
5908
  *
5352
- * @param ctx - The bridge destination containing the chain identifier
5353
- * @returns The resolved chain definition
5354
- * @throws If the chain definition cannot be resolved
5909
+ * @remarks
5910
+ * Use this in production code. For testing, inject a mock clock
5911
+ * that returns controlled timestamps.
5355
5912
  *
5356
5913
  * @example
5357
5914
  * ```typescript
5358
- * import { Blockchain } from '@core/chains'
5359
- *
5360
- * // AdapterContext
5361
- * const chain1 = resolveChainDefinition({
5362
- * adapter: mockAdapter,
5363
- * chain: 'Ethereum'
5364
- * })
5915
+ * import { defaultClock } from '@core/runtime'
5365
5916
  *
5366
- * // BridgeDestinationWithAddress
5367
- * const chain2 = resolveChainDefinition({
5368
- * adapter: mockAdapter,
5369
- * chain: 'Base',
5370
- * recipientAddress: '0x123...'
5371
- * })
5917
+ * const start = defaultClock.now()
5918
+ * // ... do work ...
5919
+ * const elapsed = defaultClock.since(start)
5372
5920
  * ```
5373
5921
  */
5374
- function resolveChainDefinition(ctx) {
5375
- return resolveChainIdentifier(ctx.chain);
5376
- }
5922
+ const defaultClock = {
5923
+ now: () => Date.now(),
5924
+ since: (start) => Date.now() - start,
5925
+ };
5926
+
5377
5927
  /**
5378
- * Resolves the signer's address from a bridge destination.
5928
+ * ID generation utilities for distributed tracing.
5379
5929
  *
5380
- * This function resolves the address that will be used for transaction signing,
5381
- * ignoring any `recipientAddress` field which is handled separately.
5930
+ * @remarks
5931
+ * | Function | Format | Use Case |
5932
+ * |----------|--------|----------|
5933
+ * | {@link createTraceId} | 32-char hex | Standard traceId (OpenTelemetry) |
5934
+ * | {@link createShortOpId} | 8-char hex | Standard opId |
5935
+ * | {@link createOpId} | `op-{ts}-{rand}` | Timestamped operation IDs |
5936
+ * | {@link createUuidV4} | RFC 4122 UUID | External system compatibility |
5937
+ * | {@link createId} | `{prefix}-{ts}-{rand}` | Custom prefixed IDs |
5382
5938
  *
5383
- * It handles two cases:
5939
+ * @packageDocumentation
5940
+ */
5941
+ // ============================================================================
5942
+ // Crypto Utilities (Internal)
5943
+ // ============================================================================
5944
+ /** @internal */
5945
+ function hasGetRandomValues() {
5946
+ return (typeof crypto !== 'undefined' &&
5947
+ typeof crypto.getRandomValues === 'function');
5948
+ }
5949
+ /**
5950
+ * Create a W3C/OpenTelemetry-compatible trace ID.
5951
+ *
5952
+ * @returns 32-character lowercase hex string (128-bit).
5953
+ *
5954
+ * @remarks
5955
+ * **Standard function for generating `traceId` values.** Compatible with
5956
+ * OpenTelemetry, Jaeger, Zipkin, and AWS X-Ray.
5957
+ *
5958
+ * @example
5959
+ * ```typescript
5960
+ * const traceId = createTraceId() // "a1b2c3d4e5f6789012345678abcdef00"
5961
+ * ```
5962
+ */
5963
+ function createTraceId() {
5964
+ const bytes = new Uint8Array(16);
5965
+ if (hasGetRandomValues()) {
5966
+ crypto.getRandomValues(bytes);
5967
+ }
5968
+ else {
5969
+ for (let i = 0; i < 16; i++) {
5970
+ bytes[i] = Math.floor(Math.random() * 256); // NOSONAR:
5971
+ }
5972
+ }
5973
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
5974
+ }
5975
+
5976
+ /**
5977
+ * Clean tags by removing keys with undefined values.
5978
+ *
5979
+ * @param tags - Tags object, may contain undefined values. Accepts `undefined` or `null`.
5980
+ * @returns A new object with undefined values removed, or empty object if input is nullish.
5981
+ *
5982
+ * @remarks
5983
+ * This helper ensures tags are safe for serialization and logging.
5984
+ * Similar to the internal `cleanUndefined` in the logger module, but
5985
+ * typed specifically for {@link Tags}.
5986
+ *
5987
+ * @example
5988
+ * ```typescript
5989
+ * import { cleanTags } from '@core/runtime'
5990
+ *
5991
+ * const tags = { chain: 'Ethereum', amount: 100, extra: undefined }
5992
+ * const cleaned = cleanTags(tags)
5993
+ * // { chain: 'Ethereum', amount: 100 }
5994
+ *
5995
+ * const empty = cleanTags(undefined)
5996
+ * // {}
5997
+ * ```
5998
+ */
5999
+ function cleanTags(tags) {
6000
+ if (tags == null)
6001
+ return {};
6002
+ return Object.fromEntries(Object.entries(tags).filter(([, value]) => value !== undefined));
6003
+ }
6004
+
6005
+ /**
6006
+ * Check if a pattern is valid.
6007
+ *
6008
+ * @remarks
6009
+ * Invalid patterns:
6010
+ * - Empty segments (e.g., `a..b`, `.a`, `a.`)
6011
+ * - `**` not at end (e.g., `a.**.b`)
6012
+ *
6013
+ * @param pattern - The pattern to validate.
6014
+ * @returns True if valid, false otherwise.
6015
+ */
6016
+ function isValidPattern(pattern) {
6017
+ // Empty pattern is valid (matches empty topic)
6018
+ if (pattern === '')
6019
+ return true;
6020
+ const segments = pattern.split('.');
6021
+ // Check for empty segments
6022
+ if (segments.includes(''))
6023
+ return false;
6024
+ // Check that ** only appears at the end
6025
+ const doubleStarIndex = segments.indexOf('**');
6026
+ if (doubleStarIndex !== -1 && doubleStarIndex !== segments.length - 1) {
6027
+ return false;
6028
+ }
6029
+ return true;
6030
+ }
6031
+ /**
6032
+ * Convert a pattern to a regular expression.
6033
+ *
6034
+ * @param pattern - The pattern to convert.
6035
+ * @returns A RegExp that matches topics according to the pattern.
6036
+ */
6037
+ function patternToRegex(pattern) {
6038
+ const segments = pattern.split('.');
6039
+ // Handle ** at end specially to match zero or more segments
6040
+ const lastSegment = segments.at(-1);
6041
+ if (lastSegment === '**') {
6042
+ // Everything before ** must match, then optionally more segments
6043
+ const prefix = segments
6044
+ .slice(0, -1)
6045
+ .map((seg) => {
6046
+ if (seg === '*')
6047
+ return '[^.]+';
6048
+ return seg.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
6049
+ })
6050
+ .join(String.raw `\.`);
6051
+ // ** matches zero or more segments:
6052
+ // - If prefix is empty (**), match anything
6053
+ // - Otherwise, match prefix, then optionally (dot + more content)
6054
+ if (prefix === '') {
6055
+ return /^.*$/;
6056
+ }
6057
+ return new RegExp(String.raw `^${prefix}(?:\..*)?$`);
6058
+ }
6059
+ // No ** - just convert segments normally
6060
+ const escaped = segments
6061
+ .map((segment) => {
6062
+ if (segment === '*')
6063
+ return '[^.]+';
6064
+ return segment.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
6065
+ })
6066
+ .join(String.raw `\.`);
6067
+ return new RegExp(String.raw `^${escaped}$`);
6068
+ }
6069
+ /**
6070
+ * Execute an event handler with error isolation.
6071
+ *
6072
+ * @param handler - The handler function to execute.
6073
+ * @param event - The event to pass to the handler.
6074
+ * @param logger - Optional logger for error reporting.
6075
+ *
6076
+ * @remarks
6077
+ * - Synchronous errors are caught and logged
6078
+ * - Promise rejections are caught without awaiting
6079
+ * - Handler errors never propagate to caller
6080
+ */
6081
+ function executeHandler(handler, event, logger) {
6082
+ try {
6083
+ const result = handler(event);
6084
+ // Handle promise rejection without awaiting
6085
+ if (result instanceof Promise) {
6086
+ result.catch((err) => {
6087
+ logger?.error('Event handler promise rejected', {
6088
+ event: event.name,
6089
+ error: extractErrorInfo(err),
6090
+ });
6091
+ });
6092
+ }
6093
+ }
6094
+ catch (err) {
6095
+ // Isolate handler errors
6096
+ logger?.error('Event handler threw', {
6097
+ event: event.name,
6098
+ error: extractErrorInfo(err),
6099
+ });
6100
+ }
6101
+ }
6102
+ /**
6103
+ * Create an event bus.
6104
+ *
6105
+ * @param opts - Options for the event bus.
6106
+ * @returns An EventBus instance.
6107
+ *
6108
+ * @example
6109
+ * ```typescript
6110
+ * import { createEventBus } from '@core/runtime'
6111
+ *
6112
+ * const bus = createEventBus({ logger })
6113
+ *
6114
+ * // Subscribe to events
6115
+ * const unsubscribe = bus.on('tx.*', (event) => {
6116
+ * console.log('Transaction event:', event.name)
6117
+ * })
6118
+ *
6119
+ * // Emit events
6120
+ * bus.emit({ name: 'tx.sent', data: { txHash: '0x123' } })
6121
+ *
6122
+ * // Unsubscribe
6123
+ * unsubscribe()
6124
+ * ```
6125
+ */
6126
+ function createEventBus(opts) {
6127
+ const logger = opts?.logger;
6128
+ const subscriptions = new Set();
6129
+ function createBus(baseTags) {
6130
+ return {
6131
+ emit(event) {
6132
+ // Invalid topic names silently don't match any subscriptions
6133
+ if (!isValidPattern(event.name))
6134
+ return;
6135
+ const mergedTags = Object.keys(baseTags).length > 0 || event.tags
6136
+ ? { ...baseTags, ...cleanTags(event.tags) }
6137
+ : undefined;
6138
+ const finalEvent = mergedTags === undefined ? event : { ...event, tags: mergedTags };
6139
+ // Notify all matching subscribers
6140
+ for (const sub of subscriptions) {
6141
+ // Match-all subscription
6142
+ if (sub.pattern === null) {
6143
+ executeHandler(sub.handler, finalEvent, logger);
6144
+ continue;
6145
+ }
6146
+ // Patterned subscription: invalid pattern => regex=null => never match
6147
+ if (sub.regex === null)
6148
+ continue;
6149
+ if (!sub.regex.test(event.name))
6150
+ continue;
6151
+ executeHandler(sub.handler, finalEvent, logger);
6152
+ }
6153
+ },
6154
+ child(tags) {
6155
+ // Merge and clean tags, don't mutate parent
6156
+ const childTags = { ...baseTags, ...cleanTags(tags) };
6157
+ return createBus(childTags);
6158
+ },
6159
+ on(patternOrHandler, handler) {
6160
+ let sub;
6161
+ if (typeof patternOrHandler === 'function') {
6162
+ // on(handler) - subscribe to all
6163
+ sub = { pattern: null, handler: patternOrHandler, regex: null };
6164
+ }
6165
+ else {
6166
+ // on(pattern, handler)
6167
+ if (typeof handler !== 'function') {
6168
+ throw new TypeError(`EventBus.on("${patternOrHandler}") expected a function handler`);
6169
+ }
6170
+ const regex = isValidPattern(patternOrHandler)
6171
+ ? patternToRegex(patternOrHandler)
6172
+ : null;
6173
+ sub = {
6174
+ pattern: patternOrHandler,
6175
+ handler,
6176
+ regex,
6177
+ };
6178
+ }
6179
+ subscriptions.add(sub);
6180
+ // Return unsubscribe function (idempotent)
6181
+ let unsubscribed = false;
6182
+ return () => {
6183
+ if (!unsubscribed) {
6184
+ subscriptions.delete(sub);
6185
+ unsubscribed = true;
6186
+ }
6187
+ };
6188
+ },
6189
+ };
6190
+ }
6191
+ return createBus({});
6192
+ }
6193
+
6194
+ /** No-op counter that discards all increments. */
6195
+ const noopCounter = {
6196
+ inc: () => {
6197
+ // Intentionally empty - discards increment
6198
+ },
6199
+ };
6200
+ /** No-op histogram that discards all observations. */
6201
+ const noopHistogram = {
6202
+ observe: () => {
6203
+ // Intentionally empty - discards observation
6204
+ },
6205
+ };
6206
+ /** No-op timer that returns an empty stop function. */
6207
+ const noopTimer = {
6208
+ start: () => () => {
6209
+ // Intentionally empty - discards timing
6210
+ },
6211
+ };
6212
+ /**
6213
+ * Create a no-op metrics instance.
6214
+ *
6215
+ * @returns A Metrics instance that discards all operations.
6216
+ */
6217
+ function createNoopMetrics() {
6218
+ // Self-referential instance - child() returns same object to avoid allocations
6219
+ const instance = {
6220
+ counter: () => noopCounter,
6221
+ histogram: () => noopHistogram,
6222
+ timer: () => noopTimer,
6223
+ child: () => instance,
6224
+ };
6225
+ return instance;
6226
+ }
6227
+ /**
6228
+ * A no-op metrics implementation that discards all operations.
6229
+ *
6230
+ * @remarks
6231
+ * Use this when metrics collection is disabled or not configured.
6232
+ * All metric operations are safe to call and return immediately.
6233
+ * The `child()` method returns the same instance to avoid allocations.
6234
+ *
6235
+ * @example
6236
+ * ```typescript
6237
+ * import { noopMetrics } from '@core/runtime'
6238
+ *
6239
+ * // Use when metrics are disabled
6240
+ * const metrics = config.metricsEnabled
6241
+ * ? createDatadogMetrics(config)
6242
+ * : noopMetrics
6243
+ *
6244
+ * // All operations are safe to call
6245
+ * metrics.counter('requests').inc()
6246
+ * const stop = metrics.timer('query').start()
6247
+ * stop()
6248
+ * ```
6249
+ */
6250
+ const noopMetrics = createNoopMetrics();
6251
+
6252
+ /**
6253
+ * Define a schema for runtime logger interfaces.
6254
+ *
6255
+ * @remarks
6256
+ * Validate that a runtime logger provides the minimal methods expected by the SDK.
6257
+ *
6258
+ * @example
6259
+ * ```typescript
6260
+ * import { loggerSchema } from '@core/runtime'
6261
+ *
6262
+ * const logger = {
6263
+ * debug: () => undefined,
6264
+ * info: () => undefined,
6265
+ * warn: () => undefined,
6266
+ * error: () => undefined,
6267
+ * child: () => logger,
6268
+ * }
6269
+ *
6270
+ * loggerSchema.parse(logger)
6271
+ * ```
6272
+ */
6273
+ const loggerSchema = zod.z.custom((value) => {
6274
+ if (value === null || typeof value !== 'object') {
6275
+ return false;
6276
+ }
6277
+ const record = value;
6278
+ return (typeof record['debug'] === 'function' &&
6279
+ typeof record['info'] === 'function' &&
6280
+ typeof record['warn'] === 'function' &&
6281
+ typeof record['error'] === 'function' &&
6282
+ typeof record['child'] === 'function');
6283
+ }, {
6284
+ message: 'Invalid logger',
6285
+ });
6286
+
6287
+ /**
6288
+ * Define a schema for runtime metrics interfaces.
6289
+ *
6290
+ * @remarks
6291
+ * Validate that a metrics implementation exposes the minimal API expected by the runtime.
6292
+ *
6293
+ * @example
6294
+ * ```typescript
6295
+ * import { metricsSchema } from '@core/runtime'
6296
+ *
6297
+ * const metrics = {
6298
+ * counter: () => ({ inc: () => undefined }),
6299
+ * histogram: () => ({ observe: () => undefined }),
6300
+ * timer: () => ({ start: () => () => undefined }),
6301
+ * child: () => metrics,
6302
+ * }
6303
+ *
6304
+ * metricsSchema.parse(metrics)
6305
+ * ```
6306
+ */
6307
+ const metricsSchema = zod.z.custom((value) => {
6308
+ if (value === null || typeof value !== 'object') {
6309
+ return false;
6310
+ }
6311
+ const record = value;
6312
+ return (typeof record['counter'] === 'function' &&
6313
+ typeof record['histogram'] === 'function' &&
6314
+ typeof record['timer'] === 'function' &&
6315
+ typeof record['child'] === 'function');
6316
+ }, {
6317
+ message: 'Invalid metrics',
6318
+ });
6319
+
6320
+ /**
6321
+ * Omit undefined values from an object.
6322
+ *
6323
+ * @param obj - The object to process.
6324
+ * @returns A new object with undefined values removed.
6325
+ *
6326
+ * @internal
6327
+ * @remarks
6328
+ * Used by both production and mock loggers to ensure consistent behavior.
6329
+ * This prevents undefined values from being serialized in log output,
6330
+ * which can cause issues with some log transports.
6331
+ */
6332
+ function omitUndefined(obj) {
6333
+ const result = {};
6334
+ for (const [key, value] of Object.entries(obj)) {
6335
+ if (value !== undefined) {
6336
+ result[key] = value;
6337
+ }
6338
+ }
6339
+ return result;
6340
+ }
6341
+
6342
+ /**
6343
+ * Default redaction paths for web3/blockchain SDKs.
6344
+ *
6345
+ * @remarks
6346
+ * These paths target common sensitive fields in blockchain applications.
6347
+ * All user fields are nested under `context`, so paths start with `context.`.
6348
+ * Wildcard `*` matches any key at that level.
6349
+ */
6350
+ const DEFAULT_REDACT_PATHS = [
6351
+ // Generic Credentials
6352
+ 'context.password',
6353
+ 'context.passphrase',
6354
+ 'context.secret',
6355
+ 'context.token',
6356
+ 'context.*.password',
6357
+ 'context.*.passphrase',
6358
+ 'context.*.secret',
6359
+ 'context.*.token',
6360
+ // API Keys & Auth Tokens
6361
+ 'context.apiKey',
6362
+ 'context.apiSecret',
6363
+ 'context.accessToken',
6364
+ 'context.refreshToken',
6365
+ 'context.jwt',
6366
+ 'context.bearerToken',
6367
+ 'context.sessionId',
6368
+ 'context.authorization',
6369
+ 'context.cookie',
6370
+ 'context.*.apiKey',
6371
+ 'context.*.apiSecret',
6372
+ 'context.*.accessToken',
6373
+ 'context.*.refreshToken',
6374
+ 'context.*.jwt',
6375
+ 'context.*.bearerToken',
6376
+ 'context.*.sessionId',
6377
+ 'context.*.authorization',
6378
+ 'context.*.cookie',
6379
+ // Web3 / Crypto Keys
6380
+ 'context.privateKey',
6381
+ 'context.secretKey',
6382
+ 'context.signingKey',
6383
+ 'context.encryptionKey',
6384
+ 'context.*.privateKey',
6385
+ 'context.*.secretKey',
6386
+ 'context.*.signingKey',
6387
+ 'context.*.encryptionKey',
6388
+ // Web3 / Crypto Mnemonics and Seeds
6389
+ 'context.mnemonic',
6390
+ 'context.seed',
6391
+ 'context.seedPhrase',
6392
+ 'context.*.mnemonic',
6393
+ 'context.*.seed',
6394
+ 'context.*.seedPhrase',
6395
+ // OTP / Verification Codes
6396
+ 'context.otp',
6397
+ 'context.verificationCode',
6398
+ 'context.*.otp',
6399
+ 'context.*.verificationCode',
6400
+ // Payment Information
6401
+ 'context.cardNumber',
6402
+ 'context.cvv',
6403
+ 'context.accountNumber',
6404
+ 'context.*.cardNumber',
6405
+ 'context.*.cvv',
6406
+ 'context.*.accountNumber',
6407
+ ];
6408
+ /**
6409
+ * Wrap user fields under `context` to prevent collision with pino internals.
6410
+ *
6411
+ * @param fields - User-provided log fields.
6412
+ * @returns Object with fields nested under `context`, or undefined if empty.
6413
+ *
6414
+ * @remarks
6415
+ * This function handles edge cases by returning undefined for null, undefined,
6416
+ * or empty objects to avoid unnecessary wrapping in log output.
6417
+ * Undefined values are cleaned before wrapping.
6418
+ */
6419
+ function wrapInContext(fields) {
6420
+ if (!fields)
6421
+ return undefined;
6422
+ // Clean undefined values for consistency and transport compatibility
6423
+ const cleaned = omitUndefined(fields);
6424
+ // Handle edge case: all values were undefined, resulting in empty object
6425
+ const keys = Object.keys(cleaned);
6426
+ if (keys.length === 0)
6427
+ return undefined;
6428
+ return { context: cleaned };
6429
+ }
6430
+ /**
6431
+ * Wrap a pino instance to conform to our Logger interface.
6432
+ *
6433
+ * @param pinoInstance - The pino logger instance to wrap.
6434
+ * @returns A Logger instance conforming to our stable interface.
6435
+ */
6436
+ function wrapPino(pinoInstance) {
6437
+ return {
6438
+ debug(message, fields) {
6439
+ const wrapped = wrapInContext(fields);
6440
+ if (wrapped) {
6441
+ pinoInstance.debug(wrapped, message);
6442
+ }
6443
+ else {
6444
+ pinoInstance.debug(message);
6445
+ }
6446
+ },
6447
+ info(message, fields) {
6448
+ const wrapped = wrapInContext(fields);
6449
+ if (wrapped) {
6450
+ pinoInstance.info(wrapped, message);
6451
+ }
6452
+ else {
6453
+ pinoInstance.info(message);
6454
+ }
6455
+ },
6456
+ warn(message, fields) {
6457
+ const wrapped = wrapInContext(fields);
6458
+ if (wrapped) {
6459
+ pinoInstance.warn(wrapped, message);
6460
+ }
6461
+ else {
6462
+ pinoInstance.warn(message);
6463
+ }
6464
+ },
6465
+ error(message, fields) {
6466
+ const wrapped = wrapInContext(fields);
6467
+ if (wrapped) {
6468
+ pinoInstance.error(wrapped, message);
6469
+ }
6470
+ else {
6471
+ pinoInstance.error(message);
6472
+ }
6473
+ },
6474
+ child(tags) {
6475
+ // Child bindings stay flat (not wrapped) - they're part of logger's base context
6476
+ const cleaned = omitUndefined(tags);
6477
+ return wrapPino(pinoInstance.child(cleaned));
6478
+ },
6479
+ };
6480
+ }
6481
+ /**
6482
+ * Build pino redact configuration from our simplified options.
6483
+ *
6484
+ * @param redact - The redact configuration option.
6485
+ * @returns Pino-compatible redact configuration or undefined.
6486
+ */
6487
+ function buildRedactConfig(redact) {
6488
+ // Explicitly disabled
6489
+ if (redact === false) {
6490
+ return undefined;
6491
+ }
6492
+ // Custom paths provided
6493
+ if (Array.isArray(redact)) {
6494
+ return redact.length > 0
6495
+ ? { paths: redact, censor: '[REDACTED]' }
6496
+ : undefined;
6497
+ }
6498
+ // Default: use web3 sensible defaults
6499
+ return {
6500
+ paths: [...DEFAULT_REDACT_PATHS],
6501
+ censor: '[REDACTED]',
6502
+ };
6503
+ }
6504
+ /**
6505
+ * Create a logger backed by pino.
6506
+ *
6507
+ * @param options - Logger options (optional).
6508
+ * @param stream - Destination stream (optional).
6509
+ * @returns A Logger instance.
6510
+ * @throws Error if invalid pino options are provided.
6511
+ *
6512
+ * @remarks
6513
+ * This is a thin wrapper around pino that exposes our stable Logger interface.
6514
+ * Pino handles all transport concerns: JSON, pretty printing, file, remote, browser, etc.
6515
+ *
6516
+ * **Security**: By default, sensitive web3 fields (privateKey, mnemonic, apiKey, etc.)
6517
+ * are automatically redacted from log output. Use `redact: false` to disable.
6518
+ *
6519
+ * @example
6520
+ * ```typescript
6521
+ * import { createLogger } from '@core/runtime'
6522
+ *
6523
+ * // Default: web3 sensitive fields are redacted
6524
+ * const logger = createLogger({ level: 'info' })
6525
+ * logger.info('Signing', { privateKey: '0x123...' })
6526
+ * // Output: { context: { privateKey: '[REDACTED]' }, msg: 'Signing' }
6527
+ *
6528
+ * // Disable redaction (use with caution)
6529
+ * const unsafeLogger = createLogger({ level: 'debug', redact: false })
6530
+ *
6531
+ * // Custom redaction paths
6532
+ * const customLogger = createLogger({
6533
+ * level: 'info',
6534
+ * redact: ['context.mySecret', 'context.*.credentials']
6535
+ * })
6536
+ *
6537
+ * // Pretty output for development
6538
+ * const devLogger = createLogger({
6539
+ * level: 'debug',
6540
+ * transport: { target: 'pino-pretty' }
6541
+ * })
6542
+ *
6543
+ * // Browser logger
6544
+ * const browserLogger = createLogger({
6545
+ * browser: { asObject: true }
6546
+ * })
6547
+ * ```
6548
+ */
6549
+ function createLogger(options, stream) {
6550
+ const { redact, ...pinoOptions } = {};
6551
+ // Build redaction config
6552
+ const redactConfig = buildRedactConfig(redact);
6553
+ // Build final pino options, only include redact if defined
6554
+ const finalOptions = redactConfig
6555
+ ? { ...pinoOptions, redact: redactConfig }
6556
+ : pinoOptions;
6557
+ const pinoInstance = pino__default(finalOptions);
6558
+ return wrapPino(pinoInstance);
6559
+ }
6560
+
6561
+ /**
6562
+ * Factory for creating Runtime instances with defaults.
6563
+ *
6564
+ * @packageDocumentation
6565
+ */
6566
+ // ============================================================================
6567
+ // Validation Schema
6568
+ // ============================================================================
6569
+ /**
6570
+ * Schema for validating {@link RuntimeOptions}.
6571
+ *
6572
+ * @remarks
6573
+ * Used internally by {@link createRuntime} to validate options from JS consumers.
6574
+ * Exported for advanced use cases where manual validation is needed.
6575
+ */
6576
+ const runtimeOptionsSchema = zod.z
6577
+ .object({
6578
+ logger: loggerSchema.optional(),
6579
+ metrics: metricsSchema.optional(),
6580
+ })
6581
+ .passthrough();
6582
+ // ============================================================================
6583
+ // Factory
6584
+ // ============================================================================
6585
+ /**
6586
+ * Create a complete Runtime with sensible defaults.
6587
+ *
6588
+ * @param options - Optional configuration to override logger and metrics.
6589
+ * @returns A frozen, immutable Runtime with all services guaranteed present.
6590
+ * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
6591
+ *
6592
+ * @remarks
6593
+ * Creates a fully-configured runtime by merging provided options with defaults.
6594
+ * The returned runtime is frozen to enforce immutability.
6595
+ *
6596
+ * | Service | Default | Configurable |
6597
+ * |---------|---------|--------------|
6598
+ * | `logger` | pino logger (info level) | Yes |
6599
+ * | `metrics` | No-op metrics | Yes |
6600
+ * | `events` | Internal event bus | No |
6601
+ * | `clock` | `Date.now()` | No |
6602
+ *
6603
+ * **Why only logger and metrics?**
6604
+ *
6605
+ * - **Logger/Metrics**: Integration points with your infrastructure
6606
+ * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
6607
+ * - **Clock**: Testing concern - use mock factories for tests
6608
+ *
6609
+ * @example
6610
+ * ```typescript
6611
+ * import { createRuntime, createLogger } from '@core/runtime'
6612
+ *
6613
+ * // Use all defaults
6614
+ * const runtime = createRuntime()
6615
+ *
6616
+ * // Custom logger
6617
+ * const runtime = createRuntime({
6618
+ * logger: createLogger({ level: 'debug' }),
6619
+ * })
6620
+ *
6621
+ * // Custom metrics (e.g., Prometheus)
6622
+ * const runtime = createRuntime({
6623
+ * metrics: myPrometheusMetrics,
6624
+ * })
6625
+ *
6626
+ * // Subscribe to events (don't replace the bus)
6627
+ * runtime.events.on('operation.*', (event) => {
6628
+ * console.log('Event:', event.name)
6629
+ * })
6630
+ * ```
6631
+ */
6632
+ function createRuntime(options) {
6633
+ // Validate options for JS consumers
6634
+ if (options != null) {
6635
+ parseOrThrow(options, runtimeOptionsSchema, 'runtime options');
6636
+ }
6637
+ // Resolve logger first (events may need it)
6638
+ const logger = options?.logger ?? createLogger();
6639
+ // Internal services - not configurable
6640
+ const events = createEventBus({ logger });
6641
+ const clock = defaultClock;
6642
+ // Resolve metrics
6643
+ const metrics = options?.metrics ?? noopMetrics;
6644
+ return Object.freeze({ logger, events, metrics, clock });
6645
+ }
6646
+
6647
+ /**
6648
+ * Invocation context resolution - resolves the **WHO/HOW** of an operation.
6649
+ *
6650
+ * @packageDocumentation
6651
+ */
6652
+ // ============================================================================
6653
+ // Validation Schemas
6654
+ // ============================================================================
6655
+ /**
6656
+ * Schema for validating Caller.
6657
+ */
6658
+ const callerSchema = zod.z.object({
6659
+ type: zod.z.string(),
6660
+ name: zod.z.string(),
6661
+ version: zod.z.string().optional(),
6662
+ });
6663
+ /**
6664
+ * Schema for validating InvocationMeta input.
6665
+ */
6666
+ const invocationMetaSchema = zod.z
6667
+ .object({
6668
+ traceId: zod.z.string().optional(),
6669
+ runtime: zod.z.object({}).passthrough().optional(),
6670
+ tokens: zod.z.object({}).passthrough().optional(),
6671
+ callers: zod.z.array(callerSchema).optional(),
6672
+ })
6673
+ .strict();
6674
+ // ============================================================================
6675
+ // Invocation Context Resolution
6676
+ // ============================================================================
6677
+ /**
6678
+ * Resolve invocation metadata to invocation context.
6679
+ *
6680
+ * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
6681
+ * @param defaults - Default runtime and tokens to use if not overridden.
6682
+ * @returns Frozen, immutable invocation context with guaranteed values.
6683
+ * @throws KitError when meta contains invalid properties.
6684
+ *
6685
+ * @remarks
6686
+ * Resolves the **WHO** called and **HOW** to observe:
6687
+ * - TraceId: Uses provided value or generates new one
6688
+ * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
6689
+ * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
6690
+ * - Callers: Uses provided array or empty array
6691
+ *
6692
+ * The returned context is frozen to enforce immutability.
6693
+ *
6694
+ * @example
6695
+ * ```typescript
6696
+ * import { resolveInvocationContext, createRuntime } from '@core/runtime'
6697
+ * import { createTokenRegistry } from '@core/tokens'
6698
+ *
6699
+ * const defaults = {
6700
+ * runtime: createRuntime(),
6701
+ * tokens: createTokenRegistry(),
6702
+ * }
6703
+ *
6704
+ * // Minimal - just using defaults
6705
+ * const ctx = resolveInvocationContext(undefined, defaults)
6706
+ *
6707
+ * // With trace ID and caller info
6708
+ * const ctx = resolveInvocationContext(
6709
+ * {
6710
+ * traceId: 'abc-123',
6711
+ * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
6712
+ * },
6713
+ * defaults
6714
+ * )
6715
+ *
6716
+ * // With runtime override (complete replacement)
6717
+ * const ctx = resolveInvocationContext(
6718
+ * { runtime: createRuntime({ logger: myLogger }) },
6719
+ * defaults
6720
+ * )
6721
+ * ```
6722
+ */
6723
+ function resolveInvocationContext(meta, defaults) {
6724
+ // Validate meta input if provided
6725
+ if (meta !== undefined) {
6726
+ const result = invocationMetaSchema.safeParse(meta);
6727
+ if (!result.success) {
6728
+ throw createValidationFailedError$1('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
6729
+ }
6730
+ }
6731
+ // Generate trace ID if not provided
6732
+ const traceId = meta?.traceId ?? createTraceId();
6733
+ // Use meta overrides or fall back to defaults
6734
+ const runtime = meta?.runtime ?? defaults.runtime;
6735
+ const tokens = meta?.tokens ?? defaults.tokens;
6736
+ const callers = meta?.callers ?? [];
6737
+ return Object.freeze({ traceId, runtime, tokens, callers });
6738
+ }
6739
+
6740
+ /**
6741
+ * Extend an invocation context by appending a caller to its call chain.
6742
+ *
6743
+ * @param context - The existing invocation context to extend.
6744
+ * @param caller - The caller to append to the call chain.
6745
+ * @returns A new frozen invocation context with the caller appended.
6746
+ *
6747
+ * @remarks
6748
+ * This function creates a new immutable context with the caller appended
6749
+ * to the `callers` array while preserving all other context properties
6750
+ * (traceId, runtime, tokens).
6751
+ *
6752
+ * The returned context is frozen to enforce immutability.
6753
+ *
6754
+ * @example
6755
+ * ```typescript
6756
+ * import { extendInvocationContext } from '@core/runtime'
6757
+ *
6758
+ * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
6759
+ * const extended = extendInvocationContext(existingContext, caller)
6760
+ * // extended.callers === [...existingContext.callers, caller]
6761
+ * ```
6762
+ */
6763
+ function extendInvocationContext(context, caller) {
6764
+ return Object.freeze({
6765
+ traceId: context.traceId,
6766
+ runtime: context.runtime,
6767
+ tokens: context.tokens,
6768
+ callers: [...context.callers, caller],
6769
+ });
6770
+ }
6771
+
6772
+ // Clock - expose defaultClock for backward compatibility
6773
+ /** Clock validation schema (backward compatibility). */
6774
+ zod.z.custom((val) => val !== null &&
6775
+ typeof val === 'object' &&
6776
+ 'now' in val &&
6777
+ typeof val['now'] === 'function');
6778
+ /** EventBus validation schema (backward compatibility). */
6779
+ zod.z.custom((val) => val !== null &&
6780
+ typeof val === 'object' &&
6781
+ 'emit' in val &&
6782
+ typeof val['emit'] === 'function');
6783
+ /** Runtime validation schema (backward compatibility). */
6784
+ zod.z
6785
+ .object({
6786
+ logger: zod.z.any().optional(),
6787
+ events: zod.z.any().optional(),
6788
+ metrics: zod.z.any().optional(),
6789
+ clock: zod.z.any().optional(),
6790
+ })
6791
+ .passthrough();
6792
+
6793
+ /**
6794
+ * Create a structured error for token resolution failures.
6795
+ *
6796
+ * @remarks
6797
+ * Returns a `KitError` with `INPUT_INVALID_TOKEN` code (1006).
6798
+ * The error trace contains the selector and chain context for debugging.
6799
+ *
6800
+ * @param message - Human-readable error description.
6801
+ * @param selector - The token selector that failed to resolve.
6802
+ * @param chainId - The chain being resolved for (optional).
6803
+ * @param cause - The underlying error, if any (optional).
6804
+ * @returns A KitError with INPUT type and FATAL recoverability.
6805
+ *
6806
+ * @example
6807
+ * ```typescript
6808
+ * throw createTokenResolutionError(
6809
+ * 'Unknown token symbol: FAKE',
6810
+ * 'FAKE',
6811
+ * 'Ethereum'
6812
+ * )
6813
+ * ```
6814
+ */
6815
+ function createTokenResolutionError(message, selector, chainId, cause) {
6816
+ const trace = {
6817
+ selector,
6818
+ ...(chainId === undefined ? {} : { chainId }),
6819
+ ...({} ),
6820
+ };
6821
+ return new KitError({
6822
+ ...InputError.INVALID_TOKEN,
6823
+ recoverability: 'FATAL',
6824
+ message,
6825
+ cause: { trace },
6826
+ });
6827
+ }
6828
+
6829
+ /**
6830
+ * USDC token definition with addresses and metadata.
6831
+ *
6832
+ * @remarks
6833
+ * This is the built-in USDC definition used by the TokenRegistry.
6834
+ * Includes all known USDC addresses across supported chains.
6835
+ *
6836
+ * Keys use the `Blockchain` enum for type safety. Both enum values
6837
+ * and string literals are supported:
6838
+ * - `Blockchain.Ethereum` or `'Ethereum'`
6839
+ *
6840
+ * @example
6841
+ * ```typescript
6842
+ * import { USDC } from '@core/tokens'
6843
+ * import { Blockchain } from '@core/chains'
6844
+ *
6845
+ * console.log(USDC.symbol) // 'USDC'
6846
+ * console.log(USDC.decimals) // 6
6847
+ * console.log(USDC.locators[Blockchain.Ethereum])
6848
+ * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
6849
+ * ```
6850
+ */
6851
+ const USDC = {
6852
+ symbol: 'USDC',
6853
+ decimals: 6,
6854
+ locators: {
6855
+ // =========================================================================
6856
+ // Mainnets (alphabetically sorted)
6857
+ // =========================================================================
6858
+ [exports.Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
6859
+ [exports.Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
6860
+ [exports.Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
6861
+ [exports.Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
6862
+ [exports.Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
6863
+ [exports.Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
6864
+ [exports.Blockchain.Hedera]: '0.0.456858',
6865
+ [exports.Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
6866
+ [exports.Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
6867
+ [exports.Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
6868
+ [exports.Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
6869
+ [exports.Blockchain.Noble]: 'uusdc',
6870
+ [exports.Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
6871
+ [exports.Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
6872
+ [exports.Blockchain.Polkadot_Asset_Hub]: '1337',
6873
+ [exports.Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
6874
+ [exports.Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
6875
+ [exports.Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
6876
+ [exports.Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
6877
+ [exports.Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
6878
+ [exports.Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
6879
+ [exports.Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
6880
+ [exports.Blockchain.World_Chain]: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
6881
+ [exports.Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
6882
+ [exports.Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
6883
+ // =========================================================================
6884
+ // Testnets (alphabetically sorted)
6885
+ // =========================================================================
6886
+ [exports.Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
6887
+ [exports.Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
6888
+ [exports.Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
6889
+ [exports.Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
6890
+ [exports.Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
6891
+ [exports.Blockchain.Hedera_Testnet]: '0.0.429274',
6892
+ [exports.Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
6893
+ [exports.Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
6894
+ [exports.Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
6895
+ [exports.Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
6896
+ [exports.Blockchain.Noble_Testnet]: 'uusdc',
6897
+ [exports.Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
6898
+ [exports.Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
6899
+ [exports.Blockchain.Polkadot_Westmint]: '31337',
6900
+ [exports.Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
6901
+ [exports.Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
6902
+ [exports.Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
6903
+ [exports.Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
6904
+ [exports.Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
6905
+ [exports.Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
6906
+ [exports.Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
6907
+ [exports.Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
6908
+ [exports.Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
6909
+ [exports.Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
6910
+ },
6911
+ };
6912
+
6913
+ // Re-export for consumers
6914
+ /**
6915
+ * All default token definitions.
6916
+ *
6917
+ * @remarks
6918
+ * These tokens are automatically included in the TokenRegistry when created
6919
+ * without explicit defaults. Extensions can override these definitions.
6920
+ *
6921
+ * @example
6922
+ * ```typescript
6923
+ * import { createTokenRegistry } from '@core/tokens'
6924
+ *
6925
+ * // Registry uses these by default
6926
+ * const registry = createTokenRegistry()
6927
+ *
6928
+ * // Add custom tokens (built-ins are still included)
6929
+ * const customRegistry = createTokenRegistry({
6930
+ * tokens: [myCustomToken],
6931
+ * })
6932
+ * ```
6933
+ */
6934
+ const DEFAULT_TOKENS = [USDC];
6935
+
6936
+ /**
6937
+ * Check if a selector is a raw token selector (object form).
6938
+ *
6939
+ * @param selector - The token selector to check.
6940
+ * @returns True if the selector is a raw token selector.
6941
+ */
6942
+ function isRawSelector(selector) {
6943
+ return typeof selector === 'object' && 'locator' in selector;
6944
+ }
6945
+ /**
6946
+ * Normalize a symbol to uppercase for case-insensitive lookup.
6947
+ *
6948
+ * @param symbol - The symbol to normalize.
6949
+ * @returns The normalized (uppercase) symbol.
6950
+ */
6951
+ function normalizeSymbol(symbol) {
6952
+ return symbol.toUpperCase();
6953
+ }
6954
+ /**
6955
+ * Create a token registry with built-in tokens and optional extensions.
6956
+ *
6957
+ * @remarks
6958
+ * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
6959
+ * Custom tokens are merged on top - use this to add new tokens or override
6960
+ * built-in definitions.
6961
+ *
6962
+ * @param options - Configuration options for the registry.
6963
+ * @returns A token registry instance.
6964
+ *
6965
+ * @example
6966
+ * ```typescript
6967
+ * import { createTokenRegistry } from '@core/tokens'
6968
+ *
6969
+ * // Create registry with built-in tokens (USDC, etc.)
6970
+ * const registry = createTokenRegistry()
6971
+ *
6972
+ * // Resolve USDC on Ethereum
6973
+ * const usdc = registry.resolve('USDC', 'Ethereum')
6974
+ * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
6975
+ * console.log(usdc.decimals) // 6
6976
+ * ```
6977
+ *
6978
+ * @example
6979
+ * ```typescript
6980
+ * // Add custom tokens (built-ins are still included)
6981
+ * const myToken: TokenDefinition = {
6982
+ * symbol: 'MY',
6983
+ * decimals: 18,
6984
+ * locators: { Ethereum: '0x...' },
6985
+ * }
6986
+ *
6987
+ * const registry = createTokenRegistry({ tokens: [myToken] })
6988
+ * registry.resolve('USDC', 'Ethereum') // Still works!
6989
+ * registry.resolve('MY', 'Ethereum') // Also works
6990
+ * ```
6991
+ *
6992
+ * @example
6993
+ * ```typescript
6994
+ * // Override a built-in token
6995
+ * const customUsdc: TokenDefinition = {
6996
+ * symbol: 'USDC',
6997
+ * decimals: 6,
6998
+ * locators: { MyChain: '0xCustomAddress' },
6999
+ * }
7000
+ *
7001
+ * const registry = createTokenRegistry({ tokens: [customUsdc] })
7002
+ * // Now USDC resolves to customUsdc definition
7003
+ * ```
7004
+ *
7005
+ * @example
7006
+ * ```typescript
7007
+ * // Resolve arbitrary tokens by raw locator
7008
+ * const registry = createTokenRegistry()
7009
+ * const token = registry.resolve(
7010
+ * { locator: '0x1234...', decimals: 18 },
7011
+ * 'Ethereum'
7012
+ * )
7013
+ * ```
7014
+ */
7015
+ function createTokenRegistry(options = {}) {
7016
+ const { tokens = [], requireDecimals = false } = options;
7017
+ // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
7018
+ const tokenMap = new Map();
7019
+ // Add built-in tokens first
7020
+ for (const def of DEFAULT_TOKENS) {
7021
+ tokenMap.set(normalizeSymbol(def.symbol), def);
7022
+ }
7023
+ // Custom tokens override built-ins
7024
+ for (const def of tokens) {
7025
+ tokenMap.set(normalizeSymbol(def.symbol), def);
7026
+ }
7027
+ /**
7028
+ * Resolve a symbol selector to token information.
7029
+ */
7030
+ function resolveSymbol(symbol, chainId) {
7031
+ const normalizedSymbol = normalizeSymbol(symbol);
7032
+ const definition = tokenMap.get(normalizedSymbol);
7033
+ if (definition === undefined) {
7034
+ throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
7035
+ }
7036
+ const locator = definition.locators[chainId];
7037
+ if (locator === undefined || locator.trim() === '') {
7038
+ throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
7039
+ }
7040
+ return {
7041
+ symbol: definition.symbol,
7042
+ decimals: definition.decimals,
7043
+ locator,
7044
+ };
7045
+ }
7046
+ /**
7047
+ * Resolve a raw selector to token information.
7048
+ */
7049
+ function resolveRaw(selector, chainId) {
7050
+ const { locator, decimals } = selector;
7051
+ // Validate locator
7052
+ if (!locator || typeof locator !== 'string') {
7053
+ throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
7054
+ }
7055
+ // Decimals are always required for raw selectors
7056
+ if (decimals === undefined) {
7057
+ const message = requireDecimals
7058
+ ? 'Decimals required for raw token selector (requireDecimals: true)'
7059
+ : 'Decimals required for raw token selector. Provide { locator, decimals }.';
7060
+ throw createTokenResolutionError(message, selector, chainId);
7061
+ }
7062
+ // Validate decimals
7063
+ if (typeof decimals !== 'number' ||
7064
+ decimals < 0 ||
7065
+ !Number.isInteger(decimals)) {
7066
+ throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
7067
+ }
7068
+ return {
7069
+ decimals,
7070
+ locator,
7071
+ };
7072
+ }
7073
+ return {
7074
+ resolve(selector, chainId) {
7075
+ // Runtime validation of inputs - these checks are for JS consumers
7076
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
7077
+ if (selector === null || selector === undefined) {
7078
+ throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
7079
+ }
7080
+ if (chainId === '' || typeof chainId !== 'string') {
7081
+ throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
7082
+ }
7083
+ // Dispatch based on selector type
7084
+ if (isRawSelector(selector)) {
7085
+ return resolveRaw(selector, chainId);
7086
+ }
7087
+ if (typeof selector === 'string') {
7088
+ return resolveSymbol(selector, chainId);
7089
+ }
7090
+ throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
7091
+ },
7092
+ get(symbol) {
7093
+ if (!symbol || typeof symbol !== 'string') {
7094
+ return undefined;
7095
+ }
7096
+ return tokenMap.get(normalizeSymbol(symbol));
7097
+ },
7098
+ has(symbol) {
7099
+ if (!symbol || typeof symbol !== 'string') {
7100
+ return false;
7101
+ }
7102
+ return tokenMap.has(normalizeSymbol(symbol));
7103
+ },
7104
+ symbols() {
7105
+ return Array.from(tokenMap.values()).map((def) => def.symbol);
7106
+ },
7107
+ entries() {
7108
+ return Array.from(tokenMap.values());
7109
+ },
7110
+ };
7111
+ }
7112
+
7113
+ /**
7114
+ * Define a schema for token registry interfaces.
7115
+ *
7116
+ * @remarks
7117
+ * Validate that a registry exposes the `resolve()` API used by adapters.
7118
+ *
7119
+ * @example
7120
+ * ```typescript
7121
+ * import { tokenRegistrySchema } from '@core/tokens'
7122
+ *
7123
+ * const registry = {
7124
+ * resolve: () => ({ locator: '0x0', decimals: 6 }),
7125
+ * }
7126
+ *
7127
+ * tokenRegistrySchema.parse(registry)
7128
+ * ```
7129
+ */
7130
+ zod.z.custom((value) => {
7131
+ if (value === null || typeof value !== 'object') {
7132
+ return false;
7133
+ }
7134
+ const record = value;
7135
+ return (typeof record['resolve'] === 'function' &&
7136
+ typeof record['get'] === 'function' &&
7137
+ typeof record['has'] === 'function' &&
7138
+ typeof record['symbols'] === 'function' &&
7139
+ typeof record['entries'] === 'function');
7140
+ }, {
7141
+ message: 'Invalid token registry',
7142
+ });
7143
+
7144
+ /**
7145
+ * Type guard to check if the destination is a forwarder-only destination.
7146
+ *
7147
+ * Forwarder-only destinations have `useForwarder: true` and no adapter.
7148
+ * They require a `recipientAddress` to be specified.
7149
+ *
7150
+ * @param dest - The bridge destination to check
7151
+ * @returns True if this is a forwarder-only destination without adapter
7152
+ */
7153
+ function isForwarderOnlyDestination(dest) {
7154
+ return (dest.useForwarder === true &&
7155
+ !('adapter' in dest) &&
7156
+ 'recipientAddress' in dest);
7157
+ }
7158
+ /**
7159
+ * Resolves a chain identifier to a chain definition.
7160
+ *
7161
+ * Both AdapterContext and BridgeDestinationWithAddress have the chain property
7162
+ * at the top level, so we can directly access it from either type.
7163
+ *
7164
+ * @param ctx - The bridge destination containing the chain identifier
7165
+ * @returns The resolved chain definition
7166
+ * @throws If the chain definition cannot be resolved
7167
+ *
7168
+ * @example
7169
+ * ```typescript
7170
+ * import { Blockchain } from '@core/chains'
7171
+ *
7172
+ * // AdapterContext
7173
+ * const chain1 = resolveChainDefinition({
7174
+ * adapter: mockAdapter,
7175
+ * chain: 'Ethereum'
7176
+ * })
7177
+ *
7178
+ * // BridgeDestinationWithAddress
7179
+ * const chain2 = resolveChainDefinition({
7180
+ * adapter: mockAdapter,
7181
+ * chain: 'Base',
7182
+ * recipientAddress: '0x123...'
7183
+ * })
7184
+ * ```
7185
+ */
7186
+ function resolveChainDefinition(ctx) {
7187
+ return resolveChainIdentifier(ctx.chain);
7188
+ }
7189
+ /**
7190
+ * Resolves the signer's address from a bridge destination.
7191
+ *
7192
+ * This function resolves the address that will be used for transaction signing,
7193
+ * ignoring any `recipientAddress` field which is handled separately.
7194
+ *
7195
+ * It handles two cases:
5384
7196
  * - Developer-controlled adapters - returns the explicit address from context
5385
7197
  * - User-controlled adapters - calls getAddress() on the adapter
5386
7198
  *
@@ -5453,6 +7265,46 @@ function resolveAmount(params) {
5453
7265
  }
5454
7266
  return params.amount;
5455
7267
  }
7268
+ /**
7269
+ * Resolves the invocation context for a bridge operation.
7270
+ *
7271
+ * Takes optional invocation metadata and resolves it into a full
7272
+ * InvocationContext with BridgeKit caller information appended.
7273
+ *
7274
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation
7275
+ * @returns An InvocationContext with traceId, runtime, tokens, and caller chain
7276
+ *
7277
+ * @example
7278
+ * ```typescript
7279
+ * // With user-provided invocation metadata
7280
+ * const invocation = resolveBridgeInvocation({
7281
+ * traceId: 'my-custom-trace-id',
7282
+ * callers: [{ type: 'app', name: 'MyDApp' }],
7283
+ * })
7284
+ * // invocation.traceId === 'my-custom-trace-id'
7285
+ * // invocation.callers === [{ type: 'app', name: 'MyDApp' }, { type: 'kit', name: 'BridgeKit' }]
7286
+ *
7287
+ * // Without invocation metadata (auto-generated traceId)
7288
+ * const invocation2 = resolveBridgeInvocation()
7289
+ * // invocation2.traceId === <auto-generated OpenTelemetry-compatible trace ID>
7290
+ * // invocation2.callers === [{ type: 'kit', name: 'BridgeKit' }]
7291
+ * ```
7292
+ */
7293
+ function resolveBridgeInvocation(invocationMeta) {
7294
+ const bridgeKitCaller = {
7295
+ type: 'kit',
7296
+ name: 'BridgeKit',
7297
+ version: pkg.version,
7298
+ };
7299
+ // Create default runtime and tokens for invocation context resolution
7300
+ const defaults = {
7301
+ runtime: createRuntime(),
7302
+ tokens: createTokenRegistry(),
7303
+ };
7304
+ // Resolve invocation metadata to full context, then extend with BridgeKit caller
7305
+ const baseContext = resolveInvocationContext(invocationMeta, defaults);
7306
+ return extendInvocationContext(baseContext, bridgeKitCaller);
7307
+ }
5456
7308
  /**
5457
7309
  * Resolves and normalizes bridge configuration for the provider.
5458
7310
  *
@@ -5521,6 +7373,7 @@ function resolveConfig(params) {
5521
7373
  * - Amount formatting
5522
7374
  *
5523
7375
  * @param params - The bridge parameters containing source/destination contexts, amount, and token
7376
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation
5524
7377
  * @returns Promise resolving to normalized bridge parameters for provider consumption
5525
7378
  * @throws \{Error\} If parameters cannot be resolved (invalid chains, etc.)
5526
7379
  *
@@ -5540,22 +7393,32 @@ function resolveConfig(params) {
5540
7393
  * ```
5541
7394
  */
5542
7395
  async function resolveBridgeParams(params) {
5543
- const fromChain = resolveChainDefinition(params.from);
5544
- const toChain = resolveChainDefinition(params.to);
7396
+ // Resolve chains
7397
+ const fromChain = resolveChainIdentifier(params.from.chain);
7398
+ const toChain = resolveChainIdentifier(params.to.chain);
7399
+ // Check if this is a forwarder-only destination (no adapter)
7400
+ const isForwarderOnly = isForwarderOnlyDestination(params.to);
5545
7401
  // Validate adapter chain support after resolution
5546
- // This ensures adapters support the resolved chains before proceeding
5547
7402
  params.from.adapter.validateChainSupport(fromChain);
5548
- params.to.adapter.validateChainSupport(toChain);
7403
+ // Only validate destination adapter if it exists
7404
+ if (!isForwarderOnly && 'adapter' in params.to) {
7405
+ params.to.adapter.validateChainSupport(toChain);
7406
+ }
7407
+ // For forwarder-only destinations, use recipientAddress directly
7408
+ // For other destinations, resolve address from adapter
5549
7409
  const [fromAddress, toAddress] = await Promise.all([
5550
7410
  resolveAddress(params.from),
5551
- resolveAddress(params.to),
7411
+ isForwarderOnly
7412
+ ? Promise.resolve(params.to.recipientAddress)
7413
+ : resolveAddress(params.to),
5552
7414
  ]);
5553
7415
  const token = params.token ?? 'USDC';
5554
- // Extract adapters - now always from explicit contexts
5555
- const fromAdapter = params.from.adapter;
5556
- const toAdapter = params.to.adapter;
5557
7416
  // Extract recipientAddress from params.to if it exists
5558
7417
  const recipientAddress = 'recipientAddress' in params.to ? params.to.recipientAddress : undefined;
7418
+ // Extract useForwarder from params.to if it exists
7419
+ const useForwarder = 'useForwarder' in params.to ? params.to.useForwarder : undefined;
7420
+ // Resolve invocation metadata to full InvocationContext with BridgeKit caller
7421
+ const resolvedInvocation = resolveBridgeInvocation(params.invocationMeta);
5559
7422
  return {
5560
7423
  amount: resolveAmount({
5561
7424
  ...params,
@@ -5565,16 +7428,21 @@ async function resolveBridgeParams(params) {
5565
7428
  config: resolveConfig({
5566
7429
  ...params}),
5567
7430
  source: {
5568
- adapter: fromAdapter,
7431
+ adapter: params.from.adapter,
5569
7432
  chain: fromChain,
5570
7433
  address: fromAddress,
5571
7434
  },
5572
7435
  destination: {
5573
- adapter: toAdapter,
7436
+ // Only include adapter if it exists (not forwarder-only)
7437
+ ...(!isForwarderOnly &&
7438
+ 'adapter' in params.to && { adapter: params.to.adapter }),
5574
7439
  chain: toChain,
5575
7440
  address: toAddress,
5576
7441
  ...(recipientAddress !== undefined && { recipientAddress }),
7442
+ ...(useForwarder !== undefined && { useForwarder }),
5577
7443
  },
7444
+ // Pass resolved InvocationContext as invocationMeta (superset is compatible)
7445
+ invocationMeta: resolvedInvocation,
5578
7446
  };
5579
7447
  }
5580
7448
 
@@ -5652,6 +7520,36 @@ const formatBridgeResult = (result, formatDirection) => {
5652
7520
  };
5653
7521
  };
5654
7522
 
7523
+ /**
7524
+ * BridgeKit caller component for retry and estimate operations.
7525
+ */
7526
+ const BRIDGE_KIT_CALLER = {
7527
+ type: 'kit',
7528
+ name: 'BridgeKit',
7529
+ version: pkg.version,
7530
+ };
7531
+ /**
7532
+ * Merge BridgeKit's caller into the invocation metadata for retry operations.
7533
+ *
7534
+ * Prepends the BridgeKit caller to the callers array.
7535
+ *
7536
+ * @param invocationMeta - Optional invocation metadata provided by caller.
7537
+ * @returns Merged invocation metadata with BridgeKit caller prepended.
7538
+ *
7539
+ * @internal
7540
+ */
7541
+ function mergeRetryInvocationMeta(invocationMeta) {
7542
+ // Prepend BridgeKit caller to existing callers array
7543
+ const existingCallers = invocationMeta?.callers ?? [];
7544
+ return invocationMeta
7545
+ ? {
7546
+ ...invocationMeta,
7547
+ callers: [BRIDGE_KIT_CALLER, ...existingCallers],
7548
+ }
7549
+ : {
7550
+ callers: [BRIDGE_KIT_CALLER, ...existingCallers],
7551
+ };
7552
+ }
5655
7553
  /**
5656
7554
  * Route cross-chain USDC bridging through Circle's Cross-Chain Transfer Protocol v2 (CCTPv2).
5657
7555
  *
@@ -5748,7 +7646,7 @@ class BridgeKit {
5748
7646
  * - CCTPv2 support for the chain pair
5749
7647
  * - Transfer configuration options
5750
7648
  *
5751
- * @param params - The transfer parameters containing source, destination, amount, and token
7649
+ * @param params - The transfer parameters containing source, destination, amount, token, and optional invocation metadata
5752
7650
  * @returns Promise resolving to the transfer result with transaction details and steps
5753
7651
  * @throws {KitError} When any parameter validation fails.
5754
7652
  * @throws {Error} When CCTPv2 does not support the specified route.
@@ -5765,18 +7663,24 @@ class BridgeKit {
5765
7663
  * privateKey: process.env.PRIVATE_KEY,
5766
7664
  * })
5767
7665
  *
7666
+ * // Basic usage
5768
7667
  * const result = await kit.bridge({
5769
- * from: {
5770
- * adapter,
5771
- * chain: 'Ethereum'
5772
- * },
5773
- * to: {
5774
- * adapter,
5775
- * chain: 'Base'
5776
- * },
7668
+ * from: { adapter, chain: 'Ethereum' },
7669
+ * to: { adapter, chain: 'Base' },
5777
7670
  * amount: '100.50'
5778
7671
  * })
5779
7672
  *
7673
+ * // With custom invocation metadata
7674
+ * const result = await kit.bridge({
7675
+ * from: { adapter, chain: 'Ethereum' },
7676
+ * to: { adapter, chain: 'Base' },
7677
+ * amount: '100.50',
7678
+ * invocationMeta: {
7679
+ * traceId: 'custom-trace-id',
7680
+ * callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
7681
+ * },
7682
+ * })
7683
+ *
5780
7684
  * // Handle result
5781
7685
  * if (result.state === 'success') {
5782
7686
  * console.log('Bridge completed!')
@@ -5822,6 +7726,8 @@ class BridgeKit {
5822
7726
  * @param context - The retry context containing fresh adapter instances for both
5823
7727
  * source and destination chains. These adapters should be properly
5824
7728
  * configured with current network connections and signing capabilities.
7729
+ * @param invocationMeta - Optional invocation metadata for tracing and correlation.
7730
+ * If not provided, uses the traceId from the original result.
5825
7731
  * @returns A promise that resolves to the updated bridge result after retry execution.
5826
7732
  * The result will contain the complete step history including both original
5827
7733
  * and retry attempts.
@@ -5853,31 +7759,35 @@ class BridgeKit {
5853
7759
  * // ... other properties
5854
7760
  * }
5855
7761
  *
7762
+ * // Basic retry (uses traceId from original result)
7763
+ * const retryResult = await kit.retry(failedResult, {
7764
+ * from: sourceAdapter,
7765
+ * to: destAdapter
7766
+ * })
5856
7767
  *
5857
- * try {
5858
- * const retryResult = await kit.retry(failedResult, {
5859
- * from: sourceAdapter,
5860
- * to: destAdapter
5861
- * })
5862
- *
5863
- * console.log('Retry completed successfully:', retryResult.state)
5864
- * console.log('Total steps executed:', retryResult.steps.length)
5865
- * } catch (error) {
5866
- * console.error('Retry failed:', error.message)
5867
- * // Handle retry failure (may require manual intervention)
5868
- * }
7768
+ * // Retry with custom invocation metadata
7769
+ * const retryResult = await kit.retry(
7770
+ * failedResult,
7771
+ * { from: sourceAdapter, to: destAdapter },
7772
+ * {
7773
+ * traceId: 'custom-trace-id',
7774
+ * callers: [{ type: 'app', name: 'MyApp' }],
7775
+ * }
7776
+ * )
5869
7777
  * ```
5870
7778
  */
5871
- async retry(result, context) {
7779
+ async retry(result, context, invocationMeta) {
5872
7780
  const provider = this.providers.find((p) => p.name === result.provider);
5873
7781
  if (!provider) {
5874
7782
  throw new Error(`Provider ${result.provider} not found`);
5875
7783
  }
7784
+ // Merge BridgeKit caller into invocation metadata for retry operation
7785
+ const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
5876
7786
  // Format the bridge result into bigint string values for internal use
5877
7787
  const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
5878
7788
  // Execute the retry using the provider
5879
7789
  // Format the bridge result into human-readable string values for the user
5880
- return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context), 'to-human-readable');
7790
+ return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
5881
7791
  }
5882
7792
  /**
5883
7793
  * Estimate the cost and fees for a cross-chain USDC bridge operation.
@@ -5885,13 +7795,15 @@ class BridgeKit {
5885
7795
  * This method calculates the expected gas fees and protocol costs for bridging
5886
7796
  * without actually executing the transaction. It performs the same validation
5887
7797
  * as the bridge method but stops before execution.
5888
- * @param params - The bridge parameters for cost estimation
7798
+ *
7799
+ * @param params - The bridge parameters for cost estimation, including optional invocation metadata
5889
7800
  * @returns Promise resolving to detailed cost breakdown including gas estimates
5890
7801
  * @throws {KitError} When the parameters are invalid.
5891
7802
  * @throws {UnsupportedRouteError} When the route is not supported.
5892
7803
  *
5893
7804
  * @example
5894
7805
  * ```typescript
7806
+ * // Basic usage
5895
7807
  * const estimate = await kit.estimate({
5896
7808
  * from: { adapter: adapter, chain: 'Ethereum' },
5897
7809
  * to: { adapter: adapter, chain: 'Base' },
@@ -5899,6 +7811,18 @@ class BridgeKit {
5899
7811
  * token: 'USDC'
5900
7812
  * })
5901
7813
  * console.log('Estimated cost:', estimate.totalCost)
7814
+ *
7815
+ * // With custom invocation metadata
7816
+ * const estimate = await kit.estimate({
7817
+ * from: { adapter: adapter, chain: 'Ethereum' },
7818
+ * to: { adapter: adapter, chain: 'Base' },
7819
+ * amount: '10.50',
7820
+ * token: 'USDC',
7821
+ * invocationMeta: {
7822
+ * traceId: 'custom-trace-id',
7823
+ * callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
7824
+ * },
7825
+ * })
5902
7826
  * ```
5903
7827
  */
5904
7828
  async estimate(params) {
@@ -5950,6 +7874,9 @@ class BridgeKit {
5950
7874
  * // Get only EVM mainnet chains
5951
7875
  * const evmMainnets = kit.getSupportedChains({ chainType: 'evm', isTestnet: false })
5952
7876
  *
7877
+ * // Get only chains that support forwarding
7878
+ * const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
7879
+ *
5953
7880
  * console.log('Supported chains:')
5954
7881
  * allChains.forEach(chain => {
5955
7882
  * console.log(`- ${chain.name} (${chain.type})`)
@@ -5983,6 +7910,18 @@ class BridgeKit {
5983
7910
  if (options?.isTestnet !== undefined) {
5984
7911
  chains = chains.filter((chain) => chain.isTestnet === options.isTestnet);
5985
7912
  }
7913
+ // Apply forwarder support filter if provided
7914
+ if (options?.forwarderSupported !== undefined) {
7915
+ chains = chains.filter((chain) => {
7916
+ const fs = chain.cctp?.forwarderSupported;
7917
+ if (!fs) {
7918
+ return !options.forwarderSupported;
7919
+ }
7920
+ return options.forwarderSupported
7921
+ ? fs.source || fs.destination
7922
+ : !fs.source && !fs.destination;
7923
+ });
7924
+ }
5986
7925
  return chains;
5987
7926
  }
5988
7927
  /**
@@ -6178,6 +8117,10 @@ exports.NetworkError = NetworkError;
6178
8117
  exports.OnchainError = OnchainError;
6179
8118
  exports.RpcError = RpcError;
6180
8119
  exports.bridgeParamsWithChainIdentifierSchema = bridgeParamsWithChainIdentifierSchema;
8120
+ exports.createRuntime = createRuntime;
8121
+ exports.createTokenRegistry = createTokenRegistry;
8122
+ exports.createTraceId = createTraceId;
8123
+ exports.extendInvocationContext = extendInvocationContext;
6181
8124
  exports.getErrorCode = getErrorCode;
6182
8125
  exports.getErrorMessage = getErrorMessage;
6183
8126
  exports.isBalanceError = isBalanceError;
@@ -6189,5 +8132,6 @@ exports.isOnchainError = isOnchainError;
6189
8132
  exports.isRetryableError = isRetryableError;
6190
8133
  exports.isRpcError = isRpcError;
6191
8134
  exports.resolveChainIdentifier = resolveChainIdentifier;
8135
+ exports.resolveInvocationContext = resolveInvocationContext;
6192
8136
  exports.setExternalPrefix = setExternalPrefix;
6193
8137
  //# sourceMappingURL=index.cjs.map