@circle-fin/provider-cctp-v2 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.mjs CHANGED
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  // Buffer polyfill setup - executes before any other code
20
- // Ensures globalThis.Buffer is available for @solana/spl-token and other Solana libraries
20
+ // Ensures globalThis.Buffer is available for Solana libraries
21
21
  import { Buffer } from 'buffer';
22
22
  if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
23
23
  globalThis.Buffer = Buffer;
@@ -66,6 +66,8 @@ var Blockchain;
66
66
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
67
67
  Blockchain["Codex"] = "Codex";
68
68
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
69
+ Blockchain["Edge"] = "Edge";
70
+ Blockchain["Edge_Testnet"] = "Edge_Testnet";
69
71
  Blockchain["Ethereum"] = "Ethereum";
70
72
  Blockchain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
71
73
  Blockchain["Hedera"] = "Hedera";
@@ -78,6 +80,8 @@ var Blockchain;
78
80
  Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
79
81
  Blockchain["Monad"] = "Monad";
80
82
  Blockchain["Monad_Testnet"] = "Monad_Testnet";
83
+ Blockchain["Morph"] = "Morph";
84
+ Blockchain["Morph_Testnet"] = "Morph_Testnet";
81
85
  Blockchain["NEAR"] = "NEAR";
82
86
  Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
83
87
  Blockchain["Noble"] = "Noble";
@@ -109,6 +113,122 @@ var Blockchain;
109
113
  Blockchain["ZKSync_Era"] = "ZKSync_Era";
110
114
  Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
111
115
  })(Blockchain || (Blockchain = {}));
116
+ /**
117
+ * Enum representing the subset of {@link Blockchain} that supports swap operations.
118
+ *
119
+ * This enum provides compile-time type safety for swap chain selection,
120
+ * ensuring only supported chains are available in IDE autocomplete
121
+ * when building swap parameters.
122
+ *
123
+ * @remarks
124
+ * Unlike the full {@link Blockchain} enum, SwapChain only includes networks
125
+ * where the swap functionality is actively supported by the library.
126
+ * Using this enum prevents runtime errors from attempting unsupported
127
+ * cross-chain swaps.
128
+ *
129
+ * Currently supports:
130
+ * - Ethereum mainnet
131
+ * - Base mainnet
132
+ * - Polygon mainnet
133
+ * - Solana mainnet
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * import { SwapChain, swap, createSwapKitContext } from '@circle-fin/swap-kit'
138
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
139
+ *
140
+ * const context = createSwapKitContext()
141
+ * const adapter = createViemAdapterFromPrivateKey({
142
+ * privateKey: process.env.PRIVATE_KEY
143
+ * })
144
+ *
145
+ * // ✅ Autocomplete shows only swap-supported chains
146
+ * const result = await swap(context, {
147
+ * from: {
148
+ * adapter,
149
+ * chain: SwapChain.Ethereum // Autocomplete: Ethereum, Base, Polygon, Solana
150
+ * },
151
+ * tokenIn: 'USDC',
152
+ * tokenOut: 'USDT',
153
+ * amount: '100.0'
154
+ * })
155
+ * ```
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * // String literals also work (constrained to SwapChain values)
160
+ * const result = await swap(context, {
161
+ * from: {
162
+ * adapter,
163
+ * chain: 'Ethereum' // ✅ Only SwapChain strings allowed
164
+ * },
165
+ * tokenIn: 'USDC',
166
+ * tokenOut: 'NATIVE',
167
+ * amount: '50.0'
168
+ * })
169
+ *
170
+ * // ❌ TypeScript error - Sui not in SwapChain enum
171
+ * const invalidResult = await swap(context, {
172
+ * from: {
173
+ * adapter,
174
+ * chain: 'Sui' // Compile-time error!
175
+ * },
176
+ * tokenIn: 'USDC',
177
+ * tokenOut: 'USDT',
178
+ * amount: '100.0'
179
+ * })
180
+ * ```
181
+ */
182
+ /**
183
+ * Enum representing chains that support same-chain swaps through the Swap Kit.
184
+ *
185
+ * Unlike the full {@link Blockchain} enum, SwapChain includes only mainnet
186
+ * networks where adapter contracts are deployed (CCTPv2 support).
187
+ *
188
+ * Dynamic validation via {@link isSwapSupportedChain} ensures chains
189
+ * automatically work when adapter contracts and supported tokens are deployed.
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * import { SwapChain } from '@core/chains'
194
+ * import { swap } from '@circle-fin/swap-kit'
195
+ *
196
+ * const result = await swap(context, {
197
+ * from: {
198
+ * adapter,
199
+ * chain: SwapChain.Arbitrum // Now supported!
200
+ * },
201
+ * tokenIn: 'USDC',
202
+ * tokenOut: 'WETH',
203
+ * amount: '100.0'
204
+ * })
205
+ * ```
206
+ *
207
+ * @see {@link isSwapSupportedChain} for runtime validation
208
+ * @see {@link getSwapSupportedChains} for all supported chains
209
+ */
210
+ var SwapChain;
211
+ (function (SwapChain) {
212
+ // Original 4 chains
213
+ SwapChain["Ethereum"] = "Ethereum";
214
+ SwapChain["Base"] = "Base";
215
+ SwapChain["Polygon"] = "Polygon";
216
+ SwapChain["Solana"] = "Solana";
217
+ // Additional supported chains
218
+ SwapChain["Arbitrum"] = "Arbitrum";
219
+ SwapChain["Optimism"] = "Optimism";
220
+ SwapChain["Avalanche"] = "Avalanche";
221
+ SwapChain["Linea"] = "Linea";
222
+ SwapChain["Ink"] = "Ink";
223
+ SwapChain["World_Chain"] = "World_Chain";
224
+ SwapChain["Unichain"] = "Unichain";
225
+ SwapChain["Plume"] = "Plume";
226
+ SwapChain["Sei"] = "Sei";
227
+ SwapChain["Sonic"] = "Sonic";
228
+ SwapChain["XDC"] = "XDC";
229
+ SwapChain["HyperEVM"] = "HyperEVM";
230
+ SwapChain["Monad"] = "Monad";
231
+ })(SwapChain || (SwapChain = {}));
112
232
  // -----------------------------------------------------------------------------
113
233
  // Bridge Chain Enum (CCTPv2 Supported Chains)
114
234
  // -----------------------------------------------------------------------------
@@ -165,11 +285,13 @@ var BridgeChain;
165
285
  BridgeChain["Avalanche"] = "Avalanche";
166
286
  BridgeChain["Base"] = "Base";
167
287
  BridgeChain["Codex"] = "Codex";
288
+ BridgeChain["Edge"] = "Edge";
168
289
  BridgeChain["Ethereum"] = "Ethereum";
169
290
  BridgeChain["HyperEVM"] = "HyperEVM";
170
291
  BridgeChain["Ink"] = "Ink";
171
292
  BridgeChain["Linea"] = "Linea";
172
293
  BridgeChain["Monad"] = "Monad";
294
+ BridgeChain["Morph"] = "Morph";
173
295
  BridgeChain["Optimism"] = "Optimism";
174
296
  BridgeChain["Plume"] = "Plume";
175
297
  BridgeChain["Polygon"] = "Polygon";
@@ -185,11 +307,13 @@ var BridgeChain;
185
307
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
186
308
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
187
309
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
310
+ BridgeChain["Edge_Testnet"] = "Edge_Testnet";
188
311
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
189
312
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
190
313
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
191
314
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
192
315
  BridgeChain["Monad_Testnet"] = "Monad_Testnet";
316
+ BridgeChain["Morph_Testnet"] = "Morph_Testnet";
193
317
  BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
194
318
  BridgeChain["Plume_Testnet"] = "Plume_Testnet";
195
319
  BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
@@ -260,6 +384,7 @@ const Algorand = defineChain({
260
384
  rpcEndpoints: ['https://mainnet-api.algonode.cloud'],
261
385
  eurcAddress: null,
262
386
  usdcAddress: '31566704',
387
+ usdtAddress: null,
263
388
  cctp: null,
264
389
  });
265
390
 
@@ -283,6 +408,7 @@ const AlgorandTestnet = defineChain({
283
408
  rpcEndpoints: ['https://testnet-api.algonode.cloud'],
284
409
  eurcAddress: null,
285
410
  usdcAddress: '10458941',
411
+ usdtAddress: null,
286
412
  cctp: null,
287
413
  });
288
414
 
