@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.cjs CHANGED
@@ -19,7 +19,7 @@
19
19
  'use strict';
20
20
 
21
21
  // Buffer polyfill setup - executes before any other code
22
- // Ensures globalThis.Buffer is available for @solana/spl-token and other Solana libraries
22
+ // Ensures globalThis.Buffer is available for Solana libraries
23
23
  const { Buffer } = require('buffer');
24
24
  if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
25
25
  globalThis.Buffer = Buffer;
@@ -73,6 +73,8 @@ var Blockchain;
73
73
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
74
74
  Blockchain["Codex"] = "Codex";
75
75
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
76
+ Blockchain["Edge"] = "Edge";
77
+ Blockchain["Edge_Testnet"] = "Edge_Testnet";
76
78
  Blockchain["Ethereum"] = "Ethereum";
77
79
  Blockchain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
78
80
  Blockchain["Hedera"] = "Hedera";
@@ -85,6 +87,8 @@ var Blockchain;
85
87
  Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
86
88
  Blockchain["Monad"] = "Monad";
87
89
  Blockchain["Monad_Testnet"] = "Monad_Testnet";
90
+ Blockchain["Morph"] = "Morph";
91
+ Blockchain["Morph_Testnet"] = "Morph_Testnet";
88
92
  Blockchain["NEAR"] = "NEAR";
89
93
  Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
90
94
  Blockchain["Noble"] = "Noble";
@@ -116,6 +120,122 @@ var Blockchain;
116
120
  Blockchain["ZKSync_Era"] = "ZKSync_Era";
117
121
  Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
118
122
  })(Blockchain || (Blockchain = {}));
123
+ /**
124
+ * Enum representing the subset of {@link Blockchain} that supports swap operations.
125
+ *
126
+ * This enum provides compile-time type safety for swap chain selection,
127
+ * ensuring only supported chains are available in IDE autocomplete
128
+ * when building swap parameters.
129
+ *
130
+ * @remarks
131
+ * Unlike the full {@link Blockchain} enum, SwapChain only includes networks
132
+ * where the swap functionality is actively supported by the library.
133
+ * Using this enum prevents runtime errors from attempting unsupported
134
+ * cross-chain swaps.
135
+ *
136
+ * Currently supports:
137
+ * - Ethereum mainnet
138
+ * - Base mainnet
139
+ * - Polygon mainnet
140
+ * - Solana mainnet
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * import { SwapChain, swap, createSwapKitContext } from '@circle-fin/swap-kit'
145
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
146
+ *
147
+ * const context = createSwapKitContext()
148
+ * const adapter = createViemAdapterFromPrivateKey({
149
+ * privateKey: process.env.PRIVATE_KEY
150
+ * })
151
+ *
152
+ * // ✅ Autocomplete shows only swap-supported chains
153
+ * const result = await swap(context, {
154
+ * from: {
155
+ * adapter,
156
+ * chain: SwapChain.Ethereum // Autocomplete: Ethereum, Base, Polygon, Solana
157
+ * },
158
+ * tokenIn: 'USDC',
159
+ * tokenOut: 'USDT',
160
+ * amount: '100.0'
161
+ * })
162
+ * ```
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * // String literals also work (constrained to SwapChain values)
167
+ * const result = await swap(context, {
168
+ * from: {
169
+ * adapter,
170
+ * chain: 'Ethereum' // ✅ Only SwapChain strings allowed
171
+ * },
172
+ * tokenIn: 'USDC',
173
+ * tokenOut: 'NATIVE',
174
+ * amount: '50.0'
175
+ * })
176
+ *
177
+ * // ❌ TypeScript error - Sui not in SwapChain enum
178
+ * const invalidResult = await swap(context, {
179
+ * from: {
180
+ * adapter,
181
+ * chain: 'Sui' // Compile-time error!
182
+ * },
183
+ * tokenIn: 'USDC',
184
+ * tokenOut: 'USDT',
185
+ * amount: '100.0'
186
+ * })
187
+ * ```
188
+ */
189
+ /**
190
+ * Enum representing chains that support same-chain swaps through the Swap Kit.
191
+ *
192
+ * Unlike the full {@link Blockchain} enum, SwapChain includes only mainnet
193
+ * networks where adapter contracts are deployed (CCTPv2 support).
194
+ *
195
+ * Dynamic validation via {@link isSwapSupportedChain} ensures chains
196
+ * automatically work when adapter contracts and supported tokens are deployed.
197
+ *
198
+ * @example
199
+ * ```typescript
200
+ * import { SwapChain } from '@core/chains'
201
+ * import { swap } from '@circle-fin/swap-kit'
202
+ *
203
+ * const result = await swap(context, {
204
+ * from: {
205
+ * adapter,
206
+ * chain: SwapChain.Arbitrum // Now supported!
207
+ * },
208
+ * tokenIn: 'USDC',
209
+ * tokenOut: 'WETH',
210
+ * amount: '100.0'
211
+ * })
212
+ * ```
213
+ *
214
+ * @see {@link isSwapSupportedChain} for runtime validation
215
+ * @see {@link getSwapSupportedChains} for all supported chains
216
+ */
217
+ var SwapChain;
218
+ (function (SwapChain) {
219
+ // Original 4 chains
220
+ SwapChain["Ethereum"] = "Ethereum";
221
+ SwapChain["Base"] = "Base";
222
+ SwapChain["Polygon"] = "Polygon";
223
+ SwapChain["Solana"] = "Solana";
224
+ // Additional supported chains
225
+ SwapChain["Arbitrum"] = "Arbitrum";
226
+ SwapChain["Optimism"] = "Optimism";
227
+ SwapChain["Avalanche"] = "Avalanche";
228
+ SwapChain["Linea"] = "Linea";
229
+ SwapChain["Ink"] = "Ink";
230
+ SwapChain["World_Chain"] = "World_Chain";
231
+ SwapChain["Unichain"] = "Unichain";
232
+ SwapChain["Plume"] = "Plume";
233
+ SwapChain["Sei"] = "Sei";
234
+ SwapChain["Sonic"] = "Sonic";
235
+ SwapChain["XDC"] = "XDC";
236
+ SwapChain["HyperEVM"] = "HyperEVM";
237
+ SwapChain["Monad"] = "Monad";
238
+ })(SwapChain || (SwapChain = {}));
119
239
  // -----------------------------------------------------------------------------
120
240
  // Bridge Chain Enum (CCTPv2 Supported Chains)
121
241
  // -----------------------------------------------------------------------------
@@ -172,11 +292,13 @@ var BridgeChain;
172
292
  BridgeChain["Avalanche"] = "Avalanche";
173
293
  BridgeChain["Base"] = "Base";
174
294
  BridgeChain["Codex"] = "Codex";
295
+ BridgeChain["Edge"] = "Edge";
175
296
  BridgeChain["Ethereum"] = "Ethereum";
176
297
  BridgeChain["HyperEVM"] = "HyperEVM";
177
298
  BridgeChain["Ink"] = "Ink";
178
299
  BridgeChain["Linea"] = "Linea";
179
300
  BridgeChain["Monad"] = "Monad";
301
+ BridgeChain["Morph"] = "Morph";
180
302
  BridgeChain["Optimism"] = "Optimism";
181
303
  BridgeChain["Plume"] = "Plume";
182
304
  BridgeChain["Polygon"] = "Polygon";
@@ -192,11 +314,13 @@ var BridgeChain;
192
314
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
193
315
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
194
316
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
317
+ BridgeChain["Edge_Testnet"] = "Edge_Testnet";
195
318
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
196
319
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
197
320
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
198
321
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
199
322
  BridgeChain["Monad_Testnet"] = "Monad_Testnet";
323
+ BridgeChain["Morph_Testnet"] = "Morph_Testnet";
200
324
  BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
201
325
  BridgeChain["Plume_Testnet"] = "Plume_Testnet";
202
326
  BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
@@ -267,6 +391,7 @@ const Algorand = defineChain({
267
391
  rpcEndpoints: ['https://mainnet-api.algonode.cloud'],
268
392
  eurcAddress: null,
269
393
  usdcAddress: '31566704',
394
+ usdtAddress: null,
270
395
  cctp: null,
271
396
  });
272
397
 
@@ -290,6 +415,7 @@ const AlgorandTestnet = defineChain({
290
415
  rpcEndpoints: ['https://testnet-api.algonode.cloud'],
291
416
  eurcAddress: null,
292
417
  usdcAddress: '10458941',
418
+ usdtAddress: null,
293
419
  cctp: null,
294
420
  });
295
421
 
@@ -313,6 +439,7 @@ const Aptos = defineChain({
313
439
  rpcEndpoints: ['https://fullnode.mainnet.aptoslabs.com/v1'],
314
440
  eurcAddress: null,
315
441
  usdcAddress: '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b',
442
+ usdtAddress: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
316
443
  cctp: {
317
444
  domain: 9,
318
445
  contracts: {
@@ -350,6 +477,7 @@ const AptosTestnet = defineChain({
350
477
  rpcEndpoints: ['https://fullnode.testnet.aptoslabs.com/v1'],
351
478
  eurcAddress: null,
352
479
  usdcAddress: '0x69091fbab5f7d635ee7ac5098cf0c1efbe31d68fec0f2cd565e8d168daf52832',
480
+ usdtAddress: null,
353
481
  cctp: {
354
482
  domain: 9,
355
483
  contracts: {
@@ -367,6 +495,121 @@ const AptosTestnet = defineChain({
367
495
  },
368
496
  });
369
497
 
498
+ /**
499
+ * Complete swap token registry - single source of truth for all swap-supported tokens.
500
+ *
501
+ * @remarks
502
+ * All packages should import from this registry for swap operations.
503
+ * Adding a new swap token requires updating only this registry.
504
+ *
505
+ * The NATIVE token is handled separately as it resolves dynamically based on chain.
506
+ *
507
+ * @example
508
+ * ```typescript
509
+ * import { SWAP_TOKEN_REGISTRY } from '@core/chains'
510
+ *
511
+ * // Get token decimals
512
+ * const decimals = SWAP_TOKEN_REGISTRY.USDC.decimals // 6
513
+ *
514
+ * // Check if token is stablecoin
515
+ * const isStable = SWAP_TOKEN_REGISTRY.DAI.category === 'stablecoin' // true
516
+ * ```
517
+ */
518
+ const SWAP_TOKEN_REGISTRY = {
519
+ // ============================================================================
520
+ // Stablecoins (6 decimals)
521
+ // ============================================================================
522
+ USDC: {
523
+ symbol: 'USDC',
524
+ decimals: 6,
525
+ category: 'stablecoin',
526
+ description: 'USD Coin',
527
+ },
528
+ EURC: {
529
+ symbol: 'EURC',
530
+ decimals: 6,
531
+ category: 'stablecoin',
532
+ description: 'Euro Coin',
533
+ },
534
+ USDT: {
535
+ symbol: 'USDT',
536
+ decimals: 6,
537
+ category: 'stablecoin',
538
+ description: 'Tether USD',
539
+ },
540
+ PYUSD: {
541
+ symbol: 'PYUSD',
542
+ decimals: 6,
543
+ category: 'stablecoin',
544
+ description: 'PayPal USD',
545
+ },
546
+ // ============================================================================
547
+ // Stablecoins (18 decimals)
548
+ // ============================================================================
549
+ DAI: {
550
+ symbol: 'DAI',
551
+ decimals: 18,
552
+ category: 'stablecoin',
553
+ description: 'MakerDAO stablecoin',
554
+ },
555
+ USDE: {
556
+ symbol: 'USDE',
557
+ decimals: 18,
558
+ category: 'stablecoin',
559
+ description: 'Ethena USD (synthetic dollar)',
560
+ },
561
+ // ============================================================================
562
+ // Wrapped Tokens
563
+ // ============================================================================
564
+ WBTC: {
565
+ symbol: 'WBTC',
566
+ decimals: 8,
567
+ category: 'wrapped',
568
+ description: 'Wrapped Bitcoin',
569
+ },
570
+ WETH: {
571
+ symbol: 'WETH',
572
+ decimals: 18,
573
+ category: 'wrapped',
574
+ description: 'Wrapped Ethereum',
575
+ },
576
+ WSOL: {
577
+ symbol: 'WSOL',
578
+ decimals: 9,
579
+ category: 'wrapped',
580
+ description: 'Wrapped Solana',
581
+ },
582
+ WAVAX: {
583
+ symbol: 'WAVAX',
584
+ decimals: 18,
585
+ category: 'wrapped',
586
+ description: 'Wrapped Avalanche',
587
+ },
588
+ WPOL: {
589
+ symbol: 'WPOL',
590
+ decimals: 18,
591
+ category: 'wrapped',
592
+ description: 'Wrapped Polygon',
593
+ },
594
+ };
595
+ /**
596
+ * Special NATIVE token constant for swap operations.
597
+ *
598
+ * @remarks
599
+ * NATIVE is handled separately from SWAP_TOKEN_REGISTRY because it resolves
600
+ * dynamically based on the chain (ETH on Ethereum, SOL on Solana, etc.).
601
+ * Its decimals are chain-specific.
602
+ */
603
+ const NATIVE_TOKEN = 'NATIVE';
604
+ /**
605
+ * Array of all supported swap token symbols including NATIVE.
606
+ * Useful for iteration, validation, and filtering.
607
+ */
608
+ [
609
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
610
+ NATIVE_TOKEN,
611
+ ];
612
+
370
613
  /**
371
614
  * The bridge contract address for EVM testnet networks.
372
615
  *
@@ -383,6 +626,13 @@ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d'
383
626
  * USDC transfers on live networks.
384
627
  */
385
628
  const BRIDGE_CONTRACT_EVM_MAINNET = '0xB3FA262d0fB521cc93bE83d87b322b8A23DAf3F0';
629
+ /**
630
+ * The adapter contract address for EVM mainnet networks.
631
+ *
632
+ * This contract serves as an adapter for integrating with various protocols
633
+ * on EVM-compatible chains. Use this address for mainnet adapter integrations.
634
+ */
635
+ const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845';
386
636
 
387
637
  /**
388
638
  * Arc Testnet chain definition
@@ -412,6 +662,7 @@ const ArcTestnet = defineChain({
412
662
  rpcEndpoints: ['https://rpc.testnet.arc.network/'],
413
663
  eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
414
664
  usdcAddress: '0x3600000000000000000000000000000000000000',
665
+ usdtAddress: null,
415
666
  cctp: {
416
667
  domain: 26,
417
668
  contracts: {
@@ -454,6 +705,7 @@ const Arbitrum = defineChain({
454
705
  rpcEndpoints: ['https://arb1.arbitrum.io/rpc'],
455
706
  eurcAddress: null,
456
707
  usdcAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
708
+ usdtAddress: null,
457
709
  cctp: {
458
710
  domain: 3,
459
711
  contracts: {
@@ -478,6 +730,7 @@ const Arbitrum = defineChain({
478
730
  },
479
731
  kitContracts: {
480
732
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
733
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
481
734
  },
482
735
  });
483
736
 
@@ -502,6 +755,7 @@ const ArbitrumSepolia = defineChain({
502
755
  rpcEndpoints: ['https://sepolia-rollup.arbitrum.io/rpc'],
503
756
  eurcAddress: null,
504
757
  usdcAddress: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
758
+ usdtAddress: null,
505
759
  cctp: {
506
760
  domain: 3,
507
761
  contracts: {
@@ -550,6 +804,7 @@ const Avalanche = defineChain({
550
804
  rpcEndpoints: ['https://api.avax.network/ext/bc/C/rpc'],
551
805
  eurcAddress: '0xc891eb4cbdeff6e073e859e987815ed1505c2acd',
552
806
  usdcAddress: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
807
+ usdtAddress: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7',
553
808
  cctp: {
554
809
  domain: 1,
555
810
  contracts: {
@@ -574,6 +829,7 @@ const Avalanche = defineChain({
574
829
  },
575
830
  kitContracts: {
576
831
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
832
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
577
833
  },
578
834
  });
579
835
 
@@ -597,6 +853,7 @@ const AvalancheFuji = defineChain({
597
853
  explorerUrl: 'https://subnets-test.avax.network/c-chain/tx/{hash}',
598
854
  eurcAddress: '0x5e44db7996c682e92a960b65ac713a54ad815c6b',
599
855
  usdcAddress: '0x5425890298aed601595a70ab815c96711a31bc65',
856
+ usdtAddress: null,
600
857
  cctp: {
601
858
  domain: 1,
602
859
  contracts: {
@@ -646,6 +903,7 @@ const Base = defineChain({
646
903
  rpcEndpoints: ['https://mainnet.base.org', 'https://base.publicnode.com'],
647
904
  eurcAddress: '0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42',
648
905
  usdcAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
906
+ usdtAddress: null,
649
907
  cctp: {
650
908
  domain: 6,
651
909
  contracts: {
@@ -670,6 +928,7 @@ const Base = defineChain({
670
928
  },
671
929
  kitContracts: {
672
930
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
931
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
673
932
  },
674
933
  });
675
934
 
@@ -694,6 +953,7 @@ const BaseSepolia = defineChain({
694
953
  rpcEndpoints: ['https://sepolia.base.org'],
695
954
  eurcAddress: '0x808456652fdb597867f38412077A9182bf77359F',
696
955
  usdcAddress: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
956
+ usdtAddress: null,
697
957
  cctp: {
698
958
  domain: 6,
699
959
  contracts: {
@@ -742,6 +1002,7 @@ const Celo = defineChain({
742
1002
  rpcEndpoints: ['https://forno.celo.org'],
743
1003
  eurcAddress: null,
744
1004
  usdcAddress: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
1005
+ usdtAddress: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
745
1006
  cctp: null,
746
1007
  });
747
1008
 
@@ -766,6 +1027,7 @@ const CeloAlfajoresTestnet = defineChain({
766
1027
  rpcEndpoints: ['https://alfajores-forno.celo-testnet.org'],
767
1028
  eurcAddress: null,
768
1029
  usdcAddress: '0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B',
1030
+ usdtAddress: null,
769
1031
  cctp: null,
770
1032
  });
771
1033
 
@@ -790,6 +1052,7 @@ const Codex = defineChain({
790
1052
  rpcEndpoints: ['https://rpc.codex.xyz'],
791
1053
  eurcAddress: null,
792
1054
  usdcAddress: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
1055
+ usdtAddress: null,
793
1056
  cctp: {
794
1057
  domain: 12,
795
1058
  contracts: {
@@ -832,6 +1095,7 @@ const CodexTestnet = defineChain({
832
1095
  rpcEndpoints: ['https://rpc.codex-stg.xyz'],
833
1096
  eurcAddress: null,
834
1097
  usdcAddress: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
1098
+ usdtAddress: null,
835
1099
  cctp: {
836
1100
  domain: 12,
837
1101
  contracts: {
@@ -853,6 +1117,94 @@ const CodexTestnet = defineChain({
853
1117
  },
854
1118
  });
855
1119
 
1120
+ /**
1121
+ * Edge Mainnet chain definition
1122
+ * @remarks
1123
+ * This represents the official production network for the Edge blockchain.
1124
+ * Edge is an EVM-compatible blockchain.
1125
+ */
1126
+ const Edge = defineChain({
1127
+ type: 'evm',
1128
+ chain: Blockchain.Edge,
1129
+ name: 'Edge',
1130
+ title: 'Edge Mainnet',
1131
+ nativeCurrency: {
1132
+ name: 'Ether',
1133
+ symbol: 'ETH',
1134
+ decimals: 18,
1135
+ },
1136
+ chainId: 3343,
1137
+ isTestnet: false,
1138
+ explorerUrl: 'https://pro.edgex.exchange/en-US/explorer/tx/{hash}',
1139
+ rpcEndpoints: ['https://edge-mainnet.g.alchemy.com/public'],
1140
+ eurcAddress: null,
1141
+ usdcAddress: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
1142
+ usdtAddress: null,
1143
+ cctp: {
1144
+ domain: 28,
1145
+ contracts: {
1146
+ v2: {
1147
+ type: 'split',
1148
+ tokenMessenger: '0x98706A006bc632Df31CAdFCBD43F38887ce2ca5c',
1149
+ messageTransmitter: '0x5b61381Fc9e58E70EfC13a4A97516997019198ee',
1150
+ confirmations: 65,
1151
+ fastConfirmations: 1,
1152
+ },
1153
+ },
1154
+ forwarderSupported: {
1155
+ source: true,
1156
+ destination: true,
1157
+ },
1158
+ },
1159
+ kitContracts: {
1160
+ bridge: '0x6D1AaE1c34Aeb582022916a67f2A655C6f4eDFF2', //Unique bridge address as CCTP address for Edge is also unique
1161
+ },
1162
+ });
1163
+
1164
+ /**
1165
+ * Edge Testnet chain definition
1166
+ * @remarks
1167
+ * This represents the official test network for the Edge blockchain.
1168
+ * Edge is an EVM-compatible blockchain.
1169
+ */
1170
+ const EdgeTestnet = defineChain({
1171
+ type: 'evm',
1172
+ chain: Blockchain.Edge_Testnet,
1173
+ name: 'Edge Testnet',
1174
+ title: 'Edge Testnet',
1175
+ nativeCurrency: {
1176
+ name: 'Ether',
1177
+ symbol: 'ETH',
1178
+ decimals: 18,
1179
+ },
1180
+ chainId: 33431,
1181
+ isTestnet: true,
1182
+ explorerUrl: 'https://edge-testnet.explorer.alchemy.com/tx/{hash}',
1183
+ rpcEndpoints: ['https://edge-testnet.g.alchemy.com/public'],
1184
+ eurcAddress: null,
1185
+ usdcAddress: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
1186
+ usdtAddress: null,
1187
+ cctp: {
1188
+ domain: 28,
1189
+ contracts: {
1190
+ v2: {
1191
+ type: 'split',
1192
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
1193
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
1194
+ confirmations: 65,
1195
+ fastConfirmations: 1,
1196
+ },
1197
+ },
1198
+ forwarderSupported: {
1199
+ source: true,
1200
+ destination: true,
1201
+ },
1202
+ },
1203
+ kitContracts: {
1204
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1205
+ },
1206
+ });
1207
+
856
1208
  /**
857
1209
  * Ethereum Mainnet chain definition
858
1210
  * @remarks
@@ -874,6 +1226,7 @@ const Ethereum = defineChain({
874
1226
  rpcEndpoints: ['https://eth.merkle.io', 'https://ethereum.publicnode.com'],
875
1227
  eurcAddress: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
876
1228
  usdcAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
1229
+ usdtAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7',
877
1230
  cctp: {
878
1231
  domain: 0,
879
1232
  contracts: {
@@ -898,6 +1251,7 @@ const Ethereum = defineChain({
898
1251
  },
899
1252
  kitContracts: {
900
1253
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1254
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
901
1255
  },
902
1256
  });
903
1257
 
@@ -922,6 +1276,7 @@ const EthereumSepolia = defineChain({
922
1276
  rpcEndpoints: ['https://sepolia.drpc.org'],
923
1277
  eurcAddress: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
924
1278
  usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
1279
+ usdtAddress: null,
925
1280
  cctp: {
926
1281
  domain: 0,
927
1282
  contracts: {
@@ -969,6 +1324,7 @@ const Hedera = defineChain({
969
1324
  rpcEndpoints: ['https://mainnet.hashio.io/api'],
970
1325
  eurcAddress: null,
971
1326
  usdcAddress: '0.0.456858',
1327
+ usdtAddress: null,
972
1328
  cctp: null,
973
1329
  });
974
1330
 
@@ -992,6 +1348,7 @@ const HederaTestnet = defineChain({
992
1348
  rpcEndpoints: ['https://testnet.hashio.io/api'],
993
1349
  eurcAddress: null,
994
1350
  usdcAddress: '0.0.429274',
1351
+ usdtAddress: null,
995
1352
  cctp: null,
996
1353
  });
997
1354
 
@@ -1018,6 +1375,7 @@ const HyperEVM = defineChain({
1018
1375
  rpcEndpoints: ['https://rpc.hyperliquid.xyz/evm'],
1019
1376
  eurcAddress: null,
1020
1377
  usdcAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
1378
+ usdtAddress: null,
1021
1379
  cctp: {
1022
1380
  domain: 19,
1023
1381
  contracts: {
@@ -1036,6 +1394,7 @@ const HyperEVM = defineChain({
1036
1394
  },
1037
1395
  kitContracts: {
1038
1396
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1397
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1039
1398
  },
1040
1399
  });
1041
1400
 
@@ -1061,6 +1420,7 @@ const HyperEVMTestnet = defineChain({
1061
1420
  rpcEndpoints: ['https://rpc.hyperliquid-testnet.xyz/evm'],
1062
1421
  eurcAddress: null,
1063
1422
  usdcAddress: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
1423
+ usdtAddress: null,
1064
1424
  cctp: {
1065
1425
  domain: 19,
1066
1426
  contracts: {
@@ -1108,6 +1468,7 @@ const Ink = defineChain({
1108
1468
  ],
1109
1469
  eurcAddress: null,
1110
1470
  usdcAddress: '0x2D270e6886d130D724215A266106e6832161EAEd',
1471
+ usdtAddress: null,
1111
1472
  cctp: {
1112
1473
  domain: 21,
1113
1474
  contracts: {
@@ -1126,6 +1487,7 @@ const Ink = defineChain({
1126
1487
  },
1127
1488
  kitContracts: {
1128
1489
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1490
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1129
1491
  },
1130
1492
  });
1131
1493
 
@@ -1154,6 +1516,7 @@ const InkTestnet = defineChain({
1154
1516
  ],
1155
1517
  eurcAddress: null,
1156
1518
  usdcAddress: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
1519
+ usdtAddress: null,
1157
1520
  cctp: {
1158
1521
  domain: 21,
1159
1522
  contracts: {
@@ -1196,6 +1559,7 @@ const Linea = defineChain({
1196
1559
  rpcEndpoints: ['https://rpc.linea.build'],
1197
1560
  eurcAddress: null,
1198
1561
  usdcAddress: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
1562
+ usdtAddress: null,
1199
1563
  cctp: {
1200
1564
  domain: 11,
1201
1565
  contracts: {
@@ -1214,6 +1578,7 @@ const Linea = defineChain({
1214
1578
  },
1215
1579
  kitContracts: {
1216
1580
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1581
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1217
1582
  },
1218
1583
  });
1219
1584
 
@@ -1238,6 +1603,7 @@ const LineaSepolia = defineChain({
1238
1603
  rpcEndpoints: ['https://rpc.sepolia.linea.build'],
1239
1604
  eurcAddress: null,
1240
1605
  usdcAddress: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
1606
+ usdtAddress: null,
1241
1607
  cctp: {
1242
1608
  domain: 11,
1243
1609
  contracts: {
@@ -1282,6 +1648,7 @@ const Monad = defineChain({
1282
1648
  rpcEndpoints: ['https://rpc.monad.xyz'],
1283
1649
  eurcAddress: null,
1284
1650
  usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
1651
+ usdtAddress: null,
1285
1652
  cctp: {
1286
1653
  domain: 15,
1287
1654
  contracts: {
@@ -1300,6 +1667,7 @@ const Monad = defineChain({
1300
1667
  },
1301
1668
  kitContracts: {
1302
1669
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1670
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1303
1671
  },
1304
1672
  });
1305
1673
 
@@ -1326,6 +1694,7 @@ const MonadTestnet = defineChain({
1326
1694
  rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
1327
1695
  eurcAddress: null,
1328
1696
  usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
1697
+ usdtAddress: null,
1329
1698
  cctp: {
1330
1699
  domain: 15,
1331
1700
  contracts: {
@@ -1347,6 +1716,94 @@ const MonadTestnet = defineChain({
1347
1716
  },
1348
1717
  });
1349
1718
 
1719
+ /**
1720
+ * Morph Mainnet chain definition
1721
+ * @remarks
1722
+ * This represents the official production network for the Morph blockchain.
1723
+ * Morph is an EVM-compatible Layer-2 blockchain built on the OP Stack.
1724
+ */
1725
+ const Morph = defineChain({
1726
+ type: 'evm',
1727
+ chain: Blockchain.Morph,
1728
+ name: 'Morph',
1729
+ title: 'Morph Mainnet',
1730
+ nativeCurrency: {
1731
+ name: 'Ether',
1732
+ symbol: 'ETH',
1733
+ decimals: 18,
1734
+ },
1735
+ chainId: 2818,
1736
+ isTestnet: false,
1737
+ explorerUrl: 'https://explorer.morph.network/tx/{hash}',
1738
+ rpcEndpoints: ['https://rpc.morphl2.io'],
1739
+ eurcAddress: null,
1740
+ usdcAddress: '0xCfb1186F4e93D60E60a8bDd997427D1F33bc372B',
1741
+ usdtAddress: null,
1742
+ cctp: {
1743
+ domain: 30,
1744
+ contracts: {
1745
+ v2: {
1746
+ type: 'split',
1747
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
1748
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
1749
+ confirmations: 64,
1750
+ fastConfirmations: 1,
1751
+ },
1752
+ },
1753
+ forwarderSupported: {
1754
+ source: false,
1755
+ destination: false,
1756
+ },
1757
+ },
1758
+ kitContracts: {
1759
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1760
+ },
1761
+ });
1762
+
1763
+ /**
1764
+ * Morph Hoodi Testnet chain definition
1765
+ * @remarks
1766
+ * This represents the official test network for the Morph blockchain.
1767
+ * Morph is an EVM-compatible Layer-2 blockchain built on the OP Stack.
1768
+ */
1769
+ const MorphTestnet = defineChain({
1770
+ type: 'evm',
1771
+ chain: Blockchain.Morph_Testnet,
1772
+ name: 'Morph Hoodi',
1773
+ title: 'Morph Hoodi Testnet',
1774
+ nativeCurrency: {
1775
+ name: 'Ether',
1776
+ symbol: 'ETH',
1777
+ decimals: 18,
1778
+ },
1779
+ chainId: 2910,
1780
+ isTestnet: true,
1781
+ explorerUrl: 'https://explorer-hoodi.morphl2.io/tx/{hash}',
1782
+ rpcEndpoints: ['https://rpc-hoodi.morphl2.io'],
1783
+ eurcAddress: null,
1784
+ usdcAddress: '0x7433b41C6c5e1d58D4Da99483609520255ab661B',
1785
+ usdtAddress: null,
1786
+ cctp: {
1787
+ domain: 30,
1788
+ contracts: {
1789
+ v2: {
1790
+ type: 'split',
1791
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
1792
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
1793
+ confirmations: 64,
1794
+ fastConfirmations: 1,
1795
+ },
1796
+ },
1797
+ forwarderSupported: {
1798
+ source: false,
1799
+ destination: false,
1800
+ },
1801
+ },
1802
+ kitContracts: {
1803
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1804
+ },
1805
+ });
1806
+
1350
1807
  /**
1351
1808
  * NEAR Protocol Mainnet chain definition
1352
1809
  * @remarks
@@ -1367,6 +1824,7 @@ const NEAR = defineChain({
1367
1824
  rpcEndpoints: ['https://eth-rpc.mainnet.near.org'],
1368
1825
  eurcAddress: null,
1369
1826
  usdcAddress: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
1827
+ usdtAddress: 'usdt.tether-token.near',
1370
1828
  cctp: null,
1371
1829
  });
1372
1830
 
@@ -1390,6 +1848,7 @@ const NEARTestnet = defineChain({
1390
1848
  rpcEndpoints: ['https://eth-rpc.testnet.near.org'],
1391
1849
  eurcAddress: null,
1392
1850
  usdcAddress: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
1851
+ usdtAddress: null,
1393
1852
  cctp: null,
1394
1853
  });
1395
1854
 
@@ -1413,6 +1872,7 @@ const Noble = defineChain({
1413
1872
  rpcEndpoints: ['https://noble-rpc.polkachu.com'],
1414
1873
  eurcAddress: null,
1415
1874
  usdcAddress: 'uusdc',
1875
+ usdtAddress: null,
1416
1876
  cctp: {
1417
1877
  domain: 4,
1418
1878
  contracts: {
@@ -1449,6 +1909,7 @@ const NobleTestnet = defineChain({
1449
1909
  rpcEndpoints: ['https://noble-testnet-rpc.polkachu.com'],
1450
1910
  eurcAddress: null,
1451
1911
  usdcAddress: 'uusdc',
1912
+ usdtAddress: null,
1452
1913
  cctp: {
1453
1914
  domain: 4,
1454
1915
  contracts: {
@@ -1486,6 +1947,7 @@ const Optimism = defineChain({
1486
1947
  rpcEndpoints: ['https://mainnet.optimism.io'],
1487
1948
  eurcAddress: null,
1488
1949
  usdcAddress: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
1950
+ usdtAddress: null,
1489
1951
  cctp: {
1490
1952
  domain: 2,
1491
1953
  contracts: {
@@ -1510,6 +1972,7 @@ const Optimism = defineChain({
1510
1972
  },
1511
1973
  kitContracts: {
1512
1974
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1975
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1513
1976
  },
1514
1977
  });
1515
1978
 
@@ -1534,6 +1997,7 @@ const OptimismSepolia = defineChain({
1534
1997
  rpcEndpoints: ['https://sepolia.optimism.io'],
1535
1998
  eurcAddress: null,
1536
1999
  usdcAddress: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
2000
+ usdtAddress: null,
1537
2001
  cctp: {
1538
2002
  domain: 2,
1539
2003
  contracts: {
@@ -1584,6 +2048,7 @@ const Plume = defineChain({
1584
2048
  rpcEndpoints: ['https://rpc.plume.org'],
1585
2049
  eurcAddress: null,
1586
2050
  usdcAddress: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
2051
+ usdtAddress: null,
1587
2052
  cctp: {
1588
2053
  domain: 22,
1589
2054
  contracts: {
@@ -1602,6 +2067,7 @@ const Plume = defineChain({
1602
2067
  },
1603
2068
  kitContracts: {
1604
2069
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2070
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1605
2071
  },
1606
2072
  });
1607
2073
 
@@ -1627,6 +2093,7 @@ const PlumeTestnet = defineChain({
1627
2093
  rpcEndpoints: ['https://testnet-rpc.plume.org'],
1628
2094
  eurcAddress: null,
1629
2095
  usdcAddress: '0xcB5f30e335672893c7eb944B374c196392C19D18',
2096
+ usdtAddress: null,
1630
2097
  cctp: {
1631
2098
  domain: 22,
1632
2099
  contracts: {
@@ -1668,6 +2135,7 @@ const PolkadotAssetHub = defineChain({
1668
2135
  rpcEndpoints: ['https://asset-hub-polkadot-rpc.n.dwellir.com'],
1669
2136
  eurcAddress: null,
1670
2137
  usdcAddress: '1337',
2138
+ usdtAddress: '1984',
1671
2139
  cctp: null,
1672
2140
  });
1673
2141
 
@@ -1691,6 +2159,7 @@ const PolkadotWestmint = defineChain({
1691
2159
  rpcEndpoints: ['https://westmint-rpc.polkadot.io'],
1692
2160
  eurcAddress: null,
1693
2161
  usdcAddress: 'Asset ID 31337',
2162
+ usdtAddress: null,
1694
2163
  cctp: null,
1695
2164
  });
1696
2165
 
@@ -1712,9 +2181,10 @@ const Polygon = defineChain({
1712
2181
  chainId: 137,
1713
2182
  isTestnet: false,
1714
2183
  explorerUrl: 'https://polygonscan.com/tx/{hash}',
1715
- rpcEndpoints: ['https://polygon-rpc.com', 'https://polygon.publicnode.com'],
2184
+ rpcEndpoints: ['https://polygon.publicnode.com', 'https://polygon.drpc.org'],
1716
2185
  eurcAddress: null,
1717
2186
  usdcAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
2187
+ usdtAddress: null,
1718
2188
  cctp: {
1719
2189
  domain: 7,
1720
2190
  contracts: {
@@ -1739,6 +2209,7 @@ const Polygon = defineChain({
1739
2209
  },
1740
2210
  kitContracts: {
1741
2211
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2212
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1742
2213
  },
1743
2214
  });
1744
2215
 
@@ -1763,6 +2234,7 @@ const PolygonAmoy = defineChain({
1763
2234
  rpcEndpoints: ['https://rpc-amoy.polygon.technology'],
1764
2235
  eurcAddress: null,
1765
2236
  usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
2237
+ usdtAddress: null,
1766
2238
  cctp: {
1767
2239
  domain: 7,
1768
2240
  contracts: {
@@ -1813,6 +2285,7 @@ const Sei = defineChain({
1813
2285
  rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
1814
2286
  eurcAddress: null,
1815
2287
  usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
2288
+ usdtAddress: null,
1816
2289
  cctp: {
1817
2290
  domain: 16,
1818
2291
  contracts: {
@@ -1831,6 +2304,7 @@ const Sei = defineChain({
1831
2304
  },
1832
2305
  kitContracts: {
1833
2306
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2307
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1834
2308
  },
1835
2309
  });
1836
2310
 
@@ -1856,6 +2330,7 @@ const SeiTestnet = defineChain({
1856
2330
  rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
1857
2331
  eurcAddress: null,
1858
2332
  usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
2333
+ usdtAddress: null,
1859
2334
  cctp: {
1860
2335
  domain: 16,
1861
2336
  contracts: {
@@ -1898,6 +2373,7 @@ const Sonic = defineChain({
1898
2373
  rpcEndpoints: ['https://rpc.soniclabs.com'],
1899
2374
  eurcAddress: null,
1900
2375
  usdcAddress: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
2376
+ usdtAddress: null,
1901
2377
  cctp: {
1902
2378
  domain: 13,
1903
2379
  contracts: {
@@ -1916,6 +2392,7 @@ const Sonic = defineChain({
1916
2392
  },
1917
2393
  kitContracts: {
1918
2394
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2395
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1919
2396
  },
1920
2397
  });
1921
2398
 
@@ -1940,6 +2417,7 @@ const SonicTestnet = defineChain({
1940
2417
  rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
1941
2418
  eurcAddress: null,
1942
2419
  usdcAddress: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
2420
+ usdtAddress: null,
1943
2421
  cctp: {
1944
2422
  domain: 13,
1945
2423
  contracts: {
@@ -1981,6 +2459,7 @@ const Solana = defineChain({
1981
2459
  rpcEndpoints: ['https://api.mainnet-beta.solana.com'],
1982
2460
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
1983
2461
  usdcAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
2462
+ usdtAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
1984
2463
  cctp: {
1985
2464
  domain: 5,
1986
2465
  contracts: {
@@ -2027,6 +2506,7 @@ const SolanaDevnet = defineChain({
2027
2506
  explorerUrl: 'https://solscan.io/tx/{hash}?cluster=devnet',
2028
2507
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
2029
2508
  usdcAddress: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
2509
+ usdtAddress: null,
2030
2510
  cctp: {
2031
2511
  domain: 5,
2032
2512
  contracts: {
@@ -2075,6 +2555,7 @@ const Stellar = defineChain({
2075
2555
  rpcEndpoints: ['https://horizon.stellar.org'],
2076
2556
  eurcAddress: 'EURC-GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2',
2077
2557
  usdcAddress: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
2558
+ usdtAddress: null,
2078
2559
  cctp: null,
2079
2560
  });
2080
2561
 
@@ -2098,6 +2579,7 @@ const StellarTestnet = defineChain({
2098
2579
  rpcEndpoints: ['https://horizon-testnet.stellar.org'],
2099
2580
  eurcAddress: 'EURC-GB3Q6QDZYTHWT7E5PVS3W7FUT5GVAFC5KSZFFLPU25GO7VTC3NM2ZTVO',
2100
2581
  usdcAddress: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
2582
+ usdtAddress: null,
2101
2583
  cctp: null,
2102
2584
  });
2103
2585
 
@@ -2121,6 +2603,7 @@ const Sui = defineChain({
2121
2603
  rpcEndpoints: ['https://fullnode.mainnet.sui.io'],
2122
2604
  eurcAddress: null,
2123
2605
  usdcAddress: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
2606
+ usdtAddress: null,
2124
2607
  cctp: {
2125
2608
  domain: 8,
2126
2609
  contracts: {
@@ -2158,6 +2641,7 @@ const SuiTestnet = defineChain({
2158
2641
  rpcEndpoints: ['https://fullnode.testnet.sui.io'],
2159
2642
  eurcAddress: null,
2160
2643
  usdcAddress: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
2644
+ usdtAddress: null,
2161
2645
  cctp: {
2162
2646
  domain: 8,
2163
2647
  contracts: {
@@ -2196,6 +2680,7 @@ const Unichain = defineChain({
2196
2680
  rpcEndpoints: ['https://mainnet.unichain.org'],
2197
2681
  eurcAddress: null,
2198
2682
  usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
2683
+ usdtAddress: null,
2199
2684
  cctp: {
2200
2685
  domain: 10,
2201
2686
  contracts: {
@@ -2220,6 +2705,7 @@ const Unichain = defineChain({
2220
2705
  },
2221
2706
  kitContracts: {
2222
2707
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2708
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2223
2709
  },
2224
2710
  });
2225
2711
 
@@ -2244,6 +2730,7 @@ const UnichainSepolia = defineChain({
2244
2730
  rpcEndpoints: ['https://sepolia.unichain.org'],
2245
2731
  eurcAddress: null,
2246
2732
  usdcAddress: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
2733
+ usdtAddress: null,
2247
2734
  cctp: {
2248
2735
  domain: 10,
2249
2736
  contracts: {
@@ -2292,6 +2779,7 @@ const WorldChain = defineChain({
2292
2779
  rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
2293
2780
  eurcAddress: null,
2294
2781
  usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
2782
+ usdtAddress: null,
2295
2783
  cctp: {
2296
2784
  domain: 14,
2297
2785
  contracts: {
@@ -2310,6 +2798,7 @@ const WorldChain = defineChain({
2310
2798
  },
2311
2799
  kitContracts: {
2312
2800
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2801
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2313
2802
  },
2314
2803
  });
2315
2804
 
@@ -2337,6 +2826,7 @@ const WorldChainSepolia = defineChain({
2337
2826
  ],
2338
2827
  eurcAddress: null,
2339
2828
  usdcAddress: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
2829
+ usdtAddress: null,
2340
2830
  cctp: {
2341
2831
  domain: 14,
2342
2832
  contracts: {
@@ -2381,6 +2871,7 @@ const XDC = defineChain({
2381
2871
  rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
2382
2872
  eurcAddress: null,
2383
2873
  usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
2874
+ usdtAddress: null,
2384
2875
  cctp: {
2385
2876
  domain: 18,
2386
2877
  contracts: {
@@ -2399,6 +2890,7 @@ const XDC = defineChain({
2399
2890
  },
2400
2891
  kitContracts: {
2401
2892
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2893
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2402
2894
  },
2403
2895
  });
2404
2896
 
@@ -2423,6 +2915,7 @@ const XDCApothem = defineChain({
2423
2915
  rpcEndpoints: ['https://erpc.apothem.network'],
2424
2916
  eurcAddress: null,
2425
2917
  usdcAddress: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
2918
+ usdtAddress: null,
2426
2919
  cctp: {
2427
2920
  domain: 18,
2428
2921
  contracts: {
@@ -2465,6 +2958,7 @@ const ZKSyncEra = defineChain({
2465
2958
  rpcEndpoints: ['https://mainnet.era.zksync.io'],
2466
2959
  eurcAddress: null,
2467
2960
  usdcAddress: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
2961
+ usdtAddress: null,
2468
2962
  cctp: null,
2469
2963
  });
2470
2964
 
@@ -2489,6 +2983,7 @@ const ZKSyncEraSepolia = defineChain({
2489
2983
  rpcEndpoints: ['https://sepolia.era.zksync.dev'],
2490
2984
  eurcAddress: null,
2491
2985
  usdcAddress: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
2986
+ usdtAddress: null,
2492
2987
  cctp: null,
2493
2988
  });
2494
2989
 
@@ -2509,6 +3004,8 @@ var Chains = {
2509
3004
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
2510
3005
  Codex: Codex,
2511
3006
  CodexTestnet: CodexTestnet,
3007
+ Edge: Edge,
3008
+ EdgeTestnet: EdgeTestnet,
2512
3009
  Ethereum: Ethereum,
2513
3010
  EthereumSepolia: EthereumSepolia,
2514
3011
  Hedera: Hedera,
@@ -2521,6 +3018,8 @@ var Chains = {
2521
3018
  LineaSepolia: LineaSepolia,
2522
3019
  Monad: Monad,
2523
3020
  MonadTestnet: MonadTestnet,
3021
+ Morph: Morph,
3022
+ MorphTestnet: MorphTestnet,
2524
3023
  NEAR: NEAR,
2525
3024
  NEARTestnet: NEARTestnet,
2526
3025
  Noble: Noble,
@@ -2656,10 +3155,12 @@ const baseChainDefinitionSchema = zod.z.object({
2656
3155
  rpcEndpoints: zod.z.array(zod.z.string()),
2657
3156
  eurcAddress: zod.z.string().nullable(),
2658
3157
  usdcAddress: zod.z.string().nullable(),
3158
+ usdtAddress: zod.z.string().nullable(),
2659
3159
  cctp: zod.z.any().nullable(), // We'll accept any CCTP config structure
2660
3160
  kitContracts: zod.z
2661
3161
  .object({
2662
3162
  bridge: zod.z.string().optional(),
3163
+ adapter: zod.z.string().optional(),
2663
3164
  })
2664
3165
  .optional(),
2665
3166
  });
@@ -2774,6 +3275,29 @@ zod.z.union([
2774
3275
  zod.z.nativeEnum(Blockchain),
2775
3276
  chainDefinitionSchema$2,
2776
3277
  ]);
3278
+ /**
3279
+ * Zod schema for validating swap-specific chain identifiers.
3280
+ *
3281
+ * Validates chains based on:
3282
+ * - CCTPv2 support (adapter contract deployed)
3283
+ * - Mainnet only (no testnets)
3284
+ * - At least one supported token available
3285
+ *
3286
+ */
3287
+ zod.z.union([
3288
+ // String blockchain identifier (accepts SwapChain enum values)
3289
+ zod.z.string().refine((val) => val in SwapChain, (val) => ({
3290
+ message: `"${val}" is not a supported swap chain. ` +
3291
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
3292
+ })),
3293
+ // SwapChain enum
3294
+ zod.z.nativeEnum(SwapChain),
3295
+ // ChainDefinition object (checks if chain.chain is in SwapChain)
3296
+ chainDefinitionSchema$2.refine((chain) => chain.chain in SwapChain, (chain) => ({
3297
+ message: `"${chain.chain}" is not a supported swap chain. ` +
3298
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
3299
+ })),
3300
+ ]);
2777
3301
  /**
2778
3302
  * Zod schema for validating bridge chain identifiers.
2779
3303
  *
@@ -2813,6 +3337,38 @@ zod.z.union([
2813
3337
  })),
2814
3338
  ]);
2815
3339
 
3340
+ /**
3341
+ * @packageDocumentation
3342
+ * @module SwapTokenSchemas
3343
+ *
3344
+ * Zod validation schemas for supported swap tokens.
3345
+ */
3346
+ // Internal enum used after input normalization.
3347
+ const swapTokenEnumSchema = zod.z.enum([
3348
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
3349
+ NATIVE_TOKEN,
3350
+ ]);
3351
+ /**
3352
+ * Zod schema for validating supported swap token symbols.
3353
+ *
3354
+ * Accepts any token symbol from the SWAP_TOKEN_REGISTRY plus NATIVE.
3355
+ * Input matching is case-insensitive and normalized to uppercase.
3356
+ *
3357
+ * @example
3358
+ * ```typescript
3359
+ * import { supportedSwapTokenSchema } from '@core/chains'
3360
+ *
3361
+ * const result = supportedSwapTokenSchema.safeParse('USDC')
3362
+ * if (result.success) {
3363
+ * console.log('Valid swap token:', result.data)
3364
+ * }
3365
+ * ```
3366
+ */
3367
+ zod.z
3368
+ .string()
3369
+ .transform((value) => value.toUpperCase())
3370
+ .pipe(swapTokenEnumSchema);
3371
+
2816
3372
  /**
2817
3373
  * Retrieve a chain definition by its blockchain enum value.
2818
3374
  *
@@ -2867,6 +3423,11 @@ const getChainByEnum = (blockchain) => {
2867
3423
  * ```
2868
3424
  */
2869
3425
  function resolveChainIdentifier(chainIdentifier) {
3426
+ // Handle null explicitly (typeof null === 'object' in JavaScript)
3427
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
3428
+ if (chainIdentifier === null) {
3429
+ throw new Error(`Invalid chain identifier type: null. Expected ChainDefinition object, Blockchain enum, or string literal.`);
3430
+ }
2870
3431
  // If it's already a ChainDefinition object, return it unchanged
2871
3432
  if (typeof chainIdentifier === 'object') {
2872
3433
  return chainIdentifier;
@@ -3158,11 +3719,11 @@ const formatComponent = (nameWithVersion) => {
3158
3719
  const createRequestContext = () => {
3159
3720
  const context = {};
3160
3721
  if (typeof globalThis !== 'undefined') {
3161
- if (globalThis.__STABLECOIN_KITS_EXTERNAL_PREFIX__ !== undefined) {
3162
- context.externalPrefix = globalThis.__STABLECOIN_KITS_EXTERNAL_PREFIX__;
3722
+ if (globalThis.__APP_KITS_EXTERNAL_PREFIX__ !== undefined) {
3723
+ context.externalPrefix = globalThis.__APP_KITS_EXTERNAL_PREFIX__;
3163
3724
  }
3164
- if (globalThis.__STABLECOIN_KITS_CURRENT_KIT__ !== undefined) {
3165
- context.kit = globalThis.__STABLECOIN_KITS_CURRENT_KIT__;
3725
+ if (globalThis.__APP_KITS_CURRENT_KIT__ !== undefined) {
3726
+ context.kit = globalThis.__APP_KITS_CURRENT_KIT__;
3166
3727
  }
3167
3728
  }
3168
3729
  return context;
@@ -3268,6 +3829,10 @@ const ERROR_TYPES = {
3268
3829
  RPC: 'RPC',
3269
3830
  /** Internet connectivity, DNS resolution, connection issues */
3270
3831
  NETWORK: 'NETWORK',
3832
+ /** API throttling, request frequency limits errors */
3833
+ RATE_LIMIT: 'RATE_LIMIT',
3834
+ /** Service errors */
3835
+ SERVICE: 'SERVICE',
3271
3836
  /** Catch-all for unrecognized errors (code 0) */
3272
3837
  UNKNOWN: 'UNKNOWN',
3273
3838
  };
@@ -3291,6 +3856,8 @@ const ERROR_CODE_RANGES = [
3291
3856
  { min: 3000, max: 3999, type: 'NETWORK' },
3292
3857
  { min: 4000, max: 4999, type: 'RPC' },
3293
3858
  { min: 5000, max: 5999, type: 'ONCHAIN' },
3859
+ { min: 7000, max: 7999, type: 'RATE_LIMIT' },
3860
+ { min: 8000, max: 8999, type: 'SERVICE' },
3294
3861
  { min: 9000, max: 9999, type: 'BALANCE' },
3295
3862
  ];
3296
3863
  /** Special code for UNKNOWN errors */
@@ -3338,6 +3905,8 @@ const errorDetailsSchema = zod.z.object({
3338
3905
  * - 3000-3999: NETWORK errors - Connectivity issues
3339
3906
  * - 4000-4999: RPC errors - Provider issues, gas estimation
3340
3907
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures
3908
+ * - 7000-7999: RATE_LIMIT errors - API throttling
3909
+ * - 8000-8999: SERVICE errors - Internal service errors
3341
3910
  * - 9000-9999: BALANCE errors - Insufficient funds
3342
3911
  */
3343
3912
  code: zod.z
@@ -3446,11 +4015,11 @@ const AMOUNT_FORMAT_ERROR_MESSAGE = 'Amount must be a numeric string with dot (.
3446
4015
  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';
3447
4016
 
3448
4017
  /**
3449
- * Structured error class for Stablecoin Kit operations.
4018
+ * Structured error class for App Kit operations.
3450
4019
  *
3451
4020
  * This class extends the native Error class while implementing the ErrorDetails
3452
4021
  * interface, providing a consistent error format for programmatic handling
3453
- * across the Stablecoin Kits ecosystem. All properties are immutable to ensure
4022
+ * across the App Kits ecosystem. All properties are immutable to ensure
3454
4023
  * error objects cannot be modified after creation.
3455
4024
  *
3456
4025
  * @example
@@ -3460,6 +4029,7 @@ const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (
3460
4029
  * const error = new KitError({
3461
4030
  * code: 1001,
3462
4031
  * name: 'INPUT_NETWORK_MISMATCH',
4032
+ * type: 'INPUT',
3463
4033
  * recoverability: 'FATAL',
3464
4034
  * message: 'Cannot bridge between mainnet and testnet'
3465
4035
  * })
@@ -3477,7 +4047,8 @@ const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (
3477
4047
  * // Error with cause information
3478
4048
  * const error = new KitError({
3479
4049
  * code: 1002,
3480
- * name: 'INVALID_AMOUNT',
4050
+ * name: 'INPUT_INVALID_AMOUNT',
4051
+ * type: 'INPUT',
3481
4052
  * recoverability: 'FATAL',
3482
4053
  * message: 'Amount must be greater than zero',
3483
4054
  * cause: {
@@ -3561,6 +4132,8 @@ class KitError extends Error {
3561
4132
  * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
3562
4133
  * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
3563
4134
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
4135
+ * - 7000-7999: RATE_LIMIT errors - API throttling, request frequency limits
4136
+ * - 8000-8999: SERVICE errors - Internal service errors, server failures
3564
4137
  * - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
3565
4138
  */
3566
4139
  /**
@@ -3621,10 +4194,22 @@ const InputError = {
3621
4194
  name: 'INPUT_INVALID_CHAIN',
3622
4195
  type: 'INPUT',
3623
4196
  },
3624
- /** Invalid or unknown token (symbol not found, missing decimals, etc.) */
3625
- INVALID_TOKEN: {
4197
+ /** Unsupported token for chain */
4198
+ UNSUPPORTED_TOKEN: {
3626
4199
  code: 1006,
3627
- name: 'INPUT_INVALID_TOKEN',
4200
+ name: 'INPUT_UNSUPPORTED_TOKEN',
4201
+ type: 'INPUT',
4202
+ },
4203
+ /** Insufficient swap amount for the token pair */
4204
+ INSUFFICIENT_SWAP_AMOUNT: {
4205
+ code: 1007,
4206
+ name: 'INPUT_INSUFFICIENT_SWAP_AMOUNT',
4207
+ type: 'INPUT',
4208
+ },
4209
+ /** Action not supported by this adapter / ecosystem */
4210
+ UNSUPPORTED_ACTION: {
4211
+ code: 1008,
4212
+ name: 'INPUT_UNSUPPORTED_ACTION',
3628
4213
  type: 'INPUT',
3629
4214
  },
3630
4215
  /** General validation failure for complex validation rules */
@@ -3633,6 +4218,12 @@ const InputError = {
3633
4218
  name: 'INPUT_VALIDATION_FAILED',
3634
4219
  type: 'INPUT',
3635
4220
  },
4221
+ /** User cancelled wallet interaction (signature, transaction, connection) */
4222
+ USER_CANCELLED: {
4223
+ code: 1099,
4224
+ name: 'INPUT_USER_CANCELLED',
4225
+ type: 'INPUT',
4226
+ },
3636
4227
  };
3637
4228
  /**
3638
4229
  * Standardized error definitions for BALANCE type errors.
@@ -3720,8 +4311,7 @@ const NetworkError = {
3720
4311
  code: 3004,
3721
4312
  name: 'NETWORK_RELAYER_PENDING',
3722
4313
  type: 'NETWORK',
3723
- },
3724
- };
4314
+ }};
3725
4315
 
3726
4316
  /**
3727
4317
  * Creates error for network type mismatch between source and destination.
@@ -3808,7 +4398,7 @@ function createInvalidChainError(chain, reason) {
3808
4398
  const errorDetails = {
3809
4399
  ...InputError.INVALID_CHAIN,
3810
4400
  recoverability: 'FATAL',
3811
- message: `Invalid chain '${chain}': ${reason}`,
4401
+ message: `Invalid chain '${chain}': ${reason}.`,
3812
4402
  cause: {
3813
4403
  trace: { chain, reason },
3814
4404
  },
@@ -4157,6 +4747,9 @@ function getErrorMessage(error) {
4157
4747
  if (typeof error === 'string') {
4158
4748
  return error;
4159
4749
  }
4750
+ if (typeof error === 'object' && error !== null && 'message' in error) {
4751
+ return String(error.message);
4752
+ }
4160
4753
  return 'An unknown error occurred';
4161
4754
  }
4162
4755
  /**
@@ -4213,6 +4806,9 @@ function extractErrorInfo(error) {
4213
4806
  if (typeof err.code === 'number') {
4214
4807
  info.code = err.code;
4215
4808
  }
4809
+ if (isKitError(error)) {
4810
+ info.type = error.type;
4811
+ }
4216
4812
  return info;
4217
4813
  }
4218
4814
 
@@ -4335,7 +4931,17 @@ const makeApiRequest = async (url, method, isValidType, config, body) => {
4335
4931
  const response = await fetch(url, requestInit);
4336
4932
  clearTimeout(timeoutId);
4337
4933
  if (!response.ok) {
4338
- throw new Error(`HTTP ${String(response.status)} - ${response.statusText}`);
4934
+ let responseBody;
4935
+ try {
4936
+ responseBody = (await response.json());
4937
+ }
4938
+ catch {
4939
+ // Parse response body for diagnostics; continue even if malformed
4940
+ // so callers always receive HTTP status context
4941
+ }
4942
+ const httpError = new Error(`HTTP ${String(response.status)} - ${response.statusText}`);
4943
+ httpError.responseBody = responseBody;
4944
+ throw httpError;
4339
4945
  }
4340
4946
  const parsedJson = (await response.json());
4341
4947
  if (!isValidType(parsedJson)) {
@@ -4907,7 +5513,7 @@ const convertAddress = (address, targetFormat) => {
4907
5513
  *
4908
5514
  * This function normalizes token values from their blockchain representation (where
4909
5515
  * everything is stored as integers in the smallest denomination) to human-readable
4910
- * decimal format. Uses the battle-tested implementation from @ethersproject/units.
5516
+ * decimal format. Uses the battle-tested implementation from \@ethersproject/units.
4911
5517
  *
4912
5518
  * @param value - The value in smallest units (e.g., "1000000" for 1 USDC with 6 decimals)
4913
5519
  * @param decimals - The number of decimal places for the unit conversion
@@ -4936,357 +5542,1082 @@ const formatUnits = (value, decimals) => {
4936
5542
  };
4937
5543
 
4938
5544
  /**
4939
- * Build a complete explorer URL from a chain definition and transaction hash.
5545
+ * Create a structured error for token resolution failures.
4940
5546
  *
4941
- * This function takes a chain definition containing an explorer URL template
4942
- * and replaces the `{hash}` placeholder with the provided transaction hash
4943
- * to create a complete, valid explorer URL.
5547
+ * @remarks
5548
+ * Returns a `KitError` with `INPUT_UNSUPPORTED_TOKEN` code (1006).
5549
+ * The error trace contains the selector and chain context for debugging.
4944
5550
  *
4945
- * @param chainDef - The chain definition containing the explorer URL template.
4946
- * @param txHash - The transaction hash to insert into the URL template.
4947
- * @returns A complete explorer URL with the transaction hash inserted.
4948
- * @throws Error if the chain definition is null/undefined.
4949
- * @throws Error if the transaction hash is null/undefined/empty.
4950
- * @throws Error if the explorer URL template is missing or empty.
4951
- * @throws Error if the explorer URL template doesn't contain the {hash} placeholder.
4952
- * @throws Error if the resulting URL is invalid.
5551
+ * @param message - Human-readable error description.
5552
+ * @param selector - The token selector that failed to resolve.
5553
+ * @param chainId - The chain being resolved for (optional).
5554
+ * @param cause - The underlying error, if any (optional).
5555
+ * @returns A KitError with INPUT type and FATAL recoverability.
4953
5556
  *
4954
5557
  * @example
4955
5558
  * ```typescript
4956
- * import { buildExplorerUrl } from '@core/utils'
4957
- * import { Ethereum } from '@core/chains'
4958
- *
4959
- * // Build URL for Ethereum transaction
4960
- * const url = buildExplorerUrl(Ethereum, '0x1234567890abcdef...')
4961
- * console.log(url) // 'https://etherscan.io/tx/0x1234567890abcdef...'
5559
+ * throw createTokenResolutionError(
5560
+ * 'Unknown token symbol: FAKE',
5561
+ * 'FAKE',
5562
+ * 'Ethereum'
5563
+ * )
4962
5564
  * ```
5565
+ */
5566
+ function createTokenResolutionError(message, selector, chainId, cause) {
5567
+ const trace = {
5568
+ selector,
5569
+ ...(chainId === undefined ? {} : { chainId }),
5570
+ ...({} ),
5571
+ };
5572
+ return new KitError({
5573
+ ...InputError.UNSUPPORTED_TOKEN,
5574
+ recoverability: 'FATAL',
5575
+ message,
5576
+ cause: { trace },
5577
+ });
5578
+ }
5579
+
5580
+ /**
5581
+ * USDC token definition with addresses and metadata.
4963
5582
  *
4964
- * @example
4965
- * ```typescript
4966
- * import { buildExplorerUrl } from '@core/utils'
4967
- * import { Solana } from '@core/chains'
5583
+ * @remarks
5584
+ * This is the built-in USDC definition used by the TokenRegistry.
5585
+ * Includes all known USDC addresses across supported chains.
4968
5586
  *
4969
- * // Build URL for Solana transaction
4970
- * const url = buildExplorerUrl(Solana, 'abc123def456...')
4971
- * console.log(url) // 'https://solscan.io/tx/abc123def456...'
4972
- * ```
5587
+ * Keys use the `Blockchain` enum for type safety. Both enum values
5588
+ * and string literals are supported:
5589
+ * - `Blockchain.Ethereum` or `'Ethereum'`
4973
5590
  *
4974
5591
  * @example
4975
5592
  * ```typescript
4976
- * import { buildExplorerUrl } from '@core/utils'
4977
- * import { Aptos } from '@core/chains'
5593
+ * import { USDC } from '@core/tokens'
5594
+ * import { Blockchain } from '@core/chains'
4978
5595
  *
4979
- * // Build URL for Aptos transaction with query parameters
4980
- * const url = buildExplorerUrl(Aptos, '0xabc123...')
4981
- * console.log(url) // 'https://explorer.aptoslabs.com/txn/0xabc123...?network=mainnet'
5596
+ * console.log(USDC.symbol) // 'USDC'
5597
+ * console.log(USDC.decimals) // 6
5598
+ * console.log(USDC.locators[Blockchain.Ethereum])
5599
+ * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
4982
5600
  * ```
4983
5601
  */
4984
- function buildExplorerUrl(chainDef, txHash) {
4985
- // Validate input parameters using our standard validation pattern
4986
- validateOrThrow({ chainDef, txHash }, buildExplorerUrlParamsSchema, 'Invalid buildExplorerUrl parameters');
4987
- // Parse and transform input parameters (e.g., trim whitespace from txHash)
4988
- const { chainDef: validChainDef, txHash: validTxHash } = buildExplorerUrlParamsSchema.parse({ chainDef, txHash });
4989
- // Replace all occurrences of the placeholder with the transaction hash
4990
- const explorerUrl = validChainDef.explorerUrl.replace(/{hash}/g, validTxHash);
4991
- // Validate the resulting URL
4992
- validate(explorerUrl, explorerUrlSchema, 'explorer URL');
4993
- return explorerUrl;
4994
- }
5602
+ const USDC = {
5603
+ symbol: 'USDC',
5604
+ decimals: 6,
5605
+ locators: {
5606
+ // =========================================================================
5607
+ // Mainnets (alphabetically sorted)
5608
+ // =========================================================================
5609
+ [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
5610
+ [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
5611
+ [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
5612
+ [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
5613
+ [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
5614
+ [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
5615
+ [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
5616
+ [Blockchain.Hedera]: '0.0.456858',
5617
+ [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
5618
+ [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
5619
+ [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
5620
+ [Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
5621
+ [Blockchain.Morph]: '0xCfb1186F4e93D60E60a8bDd997427D1F33bc372B',
5622
+ [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
5623
+ [Blockchain.Noble]: 'uusdc',
5624
+ [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
5625
+ [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
5626
+ [Blockchain.Polkadot_Asset_Hub]: '1337',
5627
+ [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
5628
+ [Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
5629
+ [Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
5630
+ [Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
5631
+ [Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
5632
+ [Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
5633
+ [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
5634
+ [Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
5635
+ [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
5636
+ [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
5637
+ // =========================================================================
5638
+ // Testnets (alphabetically sorted)
5639
+ // =========================================================================
5640
+ [Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
5641
+ [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
5642
+ [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
5643
+ [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
5644
+ [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
5645
+ [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
5646
+ [Blockchain.Hedera_Testnet]: '0.0.429274',
5647
+ [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
5648
+ [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
5649
+ [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
5650
+ [Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
5651
+ [Blockchain.Morph_Testnet]: '0x7433b41C6c5e1d58D4Da99483609520255ab661B',
5652
+ [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
5653
+ [Blockchain.Noble_Testnet]: 'uusdc',
5654
+ [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
5655
+ [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
5656
+ [Blockchain.Polkadot_Westmint]: '31337',
5657
+ [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
5658
+ [Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
5659
+ [Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
5660
+ [Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
5661
+ [Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
5662
+ [Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
5663
+ [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
5664
+ [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
5665
+ [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
5666
+ [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
5667
+ },
5668
+ };
4995
5669
 
4996
5670
  /**
4997
- * CCTP forwarding magic bytes prefix.
5671
+ * USDT (Tether) token definition with addresses and metadata.
4998
5672
  *
4999
- * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
5000
- * This prefix is right-padded to 24 bytes in the final hookData.
5673
+ * @remarks
5674
+ * Built-in USDT definition for the TokenRegistry. Includes chain locators
5675
+ * for swap-supported chains.
5001
5676
  */
5002
- const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
5677
+ const USDT = {
5678
+ symbol: 'USDT',
5679
+ decimals: 6,
5680
+ locators: {
5681
+ [Blockchain.Aptos]: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
5682
+ [Blockchain.Arbitrum]: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
5683
+ [Blockchain.Avalanche]: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7',
5684
+ [Blockchain.Celo]: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
5685
+ [Blockchain.Ethereum]: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
5686
+ [Blockchain.HyperEVM]: '0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb',
5687
+ [Blockchain.Ink]: '0x0200C29006150606B650577BBE7B6248F58470c1',
5688
+ [Blockchain.Linea]: '0xA219439258ca9da29E9Cc4cE5596924745e12B93',
5689
+ [Blockchain.Monad]: '0xe7cd86e13AC4309349F30B3435a9d337750fC82D',
5690
+ [Blockchain.NEAR]: 'usdt.tether-token.near',
5691
+ [Blockchain.Optimism]: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58',
5692
+ [Blockchain.Polkadot_Asset_Hub]: '1984',
5693
+ [Blockchain.Polygon]: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
5694
+ [Blockchain.Sei]: '0x9151434b16b9763660705744891fA906F660EcC5',
5695
+ [Blockchain.Solana]: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
5696
+ [Blockchain.Unichain]: '0x9151434b16b9763660705744891fA906F660EcC5',
5697
+ },
5698
+ };
5699
+
5003
5700
  /**
5004
- * CCTP forwarding version number.
5701
+ * EURC (Euro Coin) token definition with addresses and metadata.
5005
5702
  *
5006
- * Version 0 is used for basic forwarding requests.
5703
+ * @remarks
5704
+ * Built-in EURC definition for the TokenRegistry. Includes chain locators
5705
+ * for swap-supported chains.
5007
5706
  */
5008
- const CCTP_FORWARD_VERSION = 0;
5707
+ const EURC = {
5708
+ symbol: 'EURC',
5709
+ decimals: 6,
5710
+ locators: {
5711
+ [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
5712
+ [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
5713
+ [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
5714
+ [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
5715
+ [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
5716
+ },
5717
+ };
5718
+
5009
5719
  /**
5010
- * Length of Circle-reserved payload for basic forwarding (0 bytes).
5720
+ * DAI (Maker DAO) stablecoin token definition with addresses and metadata.
5011
5721
  *
5012
- * Set to 0 when no additional Circle-reserved data is needed.
5722
+ * @remarks
5723
+ * Built-in DAI definition for the TokenRegistry. Includes chain locators
5724
+ * for swap-supported chains.
5013
5725
  */
5014
- const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
5726
+ const DAI = {
5727
+ symbol: 'DAI',
5728
+ decimals: 18,
5729
+ chainDecimals: {
5730
+ [Blockchain.Solana]: 8,
5731
+ },
5732
+ locators: {
5733
+ [Blockchain.Arbitrum]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5734
+ [Blockchain.Avalanche]: '0xd586E7F844cEa2F87f50152665BCbc2C279D8d70',
5735
+ [Blockchain.Base]: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
5736
+ [Blockchain.Ethereum]: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
5737
+ [Blockchain.Linea]: '0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5',
5738
+ [Blockchain.Optimism]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5739
+ [Blockchain.Polygon]: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
5740
+ [Blockchain.Solana]: 'EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o',
5741
+ },
5742
+ };
5743
+
5015
5744
  /**
5016
- * Build the hookData bytes for CCTP forwarding.
5017
- *
5018
- * Constructs a 32-byte hookData payload that signals to Circle's Orbit relayer
5019
- * that the user wants automated attestation fetching and destination mint execution.
5020
- *
5021
- * The hookData format is:
5022
- * - Bytes 0-23: ASCII "cctp-forward" right-padded to 24 bytes with zeros
5023
- * - Bytes 24-27: uint32 version (big-endian) - currently 0
5024
- * - Bytes 28-31: uint32 length (big-endian) - 0 for basic forwarding
5025
- *
5026
- * @returns A 0x-prefixed hex string representing the 32-byte hookData
5027
- *
5028
- * @example
5029
- * ```typescript
5030
- * import { buildForwardingHookData } from '@core/utils'
5745
+ * USDe (Ethena) synthetic dollar token definition with addresses and metadata.
5031
5746
  *
5032
- * const hookData = buildForwardingHookData()
5033
- * // Returns: '0x636374702d666f72776172640000000000000000000000000000000000000000'
5747
+ * @remarks
5748
+ * Built-in USDE definition for the TokenRegistry. Includes chain locators
5749
+ * for swap-supported chains.
5750
+ */
5751
+ const USDE = {
5752
+ symbol: 'USDe',
5753
+ decimals: 18,
5754
+ chainDecimals: {
5755
+ [Blockchain.Solana]: 9,
5756
+ },
5757
+ locators: {
5758
+ [Blockchain.Arbitrum]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5759
+ [Blockchain.Base]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5760
+ [Blockchain.Ethereum]: '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3',
5761
+ [Blockchain.Linea]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5762
+ [Blockchain.Optimism]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5763
+ [Blockchain.Solana]: 'DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT',
5764
+ },
5765
+ };
5766
+
5767
+ /**
5768
+ * PYUSD (PayPal USD) stablecoin token definition with addresses and metadata.
5034
5769
  *
5035
- * // Use with depositForBurnWithHook action
5036
- * await adapter.action('cctp.v2.depositForBurnWithHook', {
5037
- * amount: BigInt('1000000'),
5038
- * mintRecipient: '0x...',
5039
- * maxFee: BigInt('50000'),
5040
- * minFinalityThreshold: 1000,
5041
- * fromChain: ethereum,
5042
- * toChain: base,
5043
- * hookData
5044
- * })
5045
- * ```
5770
+ * @remarks
5771
+ * Built-in PYUSD definition for the TokenRegistry. Includes chain locators
5772
+ * for swap-supported chains.
5046
5773
  */
5047
- // Cached result for memoization (computed once on first call)
5048
- let cachedHookDataHex = null;
5049
- function buildForwardingHookData() {
5050
- // Return cached result if already computed
5051
- if (cachedHookDataHex !== null) {
5052
- return cachedHookDataHex;
5053
- }
5054
- // Create a 32-byte buffer
5055
- const buffer = new Uint8Array(32);
5056
- // Write the magic prefix ("cctp-forward") - 12 bytes, rest of 24-byte section is zero-padded
5057
- const encoder = new TextEncoder();
5058
- const magicBytes = encoder.encode(CCTP_FORWARD_MAGIC_PREFIX);
5059
- buffer.set(magicBytes, 0);
5060
- // Bytes 12-23 are already zero (right-padding)
5061
- // Write uint32 version at bytes 24-27 (big-endian)
5062
- const versionView = new DataView(buffer.buffer);
5063
- versionView.setUint32(24, CCTP_FORWARD_VERSION, false); // false = big-endian
5064
- // Write uint32 length at bytes 28-31 (big-endian)
5065
- versionView.setUint32(28, CCTP_FORWARD_PAYLOAD_LENGTH, false); // false = big-endian
5066
- // Convert to hex string with 0x prefix and cache
5067
- cachedHookDataHex =
5068
- '0x' +
5069
- Array.from(buffer)
5070
- .map((b) => b.toString(16).padStart(2, '0'))
5071
- .join('');
5072
- return cachedHookDataHex;
5073
- }
5774
+ const PYUSD = {
5775
+ symbol: 'PYUSD',
5776
+ decimals: 6,
5777
+ locators: {
5778
+ [Blockchain.Arbitrum]: '0x46850aD61C2B7d64d08c9C754F45254596696984',
5779
+ [Blockchain.Ethereum]: '0x6c3ea9036406852006290770BEdFcAbA0e23A0e8',
5780
+ [Blockchain.Solana]: '2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo',
5781
+ },
5782
+ };
5074
5783
 
5075
5784
  /**
5076
- * Transfer speed options for cross-chain operations.
5785
+ * WETH (Wrapped Ether) token definition with addresses and metadata.
5077
5786
  *
5078
- * Defines the available speed modes for CCTPv2 transfers, affecting
5079
- * both transfer time and potential fee implications.
5787
+ * @remarks
5788
+ * Built-in WETH definition for the TokenRegistry. Includes chain locators
5789
+ * for swap-supported chains where WETH is available.
5080
5790
  */
5081
- var TransferSpeed;
5082
- (function (TransferSpeed) {
5083
- /** Fast burn mode - reduces transfer time but may have different fee implications */
5084
- TransferSpeed["FAST"] = "FAST";
5085
- /** Standard burn mode - normal transfer time with standard fees */
5086
- TransferSpeed["SLOW"] = "SLOW";
5087
- })(TransferSpeed || (TransferSpeed = {}));
5791
+ const WETH = {
5792
+ symbol: 'WETH',
5793
+ decimals: 18,
5794
+ chainDecimals: {
5795
+ [Blockchain.Solana]: 9,
5796
+ },
5797
+ locators: {
5798
+ [Blockchain.Arbitrum]: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
5799
+ [Blockchain.Avalanche]: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB',
5800
+ [Blockchain.Base]: '0x4200000000000000000000000000000000000006',
5801
+ [Blockchain.Ethereum]: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
5802
+ [Blockchain.Optimism]: '0x4200000000000000000000000000000000000006',
5803
+ [Blockchain.Polygon]: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
5804
+ [Blockchain.Solana]: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs',
5805
+ },
5806
+ };
5088
5807
 
5089
5808
  /**
5090
- * Factory to validate a numeric string with strict dot-decimal notation.
5091
- * Only accepts dot (.) as the decimal separator. Thousand separators are not allowed.
5809
+ * WBTC (Wrapped Bitcoin) token definition with addresses and metadata.
5092
5810
  *
5093
- * This enforces an unambiguous format for SDK inputs. Internationalization concerns
5094
- * (comma vs dot decimal separators) should be handled in the UI layer before passing
5095
- * values to the SDK.
5811
+ * @remarks
5812
+ * Built-in WBTC definition for the TokenRegistry. Includes chain locators
5813
+ * for swap-supported chains where WBTC is available.
5814
+ */
5815
+ const WBTC = {
5816
+ symbol: 'WBTC',
5817
+ decimals: 8,
5818
+ locators: {
5819
+ [Blockchain.Ethereum]: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
5820
+ [Blockchain.Arbitrum]: '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f',
5821
+ [Blockchain.Avalanche]: '0x50b7545627a5162F82A992c33b87aDc75187B218',
5822
+ },
5823
+ };
5824
+
5825
+ /**
5826
+ * WSOL (Wrapped SOL) token definition with addresses and metadata.
5096
5827
  *
5097
- * Accepts the following formats:
5098
- * - Whole numbers: "1", "100", "1000"
5099
- * - Leading zero decimals: "0.1", "0.5", "0.001"
5100
- * - Shorthand decimals: ".1", ".5", ".001"
5101
- * - Standard decimals: "1.23", "100.50"
5828
+ * @remarks
5829
+ * Built-in WSOL definition for the TokenRegistry. Includes chain locators
5830
+ * for swap-supported chains where WSOL is available.
5831
+ */
5832
+ const WSOL = {
5833
+ symbol: 'WSOL',
5834
+ decimals: 9,
5835
+ locators: {
5836
+ [Blockchain.Solana]: 'So11111111111111111111111111111111111111112',
5837
+ },
5838
+ };
5839
+
5840
+ /**
5841
+ * WAVAX (Wrapped AVAX) token definition with addresses and metadata.
5102
5842
  *
5103
- * Does NOT accept:
5104
- * - Comma decimal separator: "1,5" (use "1.5" instead)
5105
- * - Thousand separators: "1,000.50" or "1.000,50" (use "1000.50" instead)
5106
- * - Multiple decimal points: "1.2.3"
5107
- * - Negative numbers: "-100"
5108
- * - Non-numeric characters: "abc", "100a"
5843
+ * @remarks
5844
+ * Built-in WAVAX definition for the TokenRegistry. Includes chain locators
5845
+ * for swap-supported chains where WAVAX is available.
5846
+ */
5847
+ const WAVAX = {
5848
+ symbol: 'WAVAX',
5849
+ decimals: 18,
5850
+ locators: {
5851
+ [Blockchain.Avalanche]: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7',
5852
+ },
5853
+ };
5854
+
5855
+ /**
5856
+ * WPOL (Wrapped POL) token definition with addresses and metadata.
5109
5857
  *
5110
- * Behavior differences controlled by options:
5111
- * - allowZero: when false, value must be strictly greater than 0; when true, non-negative.
5112
- * - regexMessage: error message when the basic numeric format fails.
5113
- * - maxDecimals: maximum number of decimal places allowed (e.g., 6 for USDC).
5858
+ * @remarks
5859
+ * Built-in WPOL definition for the TokenRegistry. Includes chain locators
5860
+ * for swap-supported chains where WPOL is available.
5114
5861
  */
5115
- const createDecimalStringValidator = (options) => (schema) => {
5116
- // Capitalize first letter of attribute name for error messages
5117
- const capitalizedAttributeName = options.attributeName.charAt(0).toUpperCase() +
5118
- options.attributeName.slice(1);
5119
- return schema
5120
- .regex(/^-?(?:\d+(?:\.\d+)?|\.\d+)$/, options.regexMessage)
5121
- .superRefine((val, ctx) => {
5122
- const amount = Number.parseFloat(val);
5123
- if (Number.isNaN(amount)) {
5124
- ctx.addIssue({
5125
- code: zod.z.ZodIssueCode.custom,
5126
- message: options.regexMessage,
5127
- });
5128
- return;
5129
- }
5130
- // Check decimal precision if maxDecimals is specified
5131
- if (options.maxDecimals !== undefined) {
5132
- const decimalPart = val.split('.')[1];
5133
- if (decimalPart && decimalPart.length > options.maxDecimals) {
5134
- ctx.addIssue({
5135
- code: zod.z.ZodIssueCode.custom,
5136
- message: `Maximum supported decimal places: ${options.maxDecimals.toString()}`,
5137
- });
5138
- return;
5139
- }
5140
- }
5141
- if (options.allowZero && amount < 0) {
5142
- ctx.addIssue({
5143
- code: zod.z.ZodIssueCode.custom,
5144
- message: `${capitalizedAttributeName} must be non-negative`,
5145
- });
5146
- }
5147
- else if (!options.allowZero && amount <= 0) {
5148
- ctx.addIssue({
5149
- code: zod.z.ZodIssueCode.custom,
5150
- message: `${capitalizedAttributeName} must be greater than 0`,
5151
- });
5152
- }
5153
- });
5862
+ const WPOL = {
5863
+ symbol: 'WPOL',
5864
+ decimals: 18,
5865
+ locators: {
5866
+ [Blockchain.Polygon]: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
5867
+ },
5154
5868
  };
5869
+
5155
5870
  /**
5156
- * Schema for validating chain definitions.
5157
- * This ensures the basic structure of a chain definition is valid.
5158
- * A chain definition must include at minimum a name and type.
5871
+ * ETH (native Ether alias) token definition with metadata.
5159
5872
  *
5160
- * @throws KitError if validation fails
5873
+ * @remarks
5874
+ * Built-in ETH definition for the TokenRegistry. Used as a symbol alias
5875
+ * for native ETH (e.g. in swap flows). Chain locators are TBD and will
5876
+ * be added when addresses are available. Use raw selector with
5877
+ * locator + decimals where native gas token is represented as a contract.
5878
+ */
5879
+ const ETH = {
5880
+ symbol: 'ETH',
5881
+ decimals: 18,
5882
+ locators: {},
5883
+ };
5884
+
5885
+ /**
5886
+ * POL (Polygon native token) token definition with addresses and metadata.
5887
+ *
5888
+ * @remarks
5889
+ * Built-in POL definition for the TokenRegistry. Includes chain locators
5890
+ * where POL is available as a bridged/wrapped asset.
5891
+ */
5892
+ const POL = {
5893
+ symbol: 'POL',
5894
+ decimals: 18,
5895
+ locators: {
5896
+ [Blockchain.Ethereum]: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6',
5897
+ },
5898
+ };
5899
+
5900
+ /**
5901
+ * PLUME (Plume network token) token definition with addresses and metadata.
5902
+ *
5903
+ * @remarks
5904
+ * Built-in PLUME definition for the TokenRegistry. Includes chain locators
5905
+ * where PLUME is available.
5906
+ */
5907
+ const PLUME = {
5908
+ symbol: 'PLUME',
5909
+ decimals: 18,
5910
+ locators: {
5911
+ [Blockchain.Ethereum]: '0x4C1746A800D224393fE2470C70A35717eD4eA5F1',
5912
+ },
5913
+ };
5914
+
5915
+ /**
5916
+ * MON token definition with Solana mint metadata.
5917
+ *
5918
+ * @remarks
5919
+ * Built-in MON definition for the TokenRegistry. Includes chain locators
5920
+ * for swap-supported chains.
5921
+ */
5922
+ const MON = {
5923
+ symbol: 'MON',
5924
+ decimals: 18,
5925
+ chainDecimals: {
5926
+ [Blockchain.Solana]: 8,
5927
+ },
5928
+ locators: {
5929
+ [Blockchain.Solana]: 'CrAr4RRJMBVwRsZtT62pEhfA9H5utymC2mVx8e7FreP2',
5930
+ },
5931
+ };
5932
+
5933
+ // Re-export for consumers
5934
+ /**
5935
+ * All default token definitions.
5936
+ *
5937
+ * @remarks
5938
+ * These tokens are automatically included in the TokenRegistry when created
5939
+ * without explicit defaults. Extensions can override these definitions.
5940
+ * Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
5941
+ * WPOL, ETH, POL, PLUME, and MON.
5161
5942
  *
5162
5943
  * @example
5163
5944
  * ```typescript
5164
- * import { chainDefinitionSchema } from '@core/provider'
5945
+ * import { createTokenRegistry } from '@core/tokens'
5165
5946
  *
5166
- * const validChain = {
5167
- * name: 'Ethereum',
5168
- * type: 'evm'
5169
- * }
5947
+ * // Registry uses these by default
5948
+ * const registry = createTokenRegistry()
5170
5949
  *
5171
- * const result = chainDefinitionSchema.safeParse(validChain)
5172
- * console.log(result.success) // true
5950
+ * // Add custom tokens (built-ins are still included)
5951
+ * const customRegistry = createTokenRegistry({
5952
+ * tokens: [myCustomToken],
5953
+ * })
5173
5954
  * ```
5174
5955
  */
5175
- const chainDefinitionSchema = zod.z.object({
5176
- name: zod.z.string().min(1, 'Chain name is required'),
5177
- type: zod.z.string().min(1, 'Chain type is required'),
5178
- });
5956
+ const DEFAULT_TOKENS = [
5957
+ USDC,
5958
+ USDT,
5959
+ EURC,
5960
+ DAI,
5961
+ USDE,
5962
+ PYUSD,
5963
+ WETH,
5964
+ WBTC,
5965
+ WSOL,
5966
+ WAVAX,
5967
+ WPOL,
5968
+ ETH,
5969
+ POL,
5970
+ PLUME,
5971
+ MON,
5972
+ ];
5179
5973
  /**
5180
- * Schema for validating wallet contexts.
5181
- * This ensures all required fields are present and properly typed.
5182
- * A wallet context must include:
5183
- * - A valid adapter with prepare and execute methods
5184
- * - A valid Ethereum address
5185
- * - A valid chain definition with required properties
5974
+ * Uppercased token symbols approved for swap fee collection.
5186
5975
  *
5187
- * @throws KitError if validation fails
5976
+ * @remarks
5977
+ * Derived from {@link DEFAULT_TOKENS} so all consumers share a single
5978
+ * allowlist source and stay in sync when defaults change.
5979
+ */
5980
+ new Set(DEFAULT_TOKENS.map((t) => t.symbol.toUpperCase()));
5981
+
5982
+ /**
5983
+ * Resolve the effective decimals for a token on a specific chain.
5984
+ *
5985
+ * Returns the chain-specific override from `chainDecimals` when available,
5986
+ * otherwise falls back to the token's default `decimals`.
5987
+ *
5988
+ * @param token - The token definition to resolve decimals for.
5989
+ * @param chain - Optional chain identifier. When omitted, the default decimals are returned.
5990
+ * @returns The number of decimal places for the token on the given chain.
5188
5991
  *
5189
5992
  * @example
5190
5993
  * ```typescript
5191
- * import { walletContextSchema } from '@core/provider'
5994
+ * import { resolveTokenDecimals } from '\@core/tokens'
5192
5995
  *
5193
- * const validContext = {
5194
- * adapter: {
5195
- * prepare: () => Promise.resolve({}),
5196
- * waitForTransaction: () => Promise.resolve({})
5197
- * },
5198
- * address: '0x1234567890123456789012345678901234567890',
5199
- * chain: {
5200
- * name: 'Ethereum',
5201
- * type: 'evm',
5202
- * isTestnet: false
5203
- * }
5204
- * }
5205
- *
5206
- * const result = walletContextSchema.safeParse(validContext)
5207
- * console.log(result.success) // true
5996
+ * // DAI: 18 decimals on EVM, 8 on Solana
5997
+ * resolveTokenDecimals(DAI) // 18 (default)
5998
+ * resolveTokenDecimals(DAI, 'Solana') // 8 (chain override)
5999
+ * resolveTokenDecimals(DAI, 'Ethereum') // 18 (no override, falls back)
5208
6000
  * ```
5209
6001
  */
5210
- const walletContextSchema = zod.z.object({
5211
- adapter: zod.z.object({
5212
- prepare: zod.z.function().returns(zod.z.any()),
5213
- waitForTransaction: zod.z.function().returns(zod.z.any()),
5214
- }),
5215
- address: zod.z.string().min(1),
5216
- chain: zod.z.object({
5217
- name: zod.z.string(),
5218
- type: zod.z.string(),
5219
- isTestnet: zod.z.boolean(),
5220
- }),
5221
- });
6002
+ function resolveTokenDecimals(token, chain) {
6003
+ if (chain === undefined) {
6004
+ return token.decimals;
6005
+ }
6006
+ return token.chainDecimals?.[chain] ?? token.decimals;
6007
+ }
6008
+
5222
6009
  /**
5223
- * Schema for validating destination wallet contexts.
5224
- * Extends walletContextSchema with optional useForwarder flag.
6010
+ * Check if a selector is a raw token selector (object form).
5225
6011
  *
5226
- * @throws KitError if validation fails
6012
+ * @param selector - The token selector to check.
6013
+ * @returns True if the selector is a raw token selector.
5227
6014
  */
5228
- const destinationContextSchema = walletContextSchema.extend({
5229
- useForwarder: zod.z.boolean().optional(),
5230
- recipientAddress: zod.z.string().optional(),
5231
- });
6015
+ function isRawSelector(selector) {
6016
+ return typeof selector === 'object' && 'locator' in selector;
6017
+ }
5232
6018
  /**
5233
- * Schema for validating forwarder-only destination contexts.
5234
- * Used when useForwarder is true and no adapter is provided.
5235
- * Requires chain definition and recipientAddress.
6019
+ * Normalize a symbol to uppercase for case-insensitive lookup.
5236
6020
  *
5237
- * Validates that recipientAddress format is correct for the destination chain
5238
- * (EVM hex address or Solana base58 address).
6021
+ * @param symbol - The symbol to normalize.
6022
+ * @returns The normalized (uppercase) symbol.
6023
+ */
6024
+ function normalizeSymbol(symbol) {
6025
+ return symbol.toUpperCase();
6026
+ }
6027
+ /**
6028
+ * Normalize a locator for stable chain-aware lookup.
5239
6029
  *
5240
- * @throws KitError if validation fails
6030
+ * @remarks
6031
+ * EVM-style addresses (`0x...`) are normalized to lowercase for
6032
+ * case-insensitive matching. Non-EVM locators keep original casing.
5241
6033
  */
5242
- const forwarderOnlyDestinationSchema = zod.z
5243
- .object({
5244
- chain: chainDefinitionSchema.extend({
5245
- isTestnet: zod.z.boolean(),
5246
- }),
5247
- recipientAddress: zod.z
5248
- .string()
5249
- .min(1, 'Recipient address is required for forwarder-only destination'),
5250
- useForwarder: zod.z.literal(true),
5251
- })
5252
- .superRefine((data, ctx) => {
5253
- // Pass chain name as string - isValidAddressForChain will use name-based matching
5254
- if (!isValidAddressForChain(data.recipientAddress, data.chain.name)) {
5255
- ctx.addIssue({
5256
- code: zod.z.ZodIssueCode.custom,
5257
- message: `Invalid recipient address format for chain ${data.chain.name}`,
5258
- path: ['recipientAddress'],
5259
- });
6034
+ function normalizeLocator(locator) {
6035
+ if (locator.startsWith('0x') || locator.startsWith('0X')) {
6036
+ return locator.toLowerCase();
5260
6037
  }
5261
- });
6038
+ return locator;
6039
+ }
5262
6040
  /**
5263
- * Schema for validating any destination - either with adapter or forwarder-only.
6041
+ * Build a stable map key for a chain + locator pair.
5264
6042
  */
5265
- const bridgeDestinationSchema = zod.z.union([
5266
- destinationContextSchema,
5267
- forwarderOnlyDestinationSchema,
5268
- ]);
6043
+ function buildAddressKey(chainId, locator) {
6044
+ return `${chainId}::${normalizeLocator(locator)}`;
6045
+ }
5269
6046
  /**
5270
- * Schema for validating a custom fee configuration.
5271
- * Validates the simplified CustomFee interface which includes:
5272
- * - An optional fee amount as string
5273
- * - An optional fee recipient as string address
6047
+ * Create a token registry with built-in tokens and optional extensions.
5274
6048
  *
5275
- * @throws KitError if validation fails
6049
+ * @remarks
6050
+ * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
6051
+ * Custom tokens are merged on top - use this to add new tokens or override
6052
+ * built-in definitions.
6053
+ *
6054
+ * @param options - Configuration options for the registry.
6055
+ * @returns A token registry instance.
5276
6056
  *
5277
6057
  * @example
5278
6058
  * ```typescript
5279
- * import { customFeeSchema } from '@core/provider'
6059
+ * import { createTokenRegistry } from '@core/tokens'
5280
6060
  *
5281
- * const validConfig = {
5282
- * value: '1000000',
5283
- * recipientAddress: '0x1234567890123456789012345678901234567890'
5284
- * }
6061
+ * // Create registry with built-in tokens (USDC, etc.)
6062
+ * const registry = createTokenRegistry()
5285
6063
  *
5286
- * const result = customFeeSchema.safeParse(validConfig)
5287
- * console.log(result.success) // true
6064
+ * // Resolve USDC on Ethereum
6065
+ * const usdc = registry.resolve('USDC', 'Ethereum')
6066
+ * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
6067
+ * console.log(usdc.decimals) // 6
5288
6068
  * ```
5289
- */
6069
+ *
6070
+ * @example
6071
+ * ```typescript
6072
+ * // Add custom tokens (built-ins are still included)
6073
+ * const myToken: TokenDefinition = {
6074
+ * symbol: 'MY',
6075
+ * decimals: 18,
6076
+ * locators: { Ethereum: '0x...' },
6077
+ * }
6078
+ *
6079
+ * const registry = createTokenRegistry({ tokens: [myToken] })
6080
+ * registry.resolve('USDC', 'Ethereum') // Still works!
6081
+ * registry.resolve('MY', 'Ethereum') // Also works
6082
+ * ```
6083
+ *
6084
+ * @example
6085
+ * ```typescript
6086
+ * // Override a built-in token
6087
+ * const customUsdc: TokenDefinition = {
6088
+ * symbol: 'USDC',
6089
+ * decimals: 6,
6090
+ * locators: { MyChain: '0xCustomAddress' },
6091
+ * }
6092
+ *
6093
+ * const registry = createTokenRegistry({ tokens: [customUsdc] })
6094
+ * // Now USDC resolves to customUsdc definition
6095
+ * ```
6096
+ *
6097
+ * @example
6098
+ * ```typescript
6099
+ * // Resolve arbitrary tokens by raw locator
6100
+ * const registry = createTokenRegistry()
6101
+ * const token = registry.resolve(
6102
+ * { locator: '0x1234...', decimals: 18 },
6103
+ * 'Ethereum'
6104
+ * )
6105
+ * ```
6106
+ */
6107
+ function createTokenRegistry(options = {}) {
6108
+ const { tokens = [], requireDecimals = false } = options;
6109
+ // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
6110
+ const tokenMap = new Map();
6111
+ // Add built-in tokens first
6112
+ for (const def of DEFAULT_TOKENS) {
6113
+ tokenMap.set(normalizeSymbol(def.symbol), def);
6114
+ }
6115
+ // Custom tokens override built-ins
6116
+ for (const def of tokens) {
6117
+ tokenMap.set(normalizeSymbol(def.symbol), def);
6118
+ }
6119
+ // Build an address index from the resolved token map so overrides win.
6120
+ const addressMap = new Map();
6121
+ for (const token of tokenMap.values()) {
6122
+ for (const [chainId, locator] of Object.entries(token.locators)) {
6123
+ if (typeof locator !== 'string' || locator.trim() === '') {
6124
+ continue;
6125
+ }
6126
+ addressMap.set(buildAddressKey(chainId, locator), token);
6127
+ }
6128
+ }
6129
+ /**
6130
+ * Resolve a symbol selector to token information.
6131
+ */
6132
+ function resolveSymbol(symbol, chainId) {
6133
+ const normalizedSymbol = normalizeSymbol(symbol);
6134
+ const definition = tokenMap.get(normalizedSymbol);
6135
+ if (definition === undefined) {
6136
+ throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
6137
+ }
6138
+ const locator = definition.locators[chainId];
6139
+ if (locator === undefined || locator.trim() === '') {
6140
+ throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
6141
+ }
6142
+ const decimals = resolveTokenDecimals(definition, chainId);
6143
+ return {
6144
+ symbol: definition.symbol,
6145
+ decimals,
6146
+ locator: normalizeLocator(locator),
6147
+ };
6148
+ }
6149
+ /**
6150
+ * Resolve a raw selector to token information.
6151
+ */
6152
+ function resolveRaw(selector, chainId) {
6153
+ const { locator, decimals } = selector;
6154
+ // Validate locator
6155
+ if (!locator || typeof locator !== 'string') {
6156
+ throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
6157
+ }
6158
+ // Decimals are always required for raw selectors
6159
+ if (decimals === undefined) {
6160
+ const message = requireDecimals
6161
+ ? 'Decimals required for raw token selector (requireDecimals: true)'
6162
+ : 'Decimals required for raw token selector. Provide { locator, decimals }.';
6163
+ throw createTokenResolutionError(message, selector, chainId);
6164
+ }
6165
+ // Validate decimals
6166
+ if (typeof decimals !== 'number' ||
6167
+ decimals < 0 ||
6168
+ !Number.isInteger(decimals)) {
6169
+ throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
6170
+ }
6171
+ return {
6172
+ decimals,
6173
+ locator: normalizeLocator(locator),
6174
+ };
6175
+ }
6176
+ return {
6177
+ resolve(selector, chainId) {
6178
+ // Runtime validation of inputs - these checks are for JS consumers
6179
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
6180
+ if (selector === null || selector === undefined) {
6181
+ throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
6182
+ }
6183
+ if (chainId === '' || typeof chainId !== 'string') {
6184
+ throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
6185
+ }
6186
+ // Dispatch based on selector type
6187
+ if (isRawSelector(selector)) {
6188
+ return resolveRaw(selector, chainId);
6189
+ }
6190
+ if (typeof selector === 'string') {
6191
+ return resolveSymbol(selector, chainId);
6192
+ }
6193
+ throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
6194
+ },
6195
+ resolveByAddress(address, chainId) {
6196
+ if (!address || typeof address !== 'string') {
6197
+ throw createTokenResolutionError('Token address is required for address resolution', { locator: address }, chainId);
6198
+ }
6199
+ if (chainId === '' || typeof chainId !== 'string') {
6200
+ throw createTokenResolutionError('Chain ID is required for token resolution', { locator: address }, chainId);
6201
+ }
6202
+ const definition = addressMap.get(buildAddressKey(chainId, address));
6203
+ if (definition === undefined) {
6204
+ throw createTokenResolutionError(`Unknown token address: ${address} for chain ${chainId}`, { locator: address }, chainId);
6205
+ }
6206
+ const locator = definition.locators[chainId];
6207
+ if (locator === undefined || locator.trim() === '') {
6208
+ throw createTokenResolutionError(`Token ${definition.symbol} has no locator for chain ${chainId}`, definition.symbol, chainId);
6209
+ }
6210
+ const decimals = resolveTokenDecimals(definition, chainId);
6211
+ return {
6212
+ symbol: definition.symbol,
6213
+ decimals,
6214
+ locator: normalizeLocator(locator),
6215
+ };
6216
+ },
6217
+ get(symbol) {
6218
+ if (!symbol || typeof symbol !== 'string') {
6219
+ return undefined;
6220
+ }
6221
+ return tokenMap.get(normalizeSymbol(symbol));
6222
+ },
6223
+ has(symbol) {
6224
+ if (!symbol || typeof symbol !== 'string') {
6225
+ return false;
6226
+ }
6227
+ return tokenMap.has(normalizeSymbol(symbol));
6228
+ },
6229
+ symbols() {
6230
+ return Array.from(tokenMap.values()).map((def) => def.symbol);
6231
+ },
6232
+ entries() {
6233
+ return Array.from(tokenMap.values());
6234
+ },
6235
+ };
6236
+ }
6237
+
6238
+ /**
6239
+ * Define a schema for token registry interfaces.
6240
+ *
6241
+ * @remarks
6242
+ * Validate that a registry exposes the `resolve()` API used by adapters.
6243
+ *
6244
+ * @example
6245
+ * ```typescript
6246
+ * import { tokenRegistrySchema } from '@core/tokens'
6247
+ *
6248
+ * const registry = {
6249
+ * resolve: () => ({ locator: '0x0', decimals: 6 }),
6250
+ * }
6251
+ *
6252
+ * tokenRegistrySchema.parse(registry)
6253
+ * ```
6254
+ */
6255
+ zod.z.custom((value) => {
6256
+ if (value === null || typeof value !== 'object') {
6257
+ return false;
6258
+ }
6259
+ const record = value;
6260
+ return (typeof record['resolve'] === 'function' &&
6261
+ typeof record['get'] === 'function' &&
6262
+ typeof record['has'] === 'function' &&
6263
+ typeof record['symbols'] === 'function' &&
6264
+ typeof record['entries'] === 'function');
6265
+ }, {
6266
+ message: 'Invalid token registry',
6267
+ });
6268
+
6269
+ /**
6270
+ * Build a complete explorer URL from a chain definition and transaction hash.
6271
+ *
6272
+ * This function takes a chain definition containing an explorer URL template
6273
+ * and replaces the `{hash}` placeholder with the provided transaction hash
6274
+ * to create a complete, valid explorer URL.
6275
+ *
6276
+ * @param chainDef - The chain definition containing the explorer URL template.
6277
+ * @param txHash - The transaction hash to insert into the URL template.
6278
+ * @returns A complete explorer URL with the transaction hash inserted.
6279
+ * @throws Error if the chain definition is null/undefined.
6280
+ * @throws Error if the transaction hash is null/undefined/empty.
6281
+ * @throws Error if the explorer URL template is missing or empty.
6282
+ * @throws Error if the explorer URL template doesn't contain the {hash} placeholder.
6283
+ * @throws Error if the resulting URL is invalid.
6284
+ *
6285
+ * @example
6286
+ * ```typescript
6287
+ * import { buildExplorerUrl } from '@core/utils'
6288
+ * import { Ethereum } from '@core/chains'
6289
+ *
6290
+ * // Build URL for Ethereum transaction
6291
+ * const url = buildExplorerUrl(Ethereum, '0x1234567890abcdef...')
6292
+ * console.log(url) // 'https://etherscan.io/tx/0x1234567890abcdef...'
6293
+ * ```
6294
+ *
6295
+ * @example
6296
+ * ```typescript
6297
+ * import { buildExplorerUrl } from '@core/utils'
6298
+ * import { Solana } from '@core/chains'
6299
+ *
6300
+ * // Build URL for Solana transaction
6301
+ * const url = buildExplorerUrl(Solana, 'abc123def456...')
6302
+ * console.log(url) // 'https://solscan.io/tx/abc123def456...'
6303
+ * ```
6304
+ *
6305
+ * @example
6306
+ * ```typescript
6307
+ * import { buildExplorerUrl } from '@core/utils'
6308
+ * import { Aptos } from '@core/chains'
6309
+ *
6310
+ * // Build URL for Aptos transaction with query parameters
6311
+ * const url = buildExplorerUrl(Aptos, '0xabc123...')
6312
+ * console.log(url) // 'https://explorer.aptoslabs.com/txn/0xabc123...?network=mainnet'
6313
+ * ```
6314
+ */
6315
+ function buildExplorerUrl(chainDef, txHash) {
6316
+ // Validate input parameters using our standard validation pattern
6317
+ validateOrThrow({ chainDef, txHash }, buildExplorerUrlParamsSchema, 'Invalid buildExplorerUrl parameters');
6318
+ // Parse and transform input parameters (e.g., trim whitespace from txHash)
6319
+ const { chainDef: validChainDef, txHash: validTxHash } = buildExplorerUrlParamsSchema.parse({ chainDef, txHash });
6320
+ // Replace all occurrences of the placeholder with the transaction hash
6321
+ const explorerUrl = validChainDef.explorerUrl.replace(/{hash}/g, validTxHash);
6322
+ // Validate the resulting URL
6323
+ validate(explorerUrl, explorerUrlSchema, 'explorer URL');
6324
+ return explorerUrl;
6325
+ }
6326
+
6327
+ /**
6328
+ * CCTP forwarding magic bytes prefix.
6329
+ *
6330
+ * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
6331
+ * This prefix is right-padded to 24 bytes in the final hookData.
6332
+ */
6333
+ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
6334
+ /**
6335
+ * CCTP forwarding version number.
6336
+ *
6337
+ * Version 0 is used for basic forwarding requests.
6338
+ */
6339
+ const CCTP_FORWARD_VERSION = 0;
6340
+ /**
6341
+ * Length of Circle-reserved payload for basic forwarding (0 bytes).
6342
+ *
6343
+ * Set to 0 when no additional Circle-reserved data is needed.
6344
+ */
6345
+ const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
6346
+ /**
6347
+ * Build the hookData bytes for CCTP forwarding.
6348
+ *
6349
+ * Constructs a 32-byte hookData payload that signals to Circle's Orbit relayer
6350
+ * that the user wants automated attestation fetching and destination mint execution.
6351
+ *
6352
+ * The hookData format is:
6353
+ * - Bytes 0-23: ASCII "cctp-forward" right-padded to 24 bytes with zeros
6354
+ * - Bytes 24-27: uint32 version (big-endian) - currently 0
6355
+ * - Bytes 28-31: uint32 length (big-endian) - 0 for basic forwarding
6356
+ *
6357
+ * @returns A 0x-prefixed hex string representing the 32-byte hookData
6358
+ *
6359
+ * @example
6360
+ * ```typescript
6361
+ * import { buildForwardingHookData } from '@core/utils'
6362
+ *
6363
+ * const hookData = buildForwardingHookData()
6364
+ * // Returns: '0x636374702d666f72776172640000000000000000000000000000000000000000'
6365
+ *
6366
+ * // Use with depositForBurnWithHook action
6367
+ * await adapter.action('cctp.v2.depositForBurnWithHook', {
6368
+ * amount: BigInt('1000000'),
6369
+ * mintRecipient: '0x...',
6370
+ * maxFee: BigInt('50000'),
6371
+ * minFinalityThreshold: 1000,
6372
+ * fromChain: ethereum,
6373
+ * toChain: base,
6374
+ * hookData
6375
+ * })
6376
+ * ```
6377
+ */
6378
+ // Cached result for memoization (computed once on first call)
6379
+ let cachedHookDataHex = null;
6380
+ function buildForwardingHookData() {
6381
+ // Return cached result if already computed
6382
+ if (cachedHookDataHex !== null) {
6383
+ return cachedHookDataHex;
6384
+ }
6385
+ // Create a 32-byte buffer
6386
+ const buffer = new Uint8Array(32);
6387
+ // Write the magic prefix ("cctp-forward") - 12 bytes, rest of 24-byte section is zero-padded
6388
+ const encoder = new TextEncoder();
6389
+ const magicBytes = encoder.encode(CCTP_FORWARD_MAGIC_PREFIX);
6390
+ buffer.set(magicBytes, 0);
6391
+ // Bytes 12-23 are already zero (right-padding)
6392
+ // Write uint32 version at bytes 24-27 (big-endian)
6393
+ const versionView = new DataView(buffer.buffer);
6394
+ versionView.setUint32(24, CCTP_FORWARD_VERSION, false); // false = big-endian
6395
+ // Write uint32 length at bytes 28-31 (big-endian)
6396
+ versionView.setUint32(28, CCTP_FORWARD_PAYLOAD_LENGTH, false); // false = big-endian
6397
+ // Convert to hex string with 0x prefix and cache
6398
+ cachedHookDataHex =
6399
+ '0x' +
6400
+ Array.from(buffer)
6401
+ .map((b) => b.toString(16).padStart(2, '0'))
6402
+ .join('');
6403
+ return cachedHookDataHex;
6404
+ }
6405
+
6406
+ /**
6407
+ * Transfer speed options for cross-chain operations.
6408
+ *
6409
+ * Defines the available speed modes for CCTPv2 transfers, affecting
6410
+ * both transfer time and potential fee implications.
6411
+ */
6412
+ var TransferSpeed;
6413
+ (function (TransferSpeed) {
6414
+ /** Fast burn mode - reduces transfer time but may have different fee implications */
6415
+ TransferSpeed["FAST"] = "FAST";
6416
+ /** Standard burn mode - normal transfer time with standard fees */
6417
+ TransferSpeed["SLOW"] = "SLOW";
6418
+ })(TransferSpeed || (TransferSpeed = {}));
6419
+
6420
+ /**
6421
+ * Factory to validate a numeric string with strict dot-decimal notation.
6422
+ * Only accepts dot (.) as the decimal separator. Thousand separators are not allowed.
6423
+ *
6424
+ * This enforces an unambiguous format for SDK inputs. Internationalization concerns
6425
+ * (comma vs dot decimal separators) should be handled in the UI layer before passing
6426
+ * values to the SDK.
6427
+ *
6428
+ * Accepts the following formats:
6429
+ * - Whole numbers: "1", "100", "1000"
6430
+ * - Leading zero decimals: "0.1", "0.5", "0.001"
6431
+ * - Shorthand decimals: ".1", ".5", ".001"
6432
+ * - Standard decimals: "1.23", "100.50"
6433
+ *
6434
+ * Does NOT accept:
6435
+ * - Comma decimal separator: "1,5" (use "1.5" instead)
6436
+ * - Thousand separators: "1,000.50" or "1.000,50" (use "1000.50" instead)
6437
+ * - Multiple decimal points: "1.2.3"
6438
+ * - Negative numbers: "-100"
6439
+ * - Non-numeric characters: "abc", "100a"
6440
+ *
6441
+ * Behavior differences controlled by options:
6442
+ * - allowZero: when false, value must be strictly greater than 0; when true, non-negative.
6443
+ * - regexMessage: error message when the basic numeric format fails.
6444
+ * - maxDecimals: maximum number of decimal places allowed (e.g., 6 for USDC).
6445
+ */
6446
+ const createDecimalStringValidator = (options) => (schema) => {
6447
+ // Capitalize first letter of attribute name for error messages
6448
+ const capitalizedAttributeName = options.attributeName.charAt(0).toUpperCase() +
6449
+ options.attributeName.slice(1);
6450
+ return schema
6451
+ .regex(/^-?(?:\d+(?:\.\d+)?|\.\d+)$/, options.regexMessage)
6452
+ .superRefine((val, ctx) => {
6453
+ const amount = Number.parseFloat(val);
6454
+ if (Number.isNaN(amount)) {
6455
+ ctx.addIssue({
6456
+ code: zod.z.ZodIssueCode.custom,
6457
+ message: options.regexMessage,
6458
+ });
6459
+ return;
6460
+ }
6461
+ // Check decimal precision if maxDecimals is specified
6462
+ if (options.maxDecimals !== undefined) {
6463
+ const decimalPart = val.split('.')[1];
6464
+ if (decimalPart && decimalPart.length > options.maxDecimals) {
6465
+ ctx.addIssue({
6466
+ code: zod.z.ZodIssueCode.custom,
6467
+ message: `Maximum supported decimal places: ${options.maxDecimals.toString()}`,
6468
+ });
6469
+ return;
6470
+ }
6471
+ }
6472
+ if (options.allowZero && amount < 0) {
6473
+ ctx.addIssue({
6474
+ code: zod.z.ZodIssueCode.custom,
6475
+ message: `${capitalizedAttributeName} must be non-negative`,
6476
+ });
6477
+ }
6478
+ else if (!options.allowZero && amount <= 0) {
6479
+ ctx.addIssue({
6480
+ code: zod.z.ZodIssueCode.custom,
6481
+ message: `${capitalizedAttributeName} must be greater than 0`,
6482
+ });
6483
+ }
6484
+ });
6485
+ };
6486
+ /**
6487
+ * Schema for validating chain definitions.
6488
+ * This ensures the basic structure of a chain definition is valid.
6489
+ * A chain definition must include at minimum a name and type.
6490
+ *
6491
+ * @throws KitError if validation fails
6492
+ *
6493
+ * @example
6494
+ * ```typescript
6495
+ * import { chainDefinitionSchema } from '@core/provider'
6496
+ *
6497
+ * const validChain = {
6498
+ * name: 'Ethereum',
6499
+ * type: 'evm'
6500
+ * }
6501
+ *
6502
+ * const result = chainDefinitionSchema.safeParse(validChain)
6503
+ * console.log(result.success) // true
6504
+ * ```
6505
+ */
6506
+ const chainDefinitionSchema = zod.z.object({
6507
+ name: zod.z.string().min(1, 'Chain name is required'),
6508
+ type: zod.z.string().min(1, 'Chain type is required'),
6509
+ });
6510
+ /**
6511
+ * Schema for validating wallet contexts.
6512
+ * This ensures all required fields are present and properly typed.
6513
+ * A wallet context must include:
6514
+ * - A valid adapter with prepare and execute methods
6515
+ * - A valid Ethereum address
6516
+ * - A valid chain definition with required properties
6517
+ *
6518
+ * @throws KitError if validation fails
6519
+ *
6520
+ * @example
6521
+ * ```typescript
6522
+ * import { walletContextSchema } from '@core/provider'
6523
+ *
6524
+ * const validContext = {
6525
+ * adapter: {
6526
+ * prepare: () => Promise.resolve({}),
6527
+ * waitForTransaction: () => Promise.resolve({})
6528
+ * },
6529
+ * address: '0x1234567890123456789012345678901234567890',
6530
+ * chain: {
6531
+ * name: 'Ethereum',
6532
+ * type: 'evm',
6533
+ * isTestnet: false
6534
+ * }
6535
+ * }
6536
+ *
6537
+ * const result = walletContextSchema.safeParse(validContext)
6538
+ * console.log(result.success) // true
6539
+ * ```
6540
+ */
6541
+ const walletContextSchema = zod.z.object({
6542
+ adapter: zod.z.object({
6543
+ prepare: zod.z.function().returns(zod.z.any()),
6544
+ waitForTransaction: zod.z.function().returns(zod.z.any()),
6545
+ }),
6546
+ address: zod.z.string().min(1),
6547
+ chain: zod.z.object({
6548
+ name: zod.z.string(),
6549
+ type: zod.z.string(),
6550
+ isTestnet: zod.z.boolean(),
6551
+ }),
6552
+ });
6553
+ /**
6554
+ * Schema for validating destination wallet contexts.
6555
+ * Extends walletContextSchema with optional useForwarder flag.
6556
+ *
6557
+ * @throws KitError if validation fails
6558
+ */
6559
+ const destinationContextSchema = walletContextSchema.extend({
6560
+ useForwarder: zod.z.boolean().optional(),
6561
+ recipientAddress: zod.z.string().optional(),
6562
+ });
6563
+ /**
6564
+ * Schema for validating forwarder-only destination contexts.
6565
+ * Used when useForwarder is true and no adapter is provided.
6566
+ * Requires chain definition and recipientAddress.
6567
+ *
6568
+ * Validates that recipientAddress format is correct for the destination chain
6569
+ * (EVM hex address or Solana base58 address).
6570
+ *
6571
+ * @throws KitError if validation fails
6572
+ */
6573
+ const forwarderOnlyDestinationSchema = zod.z
6574
+ .object({
6575
+ chain: chainDefinitionSchema.extend({
6576
+ isTestnet: zod.z.boolean(),
6577
+ }),
6578
+ recipientAddress: zod.z
6579
+ .string()
6580
+ .min(1, 'Recipient address is required for forwarder-only destination'),
6581
+ useForwarder: zod.z.literal(true),
6582
+ })
6583
+ .superRefine((data, ctx) => {
6584
+ // Pass chain name as string - isValidAddressForChain will use name-based matching
6585
+ if (!isValidAddressForChain(data.recipientAddress, data.chain.name)) {
6586
+ ctx.addIssue({
6587
+ code: zod.z.ZodIssueCode.custom,
6588
+ message: `Invalid recipient address format for chain ${data.chain.name}`,
6589
+ path: ['recipientAddress'],
6590
+ });
6591
+ }
6592
+ });
6593
+ /**
6594
+ * Schema for validating any destination - either with adapter or forwarder-only.
6595
+ */
6596
+ const bridgeDestinationSchema = zod.z.union([
6597
+ destinationContextSchema,
6598
+ forwarderOnlyDestinationSchema,
6599
+ ]);
6600
+ /**
6601
+ * Schema for validating a custom fee configuration.
6602
+ * Validates the simplified CustomFee interface which includes:
6603
+ * - An optional fee amount as string
6604
+ * - An optional fee recipient as string address
6605
+ *
6606
+ * @throws KitError if validation fails
6607
+ *
6608
+ * @example
6609
+ * ```typescript
6610
+ * import { customFeeSchema } from '@core/provider'
6611
+ *
6612
+ * const validConfig = {
6613
+ * value: '1000000',
6614
+ * recipientAddress: '0x1234567890123456789012345678901234567890'
6615
+ * }
6616
+ *
6617
+ * const result = customFeeSchema.safeParse(validConfig)
6618
+ * console.log(result.success) // true
6619
+ * ```
6620
+ */
5290
6621
  const customFeeSchema = zod.z
5291
6622
  .object({
5292
6623
  /**
@@ -5549,6 +6880,21 @@ async function fetchUsdcFastBurnFee(sourceDomain, destinationDomain, isTestnet)
5549
6880
  return minimumFee;
5550
6881
  }
5551
6882
 
6883
+ /**
6884
+ * Well-known SPL Token program ID.
6885
+ * Duplicated here as a raw string to avoid a static import of \@core/adapter-solana
6886
+ * (which would transitively pull \@solana/web3.js into the top-level bundle and
6887
+ * break lazy-loading for EVM-only consumers).
6888
+ *
6889
+ * This MUST match TOKEN_PROGRAM_ID in @core/adapter-solana/src/utils/splTokenUtils.
6890
+ */
6891
+ const TOKEN_PROGRAM_ID_BASE58 = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; // NOSONAR — public Solana program address
6892
+ /**
6893
+ * Well-known SPL Associated Token Account program ID (same rationale as above).
6894
+ *
6895
+ * This MUST match ASSOCIATED_TOKEN_PROGRAM_ID in @core/adapter-solana/src/utils/splTokenUtils.
6896
+ */
6897
+ const ASSOCIATED_TOKEN_PROGRAM_ID_BASE58 = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'; // NOSONAR — public Solana program address
5552
6898
  /**
5553
6899
  * Get the token account address where USDC will be minted for the recipient.
5554
6900
  *
@@ -5592,20 +6938,30 @@ mintAddress) => {
5592
6938
  return rawAddress;
5593
6939
  }
5594
6940
  else {
5595
- // Solana: derive the associated token account
5596
- // Use dynamic import to avoid loading Solana dependencies for EVM-only users
6941
+ // Solana: derive the associated token account.
6942
+ // Both @solana/web3.js and the program-ID constants are resolved lazily
6943
+ // so that EVM-only consumers never load Solana code.
6944
+ const { PublicKey } = await import('@solana/web3.js').catch(() => {
6945
+ throw new KitError({
6946
+ ...InputError.VALIDATION_FAILED,
6947
+ recoverability: 'FATAL',
6948
+ message: 'Failed to load @solana/web3.js. Please ensure it is installed: npm install @solana/web3.js',
6949
+ });
6950
+ });
5597
6951
  try {
5598
- const [{ PublicKey }, { getAssociatedTokenAddressSync }] = await Promise.all([
5599
- import('@solana/web3.js'),
5600
- import('@solana/spl-token'),
5601
- ]);
5602
6952
  const owner = new PublicKey(rawAddress);
5603
6953
  const mintPub = new PublicKey(mintAddress);
5604
- const ata = getAssociatedTokenAddressSync(mintPub, owner);
6954
+ const tokenProgramId = new PublicKey(TOKEN_PROGRAM_ID_BASE58);
6955
+ const ataProgramId = new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID_BASE58);
6956
+ const [ata] = PublicKey.findProgramAddressSync([owner.toBuffer(), tokenProgramId.toBuffer(), mintPub.toBuffer()], ataProgramId);
5605
6957
  return ata.toBase58();
5606
6958
  }
5607
- catch {
5608
- 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');
6959
+ catch (error) {
6960
+ throw new KitError({
6961
+ ...InputError.INVALID_ADDRESS,
6962
+ recoverability: 'FATAL',
6963
+ message: `Failed to derive Solana Associated Token Account for recipient "${rawAddress}": ${error instanceof Error ? error.message : String(error)}`,
6964
+ });
5609
6965
  }
5610
6966
  }
5611
6967
  };
@@ -7921,590 +9277,239 @@ function createLogger(options, stream) {
7921
9277
  const finalOptions = redactConfig
7922
9278
  ? { ...pinoOptions, redact: redactConfig }
7923
9279
  : pinoOptions;
7924
- const pinoInstance = pino__default(finalOptions);
7925
- return wrapPino(pinoInstance);
7926
- }
7927
-
7928
- /**
7929
- * Factory for creating Runtime instances with defaults.
7930
- *
7931
- * @packageDocumentation
7932
- */
7933
- // ============================================================================
7934
- // Validation Schema
7935
- // ============================================================================
7936
- /**
7937
- * Schema for validating {@link RuntimeOptions}.
7938
- *
7939
- * @remarks
7940
- * Used internally by {@link createRuntime} to validate options from JS consumers.
7941
- * Exported for advanced use cases where manual validation is needed.
7942
- */
7943
- zod.z
7944
- .object({
7945
- logger: loggerSchema.optional(),
7946
- metrics: metricsSchema.optional(),
7947
- })
7948
- .passthrough();
7949
- // ============================================================================
7950
- // Factory
7951
- // ============================================================================
7952
- /**
7953
- * Create a complete Runtime with sensible defaults.
7954
- *
7955
- * @param options - Optional configuration to override logger and metrics.
7956
- * @returns A frozen, immutable Runtime with all services guaranteed present.
7957
- * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
7958
- *
7959
- * @remarks
7960
- * Creates a fully-configured runtime by merging provided options with defaults.
7961
- * The returned runtime is frozen to enforce immutability.
7962
- *
7963
- * | Service | Default | Configurable |
7964
- * |---------|---------|--------------|
7965
- * | `logger` | pino logger (info level) | Yes |
7966
- * | `metrics` | No-op metrics | Yes |
7967
- * | `events` | Internal event bus | No |
7968
- * | `clock` | `Date.now()` | No |
7969
- *
7970
- * **Why only logger and metrics?**
7971
- *
7972
- * - **Logger/Metrics**: Integration points with your infrastructure
7973
- * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
7974
- * - **Clock**: Testing concern - use mock factories for tests
7975
- *
7976
- * @example
7977
- * ```typescript
7978
- * import { createRuntime, createLogger } from '@core/runtime'
7979
- *
7980
- * // Use all defaults
7981
- * const runtime = createRuntime()
7982
- *
7983
- * // Custom logger
7984
- * const runtime = createRuntime({
7985
- * logger: createLogger({ level: 'debug' }),
7986
- * })
7987
- *
7988
- * // Custom metrics (e.g., Prometheus)
7989
- * const runtime = createRuntime({
7990
- * metrics: myPrometheusMetrics,
7991
- * })
7992
- *
7993
- * // Subscribe to events (don't replace the bus)
7994
- * runtime.events.on('operation.*', (event) => {
7995
- * console.log('Event:', event.name)
7996
- * })
7997
- * ```
7998
- */
7999
- function createRuntime(options) {
8000
- // Resolve logger first (events may need it)
8001
- const logger = createLogger();
8002
- // Internal services - not configurable
8003
- const events = createEventBus({ logger });
8004
- const clock = defaultClock;
8005
- // Resolve metrics
8006
- const metrics = noopMetrics;
8007
- return Object.freeze({ logger, events, metrics, clock });
8008
- }
8009
-
8010
- /**
8011
- * Invocation context resolution - resolves the **WHO/HOW** of an operation.
8012
- *
8013
- * @packageDocumentation
8014
- */
8015
- // ============================================================================
8016
- // Validation Schemas
8017
- // ============================================================================
8018
- /**
8019
- * Schema for validating Caller.
8020
- */
8021
- const callerSchema = zod.z.object({
8022
- type: zod.z.string(),
8023
- name: zod.z.string(),
8024
- version: zod.z.string().optional(),
8025
- });
8026
- /**
8027
- * Schema for validating InvocationMeta input.
8028
- */
8029
- const invocationMetaSchema = zod.z
8030
- .object({
8031
- traceId: zod.z.string().optional(),
8032
- runtime: zod.z.object({}).passthrough().optional(),
8033
- tokens: zod.z.object({}).passthrough().optional(),
8034
- callers: zod.z.array(callerSchema).optional(),
8035
- })
8036
- .strict();
8037
- // ============================================================================
8038
- // Invocation Context Resolution
8039
- // ============================================================================
8040
- /**
8041
- * Resolve invocation metadata to invocation context.
8042
- *
8043
- * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
8044
- * @param defaults - Default runtime and tokens to use if not overridden.
8045
- * @returns Frozen, immutable invocation context with guaranteed values.
8046
- * @throws KitError when meta contains invalid properties.
8047
- *
8048
- * @remarks
8049
- * Resolves the **WHO** called and **HOW** to observe:
8050
- * - TraceId: Uses provided value or generates new one
8051
- * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
8052
- * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
8053
- * - Callers: Uses provided array or empty array
8054
- *
8055
- * The returned context is frozen to enforce immutability.
8056
- *
8057
- * @example
8058
- * ```typescript
8059
- * import { resolveInvocationContext, createRuntime } from '@core/runtime'
8060
- * import { createTokenRegistry } from '@core/tokens'
8061
- *
8062
- * const defaults = {
8063
- * runtime: createRuntime(),
8064
- * tokens: createTokenRegistry(),
8065
- * }
8066
- *
8067
- * // Minimal - just using defaults
8068
- * const ctx = resolveInvocationContext(undefined, defaults)
8069
- *
8070
- * // With trace ID and caller info
8071
- * const ctx = resolveInvocationContext(
8072
- * {
8073
- * traceId: 'abc-123',
8074
- * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
8075
- * },
8076
- * defaults
8077
- * )
8078
- *
8079
- * // With runtime override (complete replacement)
8080
- * const ctx = resolveInvocationContext(
8081
- * { runtime: createRuntime({ logger: myLogger }) },
8082
- * defaults
8083
- * )
8084
- * ```
8085
- */
8086
- function resolveInvocationContext(meta, defaults) {
8087
- // Validate meta input if provided
8088
- if (meta !== undefined) {
8089
- const result = invocationMetaSchema.safeParse(meta);
8090
- if (!result.success) {
8091
- throw createValidationFailedError('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
8092
- }
8093
- }
8094
- // Generate trace ID if not provided
8095
- const traceId = meta?.traceId ?? createTraceId();
8096
- // Use meta overrides or fall back to defaults
8097
- const runtime = meta?.runtime ?? defaults.runtime;
8098
- const tokens = meta?.tokens ?? defaults.tokens;
8099
- const callers = meta?.callers ?? [];
8100
- return Object.freeze({ traceId, runtime, tokens, callers });
9280
+ const pinoInstance = pino__default(finalOptions);
9281
+ return wrapPino(pinoInstance);
8101
9282
  }
8102
9283
 
8103
9284
  /**
8104
- * Extend an invocation context by appending a caller to its call chain.
9285
+ * Factory for creating Runtime instances with defaults.
8105
9286
  *
8106
- * @param context - The existing invocation context to extend.
8107
- * @param caller - The caller to append to the call chain.
8108
- * @returns A new frozen invocation context with the caller appended.
9287
+ * @packageDocumentation
9288
+ */
9289
+ // ============================================================================
9290
+ // Validation Schema
9291
+ // ============================================================================
9292
+ /**
9293
+ * Schema for validating {@link RuntimeOptions}.
8109
9294
  *
8110
9295
  * @remarks
8111
- * This function creates a new immutable context with the caller appended
8112
- * to the `callers` array while preserving all other context properties
8113
- * (traceId, runtime, tokens).
8114
- *
8115
- * The returned context is frozen to enforce immutability.
8116
- *
8117
- * @example
8118
- * ```typescript
8119
- * import { extendInvocationContext } from '@core/runtime'
8120
- *
8121
- * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
8122
- * const extended = extendInvocationContext(existingContext, caller)
8123
- * // extended.callers === [...existingContext.callers, caller]
8124
- * ```
9296
+ * Used internally by {@link createRuntime} to validate options from JS consumers.
9297
+ * Exported for advanced use cases where manual validation is needed.
8125
9298
  */
8126
- function extendInvocationContext(context, caller) {
8127
- return Object.freeze({
8128
- traceId: context.traceId,
8129
- runtime: context.runtime,
8130
- tokens: context.tokens,
8131
- callers: [...context.callers, caller],
8132
- });
8133
- }
8134
-
8135
- // Clock - expose defaultClock for backward compatibility
8136
- /** Clock validation schema (backward compatibility). */
8137
- zod.z.custom((val) => val !== null &&
8138
- typeof val === 'object' &&
8139
- 'now' in val &&
8140
- typeof val['now'] === 'function');
8141
- /** EventBus validation schema (backward compatibility). */
8142
- zod.z.custom((val) => val !== null &&
8143
- typeof val === 'object' &&
8144
- 'emit' in val &&
8145
- typeof val['emit'] === 'function');
8146
- /** Runtime validation schema (backward compatibility). */
8147
9299
  zod.z
8148
9300
  .object({
8149
- logger: zod.z.any().optional(),
8150
- events: zod.z.any().optional(),
8151
- metrics: zod.z.any().optional(),
8152
- clock: zod.z.any().optional(),
9301
+ logger: loggerSchema.optional(),
9302
+ metrics: metricsSchema.optional(),
8153
9303
  })
8154
9304
  .passthrough();
8155
-
9305
+ // ============================================================================
9306
+ // Factory
9307
+ // ============================================================================
8156
9308
  /**
8157
- * Create a structured error for token resolution failures.
9309
+ * Create a complete Runtime with sensible defaults.
8158
9310
  *
8159
- * @remarks
8160
- * Returns a `KitError` with `INPUT_INVALID_TOKEN` code (1006).
8161
- * The error trace contains the selector and chain context for debugging.
9311
+ * @param options - Optional configuration to override logger and metrics.
9312
+ * @returns A frozen, immutable Runtime with all services guaranteed present.
9313
+ * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
8162
9314
  *
8163
- * @param message - Human-readable error description.
8164
- * @param selector - The token selector that failed to resolve.
8165
- * @param chainId - The chain being resolved for (optional).
8166
- * @param cause - The underlying error, if any (optional).
8167
- * @returns A KitError with INPUT type and FATAL recoverability.
9315
+ * @remarks
9316
+ * Creates a fully-configured runtime by merging provided options with defaults.
9317
+ * The returned runtime is frozen to enforce immutability.
8168
9318
  *
8169
- * @example
8170
- * ```typescript
8171
- * throw createTokenResolutionError(
8172
- * 'Unknown token symbol: FAKE',
8173
- * 'FAKE',
8174
- * 'Ethereum'
8175
- * )
8176
- * ```
8177
- */
8178
- function createTokenResolutionError(message, selector, chainId, cause) {
8179
- const trace = {
8180
- selector,
8181
- ...(chainId === undefined ? {} : { chainId }),
8182
- ...({} ),
8183
- };
8184
- return new KitError({
8185
- ...InputError.INVALID_TOKEN,
8186
- recoverability: 'FATAL',
8187
- message,
8188
- cause: { trace },
8189
- });
8190
- }
8191
-
8192
- /**
8193
- * USDC token definition with addresses and metadata.
9319
+ * | Service | Default | Configurable |
9320
+ * |---------|---------|--------------|
9321
+ * | `logger` | pino logger (info level) | Yes |
9322
+ * | `metrics` | No-op metrics | Yes |
9323
+ * | `events` | Internal event bus | No |
9324
+ * | `clock` | `Date.now()` | No |
8194
9325
  *
8195
- * @remarks
8196
- * This is the built-in USDC definition used by the TokenRegistry.
8197
- * Includes all known USDC addresses across supported chains.
9326
+ * **Why only logger and metrics?**
8198
9327
  *
8199
- * Keys use the `Blockchain` enum for type safety. Both enum values
8200
- * and string literals are supported:
8201
- * - `Blockchain.Ethereum` or `'Ethereum'`
9328
+ * - **Logger/Metrics**: Integration points with your infrastructure
9329
+ * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
9330
+ * - **Clock**: Testing concern - use mock factories for tests
8202
9331
  *
8203
9332
  * @example
8204
9333
  * ```typescript
8205
- * import { USDC } from '@core/tokens'
8206
- * import { Blockchain } from '@core/chains'
8207
- *
8208
- * console.log(USDC.symbol) // 'USDC'
8209
- * console.log(USDC.decimals) // 6
8210
- * console.log(USDC.locators[Blockchain.Ethereum])
8211
- * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
8212
- * ```
8213
- */
8214
- const USDC = {
8215
- symbol: 'USDC',
8216
- decimals: 6,
8217
- locators: {
8218
- // =========================================================================
8219
- // Mainnets (alphabetically sorted)
8220
- // =========================================================================
8221
- [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
8222
- [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
8223
- [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
8224
- [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
8225
- [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
8226
- [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
8227
- [Blockchain.Hedera]: '0.0.456858',
8228
- [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
8229
- [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
8230
- [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
8231
- [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
8232
- [Blockchain.Noble]: 'uusdc',
8233
- [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
8234
- [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
8235
- [Blockchain.Polkadot_Asset_Hub]: '1337',
8236
- [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
8237
- [Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
8238
- [Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
8239
- [Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
8240
- [Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
8241
- [Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
8242
- [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
8243
- [Blockchain.World_Chain]: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
8244
- [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
8245
- [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
8246
- // =========================================================================
8247
- // Testnets (alphabetically sorted)
8248
- // =========================================================================
8249
- [Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
8250
- [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
8251
- [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
8252
- [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
8253
- [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
8254
- [Blockchain.Hedera_Testnet]: '0.0.429274',
8255
- [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
8256
- [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
8257
- [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
8258
- [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
8259
- [Blockchain.Noble_Testnet]: 'uusdc',
8260
- [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
8261
- [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
8262
- [Blockchain.Polkadot_Westmint]: '31337',
8263
- [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
8264
- [Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
8265
- [Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
8266
- [Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
8267
- [Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
8268
- [Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
8269
- [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
8270
- [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
8271
- [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
8272
- [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
8273
- },
8274
- };
8275
-
8276
- // Re-export for consumers
8277
- /**
8278
- * All default token definitions.
9334
+ * import { createRuntime, createLogger } from '@core/runtime'
8279
9335
  *
8280
- * @remarks
8281
- * These tokens are automatically included in the TokenRegistry when created
8282
- * without explicit defaults. Extensions can override these definitions.
9336
+ * // Use all defaults
9337
+ * const runtime = createRuntime()
8283
9338
  *
8284
- * @example
8285
- * ```typescript
8286
- * import { createTokenRegistry } from '@core/tokens'
9339
+ * // Custom logger
9340
+ * const runtime = createRuntime({
9341
+ * logger: createLogger({ level: 'debug' }),
9342
+ * })
8287
9343
  *
8288
- * // Registry uses these by default
8289
- * const registry = createTokenRegistry()
9344
+ * // Custom metrics (e.g., Prometheus)
9345
+ * const runtime = createRuntime({
9346
+ * metrics: myPrometheusMetrics,
9347
+ * })
8290
9348
  *
8291
- * // Add custom tokens (built-ins are still included)
8292
- * const customRegistry = createTokenRegistry({
8293
- * tokens: [myCustomToken],
9349
+ * // Subscribe to events (don't replace the bus)
9350
+ * runtime.events.on('operation.*', (event) => {
9351
+ * console.log('Event:', event.name)
8294
9352
  * })
8295
9353
  * ```
8296
9354
  */
8297
- const DEFAULT_TOKENS = [USDC];
9355
+ function createRuntime(options) {
9356
+ // Resolve logger first (events may need it)
9357
+ const logger = createLogger();
9358
+ // Internal services - not configurable
9359
+ const events = createEventBus({ logger });
9360
+ const clock = defaultClock;
9361
+ // Resolve metrics
9362
+ const metrics = noopMetrics;
9363
+ return Object.freeze({ logger, events, metrics, clock });
9364
+ }
8298
9365
 
8299
9366
  /**
8300
- * Check if a selector is a raw token selector (object form).
9367
+ * Invocation context resolution - resolves the **WHO/HOW** of an operation.
8301
9368
  *
8302
- * @param selector - The token selector to check.
8303
- * @returns True if the selector is a raw token selector.
9369
+ * @packageDocumentation
8304
9370
  */
8305
- function isRawSelector(selector) {
8306
- return typeof selector === 'object' && 'locator' in selector;
8307
- }
9371
+ // ============================================================================
9372
+ // Validation Schemas
9373
+ // ============================================================================
9374
+ /**
9375
+ * Schema for validating Caller.
9376
+ */
9377
+ const callerSchema = zod.z.object({
9378
+ type: zod.z.string(),
9379
+ name: zod.z.string(),
9380
+ version: zod.z.string().optional(),
9381
+ });
9382
+ /**
9383
+ * Schema for validating InvocationMeta input.
9384
+ */
9385
+ const invocationMetaSchema = zod.z
9386
+ .object({
9387
+ traceId: zod.z.string().optional(),
9388
+ runtime: zod.z.object({}).passthrough().optional(),
9389
+ tokens: zod.z.object({}).passthrough().optional(),
9390
+ callers: zod.z.array(callerSchema).optional(),
9391
+ })
9392
+ .strict();
9393
+ // ============================================================================
9394
+ // Invocation Context Resolution
9395
+ // ============================================================================
8308
9396
  /**
8309
- * Normalize a symbol to uppercase for case-insensitive lookup.
9397
+ * Resolve invocation metadata to invocation context.
8310
9398
  *
8311
- * @param symbol - The symbol to normalize.
8312
- * @returns The normalized (uppercase) symbol.
8313
- */
8314
- function normalizeSymbol(symbol) {
8315
- return symbol.toUpperCase();
8316
- }
8317
- /**
8318
- * Create a token registry with built-in tokens and optional extensions.
9399
+ * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
9400
+ * @param defaults - Default runtime and tokens to use if not overridden.
9401
+ * @returns Frozen, immutable invocation context with guaranteed values.
9402
+ * @throws KitError when meta contains invalid properties.
8319
9403
  *
8320
9404
  * @remarks
8321
- * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
8322
- * Custom tokens are merged on top - use this to add new tokens or override
8323
- * built-in definitions.
9405
+ * Resolves the **WHO** called and **HOW** to observe:
9406
+ * - TraceId: Uses provided value or generates new one
9407
+ * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
9408
+ * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
9409
+ * - Callers: Uses provided array or empty array
8324
9410
  *
8325
- * @param options - Configuration options for the registry.
8326
- * @returns A token registry instance.
9411
+ * The returned context is frozen to enforce immutability.
8327
9412
  *
8328
9413
  * @example
8329
9414
  * ```typescript
9415
+ * import { resolveInvocationContext, createRuntime } from '@core/runtime'
8330
9416
  * import { createTokenRegistry } from '@core/tokens'
8331
9417
  *
8332
- * // Create registry with built-in tokens (USDC, etc.)
8333
- * const registry = createTokenRegistry()
8334
- *
8335
- * // Resolve USDC on Ethereum
8336
- * const usdc = registry.resolve('USDC', 'Ethereum')
8337
- * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
8338
- * console.log(usdc.decimals) // 6
8339
- * ```
8340
- *
8341
- * @example
8342
- * ```typescript
8343
- * // Add custom tokens (built-ins are still included)
8344
- * const myToken: TokenDefinition = {
8345
- * symbol: 'MY',
8346
- * decimals: 18,
8347
- * locators: { Ethereum: '0x...' },
9418
+ * const defaults = {
9419
+ * runtime: createRuntime(),
9420
+ * tokens: createTokenRegistry(),
8348
9421
  * }
8349
9422
  *
8350
- * const registry = createTokenRegistry({ tokens: [myToken] })
8351
- * registry.resolve('USDC', 'Ethereum') // Still works!
8352
- * registry.resolve('MY', 'Ethereum') // Also works
8353
- * ```
8354
- *
8355
- * @example
8356
- * ```typescript
8357
- * // Override a built-in token
8358
- * const customUsdc: TokenDefinition = {
8359
- * symbol: 'USDC',
8360
- * decimals: 6,
8361
- * locators: { MyChain: '0xCustomAddress' },
8362
- * }
9423
+ * // Minimal - just using defaults
9424
+ * const ctx = resolveInvocationContext(undefined, defaults)
8363
9425
  *
8364
- * const registry = createTokenRegistry({ tokens: [customUsdc] })
8365
- * // Now USDC resolves to customUsdc definition
8366
- * ```
9426
+ * // With trace ID and caller info
9427
+ * const ctx = resolveInvocationContext(
9428
+ * {
9429
+ * traceId: 'abc-123',
9430
+ * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
9431
+ * },
9432
+ * defaults
9433
+ * )
8367
9434
  *
8368
- * @example
8369
- * ```typescript
8370
- * // Resolve arbitrary tokens by raw locator
8371
- * const registry = createTokenRegistry()
8372
- * const token = registry.resolve(
8373
- * { locator: '0x1234...', decimals: 18 },
8374
- * 'Ethereum'
9435
+ * // With runtime override (complete replacement)
9436
+ * const ctx = resolveInvocationContext(
9437
+ * { runtime: createRuntime({ logger: myLogger }) },
9438
+ * defaults
8375
9439
  * )
8376
9440
  * ```
8377
9441
  */
8378
- function createTokenRegistry(options = {}) {
8379
- const { tokens = [], requireDecimals = false } = options;
8380
- // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
8381
- const tokenMap = new Map();
8382
- // Add built-in tokens first
8383
- for (const def of DEFAULT_TOKENS) {
8384
- tokenMap.set(normalizeSymbol(def.symbol), def);
8385
- }
8386
- // Custom tokens override built-ins
8387
- for (const def of tokens) {
8388
- tokenMap.set(normalizeSymbol(def.symbol), def);
8389
- }
8390
- /**
8391
- * Resolve a symbol selector to token information.
8392
- */
8393
- function resolveSymbol(symbol, chainId) {
8394
- const normalizedSymbol = normalizeSymbol(symbol);
8395
- const definition = tokenMap.get(normalizedSymbol);
8396
- if (definition === undefined) {
8397
- throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
8398
- }
8399
- const locator = definition.locators[chainId];
8400
- if (locator === undefined || locator.trim() === '') {
8401
- throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
8402
- }
8403
- return {
8404
- symbol: definition.symbol,
8405
- decimals: definition.decimals,
8406
- locator,
8407
- };
8408
- }
8409
- /**
8410
- * Resolve a raw selector to token information.
8411
- */
8412
- function resolveRaw(selector, chainId) {
8413
- const { locator, decimals } = selector;
8414
- // Validate locator
8415
- if (!locator || typeof locator !== 'string') {
8416
- throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
8417
- }
8418
- // Decimals are always required for raw selectors
8419
- if (decimals === undefined) {
8420
- const message = requireDecimals
8421
- ? 'Decimals required for raw token selector (requireDecimals: true)'
8422
- : 'Decimals required for raw token selector. Provide { locator, decimals }.';
8423
- throw createTokenResolutionError(message, selector, chainId);
8424
- }
8425
- // Validate decimals
8426
- if (typeof decimals !== 'number' ||
8427
- decimals < 0 ||
8428
- !Number.isInteger(decimals)) {
8429
- throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
9442
+ function resolveInvocationContext(meta, defaults) {
9443
+ // Validate meta input if provided
9444
+ if (meta !== undefined) {
9445
+ const result = invocationMetaSchema.safeParse(meta);
9446
+ if (!result.success) {
9447
+ throw createValidationFailedError('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
8430
9448
  }
8431
- return {
8432
- decimals,
8433
- locator,
8434
- };
8435
9449
  }
8436
- return {
8437
- resolve(selector, chainId) {
8438
- // Runtime validation of inputs - these checks are for JS consumers
8439
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
8440
- if (selector === null || selector === undefined) {
8441
- throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
8442
- }
8443
- if (chainId === '' || typeof chainId !== 'string') {
8444
- throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
8445
- }
8446
- // Dispatch based on selector type
8447
- if (isRawSelector(selector)) {
8448
- return resolveRaw(selector, chainId);
8449
- }
8450
- if (typeof selector === 'string') {
8451
- return resolveSymbol(selector, chainId);
8452
- }
8453
- throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
8454
- },
8455
- get(symbol) {
8456
- if (!symbol || typeof symbol !== 'string') {
8457
- return undefined;
8458
- }
8459
- return tokenMap.get(normalizeSymbol(symbol));
8460
- },
8461
- has(symbol) {
8462
- if (!symbol || typeof symbol !== 'string') {
8463
- return false;
8464
- }
8465
- return tokenMap.has(normalizeSymbol(symbol));
8466
- },
8467
- symbols() {
8468
- return Array.from(tokenMap.values()).map((def) => def.symbol);
8469
- },
8470
- entries() {
8471
- return Array.from(tokenMap.values());
8472
- },
8473
- };
9450
+ // Generate trace ID if not provided
9451
+ const traceId = meta?.traceId ?? createTraceId();
9452
+ // Use meta overrides or fall back to defaults
9453
+ const runtime = meta?.runtime ?? defaults.runtime;
9454
+ const tokens = meta?.tokens ?? defaults.tokens;
9455
+ const callers = meta?.callers ?? [];
9456
+ return Object.freeze({ traceId, runtime, tokens, callers });
8474
9457
  }
8475
9458
 
8476
9459
  /**
8477
- * Define a schema for token registry interfaces.
9460
+ * Extend an invocation context by appending a caller to its call chain.
9461
+ *
9462
+ * @param context - The existing invocation context to extend.
9463
+ * @param caller - The caller to append to the call chain.
9464
+ * @returns A new frozen invocation context with the caller appended.
8478
9465
  *
8479
9466
  * @remarks
8480
- * Validate that a registry exposes the `resolve()` API used by adapters.
9467
+ * This function creates a new immutable context with the caller appended
9468
+ * to the `callers` array while preserving all other context properties
9469
+ * (traceId, runtime, tokens).
9470
+ *
9471
+ * The returned context is frozen to enforce immutability.
8481
9472
  *
8482
9473
  * @example
8483
9474
  * ```typescript
8484
- * import { tokenRegistrySchema } from '@core/tokens'
8485
- *
8486
- * const registry = {
8487
- * resolve: () => ({ locator: '0x0', decimals: 6 }),
8488
- * }
9475
+ * import { extendInvocationContext } from '@core/runtime'
8489
9476
  *
8490
- * tokenRegistrySchema.parse(registry)
9477
+ * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
9478
+ * const extended = extendInvocationContext(existingContext, caller)
9479
+ * // extended.callers === [...existingContext.callers, caller]
8491
9480
  * ```
8492
9481
  */
8493
- zod.z.custom((value) => {
8494
- if (value === null || typeof value !== 'object') {
8495
- return false;
8496
- }
8497
- const record = value;
8498
- return (typeof record['resolve'] === 'function' &&
8499
- typeof record['get'] === 'function' &&
8500
- typeof record['has'] === 'function' &&
8501
- typeof record['symbols'] === 'function' &&
8502
- typeof record['entries'] === 'function');
8503
- }, {
8504
- message: 'Invalid token registry',
8505
- });
9482
+ function extendInvocationContext(context, caller) {
9483
+ return Object.freeze({
9484
+ traceId: context.traceId,
9485
+ runtime: context.runtime,
9486
+ tokens: context.tokens,
9487
+ callers: [...context.callers, caller],
9488
+ });
9489
+ }
9490
+
9491
+ // Clock - expose defaultClock for backward compatibility
9492
+ /** Clock validation schema (backward compatibility). */
9493
+ zod.z.custom((val) => val !== null &&
9494
+ typeof val === 'object' &&
9495
+ 'now' in val &&
9496
+ typeof val['now'] === 'function');
9497
+ /** EventBus validation schema (backward compatibility). */
9498
+ zod.z.custom((val) => val !== null &&
9499
+ typeof val === 'object' &&
9500
+ 'emit' in val &&
9501
+ typeof val['emit'] === 'function');
9502
+ /** Runtime validation schema (backward compatibility). */
9503
+ zod.z
9504
+ .object({
9505
+ logger: zod.z.any().optional(),
9506
+ events: zod.z.any().optional(),
9507
+ metrics: zod.z.any().optional(),
9508
+ clock: zod.z.any().optional(),
9509
+ })
9510
+ .passthrough();
8506
9511
 
8507
- var version = "1.4.0";
9512
+ var version = "1.5.0";
8508
9513
  var pkg = {
8509
9514
  version: version};
8510
9515
 
@@ -8987,6 +9992,24 @@ const validateNativeBalanceForTransaction = async (params) => {
8987
9992
  }
8988
9993
  };
8989
9994
 
9995
+ /**
9996
+ * Permit signature standards for gasless token approvals.
9997
+ *
9998
+ * Defines the permit types that can be used to approve token spending
9999
+ * without requiring a separate approval transaction.
10000
+ *
10001
+ * @remarks
10002
+ * - NONE: No permit, tokens must be pre-approved via separate transaction
10003
+ * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
10004
+ */
10005
+ var PermitType;
10006
+ (function (PermitType) {
10007
+ /** No permit required - tokens must be pre-approved */
10008
+ PermitType[PermitType["NONE"] = 0] = "NONE";
10009
+ /** EIP-2612 standard permit */
10010
+ PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
10011
+ })(PermitType || (PermitType = {}));
10012
+
8990
10013
  /**
8991
10014
  * CCTP bridge step names that can occur in the bridging flow.
8992
10015
  *