@@ -306,6 +432,7 @@ const Aptos = defineChain({
306
432
  rpcEndpoints: ['https://fullnode.mainnet.aptoslabs.com/v1'],
307
433
  eurcAddress: null,
308
434
  usdcAddress: '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b',
435
+ usdtAddress: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
309
436
  cctp: {
310
437
  domain: 9,
311
438
  contracts: {
@@ -343,6 +470,7 @@ const AptosTestnet = defineChain({
343
470
  rpcEndpoints: ['https://fullnode.testnet.aptoslabs.com/v1'],
344
471
  eurcAddress: null,
345
472
  usdcAddress: '0x69091fbab5f7d635ee7ac5098cf0c1efbe31d68fec0f2cd565e8d168daf52832',
473
+ usdtAddress: null,
346
474
  cctp: {
347
475
  domain: 9,
348
476
  contracts: {
@@ -360,6 +488,121 @@ const AptosTestnet = defineChain({
360
488
  },
361
489
  });
362
490
 
491
+ /**
492
+ * Complete swap token registry - single source of truth for all swap-supported tokens.
493
+ *
494
+ * @remarks
495
+ * All packages should import from this registry for swap operations.
496
+ * Adding a new swap token requires updating only this registry.
497
+ *
498
+ * The NATIVE token is handled separately as it resolves dynamically based on chain.
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * import { SWAP_TOKEN_REGISTRY } from '@core/chains'
503
+ *
504
+ * // Get token decimals
505
+ * const decimals = SWAP_TOKEN_REGISTRY.USDC.decimals // 6
506
+ *
507
+ * // Check if token is stablecoin
508
+ * const isStable = SWAP_TOKEN_REGISTRY.DAI.category === 'stablecoin' // true
509
+ * ```
510
+ */
511
+ const SWAP_TOKEN_REGISTRY = {
512
+ // ============================================================================
513
+ // Stablecoins (6 decimals)
514
+ // ============================================================================
515
+ USDC: {
516
+ symbol: 'USDC',
517
+ decimals: 6,
518
+ category: 'stablecoin',
519
+ description: 'USD Coin',
520
+ },
521
+ EURC: {
522
+ symbol: 'EURC',
523
+ decimals: 6,
524
+ category: 'stablecoin',
525
+ description: 'Euro Coin',
526
+ },
527
+ USDT: {
528
+ symbol: 'USDT',
529
+ decimals: 6,
530
+ category: 'stablecoin',
531
+ description: 'Tether USD',
532
+ },
533
+ PYUSD: {
534
+ symbol: 'PYUSD',
535
+ decimals: 6,
536
+ category: 'stablecoin',
537
+ description: 'PayPal USD',
538
+ },
539
+ // ============================================================================
540
+ // Stablecoins (18 decimals)
541
+ // ============================================================================
542
+ DAI: {
543
+ symbol: 'DAI',
544
+ decimals: 18,
545
+ category: 'stablecoin',
546
+ description: 'MakerDAO stablecoin',
547
+ },
548
+ USDE: {
549
+ symbol: 'USDE',
550
+ decimals: 18,
551
+ category: 'stablecoin',
552
+ description: 'Ethena USD (synthetic dollar)',
553
+ },
554
+ // ============================================================================
555
+ // Wrapped Tokens
556
+ // ============================================================================
557
+ WBTC: {
558
+ symbol: 'WBTC',
559
+ decimals: 8,
560
+ category: 'wrapped',
561
+ description: 'Wrapped Bitcoin',
562
+ },
563
+ WETH: {
564
+ symbol: 'WETH',
565
+ decimals: 18,
566
+ category: 'wrapped',
567
+ description: 'Wrapped Ethereum',
568
+ },
569
+ WSOL: {
570
+ symbol: 'WSOL',
571
+ decimals: 9,
572
+ category: 'wrapped',
573
+ description: 'Wrapped Solana',
574
+ },
575
+ WAVAX: {
576
+ symbol: 'WAVAX',
577
+ decimals: 18,
578
+ category: 'wrapped',
579
+ description: 'Wrapped Avalanche',
580
+ },
581
+ WPOL: {
582
+ symbol: 'WPOL',
583
+ decimals: 18,
584
+ category: 'wrapped',
585
+ description: 'Wrapped Polygon',
586
+ },
587
+ };
588
+ /**
589
+ * Special NATIVE token constant for swap operations.
590
+ *
591
+ * @remarks
592
+ * NATIVE is handled separately from SWAP_TOKEN_REGISTRY because it resolves
593
+ * dynamically based on the chain (ETH on Ethereum, SOL on Solana, etc.).
594
+ * Its decimals are chain-specific.
595
+ */
596
+ const NATIVE_TOKEN = 'NATIVE';
597
+ /**
598
+ * Array of all supported swap token symbols including NATIVE.
599
+ * Useful for iteration, validation, and filtering.
600
+ */
601
+ [
602
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
603
+ NATIVE_TOKEN,
604
+ ];
605
+
363
606
  /**
364
607
  * The bridge contract address for EVM testnet networks.
365
608
  *
@@ -376,6 +619,13 @@ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d'
376
619
  * USDC transfers on live networks.
377
620
  */
378
621
  const BRIDGE_CONTRACT_EVM_MAINNET = '0xB3FA262d0fB521cc93bE83d87b322b8A23DAf3F0';
622
+ /**
623
+ * The adapter contract address for EVM mainnet networks.
624
+ *
625
+ * This contract serves as an adapter for integrating with various protocols
626
+ * on EVM-compatible chains. Use this address for mainnet adapter integrations.
627
+ */
628
+ const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845';
379
629
 
380
630
  /**
381
631
  * Arc Testnet chain definition
@@ -405,6 +655,7 @@ const ArcTestnet = defineChain({
405
655
  rpcEndpoints: ['https://rpc.testnet.arc.network/'],
406
656
  eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
407
657
  usdcAddress: '0x3600000000000000000000000000000000000000',
658
+ usdtAddress: null,
408
659
  cctp: {
409
660
  domain: 26,
410
661
  contracts: {
@@ -447,6 +698,7 @@ const Arbitrum = defineChain({
447
698
  rpcEndpoints: ['https://arb1.arbitrum.io/rpc'],
448
699
  eurcAddress: null,
449
700
  usdcAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
701
+ usdtAddress: null,
450
702
  cctp: {
451
703
  domain: 3,
452
704
  contracts: {
@@ -471,6 +723,7 @@ const Arbitrum = defineChain({
471
723
  },
472
724
  kitContracts: {
473
725
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
726
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
474
727
  },
475
728
  });
476
729
 
@@ -495,6 +748,7 @@ const ArbitrumSepolia = defineChain({
495
748
  rpcEndpoints: ['https://sepolia-rollup.arbitrum.io/rpc'],
496
749
  eurcAddress: null,
497
750
  usdcAddress: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
751
+ usdtAddress: null,
498
752
  cctp: {
499
753
  domain: 3,
500
754
  contracts: {
@@ -543,6 +797,7 @@ const Avalanche = defineChain({
543
797
  rpcEndpoints: ['https://api.avax.network/ext/bc/C/rpc'],
544
798
  eurcAddress: '0xc891eb4cbdeff6e073e859e987815ed1505c2acd',
545
799
  usdcAddress: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
800
+ usdtAddress: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7',
546
801
  cctp: {
547
802
  domain: 1,
548
803
  contracts: {
@@ -567,6 +822,7 @@ const Avalanche = defineChain({
567
822
  },
568
823
  kitContracts: {
569
824
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
825
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
570
826
  },
571
827
  });
572
828
 
@@ -590,6 +846,7 @@ const AvalancheFuji = defineChain({
590
846
  explorerUrl: 'https://subnets-test.avax.network/c-chain/tx/{hash}',
591
847
  eurcAddress: '0x5e44db7996c682e92a960b65ac713a54ad815c6b',
592
848
  usdcAddress: '0x5425890298aed601595a70ab815c96711a31bc65',
849
+ usdtAddress: null,
593
850
  cctp: {
594
851
  domain: 1,
595
852
  contracts: {
@@ -639,6 +896,7 @@ const Base = defineChain({
639
896
  rpcEndpoints: ['https://mainnet.base.org', 'https://base.publicnode.com'],
640
897
  eurcAddress: '0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42',
641
898
  usdcAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
899
+ usdtAddress: null,
642
900
  cctp: {
643
901
  domain: 6,
644
902
  contracts: {
@@ -663,6 +921,7 @@ const Base = defineChain({
663
921
  },
664
922
  kitContracts: {
665
923
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
924
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
666
925
  },
667
926
  });
668
927
 
@@ -687,6 +946,7 @@ const BaseSepolia = defineChain({
687
946
  rpcEndpoints: ['https://sepolia.base.org'],
688
947
  eurcAddress: '0x808456652fdb597867f38412077A9182bf77359F',
689
948
  usdcAddress: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
949
+ usdtAddress: null,
690
950
  cctp: {
691
951
  domain: 6,
692
952
  contracts: {
@@ -735,6 +995,7 @@ const Celo = defineChain({
735
995
  rpcEndpoints: ['https://forno.celo.org'],
736
996
  eurcAddress: null,
737
997
  usdcAddress: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
998
+ usdtAddress: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
738
999
  cctp: null,
739
1000
  });
740
1001
 
@@ -759,6 +1020,7 @@ const CeloAlfajoresTestnet = defineChain({
759
1020
  rpcEndpoints: ['https://alfajores-forno.celo-testnet.org'],
760
1021
  eurcAddress: null,
761
1022
  usdcAddress: '0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B',
1023
+ usdtAddress: null,
762
1024
  cctp: null,
763
1025
  });
764
1026
 
@@ -783,6 +1045,7 @@ const Codex = defineChain({
783
1045
  rpcEndpoints: ['https://rpc.codex.xyz'],
784
1046
  eurcAddress: null,
785
1047
  usdcAddress: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
1048
+ usdtAddress: null,
786
1049
  cctp: {
787
1050
  domain: 12,
788
1051
  contracts: {
@@ -825,6 +1088,7 @@ const CodexTestnet = defineChain({
825
1088
  rpcEndpoints: ['https://rpc.codex-stg.xyz'],
826
1089
  eurcAddress: null,
827
1090
  usdcAddress: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
1091
+ usdtAddress: null,
828
1092
  cctp: {
829
1093
  domain: 12,
830
1094
  contracts: {
@@ -846,6 +1110,94 @@ const CodexTestnet = defineChain({
846
1110
  },
847
1111
  });
848
1112
 
1113
+ /**
1114
+ * Edge Mainnet chain definition
1115
+ * @remarks
1116
+ * This represents the official production network for the Edge blockchain.
1117
+ * Edge is an EVM-compatible blockchain.
1118
+ */
1119
+ const Edge = defineChain({
1120
+ type: 'evm',
1121
+ chain: Blockchain.Edge,
1122
+ name: 'Edge',
1123
+ title: 'Edge Mainnet',
1124
+ nativeCurrency: {
1125
+ name: 'Ether',
1126
+ symbol: 'ETH',
1127
+ decimals: 18,
1128
+ },
1129
+ chainId: 3343,
1130
+ isTestnet: false,
1131
+ explorerUrl: 'https://pro.edgex.exchange/en-US/explorer/tx/{hash}',
1132
+ rpcEndpoints: ['https://edge-mainnet.g.alchemy.com/public'],
1133
+ eurcAddress: null,
1134
+ usdcAddress: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
1135
+ usdtAddress: null,
1136
+ cctp: {
1137
+ domain: 28,
1138
+ contracts: {
1139
+ v2: {
1140
+ type: 'split',
1141
+ tokenMessenger: '0x98706A006bc632Df31CAdFCBD43F38887ce2ca5c',
1142
+ messageTransmitter: '0x5b61381Fc9e58E70EfC13a4A97516997019198ee',
1143
+ confirmations: 65,
1144
+ fastConfirmations: 1,
1145
+ },
1146
+ },
1147
+ forwarderSupported: {
1148
+ source: true,
1149
+ destination: true,
1150
+ },
1151
+ },
1152
+ kitContracts: {
1153
+ bridge: '0x6D1AaE1c34Aeb582022916a67f2A655C6f4eDFF2', //Unique bridge address as CCTP address for Edge is also unique
1154
+ },
1155
+ });
1156
+
1157
+ /**
1158
+ * Edge Testnet chain definition
1159
+ * @remarks
1160
+ * This represents the official test network for the Edge blockchain.
1161
+ * Edge is an EVM-compatible blockchain.
1162
+ */
1163
+ const EdgeTestnet = defineChain({
1164
+ type: 'evm',
1165
+ chain: Blockchain.Edge_Testnet,
1166
+ name: 'Edge Testnet',
1167
+ title: 'Edge Testnet',
1168
+ nativeCurrency: {
1169
+ name: 'Ether',
1170
+ symbol: 'ETH',
1171
+ decimals: 18,
1172
+ },
1173
+ chainId: 33431,
1174
+ isTestnet: true,
1175
+ explorerUrl: 'https://edge-testnet.explorer.alchemy.com/tx/{hash}',
1176
+ rpcEndpoints: ['https://edge-testnet.g.alchemy.com/public'],
1177
+ eurcAddress: null,
1178
+ usdcAddress: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
1179
+ usdtAddress: null,
1180
+ cctp: {
1181
+ domain: 28,
1182
+ contracts: {
1183
+ v2: {
1184
+ type: 'split',
1185
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
1186
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
1187
+ confirmations: 65,
1188
+ fastConfirmations: 1,
1189
+ },
1190
+ },
1191
+ forwarderSupported: {
1192
+ source: true,
1193
+ destination: true,
1194
+ },
1195
+ },
1196
+ kitContracts: {
1197
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1198
+ },
1199
+ });
1200
+
849
1201
  /**
850
1202
  * Ethereum Mainnet chain definition
851
1203
  * @remarks
@@ -867,6 +1219,7 @@ const Ethereum = defineChain({
867
1219
  rpcEndpoints: ['https://eth.merkle.io', 'https://ethereum.publicnode.com'],
868
1220
  eurcAddress: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
869
1221
  usdcAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
1222
+ usdtAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7',
870
1223
  cctp: {
871
1224
  domain: 0,
872
1225
  contracts: {
@@ -891,6 +1244,7 @@ const Ethereum = defineChain({
891
1244
  },
892
1245
  kitContracts: {
893
1246
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1247
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
894
1248
  },
895
1249
  });
896
1250
 
@@ -915,6 +1269,7 @@ const EthereumSepolia = defineChain({
915
1269
  rpcEndpoints: ['https://sepolia.drpc.org'],
916
1270
  eurcAddress: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
917
1271
  usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
1272
+ usdtAddress: null,
918
1273
  cctp: {
919
1274
  domain: 0,
920
1275
  contracts: {
@@ -962,6 +1317,7 @@ const Hedera = defineChain({
962
1317
  rpcEndpoints: ['https://mainnet.hashio.io/api'],
963
1318
  eurcAddress: null,
964
1319
  usdcAddress: '0.0.456858',
1320
+ usdtAddress: null,
965
1321
  cctp: null,
966
1322
  });
967
1323
 
@@ -985,6 +1341,7 @@ const HederaTestnet = defineChain({
985
1341
  rpcEndpoints: ['https://testnet.hashio.io/api'],
986
1342
  eurcAddress: null,
987
1343
  usdcAddress: '0.0.429274',
1344
+ usdtAddress: null,
988
1345
  cctp: null,
989
1346
  });
990
1347
 
@@ -1011,6 +1368,7 @@ const HyperEVM = defineChain({
1011
1368
  rpcEndpoints: ['https://rpc.hyperliquid.xyz/evm'],
1012
1369
  eurcAddress: null,
1013
1370
  usdcAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
1371
+ usdtAddress: null,
1014
1372
  cctp: {
1015
1373
  domain: 19,
1016
1374
  contracts: {
@@ -1029,6 +1387,7 @@ const HyperEVM = defineChain({
1029
1387
  },
1030
1388
  kitContracts: {
1031
1389
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1390
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1032
1391
  },
1033
1392
  });
1034
1393
 
@@ -1054,6 +1413,7 @@ const HyperEVMTestnet = defineChain({
1054
1413
  rpcEndpoints: ['https://rpc.hyperliquid-testnet.xyz/evm'],
1055
1414
  eurcAddress: null,
1056
1415
  usdcAddress: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
1416
+ usdtAddress: null,
1057
1417
  cctp: {
1058
1418
  domain: 19,
1059
1419
  contracts: {
@@ -1101,6 +1461,7 @@ const Ink = defineChain({
1101
1461
  ],
1102
1462
  eurcAddress: null,
1103
1463
  usdcAddress: '0x2D270e6886d130D724215A266106e6832161EAEd',
1464
+ usdtAddress: null,
1104
1465
  cctp: {
1105
1466
  domain: 21,
1106
1467
  contracts: {
@@ -1119,6 +1480,7 @@ const Ink = defineChain({
1119
1480
  },
1120
1481
  kitContracts: {
1121
1482
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1483
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1122
1484
  },
1123
1485
  });
1124
1486
 
@@ -1147,6 +1509,7 @@ const InkTestnet = defineChain({
1147
1509
  ],
1148
1510
  eurcAddress: null,
1149
1511
  usdcAddress: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
1512
+ usdtAddress: null,
1150
1513
  cctp: {
1151
1514
  domain: 21,
1152
1515
  contracts: {
@@ -1189,6 +1552,7 @@ const Linea = defineChain({
1189
1552
  rpcEndpoints: ['https://rpc.linea.build'],
1190
1553
  eurcAddress: null,
1191
1554
  usdcAddress: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
1555
+ usdtAddress: null,
1192
1556
  cctp: {
1193
1557
  domain: 11,
1194
1558
  contracts: {
@@ -1207,6 +1571,7 @@ const Linea = defineChain({
1207
1571
  },
1208
1572
  kitContracts: {
1209
1573
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1574
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1210
1575
  },
1211
1576
  });
1212
1577
 
@@ -1231,6 +1596,7 @@ const LineaSepolia = defineChain({
1231
1596
  rpcEndpoints: ['https://rpc.sepolia.linea.build'],
1232
1597
  eurcAddress: null,
1233
1598
  usdcAddress: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
1599
+ usdtAddress: null,
1234
1600
  cctp: {
1235
1601
  domain: 11,
1236
1602
  contracts: {
@@ -1275,6 +1641,7 @@ const Monad = defineChain({
1275
1641
  rpcEndpoints: ['https://rpc.monad.xyz'],
1276
1642
  eurcAddress: null,
1277
1643
  usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
1644
+ usdtAddress: null,
1278
1645
  cctp: {
1279
1646
  domain: 15,
1280
1647
  contracts: {
@@ -1293,6 +1660,7 @@ const Monad = defineChain({
1293
1660
  },
1294
1661
  kitContracts: {
1295
1662
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1663
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1296
1664
  },
1297
1665
  });
1298
1666
 
@@ -1319,6 +1687,7 @@ const MonadTestnet = defineChain({
1319
1687
  rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
1320
1688
  eurcAddress: null,
1321
1689
  usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
1690
+ usdtAddress: null,
1322
1691
  cctp: {
1323
1692
  domain: 15,
1324
1693
  contracts: {
@@ -1340,6 +1709,94 @@ const MonadTestnet = defineChain({
1340
1709
  },
1341
1710
  });
1342
1711
 
1712
+ /**
1713
+ * Morph Mainnet chain definition
1714
+ * @remarks
1715
+ * This represents the official production network for the Morph blockchain.
1716
+ * Morph is an EVM-compatible Layer-2 blockchain built on the OP Stack.
1717
+ */
1718
+ const Morph = defineChain({
1719
+ type: 'evm',
1720
+ chain: Blockchain.Morph,
1721
+ name: 'Morph',
1722
+ title: 'Morph Mainnet',
1723
+ nativeCurrency: {
1724
+ name: 'Ether',
1725
+ symbol: 'ETH',
1726
+ decimals: 18,
1727
+ },
1728
+ chainId: 2818,
1729
+ isTestnet: false,
1730
+ explorerUrl: 'https://explorer.morph.network/tx/{hash}',
1731
+ rpcEndpoints: ['https://rpc.morphl2.io'],
1732
+ eurcAddress: null,
1733
+ usdcAddress: '0xCfb1186F4e93D60E60a8bDd997427D1F33bc372B',
1734
+ usdtAddress: null,
1735
+ cctp: {
1736
+ domain: 30,
1737
+ contracts: {
1738
+ v2: {
1739
+ type: 'split',
1740
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
1741
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
1742
+ confirmations: 64,
1743
+ fastConfirmations: 1,
1744
+ },
1745
+ },
1746
+ forwarderSupported: {
1747
+ source: false,
1748
+ destination: false,
1749
+ },
1750
+ },
1751
+ kitContracts: {
1752
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1753
+ },
1754
+ });
1755
+
1756
+ /**
1757
+ * Morph Hoodi Testnet chain definition
1758
+ * @remarks
1759
+ * This represents the official test network for the Morph blockchain.
1760
+ * Morph is an EVM-compatible Layer-2 blockchain built on the OP Stack.
1761
+ */
1762
+ const MorphTestnet = defineChain({
1763
+ type: 'evm',
1764
+ chain: Blockchain.Morph_Testnet,
1765
+ name: 'Morph Hoodi',
1766
+ title: 'Morph Hoodi Testnet',
1767
+ nativeCurrency: {
1768
+ name: 'Ether',
1769
+ symbol: 'ETH',
1770
+ decimals: 18,
1771
+ },
1772
+ chainId: 2910,
1773
+ isTestnet: true,
1774
+ explorerUrl: 'https://explorer-hoodi.morphl2.io/tx/{hash}',
1775
+ rpcEndpoints: ['https://rpc-hoodi.morphl2.io'],
1776
+ eurcAddress: null,
1777
+ usdcAddress: '0x7433b41C6c5e1d58D4Da99483609520255ab661B',
1778
+ usdtAddress: null,
1779
+ cctp: {
1780
+ domain: 30,
1781
+ contracts: {
1782
+ v2: {
1783
+ type: 'split',
1784
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
1785
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
1786
+ confirmations: 64,
1787
+ fastConfirmations: 1,
1788
+ },
1789
+ },
1790
+ forwarderSupported: {
1791
+ source: false,
1792
+ destination: false,
1793
+ },
1794
+ },
1795
+ kitContracts: {
1796
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1797
+ },
1798
+ });
1799
+
1343
1800
  /**
1344
1801
  * NEAR Protocol Mainnet chain definition
1345
1802
  * @remarks
@@ -1360,6 +1817,7 @@ const NEAR = defineChain({
1360
1817
  rpcEndpoints: ['https://eth-rpc.mainnet.near.org'],
1361
1818
  eurcAddress: null,
1362
1819
  usdcAddress: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
1820
+ usdtAddress: 'usdt.tether-token.near',
1363
1821
  cctp: null,
1364
1822
  });
1365
1823
 
@@ -1383,6 +1841,7 @@ const NEARTestnet = defineChain({
1383
1841
  rpcEndpoints: ['https://eth-rpc.testnet.near.org'],
1384
1842
  eurcAddress: null,
1385
1843
  usdcAddress: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
1844
+ usdtAddress: null,
1386
1845
  cctp: null,
1387
1846
  });
1388
1847
 
@@ -1406,6 +1865,7 @@ const Noble = defineChain({
1406
1865
  rpcEndpoints: ['https://noble-rpc.polkachu.com'],
1407
1866
  eurcAddress: null,
1408
1867
  usdcAddress: 'uusdc',
1868
+ usdtAddress: null,
1409
1869
  cctp: {
1410
1870
  domain: 4,
1411
1871
  contracts: {
@@ -1442,6 +1902,7 @@ const NobleTestnet = defineChain({
1442
1902
  rpcEndpoints: ['https://noble-testnet-rpc.polkachu.com'],
1443
1903
  eurcAddress: null,
1444
1904
  usdcAddress: 'uusdc',
1905
+ usdtAddress: null,
1445
1906
  cctp: {
1446
1907
  domain: 4,
1447
1908
  contracts: {
@@ -1479,6 +1940,7 @@ const Optimism = defineChain({
1479
1940
  rpcEndpoints: ['https://mainnet.optimism.io'],
1480
1941
  eurcAddress: null,
1481
1942
  usdcAddress: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
1943
+ usdtAddress: null,
1482
1944
  cctp: {
1483
1945
  domain: 2,
1484
1946
  contracts: {
@@ -1503,6 +1965,7 @@ const Optimism = defineChain({
1503
1965
  },
1504
1966
  kitContracts: {
1505
1967
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1968
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1506
1969
  },
1507
1970
  });
1508
1971
 
@@ -1527,6 +1990,7 @@ const OptimismSepolia = defineChain({
1527
1990
  rpcEndpoints: ['https://sepolia.optimism.io'],
1528
1991
  eurcAddress: null,
1529
1992
  usdcAddress: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
1993
+ usdtAddress: null,
1530
1994
  cctp: {
1531
1995
  domain: 2,
1532
1996
  contracts: {
@@ -1577,6 +2041,7 @@ const Plume = defineChain({
1577
2041
  rpcEndpoints: ['https://rpc.plume.org'],
1578
2042
  eurcAddress: null,
1579
2043
  usdcAddress: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
2044
+ usdtAddress: null,
1580
2045
  cctp: {
1581
2046
  domain: 22,
1582
2047
  contracts: {
@@ -1595,6 +2060,7 @@ const Plume = defineChain({
1595
2060
  },
1596
2061
  kitContracts: {
1597
2062
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2063
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1598
2064
  },
1599
2065
  });
1600
2066
 
@@ -1620,6 +2086,7 @@ const PlumeTestnet = defineChain({
1620
2086
  rpcEndpoints: ['https://testnet-rpc.plume.org'],
1621
2087
  eurcAddress: null,
1622
2088
  usdcAddress: '0xcB5f30e335672893c7eb944B374c196392C19D18',
2089
+ usdtAddress: null,
1623
2090
  cctp: {
1624
2091
  domain: 22,
1625
2092
  contracts: {
@@ -1661,6 +2128,7 @@ const PolkadotAssetHub = defineChain({
1661
2128
  rpcEndpoints: ['https://asset-hub-polkadot-rpc.n.dwellir.com'],
1662
2129
  eurcAddress: null,
1663
2130
  usdcAddress: '1337',
2131
+ usdtAddress: '1984',
1664
2132
  cctp: null,
1665
2133
  });
1666
2134
 
@@ -1684,6 +2152,7 @@ const PolkadotWestmint = defineChain({
1684
2152
  rpcEndpoints: ['https://westmint-rpc.polkadot.io'],
1685
2153
  eurcAddress: null,
1686
2154
  usdcAddress: 'Asset ID 31337',
2155
+ usdtAddress: null,
1687
2156
  cctp: null,
1688
2157
  });
1689
2158
 
@@ -1705,9 +2174,10 @@ const Polygon = defineChain({
1705
2174
  chainId: 137,
1706
2175
  isTestnet: false,
1707
2176
  explorerUrl: 'https://polygonscan.com/tx/{hash}',
1708
- rpcEndpoints: ['https://polygon-rpc.com', 'https://polygon.publicnode.com'],
2177
+ rpcEndpoints: ['https://polygon.publicnode.com', 'https://polygon.drpc.org'],
1709
2178
  eurcAddress: null,
1710
2179
  usdcAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
2180
+ usdtAddress: null,
1711
2181
  cctp: {
1712
2182
  domain: 7,
1713
2183
  contracts: {
@@ -1732,6 +2202,7 @@ const Polygon = defineChain({
1732
2202
  },
1733
2203
  kitContracts: {
1734
2204
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2205
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1735
2206
  },
1736
2207
  });
1737
2208
 
@@ -1756,6 +2227,7 @@ const PolygonAmoy = defineChain({
1756
2227
  rpcEndpoints: ['https://rpc-amoy.polygon.technology'],
1757
2228
  eurcAddress: null,
1758
2229
  usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
2230
+ usdtAddress: null,
1759
2231
  cctp: {
1760
2232
  domain: 7,
1761
2233
  contracts: {
@@ -1806,6 +2278,7 @@ const Sei = defineChain({
1806
2278
  rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
1807
2279
  eurcAddress: null,
1808
2280
  usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
2281
+ usdtAddress: null,
1809
2282
  cctp: {
1810
2283
  domain: 16,
1811
2284
  contracts: {
@@ -1824,6 +2297,7 @@ const Sei = defineChain({
1824
2297
  },
1825
2298
  kitContracts: {
1826
2299
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2300
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1827
2301
  },
1828
2302
  });
1829
2303
 
@@ -1849,6 +2323,7 @@ const SeiTestnet = defineChain({
1849
2323
  rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
1850
2324
  eurcAddress: null,
1851
2325
  usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
2326
+ usdtAddress: null,
1852
2327
  cctp: {
1853
2328
  domain: 16,
1854
2329
  contracts: {
@@ -1891,6 +2366,7 @@ const Sonic = defineChain({
1891
2366
  rpcEndpoints: ['https://rpc.soniclabs.com'],
1892
2367
  eurcAddress: null,
1893
2368
  usdcAddress: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
2369
+ usdtAddress: null,
1894
2370
  cctp: {
1895
2371
  domain: 13,
1896
2372
  contracts: {
@@ -1909,6 +2385,7 @@ const Sonic = defineChain({
1909
2385
  },
1910
2386
  kitContracts: {
1911
2387
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2388
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1912
2389
  },
1913
2390
  });
1914
2391
 
@@ -1933,6 +2410,7 @@ const SonicTestnet = defineChain({
1933
2410
  rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
1934
2411
  eurcAddress: null,
1935
2412
  usdcAddress: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
2413
+ usdtAddress: null,
1936
2414
  cctp: {
1937
2415
  domain: 13,
1938
2416
  contracts: {
@@ -1974,6 +2452,7 @@ const Solana = defineChain({
1974
2452
  rpcEndpoints: ['https://api.mainnet-beta.solana.com'],
1975
2453
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
1976
2454
  usdcAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
2455
+ usdtAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
1977
2456
  cctp: {
1978
2457
  domain: 5,
1979
2458
  contracts: {
@@ -2020,6 +2499,7 @@ const SolanaDevnet = defineChain({
2020
2499
  explorerUrl: 'https://solscan.io/tx/{hash}?cluster=devnet',
2021
2500
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
2022
2501
  usdcAddress: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
2502
+ usdtAddress: null,
2023
2503
  cctp: {
2024
2504
  domain: 5,
2025
2505
  contracts: {
@@ -2068,6 +2548,7 @@ const Stellar = defineChain({
2068
2548
  rpcEndpoints: ['https://horizon.stellar.org'],
2069
2549
  eurcAddress: 'EURC-GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2',
2070
2550
  usdcAddress: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
2551
+ usdtAddress: null,
2071
2552
  cctp: null,
2072
2553
  });
2073
2554
 
@@ -2091,6 +2572,7 @@ const StellarTestnet = defineChain({
2091
2572
  rpcEndpoints: ['https://horizon-testnet.stellar.org'],
2092
2573
  eurcAddress: 'EURC-GB3Q6QDZYTHWT7E5PVS3W7FUT5GVAFC5KSZFFLPU25GO7VTC3NM2ZTVO',
2093
2574
  usdcAddress: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
2575
+ usdtAddress: null,
2094
2576
  cctp: null,
2095
2577
  });
2096
2578
 
@@ -2114,6 +2596,7 @@ const Sui = defineChain({
2114
2596
  rpcEndpoints: ['https://fullnode.mainnet.sui.io'],
2115
2597
  eurcAddress: null,
2116
2598
  usdcAddress: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
2599
+ usdtAddress: null,
2117
2600
  cctp: {
2118
2601
  domain: 8,
2119
2602
  contracts: {
@@ -2151,6 +2634,7 @@ const SuiTestnet = defineChain({
2151
2634
  rpcEndpoints: ['https://fullnode.testnet.sui.io'],
2152
2635
  eurcAddress: null,
2153
2636
  usdcAddress: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
2637
+ usdtAddress: null,
2154
2638
  cctp: {
2155
2639
  domain: 8,
2156
2640
  contracts: {
@@ -2189,6 +2673,7 @@ const Unichain = defineChain({
2189
2673
  rpcEndpoints: ['https://mainnet.unichain.org'],
2190
2674
  eurcAddress: null,
2191
2675
  usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
2676
+ usdtAddress: null,
2192
2677
  cctp: {
2193
2678
  domain: 10,
2194
2679
  contracts: {
@@ -2213,6 +2698,7 @@ const Unichain = defineChain({
2213
2698
  },
2214
2699
  kitContracts: {
2215
2700
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2701
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2216
2702
  },
2217
2703
  });
2218
2704
 
@@ -2237,6 +2723,7 @@ const UnichainSepolia = defineChain({
2237
2723
  rpcEndpoints: ['https://sepolia.unichain.org'],
2238
2724
  eurcAddress: null,
2239
2725
  usdcAddress: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
2726
+ usdtAddress: null,
2240
2727
  cctp: {
2241
2728
  domain: 10,
2242
2729
  contracts: {
@@ -2285,6 +2772,7 @@ const WorldChain = defineChain({
2285
2772
  rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
2286
2773
  eurcAddress: null,
2287
2774
  usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
2775
+ usdtAddress: null,
2288
2776
  cctp: {
2289
2777
  domain: 14,
2290
2778
  contracts: {
@@ -2303,6 +2791,7 @@ const WorldChain = defineChain({
2303
2791
  },
2304
2792
  kitContracts: {
2305
2793
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2794
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2306
2795
  },
2307
2796
  });
2308
2797
 
@@ -2330,6 +2819,7 @@ const WorldChainSepolia = defineChain({
2330
2819
  ],
2331
2820
  eurcAddress: null,
2332
2821
  usdcAddress: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
2822
+ usdtAddress: null,
2333
2823
  cctp: {
2334
2824
  domain: 14,
2335
2825
  contracts: {
@@ -2374,6 +2864,7 @@ const XDC = defineChain({
2374
2864
  rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
2375
2865
  eurcAddress: null,
2376
2866
  usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
2867
+ usdtAddress: null,
2377
2868
  cctp: {
2378
2869
  domain: 18,
2379
2870
  contracts: {
@@ -2392,6 +2883,7 @@ const XDC = defineChain({
2392
2883
  },
2393
2884
  kitContracts: {
2394
2885
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2886
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2395
2887
  },
2396
2888
  });
2397
2889
 
@@ -2416,6 +2908,7 @@ const XDCApothem = defineChain({
2416
2908
  rpcEndpoints: ['https://erpc.apothem.network'],
2417
2909
  eurcAddress: null,
2418
2910
  usdcAddress: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
2911
+ usdtAddress: null,
2419
2912
  cctp: {
2420
2913
  domain: 18,
2421
2914
  contracts: {
@@ -2458,6 +2951,7 @@ const ZKSyncEra = defineChain({
2458
2951
  rpcEndpoints: ['https://mainnet.era.zksync.io'],
2459
2952
  eurcAddress: null,
2460
2953
  usdcAddress: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
2954
+ usdtAddress: null,
2461
2955
  cctp: null,
2462
2956
  });
2463
2957
 
@@ -2482,6 +2976,7 @@ const ZKSyncEraSepolia = defineChain({
2482
2976
  rpcEndpoints: ['https://sepolia.era.zksync.dev'],
2483
2977
  eurcAddress: null,
2484
2978
  usdcAddress: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
2979
+ usdtAddress: null,
2485
2980
  cctp: null,
2486
2981
  });
2487
2982
 
@@ -2502,6 +2997,8 @@ var Chains = /*#__PURE__*/Object.freeze({
2502
2997
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
2503
2998
  Codex: Codex,
2504
2999
  CodexTestnet: CodexTestnet,
3000
+ Edge: Edge,
3001
+ EdgeTestnet: EdgeTestnet,
2505
3002
  Ethereum: Ethereum,
2506
3003
  EthereumSepolia: EthereumSepolia,
2507
3004
  Hedera: Hedera,
@@ -2514,6 +3011,8 @@ var Chains = /*#__PURE__*/Object.freeze({
2514
3011
  LineaSepolia: LineaSepolia,
2515
3012
  Monad: Monad,
2516
3013
  MonadTestnet: MonadTestnet,
3014
+ Morph: Morph,
3015
+ MorphTestnet: MorphTestnet,
2517
3016
  NEAR: NEAR,
2518
3017
  NEARTestnet: NEARTestnet,
2519
3018
  Noble: Noble,
@@ -2649,10 +3148,12 @@ const baseChainDefinitionSchema = z.object({
2649
3148
  rpcEndpoints: z.array(z.string()),
2650
3149
  eurcAddress: z.string().nullable(),
2651
3150
  usdcAddress: z.string().nullable(),
3151
+ usdtAddress: z.string().nullable(),
2652
3152
  cctp: z.any().nullable(), // We'll accept any CCTP config structure
2653
3153
  kitContracts: z
2654
3154
  .object({
2655
3155
  bridge: z.string().optional(),
3156
+ adapter: z.string().optional(),
2656
3157
  })
2657
3158
  .optional(),
2658
3159
  });
@@ -2767,6 +3268,29 @@ z.union([
2767
3268
  z.nativeEnum(Blockchain),
2768
3269
  chainDefinitionSchema$2,
2769
3270
  ]);
3271
+ /**
3272
+ * Zod schema for validating swap-specific chain identifiers.
3273
+ *
3274
+ * Validates chains based on:
3275
+ * - CCTPv2 support (adapter contract deployed)
3276
+ * - Mainnet only (no testnets)
3277
+ * - At least one supported token available
3278
+ *
3279
+ */
3280
+ z.union([
3281
+ // String blockchain identifier (accepts SwapChain enum values)
3282
+ z.string().refine((val) => val in SwapChain, (val) => ({
3283
+ message: `"${val}" is not a supported swap chain. ` +
3284
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
3285
+ })),
3286
+ // SwapChain enum
3287
+ z.nativeEnum(SwapChain),
3288
+ // ChainDefinition object (checks if chain.chain is in SwapChain)
3289
+ chainDefinitionSchema$2.refine((chain) => chain.chain in SwapChain, (chain) => ({
3290
+ message: `"${chain.chain}" is not a supported swap chain. ` +
3291
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
3292
+ })),
3293
+ ]);
2770
3294
  /**
2771
3295
  * Zod schema for validating bridge chain identifiers.
2772
3296
  *
@@ -2806,6 +3330,38 @@ z.union([
2806
3330
  })),
2807
3331
  ]);
2808
3332
 
3333
+ /**
3334
+ * @packageDocumentation
3335
+ * @module SwapTokenSchemas
3336
+ *
3337
+ * Zod validation schemas for supported swap tokens.
3338
+ */
3339
+ // Internal enum used after input normalization.
3340
+ const swapTokenEnumSchema = z.enum([
3341
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
3342
+ NATIVE_TOKEN,
3343
+ ]);
3344
+ /**
3345
+ * Zod schema for validating supported swap token symbols.
3346
+ *
3347
+ * Accepts any token symbol from the SWAP_TOKEN_REGISTRY plus NATIVE.
3348
+ * Input matching is case-insensitive and normalized to uppercase.
3349
+ *
3350
+ * @example
3351
+ * ```typescript
3352
+ * import { supportedSwapTokenSchema } from '@core/chains'
3353
+ *
3354
+ * const result = supportedSwapTokenSchema.safeParse('USDC')
3355
+ * if (result.success) {
3356
+ * console.log('Valid swap token:', result.data)
3357
+ * }
3358
+ * ```
3359
+ */
3360
+ z
3361
+ .string()
3362
+ .transform((value) => value.toUpperCase())
3363
+ .pipe(swapTokenEnumSchema);
3364
+
2809
3365
  /**
2810
3366
  * Retrieve a chain definition by its blockchain enum value.
2811
3367
  *
@@ -2860,6 +3416,11 @@ const getChainByEnum = (blockchain) => {
2860
3416
  * ```
2861
3417
  */
2862
3418
  function resolveChainIdentifier(chainIdentifier) {
3419
+ // Handle null explicitly (typeof null === 'object' in JavaScript)
3420
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
3421
+ if (chainIdentifier === null) {
3422
+ throw new Error(`Invalid chain identifier type: null. Expected ChainDefinition object, Blockchain enum, or string literal.`);
3423
+ }
2863
3424
  // If it's already a ChainDefinition object, return it unchanged
2864
3425
  if (typeof chainIdentifier === 'object') {
2865
3426
  return chainIdentifier;
@@ -3151,11 +3712,11 @@ const formatComponent = (nameWithVersion) => {
3151
3712
  const createRequestContext = () => {
3152
3713
  const context = {};
3153
3714
  if (typeof globalThis !== 'undefined') {
3154
- if (globalThis.__STABLECOIN_KITS_EXTERNAL_PREFIX__ !== undefined) {
3155
- context.externalPrefix = globalThis.__STABLECOIN_KITS_EXTERNAL_PREFIX__;
3715
+ if (globalThis.__APP_KITS_EXTERNAL_PREFIX__ !== undefined) {
3716
+ context.externalPrefix = globalThis.__APP_KITS_EXTERNAL_PREFIX__;
3156
3717
  }
3157
- if (globalThis.__STABLECOIN_KITS_CURRENT_KIT__ !== undefined) {
3158
- context.kit = globalThis.__STABLECOIN_KITS_CURRENT_KIT__;
3718
+ if (globalThis.__APP_KITS_CURRENT_KIT__ !== undefined) {
3719
+ context.kit = globalThis.__APP_KITS_CURRENT_KIT__;
3159
3720
  }
3160
3721
  }
3161
3722
  return context;
@@ -3261,6 +3822,10 @@ const ERROR_TYPES = {
3261
3822
  RPC: 'RPC',
3262
3823
  /** Internet connectivity, DNS resolution, connection issues */
3263
3824
  NETWORK: 'NETWORK',
3825
+ /** API throttling, request frequency limits errors */
3826
+ RATE_LIMIT: 'RATE_LIMIT',
3827
+ /** Service errors */
3828
+ SERVICE: 'SERVICE',
3264
3829
  /** Catch-all for unrecognized errors (code 0) */
3265
3830
  UNKNOWN: 'UNKNOWN',
3266
3831
  };
@@ -3284,6 +3849,8 @@ const ERROR_CODE_RANGES = [
3284
3849
  { min: 3000, max: 3999, type: 'NETWORK' },
3285
3850
  { min: 4000, max: 4999, type: 'RPC' },
3286
3851
  { min: 5000, max: 5999, type: 'ONCHAIN' },
3852
+ { min: 7000, max: 7999, type: 'RATE_LIMIT' },
3853
+ { min: 8000, max: 8999, type: 'SERVICE' },
3287
3854
  { min: 9000, max: 9999, type: 'BALANCE' },
3288
3855
  ];
3289
3856
  /** Special code for UNKNOWN errors */
@@ -3331,6 +3898,8 @@ const errorDetailsSchema = z.object({
3331
3898
  * - 3000-3999: NETWORK errors - Connectivity issues
3332
3899
  * - 4000-4999: RPC errors - Provider issues, gas estimation
3333
3900
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures
3901
+ * - 7000-7999: RATE_LIMIT errors - API throttling
3902
+ * - 8000-8999: SERVICE errors - Internal service errors
3334
3903
  * - 9000-9999: BALANCE errors - Insufficient funds
3335
3904
  */
3336
3905
  code: z
@@ -3439,11 +4008,11 @@ const AMOUNT_FORMAT_ERROR_MESSAGE = 'Amount must be a numeric string with dot (.
3439
4008
  const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (.) as decimal separator (e.g., "1", "0.5", ".5", "1.5"), with no thousand separators or comma decimals';
3440
4009
 
3441
4010
  /**
3442
- * Structured error class for Stablecoin Kit operations.
4011
+ * Structured error class for App Kit operations.
3443
4012
  *
3444
4013
  * This class extends the native Error class while implementing the ErrorDetails
3445
4014
  * interface, providing a consistent error format for programmatic handling
3446
- * across the Stablecoin Kits ecosystem. All properties are immutable to ensure
4015
+ * across the App Kits ecosystem. All properties are immutable to ensure
3447
4016
  * error objects cannot be modified after creation.
3448
4017
  *
3449
4018
  * @example
@@ -3453,6 +4022,7 @@ const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (
3453
4022
  * const error = new KitError({
3454
4023
  * code: 1001,
3455
4024
  * name: 'INPUT_NETWORK_MISMATCH',
4025
+ * type: 'INPUT',
3456
4026
  * recoverability: 'FATAL',
3457
4027
  * message: 'Cannot bridge between mainnet and testnet'
3458
4028
  * })
@@ -3470,7 +4040,8 @@ const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (
3470
4040
  * // Error with cause information
3471
4041
  * const error = new KitError({
3472
4042
  * code: 1002,
3473
- * name: 'INVALID_AMOUNT',
4043
+ * name: 'INPUT_INVALID_AMOUNT',
4044
+ * type: 'INPUT',
3474
4045
  * recoverability: 'FATAL',
3475
4046
  * message: 'Amount must be greater than zero',
3476
4047
  * cause: {
@@ -3554,6 +4125,8 @@ class KitError extends Error {
3554
4125
  * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
3555
4126
  * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
3556
4127
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
4128
+ * - 7000-7999: RATE_LIMIT errors - API throttling, request frequency limits
4129
+ * - 8000-8999: SERVICE errors - Internal service errors, server failures
3557
4130
  * - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
3558
4131
  */
3559
4132
  /**
@@ -3614,10 +4187,22 @@ const InputError = {
3614
4187
  name: 'INPUT_INVALID_CHAIN',
3615
4188
  type: 'INPUT',
3616
4189
  },
3617
- /** Invalid or unknown token (symbol not found, missing decimals, etc.) */
3618
- INVALID_TOKEN: {
4190
+ /** Unsupported token for chain */
4191
+ UNSUPPORTED_TOKEN: {
3619
4192
  code: 1006,
3620
- name: 'INPUT_INVALID_TOKEN',
4193
+ name: 'INPUT_UNSUPPORTED_TOKEN',
4194
+ type: 'INPUT',
4195
+ },
4196
+ /** Insufficient swap amount for the token pair */
4197
+ INSUFFICIENT_SWAP_AMOUNT: {
4198
+ code: 1007,
4199
+ name: 'INPUT_INSUFFICIENT_SWAP_AMOUNT',
4200
+ type: 'INPUT',
4201
+ },
4202
+ /** Action not supported by this adapter / ecosystem */
4203
+ UNSUPPORTED_ACTION: {
4204
+ code: 1008,
4205
+ name: 'INPUT_UNSUPPORTED_ACTION',
3621
4206
  type: 'INPUT',
3622
4207
  },
3623
4208
  /** General validation failure for complex validation rules */
@@ -3626,6 +4211,12 @@ const InputError = {
3626
4211
  name: 'INPUT_VALIDATION_FAILED',
3627
4212
  type: 'INPUT',
3628
4213
  },
4214
+ /** User cancelled wallet interaction (signature, transaction, connection) */
4215
+ USER_CANCELLED: {
4216
+ code: 1099,
4217
+ name: 'INPUT_USER_CANCELLED',
4218
+ type: 'INPUT',
4219
+ },
3629
4220
  };
3630
4221
  /**
3631
4222
  * Standardized error definitions for BALANCE type errors.
@@ -3713,8 +4304,7 @@ const NetworkError = {
3713
4304
  code: 3004,
3714
4305
  name: 'NETWORK_RELAYER_PENDING',
3715
4306
  type: 'NETWORK',
3716
- },
3717
- };
4307
+ }};
3718
4308
 
3719
4309
  /**
3720
4310
  * Creates error for network type mismatch between source and destination.
@@ -3801,7 +4391,7 @@ function createInvalidChainError(chain, reason) {
3801
4391
  const errorDetails = {
3802
4392
  ...InputError.INVALID_CHAIN,
3803
4393
  recoverability: 'FATAL',
3804
- message: `Invalid chain '${chain}': ${reason}`,
4394
+ message: `Invalid chain '${chain}': ${reason}.`,
3805
4395
  cause: {
3806
4396
  trace: { chain, reason },
3807
4397
  },
@@ -4150,6 +4740,9 @@ function getErrorMessage(error) {
4150
4740
  if (typeof error === 'string') {
4151
4741
  return error;
4152
4742
  }
4743
+ if (typeof error === 'object' && error !== null && 'message' in error) {
4744
+ return String(error.message);
4745
+ }
4153
4746
  return 'An unknown error occurred';
4154
4747
  }
4155
4748
  /**
@@ -4206,6 +4799,9 @@ function extractErrorInfo(error) {
4206
4799
  if (typeof err.code === 'number') {
4207
4800
  info.code = err.code;
4208
4801
  }
4802
+ if (isKitError(error)) {
4803
+ info.type = error.type;
4804
+ }
4209
4805
  return info;
4210
4806
  }
4211
4807
 
@@ -4328,7 +4924,17 @@ const makeApiRequest = async (url, method, isValidType, config, body) => {
4328
4924
  const response = await fetch(url, requestInit);
4329
4925
  clearTimeout(timeoutId);
4330
4926
  if (!response.ok) {
4331
- throw new Error(`HTTP ${String(response.status)} - ${response.statusText}`);
4927
+ let responseBody;
4928
+ try {
4929
+ responseBody = (await response.json());
4930
+ }
4931
+ catch {
4932
+ // Parse response body for diagnostics; continue even if malformed
4933
+ // so callers always receive HTTP status context
4934
+ }
4935
+ const httpError = new Error(`HTTP ${String(response.status)} - ${response.statusText}`);
4936
+ httpError.responseBody = responseBody;
4937
+ throw httpError;
4332
4938
  }
4333
4939
  const parsedJson = (await response.json());
4334
4940
  if (!isValidType(parsedJson)) {
@@ -4900,7 +5506,7 @@ const convertAddress = (address, targetFormat) => {
4900
5506
  *
4901
5507
  * This function normalizes token values from their blockchain representation (where
4902
5508
  * everything is stored as integers in the smallest denomination) to human-readable
4903
- * decimal format. Uses the battle-tested implementation from @ethersproject/units.
5509
+ * decimal format. Uses the battle-tested implementation from \@ethersproject/units.
4904
5510
  *
4905
5511
  * @param value - The value in smallest units (e.g., "1000000" for 1 USDC with 6 decimals)
4906
5512
  * @param decimals - The number of decimal places for the unit conversion
@@ -4929,357 +5535,1082 @@ const formatUnits = (value, decimals) => {
4929
5535
  };
4930
5536
 
4931
5537
  /**
4932
- * Build a complete explorer URL from a chain definition and transaction hash.
5538
+ * Create a structured error for token resolution failures.
4933
5539
  *
4934
- * This function takes a chain definition containing an explorer URL template
4935
- * and replaces the `{hash}` placeholder with the provided transaction hash
4936
- * to create a complete, valid explorer URL.
5540
+ * @remarks
5541
+ * Returns a `KitError` with `INPUT_UNSUPPORTED_TOKEN` code (1006).
5542
+ * The error trace contains the selector and chain context for debugging.
4937
5543
  *
4938
- * @param chainDef - The chain definition containing the explorer URL template.
4939
- * @param txHash - The transaction hash to insert into the URL template.
4940
- * @returns A complete explorer URL with the transaction hash inserted.
4941
- * @throws Error if the chain definition is null/undefined.
4942
- * @throws Error if the transaction hash is null/undefined/empty.
4943
- * @throws Error if the explorer URL template is missing or empty.
4944
- * @throws Error if the explorer URL template doesn't contain the {hash} placeholder.
4945
- * @throws Error if the resulting URL is invalid.
5544
+ * @param message - Human-readable error description.
5545
+ * @param selector - The token selector that failed to resolve.
5546
+ * @param chainId - The chain being resolved for (optional).
5547
+ * @param cause - The underlying error, if any (optional).
5548
+ * @returns A KitError with INPUT type and FATAL recoverability.
4946
5549
  *
4947
5550
  * @example
4948
5551
  * ```typescript
4949
- * import { buildExplorerUrl } from '@core/utils'
4950
- * import { Ethereum } from '@core/chains'
4951
- *
4952
- * // Build URL for Ethereum transaction
4953
- * const url = buildExplorerUrl(Ethereum, '0x1234567890abcdef...')
4954
- * console.log(url) // 'https://etherscan.io/tx/0x1234567890abcdef...'
5552
+ * throw createTokenResolutionError(
5553
+ * 'Unknown token symbol: FAKE',
5554
+ * 'FAKE',
5555
+ * 'Ethereum'
5556
+ * )
4955
5557
  * ```
5558
+ */
5559
+ function createTokenResolutionError(message, selector, chainId, cause) {
5560
+ const trace = {
5561
+ selector,
5562
+ ...(chainId === undefined ? {} : { chainId }),
5563
+ ...({} ),
5564
+ };
5565
+ return new KitError({
5566
+ ...InputError.UNSUPPORTED_TOKEN,
5567
+ recoverability: 'FATAL',
5568
+ message,
5569
+ cause: { trace },
5570
+ });
5571
+ }
5572
+
5573
+ /**
5574
+ * USDC token definition with addresses and metadata.
4956
5575
  *
4957
- * @example
4958
- * ```typescript
4959
- * import { buildExplorerUrl } from '@core/utils'
4960
- * import { Solana } from '@core/chains'
5576
+ * @remarks
5577
+ * This is the built-in USDC definition used by the TokenRegistry.
5578
+ * Includes all known USDC addresses across supported chains.
4961
5579
  *
4962
- * // Build URL for Solana transaction
4963
- * const url = buildExplorerUrl(Solana, 'abc123def456...')
4964
- * console.log(url) // 'https://solscan.io/tx/abc123def456...'
4965
- * ```
5580
+ * Keys use the `Blockchain` enum for type safety. Both enum values
5581
+ * and string literals are supported:
5582
+ * - `Blockchain.Ethereum` or `'Ethereum'`
4966
5583
  *
4967
5584
  * @example
4968
5585
  * ```typescript
4969
- * import { buildExplorerUrl } from '@core/utils'
4970
- * import { Aptos } from '@core/chains'
5586
+ * import { USDC } from '@core/tokens'
5587
+ * import { Blockchain } from '@core/chains'
4971
5588
  *
4972
- * // Build URL for Aptos transaction with query parameters
4973
- * const url = buildExplorerUrl(Aptos, '0xabc123...')
4974
- * console.log(url) // 'https://explorer.aptoslabs.com/txn/0xabc123...?network=mainnet'
5589
+ * console.log(USDC.symbol) // 'USDC'
5590
+ * console.log(USDC.decimals) // 6
5591
+ * console.log(USDC.locators[Blockchain.Ethereum])
5592
+ * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
4975
5593
  * ```
4976
5594
  */
4977
- function buildExplorerUrl(chainDef, txHash) {
4978
- // Validate input parameters using our standard validation pattern
4979
- validateOrThrow({ chainDef, txHash }, buildExplorerUrlParamsSchema, 'Invalid buildExplorerUrl parameters');
4980
- // Parse and transform input parameters (e.g., trim whitespace from txHash)
4981
- const { chainDef: validChainDef, txHash: validTxHash } = buildExplorerUrlParamsSchema.parse({ chainDef, txHash });
4982
- // Replace all occurrences of the placeholder with the transaction hash
4983
- const explorerUrl = validChainDef.explorerUrl.replace(/{hash}/g, validTxHash);
4984
- // Validate the resulting URL
4985
- validate(explorerUrl, explorerUrlSchema, 'explorer URL');
4986
- return explorerUrl;
4987
- }
5595
+ const USDC = {
5596
+ symbol: 'USDC',
5597
+ decimals: 6,
5598
+ locators: {
5599
+ // =========================================================================
5600
+ // Mainnets (alphabetically sorted)
5601
+ // =========================================================================
5602
+ [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
5603
+ [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
5604
+ [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
5605
+ [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
5606
+ [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
5607
+ [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
5608
+ [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
5609
+ [Blockchain.Hedera]: '0.0.456858',
5610
+ [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
5611
+ [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
5612
+ [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
5613
+ [Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
5614
+ [Blockchain.Morph]: '0xCfb1186F4e93D60E60a8bDd997427D1F33bc372B',
5615
+ [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
5616
+ [Blockchain.Noble]: 'uusdc',
5617
+ [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
5618
+ [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
5619
+ [Blockchain.Polkadot_Asset_Hub]: '1337',
5620
+ [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
5621
+ [Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
5622
+ [Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
5623
+ [Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
5624
+ [Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
5625
+ [Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
5626
+ [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
5627
+ [Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
5628
+ [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
5629
+ [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
5630
+ // =========================================================================
5631
+ // Testnets (alphabetically sorted)
5632
+ // =========================================================================
5633
+ [Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
5634
+ [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
5635
+ [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
5636
+ [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
5637
+ [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
5638
+ [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
5639
+ [Blockchain.Hedera_Testnet]: '0.0.429274',
5640
+ [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
5641
+ [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
5642
+ [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
5643
+ [Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
5644
+ [Blockchain.Morph_Testnet]: '0x7433b41C6c5e1d58D4Da99483609520255ab661B',
5645
+ [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
5646
+ [Blockchain.Noble_Testnet]: 'uusdc',
5647
+ [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
5648
+ [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
5649
+ [Blockchain.Polkadot_Westmint]: '31337',
5650
+ [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
5651
+ [Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
5652
+ [Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
5653
+ [Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
5654
+ [Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
5655
+ [Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
5656
+ [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
5657
+ [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
5658
+ [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
5659
+ [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
5660
+ },
5661
+ };
4988
5662
 
4989
5663
  /**
4990
- * CCTP forwarding magic bytes prefix.
5664
+ * USDT (Tether) token definition with addresses and metadata.
4991
5665
  *
4992
- * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
4993
- * This prefix is right-padded to 24 bytes in the final hookData.
5666
+ * @remarks
5667
+ * Built-in USDT definition for the TokenRegistry. Includes chain locators
5668
+ * for swap-supported chains.
4994
5669
  */
4995
- const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
5670
+ const USDT = {
5671
+ symbol: 'USDT',
5672
+ decimals: 6,
5673
+ locators: {
5674
+ [Blockchain.Aptos]: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
5675
+ [Blockchain.Arbitrum]: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
5676
+ [Blockchain.Avalanche]: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7',
5677
+ [Blockchain.Celo]: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
5678
+ [Blockchain.Ethereum]: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
5679
+ [Blockchain.HyperEVM]: '0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb',
5680
+ [Blockchain.Ink]: '0x0200C29006150606B650577BBE7B6248F58470c1',
5681
+ [Blockchain.Linea]: '0xA219439258ca9da29E9Cc4cE5596924745e12B93',
5682
+ [Blockchain.Monad]: '0xe7cd86e13AC4309349F30B3435a9d337750fC82D',
5683
+ [Blockchain.NEAR]: 'usdt.tether-token.near',
5684
+ [Blockchain.Optimism]: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58',
5685
+ [Blockchain.Polkadot_Asset_Hub]: '1984',
5686
+ [Blockchain.Polygon]: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
5687
+ [Blockchain.Sei]: '0x9151434b16b9763660705744891fA906F660EcC5',
5688
+ [Blockchain.Solana]: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
5689
+ [Blockchain.Unichain]: '0x9151434b16b9763660705744891fA906F660EcC5',
5690
+ },
5691
+ };
5692
+
4996
5693
  /**
4997
- * CCTP forwarding version number.
5694
+ * EURC (Euro Coin) token definition with addresses and metadata.
4998
5695
  *
4999
- * Version 0 is used for basic forwarding requests.
5696
+ * @remarks
5697
+ * Built-in EURC definition for the TokenRegistry. Includes chain locators
5698
+ * for swap-supported chains.
5000
5699
  */
5001
- const CCTP_FORWARD_VERSION = 0;
5700
+ const EURC = {
5701
+ symbol: 'EURC',
5702
+ decimals: 6,
5703
+ locators: {
5704
+ [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
5705
+ [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
5706
+ [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
5707
+ [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
5708
+ [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
5709
+ },
5710
+ };
5711
+
5002
5712
  /**
5003
- * Length of Circle-reserved payload for basic forwarding (0 bytes).
5713
+ * DAI (Maker DAO) stablecoin token definition with addresses and metadata.
5004
5714
  *
5005
- * Set to 0 when no additional Circle-reserved data is needed.
5715
+ * @remarks
5716
+ * Built-in DAI definition for the TokenRegistry. Includes chain locators
5717
+ * for swap-supported chains.
5006
5718
  */
5007
- const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
5719
+ const DAI = {
5720
+ symbol: 'DAI',
5721
+ decimals: 18,
5722
+ chainDecimals: {
5723
+ [Blockchain.Solana]: 8,
5724
+ },
5725
+ locators: {
5726
+ [Blockchain.Arbitrum]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5727
+ [Blockchain.Avalanche]: '0xd586E7F844cEa2F87f50152665BCbc2C279D8d70',
5728
+ [Blockchain.Base]: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
5729
+ [Blockchain.Ethereum]: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
5730
+ [Blockchain.Linea]: '0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5',
5731
+ [Blockchain.Optimism]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5732
+ [Blockchain.Polygon]: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
5733
+ [Blockchain.Solana]: 'EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o',
5734
+ },
5735
+ };
5736
+
5008
5737
  /**
5009
- * Build the hookData bytes for CCTP forwarding.
5010
- *
5011
- * Constructs a 32-byte hookData payload that signals to Circle's Orbit relayer
5012
- * that the user wants automated attestation fetching and destination mint execution.
5013
- *
5014
- * The hookData format is:
5015
- * - Bytes 0-23: ASCII "cctp-forward" right-padded to 24 bytes with zeros
5016
- * - Bytes 24-27: uint32 version (big-endian) - currently 0
5017
- * - Bytes 28-31: uint32 length (big-endian) - 0 for basic forwarding
5018
- *
5019
- * @returns A 0x-prefixed hex string representing the 32-byte hookData
5020
- *
5021
- * @example
5022
- * ```typescript
5023
- * import { buildForwardingHookData } from '@core/utils'
5738
+ * USDe (Ethena) synthetic dollar token definition with addresses and metadata.
5024
5739
  *
5025
- * const hookData = buildForwardingHookData()
5026
- * // Returns: '0x636374702d666f72776172640000000000000000000000000000000000000000'
5740
+ * @remarks
5741
+ * Built-in USDE definition for the TokenRegistry. Includes chain locators
5742
+ * for swap-supported chains.
5743
+ */
5744
+ const USDE = {
5745
+ symbol: 'USDe',
5746
+ decimals: 18,
5747
+ chainDecimals: {
5748
+ [Blockchain.Solana]: 9,
5749
+ },
5750
+ locators: {
5751
+ [Blockchain.Arbitrum]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5752
+ [Blockchain.Base]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5753
+ [Blockchain.Ethereum]: '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3',
5754
+ [Blockchain.Linea]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5755
+ [Blockchain.Optimism]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5756
+ [Blockchain.Solana]: 'DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT',
5757
+ },
5758
+ };
5759
+
5760
+ /**
5761
+ * PYUSD (PayPal USD) stablecoin token definition with addresses and metadata.
5027
5762
  *
5028
- * // Use with depositForBurnWithHook action
5029
- * await adapter.action('cctp.v2.depositForBurnWithHook', {
5030
- * amount: BigInt('1000000'),
5031
- * mintRecipient: '0x...',
5032
- * maxFee: BigInt('50000'),
5033
- * minFinalityThreshold: 1000,
5034
- * fromChain: ethereum,
5035
- * toChain: base,
5036
- * hookData
5037
- * })
5038
- * ```
5763
+ * @remarks
5764
+ * Built-in PYUSD definition for the TokenRegistry. Includes chain locators
5765
+ * for swap-supported chains.
5039
5766
  */
5040
- // Cached result for memoization (computed once on first call)
5041
- let cachedHookDataHex = null;
5042
- function buildForwardingHookData() {
5043
- // Return cached result if already computed
5044
- if (cachedHookDataHex !== null) {
5045
- return cachedHookDataHex;
5046
- }
5047
- // Create a 32-byte buffer
5048
- const buffer = new Uint8Array(32);
5049
- // Write the magic prefix ("cctp-forward") - 12 bytes, rest of 24-byte section is zero-padded
5050
- const encoder = new TextEncoder();
5051
- const magicBytes = encoder.encode(CCTP_FORWARD_MAGIC_PREFIX);
5052
- buffer.set(magicBytes, 0);
5053
- // Bytes 12-23 are already zero (right-padding)
5054
- // Write uint32 version at bytes 24-27 (big-endian)
5055
- const versionView = new DataView(buffer.buffer);
5056
- versionView.setUint32(24, CCTP_FORWARD_VERSION, false); // false = big-endian
5057
- // Write uint32 length at bytes 28-31 (big-endian)
5058
- versionView.setUint32(28, CCTP_FORWARD_PAYLOAD_LENGTH, false); // false = big-endian
5059
- // Convert to hex string with 0x prefix and cache
5060
- cachedHookDataHex =
5061
- '0x' +
5062
- Array.from(buffer)
5063
- .map((b) => b.toString(16).padStart(2, '0'))
5064
- .join('');
5065
- return cachedHookDataHex;
5066
- }
5767
+ const PYUSD = {
5768
+ symbol: 'PYUSD',
5769
+ decimals: 6,
5770
+ locators: {
5771
+ [Blockchain.Arbitrum]: '0x46850aD61C2B7d64d08c9C754F45254596696984',
5772
+ [Blockchain.Ethereum]: '0x6c3ea9036406852006290770BEdFcAbA0e23A0e8',
5773
+ [Blockchain.Solana]: '2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo',
5774
+ },
5775
+ };
5067
5776
 
5068
5777
  /**
5069
- * Transfer speed options for cross-chain operations.
5778
+ * WETH (Wrapped Ether) token definition with addresses and metadata.
5070
5779
  *
5071
- * Defines the available speed modes for CCTPv2 transfers, affecting
5072
- * both transfer time and potential fee implications.
5780
+ * @remarks
5781
+ * Built-in WETH definition for the TokenRegistry. Includes chain locators
5782
+ * for swap-supported chains where WETH is available.
5073
5783
  */
5074
- var TransferSpeed;
5075
- (function (TransferSpeed) {
5076
- /** Fast burn mode - reduces transfer time but may have different fee implications */
5077
- TransferSpeed["FAST"] = "FAST";
5078
- /** Standard burn mode - normal transfer time with standard fees */
5079
- TransferSpeed["SLOW"] = "SLOW";
5080
- })(TransferSpeed || (TransferSpeed = {}));
5784
+ const WETH = {
5785
+ symbol: 'WETH',
5786
+ decimals: 18,
5787
+ chainDecimals: {
5788
+ [Blockchain.Solana]: 9,
5789
+ },
5790
+ locators: {
5791
+ [Blockchain.Arbitrum]: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
5792
+ [Blockchain.Avalanche]: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB',
5793
+ [Blockchain.Base]: '0x4200000000000000000000000000000000000006',
5794
+ [Blockchain.Ethereum]: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
5795
+ [Blockchain.Optimism]: '0x4200000000000000000000000000000000000006',
5796
+ [Blockchain.Polygon]: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
5797
+ [Blockchain.Solana]: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs',
5798
+ },
5799
+ };
5081
5800
 
5082
5801
  /**
5083
- * Factory to validate a numeric string with strict dot-decimal notation.
5084
- * Only accepts dot (.) as the decimal separator. Thousand separators are not allowed.
5802
+ * WBTC (Wrapped Bitcoin) token definition with addresses and metadata.
5085
5803
  *
5086
- * This enforces an unambiguous format for SDK inputs. Internationalization concerns
5087
- * (comma vs dot decimal separators) should be handled in the UI layer before passing
5088
- * values to the SDK.
5804
+ * @remarks
5805
+ * Built-in WBTC definition for the TokenRegistry. Includes chain locators
5806
+ * for swap-supported chains where WBTC is available.
5807
+ */
5808
+ const WBTC = {
5809
+ symbol: 'WBTC',
5810
+ decimals: 8,
5811
+ locators: {
5812
+ [Blockchain.Ethereum]: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
5813
+ [Blockchain.Arbitrum]: '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f',
5814
+ [Blockchain.Avalanche]: '0x50b7545627a5162F82A992c33b87aDc75187B218',
5815
+ },
5816
+ };
5817
+
5818
+ /**
5819
+ * WSOL (Wrapped SOL) token definition with addresses and metadata.
5089
5820
  *
5090
- * Accepts the following formats:
5091
- * - Whole numbers: "1", "100", "1000"
5092
- * - Leading zero decimals: "0.1", "0.5", "0.001"
5093
- * - Shorthand decimals: ".1", ".5", ".001"
5094
- * - Standard decimals: "1.23", "100.50"
5821
+ * @remarks
5822
+ * Built-in WSOL definition for the TokenRegistry. Includes chain locators
5823
+ * for swap-supported chains where WSOL is available.
5824
+ */
5825
+ const WSOL = {
5826
+ symbol: 'WSOL',
5827
+ decimals: 9,
5828
+ locators: {
5829
+ [Blockchain.Solana]: 'So11111111111111111111111111111111111111112',
5830
+ },
5831
+ };
5832
+
5833
+ /**
5834
+ * WAVAX (Wrapped AVAX) token definition with addresses and metadata.
5095
5835
  *
5096
- * Does NOT accept:
5097
- * - Comma decimal separator: "1,5" (use "1.5" instead)
5098
- * - Thousand separators: "1,000.50" or "1.000,50" (use "1000.50" instead)
5099
- * - Multiple decimal points: "1.2.3"
5100
- * - Negative numbers: "-100"
5101
- * - Non-numeric characters: "abc", "100a"
5836
+ * @remarks
5837
+ * Built-in WAVAX definition for the TokenRegistry. Includes chain locators
5838
+ * for swap-supported chains where WAVAX is available.
5839
+ */
5840
+ const WAVAX = {
5841
+ symbol: 'WAVAX',
5842
+ decimals: 18,
5843
+ locators: {
5844
+ [Blockchain.Avalanche]: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7',
5845
+ },
5846
+ };
5847
+
5848
+ /**
5849
+ * WPOL (Wrapped POL) token definition with addresses and metadata.
5102
5850
  *
5103
- * Behavior differences controlled by options:
5104
- * - allowZero: when false, value must be strictly greater than 0; when true, non-negative.
5105
- * - regexMessage: error message when the basic numeric format fails.
5106
- * - maxDecimals: maximum number of decimal places allowed (e.g., 6 for USDC).
5851
+ * @remarks
5852
+ * Built-in WPOL definition for the TokenRegistry. Includes chain locators
5853
+ * for swap-supported chains where WPOL is available.
5107
5854
  */
5108
- const createDecimalStringValidator = (options) => (schema) => {
5109
- // Capitalize first letter of attribute name for error messages
5110
- const capitalizedAttributeName = options.attributeName.charAt(0).toUpperCase() +
5111
- options.attributeName.slice(1);
5112
- return schema
5113
- .regex(/^-?(?:\d+(?:\.\d+)?|\.\d+)$/, options.regexMessage)
5114
- .superRefine((val, ctx) => {
5115
- const amount = Number.parseFloat(val);
5116
- if (Number.isNaN(amount)) {
5117
- ctx.addIssue({
5118
- code: z.ZodIssueCode.custom,
5119
- message: options.regexMessage,
5120
- });
5121
- return;
5122
- }
5123
- // Check decimal precision if maxDecimals is specified
5124
- if (options.maxDecimals !== undefined) {
5125
- const decimalPart = val.split('.')[1];
5126
- if (decimalPart && decimalPart.length > options.maxDecimals) {
5127
- ctx.addIssue({
5128
- code: z.ZodIssueCode.custom,
5129
- message: `Maximum supported decimal places: ${options.maxDecimals.toString()}`,
5130
- });
5131
- return;
5132
- }
5133
- }
5134
- if (options.allowZero && amount < 0) {
5135
- ctx.addIssue({
5136
- code: z.ZodIssueCode.custom,
5137
- message: `${capitalizedAttributeName} must be non-negative`,
5138
- });
5139
- }
5140
- else if (!options.allowZero && amount <= 0) {
5141
- ctx.addIssue({
5142
- code: z.ZodIssueCode.custom,
5143
- message: `${capitalizedAttributeName} must be greater than 0`,
5144
- });
5145
- }
5146
- });
5855
+ const WPOL = {
5856
+ symbol: 'WPOL',
5857
+ decimals: 18,
5858
+ locators: {
5859
+ [Blockchain.Polygon]: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
5860
+ },
5147
5861
  };
5862
+
5148
5863
  /**
5149
- * Schema for validating chain definitions.
5150
- * This ensures the basic structure of a chain definition is valid.
5151
- * A chain definition must include at minimum a name and type.
5864
+ * ETH (native Ether alias) token definition with metadata.
5152
5865
  *
5153
- * @throws KitError if validation fails
5866
+ * @remarks
5867
+ * Built-in ETH definition for the TokenRegistry. Used as a symbol alias
5868
+ * for native ETH (e.g. in swap flows). Chain locators are TBD and will
5869
+ * be added when addresses are available. Use raw selector with
5870
+ * locator + decimals where native gas token is represented as a contract.
5871
+ */
5872
+ const ETH = {
5873
+ symbol: 'ETH',
5874
+ decimals: 18,
5875
+ locators: {},
5876
+ };
5877
+
5878
+ /**
5879
+ * POL (Polygon native token) token definition with addresses and metadata.
5880
+ *
5881
+ * @remarks
5882
+ * Built-in POL definition for the TokenRegistry. Includes chain locators
5883
+ * where POL is available as a bridged/wrapped asset.
5884
+ */
5885
+ const POL = {
5886
+ symbol: 'POL',
5887
+ decimals: 18,
5888
+ locators: {
5889
+ [Blockchain.Ethereum]: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6',
5890
+ },
5891
+ };
5892
+
5893
+ /**
5894
+ * PLUME (Plume network token) token definition with addresses and metadata.
5895
+ *
5896
+ * @remarks
5897
+ * Built-in PLUME definition for the TokenRegistry. Includes chain locators
5898
+ * where PLUME is available.
5899
+ */
5900
+ const PLUME = {
5901
+ symbol: 'PLUME',
5902
+ decimals: 18,
5903
+ locators: {
5904
+ [Blockchain.Ethereum]: '0x4C1746A800D224393fE2470C70A35717eD4eA5F1',
5905
+ },
5906
+ };
5907
+
5908
+ /**
5909
+ * MON token definition with Solana mint metadata.
5910
+ *
5911
+ * @remarks
5912
+ * Built-in MON definition for the TokenRegistry. Includes chain locators
5913
+ * for swap-supported chains.
5914
+ */
5915
+ const MON = {
5916
+ symbol: 'MON',
5917
+ decimals: 18,
5918
+ chainDecimals: {
5919
+ [Blockchain.Solana]: 8,
5920
+ },
5921
+ locators: {
5922
+ [Blockchain.Solana]: 'CrAr4RRJMBVwRsZtT62pEhfA9H5utymC2mVx8e7FreP2',
5923
+ },
5924
+ };
5925
+
5926
+ // Re-export for consumers
5927
+ /**
5928
+ * All default token definitions.
5929
+ *
5930
+ * @remarks
5931
+ * These tokens are automatically included in the TokenRegistry when created
5932
+ * without explicit defaults. Extensions can override these definitions.
5933
+ * Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
5934
+ * WPOL, ETH, POL, PLUME, and MON.
5154
5935
  *
5155
5936
  * @example
5156
5937
  * ```typescript
5157
- * import { chainDefinitionSchema } from '@core/provider'
5938
+ * import { createTokenRegistry } from '@core/tokens'
5158
5939
  *
5159
- * const validChain = {
5160
- * name: 'Ethereum',
5161
- * type: 'evm'
5162
- * }
5940
+ * // Registry uses these by default
5941
+ * const registry = createTokenRegistry()
5163
5942
  *
5164
- * const result = chainDefinitionSchema.safeParse(validChain)
5165
- * console.log(result.success) // true
5943
+ * // Add custom tokens (built-ins are still included)
5944
+ * const customRegistry = createTokenRegistry({
5945
+ * tokens: [myCustomToken],
5946
+ * })
5166
5947
  * ```
5167
5948
  */
5168
- const chainDefinitionSchema = z.object({
5169
- name: z.string().min(1, 'Chain name is required'),
5170
- type: z.string().min(1, 'Chain type is required'),
5171
- });
5949
+ const DEFAULT_TOKENS = [
5950
+ USDC,
5951
+ USDT,
5952
+ EURC,
5953
+ DAI,
5954
+ USDE,
5955
+ PYUSD,
5956
+ WETH,
5957
+ WBTC,
5958
+ WSOL,
5959
+ WAVAX,
5960
+ WPOL,
5961
+ ETH,
5962
+ POL,
5963
+ PLUME,
5964
+ MON,
5965
+ ];
5172
5966
  /**
5173
- * Schema for validating wallet contexts.
5174
- * This ensures all required fields are present and properly typed.
5175
- * A wallet context must include:
5176
- * - A valid adapter with prepare and execute methods
5177
- * - A valid Ethereum address
5178
- * - A valid chain definition with required properties
5967
+ * Uppercased token symbols approved for swap fee collection.
5179
5968
  *
5180
- * @throws KitError if validation fails
5969
+ * @remarks
5970
+ * Derived from {@link DEFAULT_TOKENS} so all consumers share a single
5971
+ * allowlist source and stay in sync when defaults change.
5972
+ */
5973
+ new Set(DEFAULT_TOKENS.map((t) => t.symbol.toUpperCase()));
5974
+
5975
+ /**
5976
+ * Resolve the effective decimals for a token on a specific chain.
5977
+ *
5978
+ * Returns the chain-specific override from `chainDecimals` when available,
5979
+ * otherwise falls back to the token's default `decimals`.
5980
+ *
5981
+ * @param token - The token definition to resolve decimals for.
5982
+ * @param chain - Optional chain identifier. When omitted, the default decimals are returned.
5983
+ * @returns The number of decimal places for the token on the given chain.
5181
5984
  *
5182
5985
  * @example
5183
5986
  * ```typescript
5184
- * import { walletContextSchema } from '@core/provider'
5987
+ * import { resolveTokenDecimals } from '\@core/tokens'
5185
5988
  *
5186
- * const validContext = {
5187
- * adapter: {
5188
- * prepare: () => Promise.resolve({}),
5189
- * waitForTransaction: () => Promise.resolve({})
5190
- * },
5191
- * address: '0x1234567890123456789012345678901234567890',
5192
- * chain: {
5193
- * name: 'Ethereum',
5194
- * type: 'evm',
5195
- * isTestnet: false
5196
- * }
5197
- * }
5198
- *
5199
- * const result = walletContextSchema.safeParse(validContext)
5200
- * console.log(result.success) // true
5989
+ * // DAI: 18 decimals on EVM, 8 on Solana
5990
+ * resolveTokenDecimals(DAI) // 18 (default)
5991
+ * resolveTokenDecimals(DAI, 'Solana') // 8 (chain override)
5992
+ * resolveTokenDecimals(DAI, 'Ethereum') // 18 (no override, falls back)
5201
5993
  * ```
5202
5994
  */
5203
- const walletContextSchema = z.object({
5204
- adapter: z.object({
5205
- prepare: z.function().returns(z.any()),
5206
- waitForTransaction: z.function().returns(z.any()),
5207
- }),
5208
- address: z.string().min(1),
5209
- chain: z.object({
5210
- name: z.string(),
5211
- type: z.string(),
5212
- isTestnet: z.boolean(),
5213
- }),
5214
- });
5995
+ function resolveTokenDecimals(token, chain) {
5996
+ if (chain === undefined) {
5997
+ return token.decimals;
5998
+ }
5999
+ return token.chainDecimals?.[chain] ?? token.decimals;
6000
+ }
6001
+
5215
6002
  /**
5216
- * Schema for validating destination wallet contexts.
5217
- * Extends walletContextSchema with optional useForwarder flag.
6003
+ * Check if a selector is a raw token selector (object form).
5218
6004
  *
5219
- * @throws KitError if validation fails
6005
+ * @param selector - The token selector to check.
6006
+ * @returns True if the selector is a raw token selector.
5220
6007
  */
5221
- const destinationContextSchema = walletContextSchema.extend({
5222
- useForwarder: z.boolean().optional(),
5223
- recipientAddress: z.string().optional(),
5224
- });
6008
+ function isRawSelector(selector) {
6009
+ return typeof selector === 'object' && 'locator' in selector;
6010
+ }
5225
6011
  /**
5226
- * Schema for validating forwarder-only destination contexts.
5227
- * Used when useForwarder is true and no adapter is provided.
5228
- * Requires chain definition and recipientAddress.
6012
+ * Normalize a symbol to uppercase for case-insensitive lookup.
5229
6013
  *
5230
- * Validates that recipientAddress format is correct for the destination chain
5231
- * (EVM hex address or Solana base58 address).
6014
+ * @param symbol - The symbol to normalize.
6015
+ * @returns The normalized (uppercase) symbol.
6016
+ */
6017
+ function normalizeSymbol(symbol) {
6018
+ return symbol.toUpperCase();
6019
+ }
6020
+ /**
6021
+ * Normalize a locator for stable chain-aware lookup.
5232
6022
  *
5233
- * @throws KitError if validation fails
6023
+ * @remarks
6024
+ * EVM-style addresses (`0x...`) are normalized to lowercase for
6025
+ * case-insensitive matching. Non-EVM locators keep original casing.
5234
6026
  */
5235
- const forwarderOnlyDestinationSchema = z
5236
- .object({
5237
- chain: chainDefinitionSchema.extend({
5238
- isTestnet: z.boolean(),
5239
- }),
5240
- recipientAddress: z
5241
- .string()
5242
- .min(1, 'Recipient address is required for forwarder-only destination'),
5243
- useForwarder: z.literal(true),
5244
- })
5245
- .superRefine((data, ctx) => {
5246
- // Pass chain name as string - isValidAddressForChain will use name-based matching
5247
- if (!isValidAddressForChain(data.recipientAddress, data.chain.name)) {
5248
- ctx.addIssue({
5249
- code: z.ZodIssueCode.custom,
5250
- message: `Invalid recipient address format for chain ${data.chain.name}`,
5251
- path: ['recipientAddress'],
5252
- });
6027
+ function normalizeLocator(locator) {
6028
+ if (locator.startsWith('0x') || locator.startsWith('0X')) {
6029
+ return locator.toLowerCase();
5253
6030
  }
5254
- });
6031
+ return locator;
6032
+ }
5255
6033
  /**
5256
- * Schema for validating any destination - either with adapter or forwarder-only.
6034
+ * Build a stable map key for a chain + locator pair.
5257
6035
  */
5258
- const bridgeDestinationSchema = z.union([
5259
- destinationContextSchema,
5260
- forwarderOnlyDestinationSchema,
5261
- ]);
6036
+ function buildAddressKey(chainId, locator) {
6037
+ return `${chainId}::${normalizeLocator(locator)}`;
6038
+ }
5262
6039
  /**
5263
- * Schema for validating a custom fee configuration.
5264
- * Validates the simplified CustomFee interface which includes:
5265
- * - An optional fee amount as string
5266
- * - An optional fee recipient as string address
6040
+ * Create a token registry with built-in tokens and optional extensions.
5267
6041
  *
5268
- * @throws KitError if validation fails
6042
+ * @remarks
6043
+ * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
6044
+ * Custom tokens are merged on top - use this to add new tokens or override
6045
+ * built-in definitions.
6046
+ *
6047
+ * @param options - Configuration options for the registry.
6048
+ * @returns A token registry instance.
5269
6049
  *
5270
6050
  * @example
5271
6051
  * ```typescript
5272
- * import { customFeeSchema } from '@core/provider'
6052
+ * import { createTokenRegistry } from '@core/tokens'
5273
6053
  *
5274
- * const validConfig = {
5275
- * value: '1000000',
5276
- * recipientAddress: '0x1234567890123456789012345678901234567890'
5277
- * }
6054
+ * // Create registry with built-in tokens (USDC, etc.)
6055
+ * const registry = createTokenRegistry()
5278
6056
  *
5279
- * const result = customFeeSchema.safeParse(validConfig)
5280
- * console.log(result.success) // true
6057
+ * // Resolve USDC on Ethereum
6058
+ * const usdc = registry.resolve('USDC', 'Ethereum')
6059
+ * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
6060
+ * console.log(usdc.decimals) // 6
5281
6061
  * ```
5282
- */
6062
+ *
6063
+ * @example
6064
+ * ```typescript
6065
+ * // Add custom tokens (built-ins are still included)
6066
+ * const myToken: TokenDefinition = {
6067
+ * symbol: 'MY',
6068
+ * decimals: 18,
6069
+ * locators: { Ethereum: '0x...' },
6070
+ * }
6071
+ *
6072
+ * const registry = createTokenRegistry({ tokens: [myToken] })
6073
+ * registry.resolve('USDC', 'Ethereum') // Still works!
6074
+ * registry.resolve('MY', 'Ethereum') // Also works
6075
+ * ```
6076
+ *
6077
+ * @example
6078
+ * ```typescript
6079
+ * // Override a built-in token
6080
+ * const customUsdc: TokenDefinition = {
6081
+ * symbol: 'USDC',
6082
+ * decimals: 6,
6083
+ * locators: { MyChain: '0xCustomAddress' },
6084
+ * }
6085
+ *
6086
+ * const registry = createTokenRegistry({ tokens: [customUsdc] })
6087
+ * // Now USDC resolves to customUsdc definition
6088
+ * ```
6089
+ *
6090
+ * @example
6091
+ * ```typescript
6092
+ * // Resolve arbitrary tokens by raw locator
6093
+ * const registry = createTokenRegistry()
6094
+ * const token = registry.resolve(
6095
+ * { locator: '0x1234...', decimals: 18 },
6096
+ * 'Ethereum'
6097
+ * )
6098
+ * ```
6099
+ */
6100
+ function createTokenRegistry(options = {}) {
6101
+ const { tokens = [], requireDecimals = false } = options;
6102
+ // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
6103
+ const tokenMap = new Map();
6104
+ // Add built-in tokens first
6105
+ for (const def of DEFAULT_TOKENS) {
6106
+ tokenMap.set(normalizeSymbol(def.symbol), def);
6107
+ }
6108
+ // Custom tokens override built-ins
6109
+ for (const def of tokens) {
6110
+ tokenMap.set(normalizeSymbol(def.symbol), def);
6111
+ }
6112
+ // Build an address index from the resolved token map so overrides win.
6113
+ const addressMap = new Map();
6114
+ for (const token of tokenMap.values()) {
6115
+ for (const [chainId, locator] of Object.entries(token.locators)) {
6116
+ if (typeof locator !== 'string' || locator.trim() === '') {
6117
+ continue;
6118
+ }
6119
+ addressMap.set(buildAddressKey(chainId, locator), token);
6120
+ }
6121
+ }
6122
+ /**
6123
+ * Resolve a symbol selector to token information.
6124
+ */
6125
+ function resolveSymbol(symbol, chainId) {
6126
+ const normalizedSymbol = normalizeSymbol(symbol);
6127
+ const definition = tokenMap.get(normalizedSymbol);
6128
+ if (definition === undefined) {
6129
+ throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
6130
+ }
6131
+ const locator = definition.locators[chainId];
6132
+ if (locator === undefined || locator.trim() === '') {
6133
+ throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
6134
+ }
6135
+ const decimals = resolveTokenDecimals(definition, chainId);
6136
+ return {
6137
+ symbol: definition.symbol,
6138
+ decimals,
6139
+ locator: normalizeLocator(locator),
6140
+ };
6141
+ }
6142
+ /**
6143
+ * Resolve a raw selector to token information.
6144
+ */
6145
+ function resolveRaw(selector, chainId) {
6146
+ const { locator, decimals } = selector;
6147
+ // Validate locator
6148
+ if (!locator || typeof locator !== 'string') {
6149
+ throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
6150
+ }
6151
+ // Decimals are always required for raw selectors
6152
+ if (decimals === undefined) {
6153
+ const message = requireDecimals
6154
+ ? 'Decimals required for raw token selector (requireDecimals: true)'
6155
+ : 'Decimals required for raw token selector. Provide { locator, decimals }.';
6156
+ throw createTokenResolutionError(message, selector, chainId);
6157
+ }
6158
+ // Validate decimals
6159
+ if (typeof decimals !== 'number' ||
6160
+ decimals < 0 ||
6161
+ !Number.isInteger(decimals)) {
6162
+ throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
6163
+ }
6164
+ return {
6165
+ decimals,
6166
+ locator: normalizeLocator(locator),
6167
+ };
6168
+ }
6169
+ return {
6170
+ resolve(selector, chainId) {
6171
+ // Runtime validation of inputs - these checks are for JS consumers
6172
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
6173
+ if (selector === null || selector === undefined) {
6174
+ throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
6175
+ }
6176
+ if (chainId === '' || typeof chainId !== 'string') {
6177
+ throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
6178
+ }
6179
+ // Dispatch based on selector type
6180
+ if (isRawSelector(selector)) {
6181
+ return resolveRaw(selector, chainId);
6182
+ }
6183
+ if (typeof selector === 'string') {
6184
+ return resolveSymbol(selector, chainId);
6185
+ }
6186
+ throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
6187
+ },
6188
+ resolveByAddress(address, chainId) {
6189
+ if (!address || typeof address !== 'string') {
6190
+ throw createTokenResolutionError('Token address is required for address resolution', { locator: address }, chainId);
6191
+ }
6192
+ if (chainId === '' || typeof chainId !== 'string') {
6193
+ throw createTokenResolutionError('Chain ID is required for token resolution', { locator: address }, chainId);
6194
+ }
6195
+ const definition = addressMap.get(buildAddressKey(chainId, address));
6196
+ if (definition === undefined) {
6197
+ throw createTokenResolutionError(`Unknown token address: ${address} for chain ${chainId}`, { locator: address }, chainId);
6198
+ }
6199
+ const locator = definition.locators[chainId];
6200
+ if (locator === undefined || locator.trim() === '') {
6201
+ throw createTokenResolutionError(`Token ${definition.symbol} has no locator for chain ${chainId}`, definition.symbol, chainId);
6202
+ }
6203
+ const decimals = resolveTokenDecimals(definition, chainId);
6204
+ return {
6205
+ symbol: definition.symbol,
6206
+ decimals,
6207
+ locator: normalizeLocator(locator),
6208
+ };
6209
+ },
6210
+ get(symbol) {
6211
+ if (!symbol || typeof symbol !== 'string') {
6212
+ return undefined;
6213
+ }
6214
+ return tokenMap.get(normalizeSymbol(symbol));
6215
+ },
6216
+ has(symbol) {
6217
+ if (!symbol || typeof symbol !== 'string') {
6218
+ return false;
6219
+ }
6220
+ return tokenMap.has(normalizeSymbol(symbol));
6221
+ },
6222
+ symbols() {
6223
+ return Array.from(tokenMap.values()).map((def) => def.symbol);
6224
+ },
6225
+ entries() {
6226
+ return Array.from(tokenMap.values());
6227
+ },
6228
+ };
6229
+ }
6230
+
6231
+ /**
6232
+ * Define a schema for token registry interfaces.
6233
+ *
6234
+ * @remarks
6235
+ * Validate that a registry exposes the `resolve()` API used by adapters.
6236
+ *
6237
+ * @example
6238
+ * ```typescript
6239
+ * import { tokenRegistrySchema } from '@core/tokens'
6240
+ *
6241
+ * const registry = {
6242
+ * resolve: () => ({ locator: '0x0', decimals: 6 }),
6243
+ * }
6244
+ *
6245
+ * tokenRegistrySchema.parse(registry)
6246
+ * ```
6247
+ */
6248
+ z.custom((value) => {
6249
+ if (value === null || typeof value !== 'object') {
6250
+ return false;
6251
+ }
6252
+ const record = value;
6253
+ return (typeof record['resolve'] === 'function' &&
6254
+ typeof record['get'] === 'function' &&
6255
+ typeof record['has'] === 'function' &&
6256
+ typeof record['symbols'] === 'function' &&
6257
+ typeof record['entries'] === 'function');
6258
+ }, {
6259
+ message: 'Invalid token registry',
6260
+ });
6261
+
6262
+ /**
6263
+ * Build a complete explorer URL from a chain definition and transaction hash.
6264
+ *
6265
+ * This function takes a chain definition containing an explorer URL template
6266
+ * and replaces the `{hash}` placeholder with the provided transaction hash
6267
+ * to create a complete, valid explorer URL.
6268
+ *
6269
+ * @param chainDef - The chain definition containing the explorer URL template.
6270
+ * @param txHash - The transaction hash to insert into the URL template.
6271
+ * @returns A complete explorer URL with the transaction hash inserted.
6272
+ * @throws Error if the chain definition is null/undefined.
6273
+ * @throws Error if the transaction hash is null/undefined/empty.
6274
+ * @throws Error if the explorer URL template is missing or empty.
6275
+ * @throws Error if the explorer URL template doesn't contain the {hash} placeholder.
6276
+ * @throws Error if the resulting URL is invalid.
6277
+ *
6278
+ * @example
6279
+ * ```typescript
6280
+ * import { buildExplorerUrl } from '@core/utils'
6281
+ * import { Ethereum } from '@core/chains'
6282
+ *
6283
+ * // Build URL for Ethereum transaction
6284
+ * const url = buildExplorerUrl(Ethereum, '0x1234567890abcdef...')
6285
+ * console.log(url) // 'https://etherscan.io/tx/0x1234567890abcdef...'
6286
+ * ```
6287
+ *
6288
+ * @example
6289
+ * ```typescript
6290
+ * import { buildExplorerUrl } from '@core/utils'
6291
+ * import { Solana } from '@core/chains'
6292
+ *
6293
+ * // Build URL for Solana transaction
6294
+ * const url = buildExplorerUrl(Solana, 'abc123def456...')
6295
+ * console.log(url) // 'https://solscan.io/tx/abc123def456...'
6296
+ * ```
6297
+ *
6298
+ * @example
6299
+ * ```typescript
6300
+ * import { buildExplorerUrl } from '@core/utils'
6301
+ * import { Aptos } from '@core/chains'
6302
+ *
6303
+ * // Build URL for Aptos transaction with query parameters
6304
+ * const url = buildExplorerUrl(Aptos, '0xabc123...')
6305
+ * console.log(url) // 'https://explorer.aptoslabs.com/txn/0xabc123...?network=mainnet'
6306
+ * ```
6307
+ */
6308
+ function buildExplorerUrl(chainDef, txHash) {
6309
+ // Validate input parameters using our standard validation pattern
6310
+ validateOrThrow({ chainDef, txHash }, buildExplorerUrlParamsSchema, 'Invalid buildExplorerUrl parameters');
6311
+ // Parse and transform input parameters (e.g., trim whitespace from txHash)
6312
+ const { chainDef: validChainDef, txHash: validTxHash } = buildExplorerUrlParamsSchema.parse({ chainDef, txHash });
6313
+ // Replace all occurrences of the placeholder with the transaction hash
6314
+ const explorerUrl = validChainDef.explorerUrl.replace(/{hash}/g, validTxHash);
6315
+ // Validate the resulting URL
6316
+ validate(explorerUrl, explorerUrlSchema, 'explorer URL');
6317
+ return explorerUrl;
6318
+ }
6319
+
6320
+ /**
6321
+ * CCTP forwarding magic bytes prefix.
6322
+ *
6323
+ * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
6324
+ * This prefix is right-padded to 24 bytes in the final hookData.
6325
+ */
6326
+ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
6327
+ /**
6328
+ * CCTP forwarding version number.
6329
+ *
6330
+ * Version 0 is used for basic forwarding requests.
6331
+ */
6332
+ const CCTP_FORWARD_VERSION = 0;
6333
+ /**
6334
+ * Length of Circle-reserved payload for basic forwarding (0 bytes).
6335
+ *
6336
+ * Set to 0 when no additional Circle-reserved data is needed.
6337
+ */
6338
+ const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
6339
+ /**
6340
+ * Build the hookData bytes for CCTP forwarding.
6341
+ *
6342
+ * Constructs a 32-byte hookData payload that signals to Circle's Orbit relayer
6343
+ * that the user wants automated attestation fetching and destination mint execution.
6344
+ *
6345
+ * The hookData format is:
6346
+ * - Bytes 0-23: ASCII "cctp-forward" right-padded to 24 bytes with zeros
6347
+ * - Bytes 24-27: uint32 version (big-endian) - currently 0
6348
+ * - Bytes 28-31: uint32 length (big-endian) - 0 for basic forwarding
6349
+ *
6350
+ * @returns A 0x-prefixed hex string representing the 32-byte hookData
6351
+ *
6352
+ * @example
6353
+ * ```typescript
6354
+ * import { buildForwardingHookData } from '@core/utils'
6355
+ *
6356
+ * const hookData = buildForwardingHookData()
6357
+ * // Returns: '0x636374702d666f72776172640000000000000000000000000000000000000000'
6358
+ *
6359
+ * // Use with depositForBurnWithHook action
6360
+ * await adapter.action('cctp.v2.depositForBurnWithHook', {
6361
+ * amount: BigInt('1000000'),
6362
+ * mintRecipient: '0x...',
6363
+ * maxFee: BigInt('50000'),
6364
+ * minFinalityThreshold: 1000,
6365
+ * fromChain: ethereum,
6366
+ * toChain: base,
6367
+ * hookData
6368
+ * })
6369
+ * ```
6370
+ */
6371
+ // Cached result for memoization (computed once on first call)
6372
+ let cachedHookDataHex = null;
6373
+ function buildForwardingHookData() {
6374
+ // Return cached result if already computed
6375
+ if (cachedHookDataHex !== null) {
6376
+ return cachedHookDataHex;
6377
+ }
6378
+ // Create a 32-byte buffer
6379
+ const buffer = new Uint8Array(32);
6380
+ // Write the magic prefix ("cctp-forward") - 12 bytes, rest of 24-byte section is zero-padded
6381
+ const encoder = new TextEncoder();
6382
+ const magicBytes = encoder.encode(CCTP_FORWARD_MAGIC_PREFIX);
6383
+ buffer.set(magicBytes, 0);
6384
+ // Bytes 12-23 are already zero (right-padding)
6385
+ // Write uint32 version at bytes 24-27 (big-endian)
6386
+ const versionView = new DataView(buffer.buffer);
6387
+ versionView.setUint32(24, CCTP_FORWARD_VERSION, false); // false = big-endian
6388
+ // Write uint32 length at bytes 28-31 (big-endian)
6389
+ versionView.setUint32(28, CCTP_FORWARD_PAYLOAD_LENGTH, false); // false = big-endian
6390
+ // Convert to hex string with 0x prefix and cache
6391
+ cachedHookDataHex =
6392
+ '0x' +
6393
+ Array.from(buffer)
6394
+ .map((b) => b.toString(16).padStart(2, '0'))
6395
+ .join('');
6396
+ return cachedHookDataHex;
6397
+ }
6398
+
6399
+ /**
6400
+ * Transfer speed options for cross-chain operations.
6401
+ *
6402
+ * Defines the available speed modes for CCTPv2 transfers, affecting
6403
+ * both transfer time and potential fee implications.
6404
+ */
6405
+ var TransferSpeed;
6406
+ (function (TransferSpeed) {
6407
+ /** Fast burn mode - reduces transfer time but may have different fee implications */
6408
+ TransferSpeed["FAST"] = "FAST";
6409
+ /** Standard burn mode - normal transfer time with standard fees */
6410
+ TransferSpeed["SLOW"] = "SLOW";
6411
+ })(TransferSpeed || (TransferSpeed = {}));
6412
+
6413
+ /**
6414
+ * Factory to validate a numeric string with strict dot-decimal notation.
6415
+ * Only accepts dot (.) as the decimal separator. Thousand separators are not allowed.
6416
+ *
6417
+ * This enforces an unambiguous format for SDK inputs. Internationalization concerns
6418
+ * (comma vs dot decimal separators) should be handled in the UI layer before passing
6419
+ * values to the SDK.
6420
+ *
6421
+ * Accepts the following formats:
6422
+ * - Whole numbers: "1", "100", "1000"
6423
+ * - Leading zero decimals: "0.1", "0.5", "0.001"
6424
+ * - Shorthand decimals: ".1", ".5", ".001"
6425
+ * - Standard decimals: "1.23", "100.50"
6426
+ *
6427
+ * Does NOT accept:
6428
+ * - Comma decimal separator: "1,5" (use "1.5" instead)
6429
+ * - Thousand separators: "1,000.50" or "1.000,50" (use "1000.50" instead)
6430
+ * - Multiple decimal points: "1.2.3"
6431
+ * - Negative numbers: "-100"
6432
+ * - Non-numeric characters: "abc", "100a"
6433
+ *
6434
+ * Behavior differences controlled by options:
6435
+ * - allowZero: when false, value must be strictly greater than 0; when true, non-negative.
6436
+ * - regexMessage: error message when the basic numeric format fails.
6437
+ * - maxDecimals: maximum number of decimal places allowed (e.g., 6 for USDC).
6438
+ */
6439
+ const createDecimalStringValidator = (options) => (schema) => {
6440
+ // Capitalize first letter of attribute name for error messages
6441
+ const capitalizedAttributeName = options.attributeName.charAt(0).toUpperCase() +
6442
+ options.attributeName.slice(1);
6443
+ return schema
6444
+ .regex(/^-?(?:\d+(?:\.\d+)?|\.\d+)$/, options.regexMessage)
6445
+ .superRefine((val, ctx) => {
6446
+ const amount = Number.parseFloat(val);
6447
+ if (Number.isNaN(amount)) {
6448
+ ctx.addIssue({
6449
+ code: z.ZodIssueCode.custom,
6450
+ message: options.regexMessage,
6451
+ });
6452
+ return;
6453
+ }
6454
+ // Check decimal precision if maxDecimals is specified
6455
+ if (options.maxDecimals !== undefined) {
6456
+ const decimalPart = val.split('.')[1];
6457
+ if (decimalPart && decimalPart.length > options.maxDecimals) {
6458
+ ctx.addIssue({
6459
+ code: z.ZodIssueCode.custom,
6460
+ message: `Maximum supported decimal places: ${options.maxDecimals.toString()}`,
6461
+ });
6462
+ return;
6463
+ }
6464
+ }
6465
+ if (options.allowZero && amount < 0) {
6466
+ ctx.addIssue({
6467
+ code: z.ZodIssueCode.custom,
6468
+ message: `${capitalizedAttributeName} must be non-negative`,
6469
+ });
6470
+ }
6471
+ else if (!options.allowZero && amount <= 0) {
6472
+ ctx.addIssue({
6473
+ code: z.ZodIssueCode.custom,
6474
+ message: `${capitalizedAttributeName} must be greater than 0`,
6475
+ });
6476
+ }
6477
+ });
6478
+ };
6479
+ /**
6480
+ * Schema for validating chain definitions.
6481
+ * This ensures the basic structure of a chain definition is valid.
6482
+ * A chain definition must include at minimum a name and type.
6483
+ *
6484
+ * @throws KitError if validation fails
6485
+ *
6486
+ * @example
6487
+ * ```typescript
6488
+ * import { chainDefinitionSchema } from '@core/provider'
6489
+ *
6490
+ * const validChain = {
6491
+ * name: 'Ethereum',
6492
+ * type: 'evm'
6493
+ * }
6494
+ *
6495
+ * const result = chainDefinitionSchema.safeParse(validChain)
6496
+ * console.log(result.success) // true
6497
+ * ```
6498
+ */
6499
+ const chainDefinitionSchema = z.object({
6500
+ name: z.string().min(1, 'Chain name is required'),
6501
+ type: z.string().min(1, 'Chain type is required'),
6502
+ });
6503
+ /**
6504
+ * Schema for validating wallet contexts.
6505
+ * This ensures all required fields are present and properly typed.
6506
+ * A wallet context must include:
6507
+ * - A valid adapter with prepare and execute methods
6508
+ * - A valid Ethereum address
6509
+ * - A valid chain definition with required properties
6510
+ *
6511
+ * @throws KitError if validation fails
6512
+ *
6513
+ * @example
6514
+ * ```typescript
6515
+ * import { walletContextSchema } from '@core/provider'
6516
+ *
6517
+ * const validContext = {
6518
+ * adapter: {
6519
+ * prepare: () => Promise.resolve({}),
6520
+ * waitForTransaction: () => Promise.resolve({})
6521
+ * },
6522
+ * address: '0x1234567890123456789012345678901234567890',
6523
+ * chain: {
6524
+ * name: 'Ethereum',
6525
+ * type: 'evm',
6526
+ * isTestnet: false
6527
+ * }
6528
+ * }
6529
+ *
6530
+ * const result = walletContextSchema.safeParse(validContext)
6531
+ * console.log(result.success) // true
6532
+ * ```
6533
+ */
6534
+ const walletContextSchema = z.object({
6535
+ adapter: z.object({
6536
+ prepare: z.function().returns(z.any()),
6537
+ waitForTransaction: z.function().returns(z.any()),
6538
+ }),
6539
+ address: z.string().min(1),
6540
+ chain: z.object({
6541
+ name: z.string(),
6542
+ type: z.string(),
6543
+ isTestnet: z.boolean(),
6544
+ }),
6545
+ });
6546
+ /**
6547
+ * Schema for validating destination wallet contexts.
6548
+ * Extends walletContextSchema with optional useForwarder flag.
6549
+ *
6550
+ * @throws KitError if validation fails
6551
+ */
6552
+ const destinationContextSchema = walletContextSchema.extend({
6553
+ useForwarder: z.boolean().optional(),
6554
+ recipientAddress: z.string().optional(),
6555
+ });
6556
+ /**
6557
+ * Schema for validating forwarder-only destination contexts.
6558
+ * Used when useForwarder is true and no adapter is provided.
6559
+ * Requires chain definition and recipientAddress.
6560
+ *
6561
+ * Validates that recipientAddress format is correct for the destination chain
6562
+ * (EVM hex address or Solana base58 address).
6563
+ *
6564
+ * @throws KitError if validation fails
6565
+ */
6566
+ const forwarderOnlyDestinationSchema = z
6567
+ .object({
6568
+ chain: chainDefinitionSchema.extend({
6569
+ isTestnet: z.boolean(),
6570
+ }),
6571
+ recipientAddress: z
6572
+ .string()
6573
+ .min(1, 'Recipient address is required for forwarder-only destination'),
6574
+ useForwarder: z.literal(true),
6575
+ })
6576
+ .superRefine((data, ctx) => {
6577
+ // Pass chain name as string - isValidAddressForChain will use name-based matching
6578
+ if (!isValidAddressForChain(data.recipientAddress, data.chain.name)) {
6579
+ ctx.addIssue({
6580
+ code: z.ZodIssueCode.custom,
6581
+ message: `Invalid recipient address format for chain ${data.chain.name}`,
6582
+ path: ['recipientAddress'],
6583
+ });
6584
+ }
6585
+ });
6586
+ /**
6587
+ * Schema for validating any destination - either with adapter or forwarder-only.
6588
+ */
6589
+ const bridgeDestinationSchema = z.union([
6590
+ destinationContextSchema,
6591
+ forwarderOnlyDestinationSchema,
6592
+ ]);
6593
+ /**
6594
+ * Schema for validating a custom fee configuration.
6595
+ * Validates the simplified CustomFee interface which includes:
6596
+ * - An optional fee amount as string
6597
+ * - An optional fee recipient as string address
6598
+ *
6599
+ * @throws KitError if validation fails
6600
+ *
6601
+ * @example
6602
+ * ```typescript
6603
+ * import { customFeeSchema } from '@core/provider'
6604
+ *
6605
+ * const validConfig = {
6606
+ * value: '1000000',
6607
+ * recipientAddress: '0x1234567890123456789012345678901234567890'
6608
+ * }
6609
+ *
6610
+ * const result = customFeeSchema.safeParse(validConfig)
6611
+ * console.log(result.success) // true
6612
+ * ```
6613
+ */
5283
6614
  const customFeeSchema = z
5284
6615
  .object({
5285
6616
  /**
@@ -5542,6 +6873,21 @@ async function fetchUsdcFastBurnFee(sourceDomain, destinationDomain, isTestnet)
5542
6873
  return minimumFee;
5543
6874
  }
5544
6875
 
6876
+ /**
6877
+ * Well-known SPL Token program ID.
6878
+ * Duplicated here as a raw string to avoid a static import of \@core/adapter-solana
6879
+ * (which would transitively pull \@solana/web3.js into the top-level bundle and
6880
+ * break lazy-loading for EVM-only consumers).
6881
+ *
6882
+ * This MUST match TOKEN_PROGRAM_ID in @core/adapter-solana/src/utils/splTokenUtils.
6883
+ */
6884
+ const TOKEN_PROGRAM_ID_BASE58 = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; // NOSONAR — public Solana program address
6885
+ /**
6886
+ * Well-known SPL Associated Token Account program ID (same rationale as above).
6887
+ *
6888
+ * This MUST match ASSOCIATED_TOKEN_PROGRAM_ID in @core/adapter-solana/src/utils/splTokenUtils.
6889
+ */
6890
+ const ASSOCIATED_TOKEN_PROGRAM_ID_BASE58 = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'; // NOSONAR — public Solana program address
5545
6891
  /**
5546
6892
  * Get the token account address where USDC will be minted for the recipient.
5547
6893
  *
@@ -5585,20 +6931,30 @@ mintAddress) => {
5585
6931
  return rawAddress;
5586
6932
  }
5587
6933
  else {
5588
- // Solana: derive the associated token account
5589
- // Use dynamic import to avoid loading Solana dependencies for EVM-only users
6934
+ // Solana: derive the associated token account.
6935
+ // Both @solana/web3.js and the program-ID constants are resolved lazily
6936
+ // so that EVM-only consumers never load Solana code.
6937
+ const { PublicKey } = await import('@solana/web3.js').catch(() => {
6938
+ throw new KitError({
6939
+ ...InputError.VALIDATION_FAILED,
6940
+ recoverability: 'FATAL',
6941
+ message: 'Failed to load @solana/web3.js. Please ensure it is installed: npm install @solana/web3.js',
6942
+ });
6943
+ });
5590
6944
  try {
5591
- const [{ PublicKey }, { getAssociatedTokenAddressSync }] = await Promise.all([
5592
- import('@solana/web3.js'),
5593
- import('@solana/spl-token'),
5594
- ]);
5595
6945
  const owner = new PublicKey(rawAddress);
5596
6946
  const mintPub = new PublicKey(mintAddress);
5597
- const ata = getAssociatedTokenAddressSync(mintPub, owner);
6947
+ const tokenProgramId = new PublicKey(TOKEN_PROGRAM_ID_BASE58);
6948
+ const ataProgramId = new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID_BASE58);
6949
+ const [ata] = PublicKey.findProgramAddressSync([owner.toBuffer(), tokenProgramId.toBuffer(), mintPub.toBuffer()], ataProgramId);
5598
6950
  return ata.toBase58();
5599
6951
  }
5600
- catch {
5601
- throw new Error('Failed to derive Solana token account. Please ensure @solana/web3.js and @solana/spl-token are installed: npm install @solana/web3.js @solana/spl-token');
6952
+ catch (error) {
6953
+ throw new KitError({
6954
+ ...InputError.INVALID_ADDRESS,
6955
+ recoverability: 'FATAL',
6956
+ message: `Failed to derive Solana Associated Token Account for recipient "${rawAddress}": ${error instanceof Error ? error.message : String(error)}`,
6957
+ });
5602
6958
  }
5603
6959
  }
5604
6960
  };
@@ -7914,590 +9270,239 @@ function createLogger(options, stream) {
7914
9270
  const finalOptions = redactConfig
7915
9271
  ? { ...pinoOptions, redact: redactConfig }
7916
9272
  : pinoOptions;
7917
- const pinoInstance = pino(finalOptions);
7918
- return wrapPino(pinoInstance);
7919
- }
7920
-
7921
- /**
7922
- * Factory for creating Runtime instances with defaults.
7923
- *
7924
- * @packageDocumentation
7925
- */
7926
- // ============================================================================
7927
- // Validation Schema
7928
- // ============================================================================
7929
- /**
7930
- * Schema for validating {@link RuntimeOptions}.
7931
- *
7932
- * @remarks
7933
- * Used internally by {@link createRuntime} to validate options from JS consumers.
7934
- * Exported for advanced use cases where manual validation is needed.
7935
- */
7936
- z
7937
- .object({
7938
- logger: loggerSchema.optional(),
7939
- metrics: metricsSchema.optional(),
7940
- })
7941
- .passthrough();
7942
- // ============================================================================
7943
- // Factory
7944
- // ============================================================================
7945
- /**
7946
- * Create a complete Runtime with sensible defaults.
7947
- *
7948
- * @param options - Optional configuration to override logger and metrics.
7949
- * @returns A frozen, immutable Runtime with all services guaranteed present.
7950
- * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
7951
- *
7952
- * @remarks
7953
- * Creates a fully-configured runtime by merging provided options with defaults.
7954
- * The returned runtime is frozen to enforce immutability.
7955
- *
7956
- * | Service | Default | Configurable |
7957
- * |---------|---------|--------------|
7958
- * | `logger` | pino logger (info level) | Yes |
7959
- * | `metrics` | No-op metrics | Yes |
7960
- * | `events` | Internal event bus | No |
7961
- * | `clock` | `Date.now()` | No |
7962
- *
7963
- * **Why only logger and metrics?**
7964
- *
7965
- * - **Logger/Metrics**: Integration points with your infrastructure
7966
- * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
7967
- * - **Clock**: Testing concern - use mock factories for tests
7968
- *
7969
- * @example
7970
- * ```typescript
7971
- * import { createRuntime, createLogger } from '@core/runtime'
7972
- *
7973
- * // Use all defaults
7974
- * const runtime = createRuntime()
7975
- *
7976
- * // Custom logger
7977
- * const runtime = createRuntime({
7978
- * logger: createLogger({ level: 'debug' }),
7979
- * })
7980
- *
7981
- * // Custom metrics (e.g., Prometheus)
7982
- * const runtime = createRuntime({
7983
- * metrics: myPrometheusMetrics,
7984
- * })
7985
- *
7986
- * // Subscribe to events (don't replace the bus)
7987
- * runtime.events.on('operation.*', (event) => {
7988
- * console.log('Event:', event.name)
7989
- * })
7990
- * ```
7991
- */
7992
- function createRuntime(options) {
7993
- // Resolve logger first (events may need it)
7994
- const logger = createLogger();
7995
- // Internal services - not configurable
7996
- const events = createEventBus({ logger });
7997
- const clock = defaultClock;
7998
- // Resolve metrics
7999
- const metrics = noopMetrics;
8000
- return Object.freeze({ logger, events, metrics, clock });
8001
- }
8002
-
8003
- /**
8004
- * Invocation context resolution - resolves the **WHO/HOW** of an operation.
8005
- *
8006
- * @packageDocumentation
8007
- */
8008
- // ============================================================================
8009
- // Validation Schemas
8010
- // ============================================================================
8011
- /**
8012
- * Schema for validating Caller.
8013
- */
8014
- const callerSchema = z.object({
8015
- type: z.string(),
8016
- name: z.string(),
8017
- version: z.string().optional(),
8018
- });
8019
- /**
8020
- * Schema for validating InvocationMeta input.
8021
- */
8022
- const invocationMetaSchema = z
8023
- .object({
8024
- traceId: z.string().optional(),
8025
- runtime: z.object({}).passthrough().optional(),
8026
- tokens: z.object({}).passthrough().optional(),
8027
- callers: z.array(callerSchema).optional(),
8028
- })
8029
- .strict();
8030
- // ============================================================================
8031
- // Invocation Context Resolution
8032
- // ============================================================================
8033
- /**
8034
- * Resolve invocation metadata to invocation context.
8035
- *
8036
- * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
8037
- * @param defaults - Default runtime and tokens to use if not overridden.
8038
- * @returns Frozen, immutable invocation context with guaranteed values.
8039
- * @throws KitError when meta contains invalid properties.
8040
- *
8041
- * @remarks
8042
- * Resolves the **WHO** called and **HOW** to observe:
8043
- * - TraceId: Uses provided value or generates new one
8044
- * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
8045
- * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
8046
- * - Callers: Uses provided array or empty array
8047
- *
8048
- * The returned context is frozen to enforce immutability.
8049
- *
8050
- * @example
8051
- * ```typescript
8052
- * import { resolveInvocationContext, createRuntime } from '@core/runtime'
8053
- * import { createTokenRegistry } from '@core/tokens'
8054
- *
8055
- * const defaults = {
8056
- * runtime: createRuntime(),
8057
- * tokens: createTokenRegistry(),
8058
- * }
8059
- *
8060
- * // Minimal - just using defaults
8061
- * const ctx = resolveInvocationContext(undefined, defaults)
8062
- *
8063
- * // With trace ID and caller info
8064
- * const ctx = resolveInvocationContext(
8065
- * {
8066
- * traceId: 'abc-123',
8067
- * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
8068
- * },
8069
- * defaults
8070
- * )
8071
- *
8072
- * // With runtime override (complete replacement)
8073
- * const ctx = resolveInvocationContext(
8074
- * { runtime: createRuntime({ logger: myLogger }) },
8075
- * defaults
8076
- * )
8077
- * ```
8078
- */
8079
- function resolveInvocationContext(meta, defaults) {
8080
- // Validate meta input if provided
8081
- if (meta !== undefined) {
8082
- const result = invocationMetaSchema.safeParse(meta);
8083
- if (!result.success) {
8084
- throw createValidationFailedError('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
8085
- }
8086
- }
8087
- // Generate trace ID if not provided
8088
- const traceId = meta?.traceId ?? createTraceId();
8089
- // Use meta overrides or fall back to defaults
8090
- const runtime = meta?.runtime ?? defaults.runtime;
8091
- const tokens = meta?.tokens ?? defaults.tokens;
8092
- const callers = meta?.callers ?? [];
8093
- return Object.freeze({ traceId, runtime, tokens, callers });
9273
+ const pinoInstance = pino(finalOptions);
9274
+ return wrapPino(pinoInstance);
8094
9275
  }
8095
9276
 
8096
9277
  /**
8097
- * Extend an invocation context by appending a caller to its call chain.
9278
+ * Factory for creating Runtime instances with defaults.
8098
9279
  *
8099
- * @param context - The existing invocation context to extend.
8100
- * @param caller - The caller to append to the call chain.
8101
- * @returns A new frozen invocation context with the caller appended.
9280
+ * @packageDocumentation
9281
+ */
9282
+ // ============================================================================
9283
+ // Validation Schema
9284
+ // ============================================================================
9285
+ /**
9286
+ * Schema for validating {@link RuntimeOptions}.
8102
9287
  *
8103
9288
  * @remarks
8104
- * This function creates a new immutable context with the caller appended
8105
- * to the `callers` array while preserving all other context properties
8106
- * (traceId, runtime, tokens).
8107
- *
8108
- * The returned context is frozen to enforce immutability.
8109
- *
8110
- * @example
8111
- * ```typescript
8112
- * import { extendInvocationContext } from '@core/runtime'
8113
- *
8114
- * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
8115
- * const extended = extendInvocationContext(existingContext, caller)
8116
- * // extended.callers === [...existingContext.callers, caller]
8117
- * ```
9289
+ * Used internally by {@link createRuntime} to validate options from JS consumers.
9290
+ * Exported for advanced use cases where manual validation is needed.
8118
9291
  */
8119
- function extendInvocationContext(context, caller) {
8120
- return Object.freeze({
8121
- traceId: context.traceId,
8122
- runtime: context.runtime,
8123
- tokens: context.tokens,
8124
- callers: [...context.callers, caller],
8125
- });
8126
- }
8127
-
8128
- // Clock - expose defaultClock for backward compatibility
8129
- /** Clock validation schema (backward compatibility). */
8130
- z.custom((val) => val !== null &&
8131
- typeof val === 'object' &&
8132
- 'now' in val &&
8133
- typeof val['now'] === 'function');
8134
- /** EventBus validation schema (backward compatibility). */
8135
- z.custom((val) => val !== null &&
8136
- typeof val === 'object' &&
8137
- 'emit' in val &&
8138
- typeof val['emit'] === 'function');
8139
- /** Runtime validation schema (backward compatibility). */
8140
9292
  z
8141
9293
  .object({
8142
- logger: z.any().optional(),
8143
- events: z.any().optional(),
8144
- metrics: z.any().optional(),
8145
- clock: z.any().optional(),
9294
+ logger: loggerSchema.optional(),
9295
+ metrics: metricsSchema.optional(),
8146
9296
  })
8147
9297
  .passthrough();
8148
-
9298
+ // ============================================================================
9299
+ // Factory
9300
+ // ============================================================================
8149
9301
  /**
8150
- * Create a structured error for token resolution failures.
9302
+ * Create a complete Runtime with sensible defaults.
8151
9303
  *
8152
- * @remarks
8153
- * Returns a `KitError` with `INPUT_INVALID_TOKEN` code (1006).
8154
- * The error trace contains the selector and chain context for debugging.
9304
+ * @param options - Optional configuration to override logger and metrics.
9305
+ * @returns A frozen, immutable Runtime with all services guaranteed present.
9306
+ * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
8155
9307
  *
8156
- * @param message - Human-readable error description.
8157
- * @param selector - The token selector that failed to resolve.
8158
- * @param chainId - The chain being resolved for (optional).
8159
- * @param cause - The underlying error, if any (optional).
8160
- * @returns A KitError with INPUT type and FATAL recoverability.
9308
+ * @remarks
9309
+ * Creates a fully-configured runtime by merging provided options with defaults.
9310
+ * The returned runtime is frozen to enforce immutability.
8161
9311
  *
8162
- * @example
8163
- * ```typescript
8164
- * throw createTokenResolutionError(
8165
- * 'Unknown token symbol: FAKE',
8166
- * 'FAKE',
8167
- * 'Ethereum'
8168
- * )
8169
- * ```
8170
- */
8171
- function createTokenResolutionError(message, selector, chainId, cause) {
8172
- const trace = {
8173
- selector,
8174
- ...(chainId === undefined ? {} : { chainId }),
8175
- ...({} ),
8176
- };
8177
- return new KitError({
8178
- ...InputError.INVALID_TOKEN,
8179
- recoverability: 'FATAL',
8180
- message,
8181
- cause: { trace },
8182
- });
8183
- }
8184
-
8185
- /**
8186
- * USDC token definition with addresses and metadata.
9312
+ * | Service | Default | Configurable |
9313
+ * |---------|---------|--------------|
9314
+ * | `logger` | pino logger (info level) | Yes |
9315
+ * | `metrics` | No-op metrics | Yes |
9316
+ * | `events` | Internal event bus | No |
9317
+ * | `clock` | `Date.now()` | No |
8187
9318
  *
8188
- * @remarks
8189
- * This is the built-in USDC definition used by the TokenRegistry.
8190
- * Includes all known USDC addresses across supported chains.
9319
+ * **Why only logger and metrics?**
8191
9320
  *
8192
- * Keys use the `Blockchain` enum for type safety. Both enum values
8193
- * and string literals are supported:
8194
- * - `Blockchain.Ethereum` or `'Ethereum'`
9321
+ * - **Logger/Metrics**: Integration points with your infrastructure
9322
+ * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
9323
+ * - **Clock**: Testing concern - use mock factories for tests
8195
9324
  *
8196
9325
  * @example
8197
9326
  * ```typescript
8198
- * import { USDC } from '@core/tokens'
8199
- * import { Blockchain } from '@core/chains'
8200
- *
8201
- * console.log(USDC.symbol) // 'USDC'
8202
- * console.log(USDC.decimals) // 6
8203
- * console.log(USDC.locators[Blockchain.Ethereum])
8204
- * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
8205
- * ```
8206
- */
8207
- const USDC = {
8208
- symbol: 'USDC',
8209
- decimals: 6,
8210
- locators: {
8211
- // =========================================================================
8212
- // Mainnets (alphabetically sorted)
8213
- // =========================================================================
8214
- [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
8215
- [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
8216
- [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
8217
- [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
8218
- [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
8219
- [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
8220
- [Blockchain.Hedera]: '0.0.456858',
8221
- [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
8222
- [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
8223
- [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
8224
- [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
8225
- [Blockchain.Noble]: 'uusdc',
8226
- [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
8227
- [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
8228
- [Blockchain.Polkadot_Asset_Hub]: '1337',
8229
- [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
8230
- [Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
8231
- [Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
8232
- [Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
8233
- [Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
8234
- [Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
8235
- [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
8236
- [Blockchain.World_Chain]: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
8237
- [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
8238
- [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
8239
- // =========================================================================
8240
- // Testnets (alphabetically sorted)
8241
- // =========================================================================
8242
- [Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
8243
- [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
8244
- [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
8245
- [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
8246
- [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
8247
- [Blockchain.Hedera_Testnet]: '0.0.429274',
8248
- [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
8249
- [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
8250
- [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
8251
- [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
8252
- [Blockchain.Noble_Testnet]: 'uusdc',
8253
- [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
8254
- [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
8255
- [Blockchain.Polkadot_Westmint]: '31337',
8256
- [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
8257
- [Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
8258
- [Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
8259
- [Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
8260
- [Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
8261
- [Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
8262
- [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
8263
- [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
8264
- [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
8265
- [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
8266
- },
8267
- };
8268
-
8269
- // Re-export for consumers
8270
- /**
8271
- * All default token definitions.
9327
+ * import { createRuntime, createLogger } from '@core/runtime'
8272
9328
  *
8273
- * @remarks
8274
- * These tokens are automatically included in the TokenRegistry when created
8275
- * without explicit defaults. Extensions can override these definitions.
9329
+ * // Use all defaults
9330
+ * const runtime = createRuntime()
8276
9331
  *
8277
- * @example
8278
- * ```typescript
8279
- * import { createTokenRegistry } from '@core/tokens'
9332
+ * // Custom logger
9333
+ * const runtime = createRuntime({
9334
+ * logger: createLogger({ level: 'debug' }),
9335
+ * })
8280
9336
  *
8281
- * // Registry uses these by default
8282
- * const registry = createTokenRegistry()
9337
+ * // Custom metrics (e.g., Prometheus)
9338
+ * const runtime = createRuntime({
9339
+ * metrics: myPrometheusMetrics,
9340
+ * })
8283
9341
  *
8284
- * // Add custom tokens (built-ins are still included)
8285
- * const customRegistry = createTokenRegistry({
8286
- * tokens: [myCustomToken],
9342
+ * // Subscribe to events (don't replace the bus)
9343
+ * runtime.events.on('operation.*', (event) => {
9344
+ * console.log('Event:', event.name)
8287
9345
  * })
8288
9346
  * ```
8289
9347
  */
8290
- const DEFAULT_TOKENS = [USDC];
9348
+ function createRuntime(options) {
9349
+ // Resolve logger first (events may need it)
9350
+ const logger = createLogger();
9351
+ // Internal services - not configurable
9352
+ const events = createEventBus({ logger });
9353
+ const clock = defaultClock;
9354
+ // Resolve metrics
9355
+ const metrics = noopMetrics;
9356
+ return Object.freeze({ logger, events, metrics, clock });
9357
+ }
8291
9358
 
8292
9359
  /**
8293
- * Check if a selector is a raw token selector (object form).
9360
+ * Invocation context resolution - resolves the **WHO/HOW** of an operation.
8294
9361
  *
8295
- * @param selector - The token selector to check.
8296
- * @returns True if the selector is a raw token selector.
9362
+ * @packageDocumentation
8297
9363
  */
8298
- function isRawSelector(selector) {
8299
- return typeof selector === 'object' && 'locator' in selector;
8300
- }
9364
+ // ============================================================================
9365
+ // Validation Schemas
9366
+ // ============================================================================
9367
+ /**
9368
+ * Schema for validating Caller.
9369
+ */
9370
+ const callerSchema = z.object({
9371
+ type: z.string(),
9372
+ name: z.string(),
9373
+ version: z.string().optional(),
9374
+ });
9375
+ /**
9376
+ * Schema for validating InvocationMeta input.
9377
+ */
9378
+ const invocationMetaSchema = z
9379
+ .object({
9380
+ traceId: z.string().optional(),
9381
+ runtime: z.object({}).passthrough().optional(),
9382
+ tokens: z.object({}).passthrough().optional(),
9383
+ callers: z.array(callerSchema).optional(),
9384
+ })
9385
+ .strict();
9386
+ // ============================================================================
9387
+ // Invocation Context Resolution
9388
+ // ============================================================================
8301
9389
  /**
8302
- * Normalize a symbol to uppercase for case-insensitive lookup.
9390
+ * Resolve invocation metadata to invocation context.
8303
9391
  *
8304
- * @param symbol - The symbol to normalize.
8305
- * @returns The normalized (uppercase) symbol.
8306
- */
8307
- function normalizeSymbol(symbol) {
8308
- return symbol.toUpperCase();
8309
- }
8310
- /**
8311
- * Create a token registry with built-in tokens and optional extensions.
9392
+ * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
9393
+ * @param defaults - Default runtime and tokens to use if not overridden.
9394
+ * @returns Frozen, immutable invocation context with guaranteed values.
9395
+ * @throws KitError when meta contains invalid properties.
8312
9396
  *
8313
9397
  * @remarks
8314
- * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
8315
- * Custom tokens are merged on top - use this to add new tokens or override
8316
- * built-in definitions.
9398
+ * Resolves the **WHO** called and **HOW** to observe:
9399
+ * - TraceId: Uses provided value or generates new one
9400
+ * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
9401
+ * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
9402
+ * - Callers: Uses provided array or empty array
8317
9403
  *
8318
- * @param options - Configuration options for the registry.
8319
- * @returns A token registry instance.
9404
+ * The returned context is frozen to enforce immutability.
8320
9405
  *
8321
9406
  * @example
8322
9407
  * ```typescript
9408
+ * import { resolveInvocationContext, createRuntime } from '@core/runtime'
8323
9409
  * import { createTokenRegistry } from '@core/tokens'
8324
9410
  *
8325
- * // Create registry with built-in tokens (USDC, etc.)
8326
- * const registry = createTokenRegistry()
8327
- *
8328
- * // Resolve USDC on Ethereum
8329
- * const usdc = registry.resolve('USDC', 'Ethereum')
8330
- * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
8331
- * console.log(usdc.decimals) // 6
8332
- * ```
8333
- *
8334
- * @example
8335
- * ```typescript
8336
- * // Add custom tokens (built-ins are still included)
8337
- * const myToken: TokenDefinition = {
8338
- * symbol: 'MY',
8339
- * decimals: 18,
8340
- * locators: { Ethereum: '0x...' },
9411
+ * const defaults = {
9412
+ * runtime: createRuntime(),
9413
+ * tokens: createTokenRegistry(),
8341
9414
  * }
8342
9415
  *
8343
- * const registry = createTokenRegistry({ tokens: [myToken] })
8344
- * registry.resolve('USDC', 'Ethereum') // Still works!
8345
- * registry.resolve('MY', 'Ethereum') // Also works
8346
- * ```
8347
- *
8348
- * @example
8349
- * ```typescript
8350
- * // Override a built-in token
8351
- * const customUsdc: TokenDefinition = {
8352
- * symbol: 'USDC',
8353
- * decimals: 6,
8354
- * locators: { MyChain: '0xCustomAddress' },
8355
- * }
9416
+ * // Minimal - just using defaults
9417
+ * const ctx = resolveInvocationContext(undefined, defaults)
8356
9418
  *
8357
- * const registry = createTokenRegistry({ tokens: [customUsdc] })
8358
- * // Now USDC resolves to customUsdc definition
8359
- * ```
9419
+ * // With trace ID and caller info
9420
+ * const ctx = resolveInvocationContext(
9421
+ * {
9422
+ * traceId: 'abc-123',
9423
+ * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
9424
+ * },
9425
+ * defaults
9426
+ * )
8360
9427
  *
8361
- * @example
8362
- * ```typescript
8363
- * // Resolve arbitrary tokens by raw locator
8364
- * const registry = createTokenRegistry()
8365
- * const token = registry.resolve(
8366
- * { locator: '0x1234...', decimals: 18 },
8367
- * 'Ethereum'
9428
+ * // With runtime override (complete replacement)
9429
+ * const ctx = resolveInvocationContext(
9430
+ * { runtime: createRuntime({ logger: myLogger }) },
9431
+ * defaults
8368
9432
  * )
8369
9433
  * ```
8370
9434
  */
8371
- function createTokenRegistry(options = {}) {
8372
- const { tokens = [], requireDecimals = false } = options;
8373
- // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
8374
- const tokenMap = new Map();
8375
- // Add built-in tokens first
8376
- for (const def of DEFAULT_TOKENS) {
8377
- tokenMap.set(normalizeSymbol(def.symbol), def);
8378
- }
8379
- // Custom tokens override built-ins
8380
- for (const def of tokens) {
8381
- tokenMap.set(normalizeSymbol(def.symbol), def);
8382
- }
8383
- /**
8384
- * Resolve a symbol selector to token information.
8385
- */
8386
- function resolveSymbol(symbol, chainId) {
8387
- const normalizedSymbol = normalizeSymbol(symbol);
8388
- const definition = tokenMap.get(normalizedSymbol);
8389
- if (definition === undefined) {
8390
- throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
8391
- }
8392
- const locator = definition.locators[chainId];
8393
- if (locator === undefined || locator.trim() === '') {
8394
- throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
8395
- }
8396
- return {
8397
- symbol: definition.symbol,
8398
- decimals: definition.decimals,
8399
- locator,
8400
- };
8401
- }
8402
- /**
8403
- * Resolve a raw selector to token information.
8404
- */
8405
- function resolveRaw(selector, chainId) {
8406
- const { locator, decimals } = selector;
8407
- // Validate locator
8408
- if (!locator || typeof locator !== 'string') {
8409
- throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
8410
- }
8411
- // Decimals are always required for raw selectors
8412
- if (decimals === undefined) {
8413
- const message = requireDecimals
8414
- ? 'Decimals required for raw token selector (requireDecimals: true)'
8415
- : 'Decimals required for raw token selector. Provide { locator, decimals }.';
8416
- throw createTokenResolutionError(message, selector, chainId);
8417
- }
8418
- // Validate decimals
8419
- if (typeof decimals !== 'number' ||
8420
- decimals < 0 ||
8421
- !Number.isInteger(decimals)) {
8422
- throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
9435
+ function resolveInvocationContext(meta, defaults) {
9436
+ // Validate meta input if provided
9437
+ if (meta !== undefined) {
9438
+ const result = invocationMetaSchema.safeParse(meta);
9439
+ if (!result.success) {
9440
+ throw createValidationFailedError('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
8423
9441
  }
8424
- return {
8425
- decimals,
8426
- locator,
8427
- };
8428
9442
  }
8429
- return {
8430
- resolve(selector, chainId) {
8431
- // Runtime validation of inputs - these checks are for JS consumers
8432
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
8433
- if (selector === null || selector === undefined) {
8434
- throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
8435
- }
8436
- if (chainId === '' || typeof chainId !== 'string') {
8437
- throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
8438
- }
8439
- // Dispatch based on selector type
8440
- if (isRawSelector(selector)) {
8441
- return resolveRaw(selector, chainId);
8442
- }
8443
- if (typeof selector === 'string') {
8444
- return resolveSymbol(selector, chainId);
8445
- }
8446
- throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
8447
- },
8448
- get(symbol) {
8449
- if (!symbol || typeof symbol !== 'string') {
8450
- return undefined;
8451
- }
8452
- return tokenMap.get(normalizeSymbol(symbol));
8453
- },
8454
- has(symbol) {
8455
- if (!symbol || typeof symbol !== 'string') {
8456
- return false;
8457
- }
8458
- return tokenMap.has(normalizeSymbol(symbol));
8459
- },
8460
- symbols() {
8461
- return Array.from(tokenMap.values()).map((def) => def.symbol);
8462
- },
8463
- entries() {
8464
- return Array.from(tokenMap.values());
8465
- },
8466
- };
9443
+ // Generate trace ID if not provided
9444
+ const traceId = meta?.traceId ?? createTraceId();
9445
+ // Use meta overrides or fall back to defaults
9446
+ const runtime = meta?.runtime ?? defaults.runtime;
9447
+ const tokens = meta?.tokens ?? defaults.tokens;
9448
+ const callers = meta?.callers ?? [];
9449
+ return Object.freeze({ traceId, runtime, tokens, callers });
8467
9450
  }
8468
9451
 
8469
9452
  /**
8470
- * Define a schema for token registry interfaces.
9453
+ * Extend an invocation context by appending a caller to its call chain.
9454
+ *
9455
+ * @param context - The existing invocation context to extend.
9456
+ * @param caller - The caller to append to the call chain.
9457
+ * @returns A new frozen invocation context with the caller appended.
8471
9458
  *
8472
9459
  * @remarks
8473
- * Validate that a registry exposes the `resolve()` API used by adapters.
9460
+ * This function creates a new immutable context with the caller appended
9461
+ * to the `callers` array while preserving all other context properties
9462
+ * (traceId, runtime, tokens).
9463
+ *
9464
+ * The returned context is frozen to enforce immutability.
8474
9465
  *
8475
9466
  * @example
8476
9467
  * ```typescript
8477
- * import { tokenRegistrySchema } from '@core/tokens'
8478
- *
8479
- * const registry = {
8480
- * resolve: () => ({ locator: '0x0', decimals: 6 }),
8481
- * }
9468
+ * import { extendInvocationContext } from '@core/runtime'
8482
9469
  *
8483
- * tokenRegistrySchema.parse(registry)
9470
+ * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
9471
+ * const extended = extendInvocationContext(existingContext, caller)
9472
+ * // extended.callers === [...existingContext.callers, caller]
8484
9473
  * ```
8485
9474
  */
8486
- z.custom((value) => {
8487
- if (value === null || typeof value !== 'object') {
8488
- return false;
8489
- }
8490
- const record = value;
8491
- return (typeof record['resolve'] === 'function' &&
8492
- typeof record['get'] === 'function' &&
8493
- typeof record['has'] === 'function' &&
8494
- typeof record['symbols'] === 'function' &&
8495
- typeof record['entries'] === 'function');
8496
- }, {
8497
- message: 'Invalid token registry',
8498
- });
9475
+ function extendInvocationContext(context, caller) {
9476
+ return Object.freeze({
9477
+ traceId: context.traceId,
9478
+ runtime: context.runtime,
9479
+ tokens: context.tokens,
9480
+ callers: [...context.callers, caller],
9481
+ });
9482
+ }
9483
+
9484
+ // Clock - expose defaultClock for backward compatibility
9485
+ /** Clock validation schema (backward compatibility). */
9486
+ z.custom((val) => val !== null &&
9487
+ typeof val === 'object' &&
9488
+ 'now' in val &&
9489
+ typeof val['now'] === 'function');
9490
+ /** EventBus validation schema (backward compatibility). */
9491
+ z.custom((val) => val !== null &&
9492
+ typeof val === 'object' &&
9493
+ 'emit' in val &&
9494
+ typeof val['emit'] === 'function');
9495
+ /** Runtime validation schema (backward compatibility). */
9496
+ z
9497
+ .object({
9498
+ logger: z.any().optional(),
9499
+ events: z.any().optional(),
9500
+ metrics: z.any().optional(),
9501
+ clock: z.any().optional(),
9502
+ })
9503
+ .passthrough();
8499
9504
 
8500
- var version = "1.4.0";
9505
+ var version = "1.5.0";
8501
9506
  var pkg = {
8502
9507
  version: version};
8503
9508
 
@@ -8980,6 +9985,24 @@ const validateNativeBalanceForTransaction = async (params) => {
8980
9985
  }
8981
9986
  };
8982
9987
 
9988
+ /**
9989
+ * Permit signature standards for gasless token approvals.
9990
+ *
9991
+ * Defines the permit types that can be used to approve token spending
9992
+ * without requiring a separate approval transaction.
9993
+ *
9994
+ * @remarks
9995
+ * - NONE: No permit, tokens must be pre-approved via separate transaction
9996
+ * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
9997
+ */
9998
+ var PermitType;
9999
+ (function (PermitType) {
10000
+ /** No permit required - tokens must be pre-approved */
10001
+ PermitType[PermitType["NONE"] = 0] = "NONE";
10002
+ /** EIP-2612 standard permit */
10003
+ PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
10004
+ })(PermitType || (PermitType = {}));
10005
+
8983
10006
  /**
8984
10007
  * CCTP bridge step names that can occur in the bridging flow.
8985
10008
  *