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

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;
@@ -116,6 +116,122 @@ var Blockchain;
116
116
  Blockchain["ZKSync_Era"] = "ZKSync_Era";
117
117
  Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
118
118
  })(Blockchain || (Blockchain = {}));
119
+ /**
120
+ * Enum representing the subset of {@link Blockchain} that supports swap operations.
121
+ *
122
+ * This enum provides compile-time type safety for swap chain selection,
123
+ * ensuring only supported chains are available in IDE autocomplete
124
+ * when building swap parameters.
125
+ *
126
+ * @remarks
127
+ * Unlike the full {@link Blockchain} enum, SwapChain only includes networks
128
+ * where the swap functionality is actively supported by the library.
129
+ * Using this enum prevents runtime errors from attempting unsupported
130
+ * cross-chain swaps.
131
+ *
132
+ * Currently supports:
133
+ * - Ethereum mainnet
134
+ * - Base mainnet
135
+ * - Polygon mainnet
136
+ * - Solana mainnet
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * import { SwapChain, swap, createSwapKitContext } from '@circle-fin/swap-kit'
141
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
142
+ *
143
+ * const context = createSwapKitContext()
144
+ * const adapter = createViemAdapterFromPrivateKey({
145
+ * privateKey: process.env.PRIVATE_KEY
146
+ * })
147
+ *
148
+ * // ✅ Autocomplete shows only swap-supported chains
149
+ * const result = await swap(context, {
150
+ * from: {
151
+ * adapter,
152
+ * chain: SwapChain.Ethereum // Autocomplete: Ethereum, Base, Polygon, Solana
153
+ * },
154
+ * tokenIn: 'USDC',
155
+ * tokenOut: 'USDT',
156
+ * amount: '100.0'
157
+ * })
158
+ * ```
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * // String literals also work (constrained to SwapChain values)
163
+ * const result = await swap(context, {
164
+ * from: {
165
+ * adapter,
166
+ * chain: 'Ethereum' // ✅ Only SwapChain strings allowed
167
+ * },
168
+ * tokenIn: 'USDC',
169
+ * tokenOut: 'NATIVE',
170
+ * amount: '50.0'
171
+ * })
172
+ *
173
+ * // ❌ TypeScript error - Sui not in SwapChain enum
174
+ * const invalidResult = await swap(context, {
175
+ * from: {
176
+ * adapter,
177
+ * chain: 'Sui' // Compile-time error!
178
+ * },
179
+ * tokenIn: 'USDC',
180
+ * tokenOut: 'USDT',
181
+ * amount: '100.0'
182
+ * })
183
+ * ```
184
+ */
185
+ /**
186
+ * Enum representing chains that support same-chain swaps through the Swap Kit.
187
+ *
188
+ * Unlike the full {@link Blockchain} enum, SwapChain includes only mainnet
189
+ * networks where adapter contracts are deployed (CCTPv2 support).
190
+ *
191
+ * Dynamic validation via {@link isSwapSupportedChain} ensures chains
192
+ * automatically work when adapter contracts and supported tokens are deployed.
193
+ *
194
+ * @example
195
+ * ```typescript
196
+ * import { SwapChain } from '@core/chains'
197
+ * import { swap } from '@circle-fin/swap-kit'
198
+ *
199
+ * const result = await swap(context, {
200
+ * from: {
201
+ * adapter,
202
+ * chain: SwapChain.Arbitrum // Now supported!
203
+ * },
204
+ * tokenIn: 'USDC',
205
+ * tokenOut: 'WETH',
206
+ * amount: '100.0'
207
+ * })
208
+ * ```
209
+ *
210
+ * @see {@link isSwapSupportedChain} for runtime validation
211
+ * @see {@link getSwapSupportedChains} for all supported chains
212
+ */
213
+ var SwapChain;
214
+ (function (SwapChain) {
215
+ // Original 4 chains
216
+ SwapChain["Ethereum"] = "Ethereum";
217
+ SwapChain["Base"] = "Base";
218
+ SwapChain["Polygon"] = "Polygon";
219
+ SwapChain["Solana"] = "Solana";
220
+ // Additional supported chains
221
+ SwapChain["Arbitrum"] = "Arbitrum";
222
+ SwapChain["Optimism"] = "Optimism";
223
+ SwapChain["Avalanche"] = "Avalanche";
224
+ SwapChain["Linea"] = "Linea";
225
+ SwapChain["Ink"] = "Ink";
226
+ SwapChain["World_Chain"] = "World_Chain";
227
+ SwapChain["Unichain"] = "Unichain";
228
+ SwapChain["Plume"] = "Plume";
229
+ SwapChain["Sei"] = "Sei";
230
+ SwapChain["Sonic"] = "Sonic";
231
+ SwapChain["XDC"] = "XDC";
232
+ SwapChain["HyperEVM"] = "HyperEVM";
233
+ SwapChain["Monad"] = "Monad";
234
+ })(SwapChain || (SwapChain = {}));
119
235
  // -----------------------------------------------------------------------------
120
236
  // Bridge Chain Enum (CCTPv2 Supported Chains)
121
237
  // -----------------------------------------------------------------------------
@@ -267,6 +383,7 @@ const Algorand = defineChain({
267
383
  rpcEndpoints: ['https://mainnet-api.algonode.cloud'],
268
384
  eurcAddress: null,
269
385
  usdcAddress: '31566704',
386
+ usdtAddress: null,
270
387
  cctp: null,
271
388
  });
272
389
 
@@ -290,6 +407,7 @@ const AlgorandTestnet = defineChain({
290
407
  rpcEndpoints: ['https://testnet-api.algonode.cloud'],
291
408
  eurcAddress: null,
292
409
  usdcAddress: '10458941',
410
+ usdtAddress: null,
293
411
  cctp: null,
294
412
  });
295
413
 
@@ -313,6 +431,7 @@ const Aptos = defineChain({
313
431
  rpcEndpoints: ['https://fullnode.mainnet.aptoslabs.com/v1'],
314
432
  eurcAddress: null,
315
433
  usdcAddress: '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b',
434
+ usdtAddress: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
316
435
  cctp: {
317
436
  domain: 9,
318
437
  contracts: {
@@ -350,6 +469,7 @@ const AptosTestnet = defineChain({
350
469
  rpcEndpoints: ['https://fullnode.testnet.aptoslabs.com/v1'],
351
470
  eurcAddress: null,
352
471
  usdcAddress: '0x69091fbab5f7d635ee7ac5098cf0c1efbe31d68fec0f2cd565e8d168daf52832',
472
+ usdtAddress: null,
353
473
  cctp: {
354
474
  domain: 9,
355
475
  contracts: {
@@ -367,6 +487,121 @@ const AptosTestnet = defineChain({
367
487
  },
368
488
  });
369
489
 
490
+ /**
491
+ * Complete swap token registry - single source of truth for all swap-supported tokens.
492
+ *
493
+ * @remarks
494
+ * All packages should import from this registry for swap operations.
495
+ * Adding a new swap token requires updating only this registry.
496
+ *
497
+ * The NATIVE token is handled separately as it resolves dynamically based on chain.
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * import { SWAP_TOKEN_REGISTRY } from '@core/chains'
502
+ *
503
+ * // Get token decimals
504
+ * const decimals = SWAP_TOKEN_REGISTRY.USDC.decimals // 6
505
+ *
506
+ * // Check if token is stablecoin
507
+ * const isStable = SWAP_TOKEN_REGISTRY.DAI.category === 'stablecoin' // true
508
+ * ```
509
+ */
510
+ const SWAP_TOKEN_REGISTRY = {
511
+ // ============================================================================
512
+ // Stablecoins (6 decimals)
513
+ // ============================================================================
514
+ USDC: {
515
+ symbol: 'USDC',
516
+ decimals: 6,
517
+ category: 'stablecoin',
518
+ description: 'USD Coin',
519
+ },
520
+ EURC: {
521
+ symbol: 'EURC',
522
+ decimals: 6,
523
+ category: 'stablecoin',
524
+ description: 'Euro Coin',
525
+ },
526
+ USDT: {
527
+ symbol: 'USDT',
528
+ decimals: 6,
529
+ category: 'stablecoin',
530
+ description: 'Tether USD',
531
+ },
532
+ PYUSD: {
533
+ symbol: 'PYUSD',
534
+ decimals: 6,
535
+ category: 'stablecoin',
536
+ description: 'PayPal USD',
537
+ },
538
+ // ============================================================================
539
+ // Stablecoins (18 decimals)
540
+ // ============================================================================
541
+ DAI: {
542
+ symbol: 'DAI',
543
+ decimals: 18,
544
+ category: 'stablecoin',
545
+ description: 'MakerDAO stablecoin',
546
+ },
547
+ USDE: {
548
+ symbol: 'USDE',
549
+ decimals: 18,
550
+ category: 'stablecoin',
551
+ description: 'Ethena USD (synthetic dollar)',
552
+ },
553
+ // ============================================================================
554
+ // Wrapped Tokens
555
+ // ============================================================================
556
+ WBTC: {
557
+ symbol: 'WBTC',
558
+ decimals: 8,
559
+ category: 'wrapped',
560
+ description: 'Wrapped Bitcoin',
561
+ },
562
+ WETH: {
563
+ symbol: 'WETH',
564
+ decimals: 18,
565
+ category: 'wrapped',
566
+ description: 'Wrapped Ethereum',
567
+ },
568
+ WSOL: {
569
+ symbol: 'WSOL',
570
+ decimals: 9,
571
+ category: 'wrapped',
572
+ description: 'Wrapped Solana',
573
+ },
574
+ WAVAX: {
575
+ symbol: 'WAVAX',
576
+ decimals: 18,
577
+ category: 'wrapped',
578
+ description: 'Wrapped Avalanche',
579
+ },
580
+ WPOL: {
581
+ symbol: 'WPOL',
582
+ decimals: 18,
583
+ category: 'wrapped',
584
+ description: 'Wrapped Polygon',
585
+ },
586
+ };
587
+ /**
588
+ * Special NATIVE token constant for swap operations.
589
+ *
590
+ * @remarks
591
+ * NATIVE is handled separately from SWAP_TOKEN_REGISTRY because it resolves
592
+ * dynamically based on the chain (ETH on Ethereum, SOL on Solana, etc.).
593
+ * Its decimals are chain-specific.
594
+ */
595
+ const NATIVE_TOKEN = 'NATIVE';
596
+ /**
597
+ * Array of all supported swap token symbols including NATIVE.
598
+ * Useful for iteration, validation, and filtering.
599
+ */
600
+ [
601
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
602
+ NATIVE_TOKEN,
603
+ ];
604
+
370
605
  /**
371
606
  * The bridge contract address for EVM testnet networks.
372
607
  *
@@ -383,6 +618,13 @@ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d'
383
618
  * USDC transfers on live networks.
384
619
  */
385
620
  const BRIDGE_CONTRACT_EVM_MAINNET = '0xB3FA262d0fB521cc93bE83d87b322b8A23DAf3F0';
621
+ /**
622
+ * The adapter contract address for EVM mainnet networks.
623
+ *
624
+ * This contract serves as an adapter for integrating with various protocols
625
+ * on EVM-compatible chains. Use this address for mainnet adapter integrations.
626
+ */
627
+ const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845';
386
628
 
387
629
  /**
388
630
  * Arc Testnet chain definition
@@ -412,6 +654,7 @@ const ArcTestnet = defineChain({
412
654
  rpcEndpoints: ['https://rpc.testnet.arc.network/'],
413
655
  eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
414
656
  usdcAddress: '0x3600000000000000000000000000000000000000',
657
+ usdtAddress: null,
415
658
  cctp: {
416
659
  domain: 26,
417
660
  contracts: {
@@ -454,6 +697,7 @@ const Arbitrum = defineChain({
454
697
  rpcEndpoints: ['https://arb1.arbitrum.io/rpc'],
455
698
  eurcAddress: null,
456
699
  usdcAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
700
+ usdtAddress: null,
457
701
  cctp: {
458
702
  domain: 3,
459
703
  contracts: {
@@ -478,6 +722,7 @@ const Arbitrum = defineChain({
478
722
  },
479
723
  kitContracts: {
480
724
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
725
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
481
726
  },
482
727
  });
483
728
 
@@ -502,6 +747,7 @@ const ArbitrumSepolia = defineChain({
502
747
  rpcEndpoints: ['https://sepolia-rollup.arbitrum.io/rpc'],
503
748
  eurcAddress: null,
504
749
  usdcAddress: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
750
+ usdtAddress: null,
505
751
  cctp: {
506
752
  domain: 3,
507
753
  contracts: {
@@ -550,6 +796,7 @@ const Avalanche = defineChain({
550
796
  rpcEndpoints: ['https://api.avax.network/ext/bc/C/rpc'],
551
797
  eurcAddress: '0xc891eb4cbdeff6e073e859e987815ed1505c2acd',
552
798
  usdcAddress: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
799
+ usdtAddress: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7',
553
800
  cctp: {
554
801
  domain: 1,
555
802
  contracts: {
@@ -574,6 +821,7 @@ const Avalanche = defineChain({
574
821
  },
575
822
  kitContracts: {
576
823
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
824
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
577
825
  },
578
826
  });
579
827
 
@@ -597,6 +845,7 @@ const AvalancheFuji = defineChain({
597
845
  explorerUrl: 'https://subnets-test.avax.network/c-chain/tx/{hash}',
598
846
  eurcAddress: '0x5e44db7996c682e92a960b65ac713a54ad815c6b',
599
847
  usdcAddress: '0x5425890298aed601595a70ab815c96711a31bc65',
848
+ usdtAddress: null,
600
849
  cctp: {
601
850
  domain: 1,
602
851
  contracts: {
@@ -646,6 +895,7 @@ const Base = defineChain({
646
895
  rpcEndpoints: ['https://mainnet.base.org', 'https://base.publicnode.com'],
647
896
  eurcAddress: '0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42',
648
897
  usdcAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
898
+ usdtAddress: null,
649
899
  cctp: {
650
900
  domain: 6,
651
901
  contracts: {
@@ -670,6 +920,7 @@ const Base = defineChain({
670
920
  },
671
921
  kitContracts: {
672
922
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
923
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
673
924
  },
674
925
  });
675
926
 
@@ -694,6 +945,7 @@ const BaseSepolia = defineChain({
694
945
  rpcEndpoints: ['https://sepolia.base.org'],
695
946
  eurcAddress: '0x808456652fdb597867f38412077A9182bf77359F',
696
947
  usdcAddress: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
948
+ usdtAddress: null,
697
949
  cctp: {
698
950
  domain: 6,
699
951
  contracts: {
@@ -742,6 +994,7 @@ const Celo = defineChain({
742
994
  rpcEndpoints: ['https://forno.celo.org'],
743
995
  eurcAddress: null,
744
996
  usdcAddress: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
997
+ usdtAddress: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
745
998
  cctp: null,
746
999
  });
747
1000
 
@@ -766,6 +1019,7 @@ const CeloAlfajoresTestnet = defineChain({
766
1019
  rpcEndpoints: ['https://alfajores-forno.celo-testnet.org'],
767
1020
  eurcAddress: null,
768
1021
  usdcAddress: '0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B',
1022
+ usdtAddress: null,
769
1023
  cctp: null,
770
1024
  });
771
1025
 
@@ -790,6 +1044,7 @@ const Codex = defineChain({
790
1044
  rpcEndpoints: ['https://rpc.codex.xyz'],
791
1045
  eurcAddress: null,
792
1046
  usdcAddress: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
1047
+ usdtAddress: null,
793
1048
  cctp: {
794
1049
  domain: 12,
795
1050
  contracts: {
@@ -832,6 +1087,7 @@ const CodexTestnet = defineChain({
832
1087
  rpcEndpoints: ['https://rpc.codex-stg.xyz'],
833
1088
  eurcAddress: null,
834
1089
  usdcAddress: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
1090
+ usdtAddress: null,
835
1091
  cctp: {
836
1092
  domain: 12,
837
1093
  contracts: {
@@ -874,6 +1130,7 @@ const Ethereum = defineChain({
874
1130
  rpcEndpoints: ['https://eth.merkle.io', 'https://ethereum.publicnode.com'],
875
1131
  eurcAddress: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
876
1132
  usdcAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
1133
+ usdtAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7',
877
1134
  cctp: {
878
1135
  domain: 0,
879
1136
  contracts: {
@@ -898,6 +1155,7 @@ const Ethereum = defineChain({
898
1155
  },
899
1156
  kitContracts: {
900
1157
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1158
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
901
1159
  },
902
1160
  });
903
1161
 
@@ -922,6 +1180,7 @@ const EthereumSepolia = defineChain({
922
1180
  rpcEndpoints: ['https://sepolia.drpc.org'],
923
1181
  eurcAddress: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
924
1182
  usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
1183
+ usdtAddress: null,
925
1184
  cctp: {
926
1185
  domain: 0,
927
1186
  contracts: {
@@ -969,6 +1228,7 @@ const Hedera = defineChain({
969
1228
  rpcEndpoints: ['https://mainnet.hashio.io/api'],
970
1229
  eurcAddress: null,
971
1230
  usdcAddress: '0.0.456858',
1231
+ usdtAddress: null,
972
1232
  cctp: null,
973
1233
  });
974
1234
 
@@ -992,6 +1252,7 @@ const HederaTestnet = defineChain({
992
1252
  rpcEndpoints: ['https://testnet.hashio.io/api'],
993
1253
  eurcAddress: null,
994
1254
  usdcAddress: '0.0.429274',
1255
+ usdtAddress: null,
995
1256
  cctp: null,
996
1257
  });
997
1258
 
@@ -1018,6 +1279,7 @@ const HyperEVM = defineChain({
1018
1279
  rpcEndpoints: ['https://rpc.hyperliquid.xyz/evm'],
1019
1280
  eurcAddress: null,
1020
1281
  usdcAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
1282
+ usdtAddress: null,
1021
1283
  cctp: {
1022
1284
  domain: 19,
1023
1285
  contracts: {
@@ -1036,6 +1298,7 @@ const HyperEVM = defineChain({
1036
1298
  },
1037
1299
  kitContracts: {
1038
1300
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1301
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1039
1302
  },
1040
1303
  });
1041
1304
 
@@ -1061,6 +1324,7 @@ const HyperEVMTestnet = defineChain({
1061
1324
  rpcEndpoints: ['https://rpc.hyperliquid-testnet.xyz/evm'],
1062
1325
  eurcAddress: null,
1063
1326
  usdcAddress: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
1327
+ usdtAddress: null,
1064
1328
  cctp: {
1065
1329
  domain: 19,
1066
1330
  contracts: {
@@ -1108,6 +1372,7 @@ const Ink = defineChain({
1108
1372
  ],
1109
1373
  eurcAddress: null,
1110
1374
  usdcAddress: '0x2D270e6886d130D724215A266106e6832161EAEd',
1375
+ usdtAddress: null,
1111
1376
  cctp: {
1112
1377
  domain: 21,
1113
1378
  contracts: {
@@ -1126,6 +1391,7 @@ const Ink = defineChain({
1126
1391
  },
1127
1392
  kitContracts: {
1128
1393
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1394
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1129
1395
  },
1130
1396
  });
1131
1397
 
@@ -1154,6 +1420,7 @@ const InkTestnet = defineChain({
1154
1420
  ],
1155
1421
  eurcAddress: null,
1156
1422
  usdcAddress: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
1423
+ usdtAddress: null,
1157
1424
  cctp: {
1158
1425
  domain: 21,
1159
1426
  contracts: {
@@ -1196,6 +1463,7 @@ const Linea = defineChain({
1196
1463
  rpcEndpoints: ['https://rpc.linea.build'],
1197
1464
  eurcAddress: null,
1198
1465
  usdcAddress: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
1466
+ usdtAddress: null,
1199
1467
  cctp: {
1200
1468
  domain: 11,
1201
1469
  contracts: {
@@ -1214,6 +1482,7 @@ const Linea = defineChain({
1214
1482
  },
1215
1483
  kitContracts: {
1216
1484
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1485
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1217
1486
  },
1218
1487
  });
1219
1488
 
@@ -1238,6 +1507,7 @@ const LineaSepolia = defineChain({
1238
1507
  rpcEndpoints: ['https://rpc.sepolia.linea.build'],
1239
1508
  eurcAddress: null,
1240
1509
  usdcAddress: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
1510
+ usdtAddress: null,
1241
1511
  cctp: {
1242
1512
  domain: 11,
1243
1513
  contracts: {
@@ -1282,6 +1552,7 @@ const Monad = defineChain({
1282
1552
  rpcEndpoints: ['https://rpc.monad.xyz'],
1283
1553
  eurcAddress: null,
1284
1554
  usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
1555
+ usdtAddress: null,
1285
1556
  cctp: {
1286
1557
  domain: 15,
1287
1558
  contracts: {
@@ -1300,6 +1571,7 @@ const Monad = defineChain({
1300
1571
  },
1301
1572
  kitContracts: {
1302
1573
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1574
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1303
1575
  },
1304
1576
  });
1305
1577
 
@@ -1326,6 +1598,7 @@ const MonadTestnet = defineChain({
1326
1598
  rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
1327
1599
  eurcAddress: null,
1328
1600
  usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
1601
+ usdtAddress: null,
1329
1602
  cctp: {
1330
1603
  domain: 15,
1331
1604
  contracts: {
@@ -1367,6 +1640,7 @@ const NEAR = defineChain({
1367
1640
  rpcEndpoints: ['https://eth-rpc.mainnet.near.org'],
1368
1641
  eurcAddress: null,
1369
1642
  usdcAddress: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
1643
+ usdtAddress: 'usdt.tether-token.near',
1370
1644
  cctp: null,
1371
1645
  });
1372
1646
 
@@ -1390,6 +1664,7 @@ const NEARTestnet = defineChain({
1390
1664
  rpcEndpoints: ['https://eth-rpc.testnet.near.org'],
1391
1665
  eurcAddress: null,
1392
1666
  usdcAddress: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
1667
+ usdtAddress: null,
1393
1668
  cctp: null,
1394
1669
  });
1395
1670
 
@@ -1413,6 +1688,7 @@ const Noble = defineChain({
1413
1688
  rpcEndpoints: ['https://noble-rpc.polkachu.com'],
1414
1689
  eurcAddress: null,
1415
1690
  usdcAddress: 'uusdc',
1691
+ usdtAddress: null,
1416
1692
  cctp: {
1417
1693
  domain: 4,
1418
1694
  contracts: {
@@ -1449,6 +1725,7 @@ const NobleTestnet = defineChain({
1449
1725
  rpcEndpoints: ['https://noble-testnet-rpc.polkachu.com'],
1450
1726
  eurcAddress: null,
1451
1727
  usdcAddress: 'uusdc',
1728
+ usdtAddress: null,
1452
1729
  cctp: {
1453
1730
  domain: 4,
1454
1731
  contracts: {
@@ -1486,6 +1763,7 @@ const Optimism = defineChain({
1486
1763
  rpcEndpoints: ['https://mainnet.optimism.io'],
1487
1764
  eurcAddress: null,
1488
1765
  usdcAddress: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
1766
+ usdtAddress: null,
1489
1767
  cctp: {
1490
1768
  domain: 2,
1491
1769
  contracts: {
@@ -1510,6 +1788,7 @@ const Optimism = defineChain({
1510
1788
  },
1511
1789
  kitContracts: {
1512
1790
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1791
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1513
1792
  },
1514
1793
  });
1515
1794
 
@@ -1534,6 +1813,7 @@ const OptimismSepolia = defineChain({
1534
1813
  rpcEndpoints: ['https://sepolia.optimism.io'],
1535
1814
  eurcAddress: null,
1536
1815
  usdcAddress: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
1816
+ usdtAddress: null,
1537
1817
  cctp: {
1538
1818
  domain: 2,
1539
1819
  contracts: {
@@ -1584,6 +1864,7 @@ const Plume = defineChain({
1584
1864
  rpcEndpoints: ['https://rpc.plume.org'],
1585
1865
  eurcAddress: null,
1586
1866
  usdcAddress: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
1867
+ usdtAddress: null,
1587
1868
  cctp: {
1588
1869
  domain: 22,
1589
1870
  contracts: {
@@ -1602,6 +1883,7 @@ const Plume = defineChain({
1602
1883
  },
1603
1884
  kitContracts: {
1604
1885
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1886
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1605
1887
  },
1606
1888
  });
1607
1889
 
@@ -1627,6 +1909,7 @@ const PlumeTestnet = defineChain({
1627
1909
  rpcEndpoints: ['https://testnet-rpc.plume.org'],
1628
1910
  eurcAddress: null,
1629
1911
  usdcAddress: '0xcB5f30e335672893c7eb944B374c196392C19D18',
1912
+ usdtAddress: null,
1630
1913
  cctp: {
1631
1914
  domain: 22,
1632
1915
  contracts: {
@@ -1668,6 +1951,7 @@ const PolkadotAssetHub = defineChain({
1668
1951
  rpcEndpoints: ['https://asset-hub-polkadot-rpc.n.dwellir.com'],
1669
1952
  eurcAddress: null,
1670
1953
  usdcAddress: '1337',
1954
+ usdtAddress: '1984',
1671
1955
  cctp: null,
1672
1956
  });
1673
1957
 
@@ -1691,6 +1975,7 @@ const PolkadotWestmint = defineChain({
1691
1975
  rpcEndpoints: ['https://westmint-rpc.polkadot.io'],
1692
1976
  eurcAddress: null,
1693
1977
  usdcAddress: 'Asset ID 31337',
1978
+ usdtAddress: null,
1694
1979
  cctp: null,
1695
1980
  });
1696
1981
 
@@ -1712,9 +1997,10 @@ const Polygon = defineChain({
1712
1997
  chainId: 137,
1713
1998
  isTestnet: false,
1714
1999
  explorerUrl: 'https://polygonscan.com/tx/{hash}',
1715
- rpcEndpoints: ['https://polygon-rpc.com', 'https://polygon.publicnode.com'],
2000
+ rpcEndpoints: ['https://polygon.publicnode.com', 'https://polygon.drpc.org'],
1716
2001
  eurcAddress: null,
1717
2002
  usdcAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
2003
+ usdtAddress: null,
1718
2004
  cctp: {
1719
2005
  domain: 7,
1720
2006
  contracts: {
@@ -1739,6 +2025,7 @@ const Polygon = defineChain({
1739
2025
  },
1740
2026
  kitContracts: {
1741
2027
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2028
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1742
2029
  },
1743
2030
  });
1744
2031
 
@@ -1763,6 +2050,7 @@ const PolygonAmoy = defineChain({
1763
2050
  rpcEndpoints: ['https://rpc-amoy.polygon.technology'],
1764
2051
  eurcAddress: null,
1765
2052
  usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
2053
+ usdtAddress: null,
1766
2054
  cctp: {
1767
2055
  domain: 7,
1768
2056
  contracts: {
@@ -1813,6 +2101,7 @@ const Sei = defineChain({
1813
2101
  rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
1814
2102
  eurcAddress: null,
1815
2103
  usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
2104
+ usdtAddress: null,
1816
2105
  cctp: {
1817
2106
  domain: 16,
1818
2107
  contracts: {
@@ -1831,6 +2120,7 @@ const Sei = defineChain({
1831
2120
  },
1832
2121
  kitContracts: {
1833
2122
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2123
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1834
2124
  },
1835
2125
  });
1836
2126
 
@@ -1856,6 +2146,7 @@ const SeiTestnet = defineChain({
1856
2146
  rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
1857
2147
  eurcAddress: null,
1858
2148
  usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
2149
+ usdtAddress: null,
1859
2150
  cctp: {
1860
2151
  domain: 16,
1861
2152
  contracts: {
@@ -1898,6 +2189,7 @@ const Sonic = defineChain({
1898
2189
  rpcEndpoints: ['https://rpc.soniclabs.com'],
1899
2190
  eurcAddress: null,
1900
2191
  usdcAddress: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
2192
+ usdtAddress: null,
1901
2193
  cctp: {
1902
2194
  domain: 13,
1903
2195
  contracts: {
@@ -1916,6 +2208,7 @@ const Sonic = defineChain({
1916
2208
  },
1917
2209
  kitContracts: {
1918
2210
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2211
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1919
2212
  },
1920
2213
  });
1921
2214
 
@@ -1940,6 +2233,7 @@ const SonicTestnet = defineChain({
1940
2233
  rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
1941
2234
  eurcAddress: null,
1942
2235
  usdcAddress: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
2236
+ usdtAddress: null,
1943
2237
  cctp: {
1944
2238
  domain: 13,
1945
2239
  contracts: {
@@ -1981,6 +2275,7 @@ const Solana = defineChain({
1981
2275
  rpcEndpoints: ['https://api.mainnet-beta.solana.com'],
1982
2276
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
1983
2277
  usdcAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
2278
+ usdtAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
1984
2279
  cctp: {
1985
2280
  domain: 5,
1986
2281
  contracts: {
@@ -2027,6 +2322,7 @@ const SolanaDevnet = defineChain({
2027
2322
  explorerUrl: 'https://solscan.io/tx/{hash}?cluster=devnet',
2028
2323
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
2029
2324
  usdcAddress: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
2325
+ usdtAddress: null,
2030
2326
  cctp: {
2031
2327
  domain: 5,
2032
2328
  contracts: {
@@ -2075,6 +2371,7 @@ const Stellar = defineChain({
2075
2371
  rpcEndpoints: ['https://horizon.stellar.org'],
2076
2372
  eurcAddress: 'EURC-GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2',
2077
2373
  usdcAddress: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
2374
+ usdtAddress: null,
2078
2375
  cctp: null,
2079
2376
  });
2080
2377
 
@@ -2098,6 +2395,7 @@ const StellarTestnet = defineChain({
2098
2395
  rpcEndpoints: ['https://horizon-testnet.stellar.org'],
2099
2396
  eurcAddress: 'EURC-GB3Q6QDZYTHWT7E5PVS3W7FUT5GVAFC5KSZFFLPU25GO7VTC3NM2ZTVO',
2100
2397
  usdcAddress: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
2398
+ usdtAddress: null,
2101
2399
  cctp: null,
2102
2400
  });
2103
2401
 
@@ -2121,6 +2419,7 @@ const Sui = defineChain({
2121
2419
  rpcEndpoints: ['https://fullnode.mainnet.sui.io'],
2122
2420
  eurcAddress: null,
2123
2421
  usdcAddress: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
2422
+ usdtAddress: null,
2124
2423
  cctp: {
2125
2424
  domain: 8,
2126
2425
  contracts: {
@@ -2158,6 +2457,7 @@ const SuiTestnet = defineChain({
2158
2457
  rpcEndpoints: ['https://fullnode.testnet.sui.io'],
2159
2458
  eurcAddress: null,
2160
2459
  usdcAddress: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
2460
+ usdtAddress: null,
2161
2461
  cctp: {
2162
2462
  domain: 8,
2163
2463
  contracts: {
@@ -2196,6 +2496,7 @@ const Unichain = defineChain({
2196
2496
  rpcEndpoints: ['https://mainnet.unichain.org'],
2197
2497
  eurcAddress: null,
2198
2498
  usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
2499
+ usdtAddress: null,
2199
2500
  cctp: {
2200
2501
  domain: 10,
2201
2502
  contracts: {
@@ -2220,6 +2521,7 @@ const Unichain = defineChain({
2220
2521
  },
2221
2522
  kitContracts: {
2222
2523
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2524
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2223
2525
  },
2224
2526
  });
2225
2527
 
@@ -2244,6 +2546,7 @@ const UnichainSepolia = defineChain({
2244
2546
  rpcEndpoints: ['https://sepolia.unichain.org'],
2245
2547
  eurcAddress: null,
2246
2548
  usdcAddress: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
2549
+ usdtAddress: null,
2247
2550
  cctp: {
2248
2551
  domain: 10,
2249
2552
  contracts: {
@@ -2292,6 +2595,7 @@ const WorldChain = defineChain({
2292
2595
  rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
2293
2596
  eurcAddress: null,
2294
2597
  usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
2598
+ usdtAddress: null,
2295
2599
  cctp: {
2296
2600
  domain: 14,
2297
2601
  contracts: {
@@ -2310,6 +2614,7 @@ const WorldChain = defineChain({
2310
2614
  },
2311
2615
  kitContracts: {
2312
2616
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2617
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2313
2618
  },
2314
2619
  });
2315
2620
 
@@ -2337,6 +2642,7 @@ const WorldChainSepolia = defineChain({
2337
2642
  ],
2338
2643
  eurcAddress: null,
2339
2644
  usdcAddress: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
2645
+ usdtAddress: null,
2340
2646
  cctp: {
2341
2647
  domain: 14,
2342
2648
  contracts: {
@@ -2381,6 +2687,7 @@ const XDC = defineChain({
2381
2687
  rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
2382
2688
  eurcAddress: null,
2383
2689
  usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
2690
+ usdtAddress: null,
2384
2691
  cctp: {
2385
2692
  domain: 18,
2386
2693
  contracts: {
@@ -2399,6 +2706,7 @@ const XDC = defineChain({
2399
2706
  },
2400
2707
  kitContracts: {
2401
2708
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2709
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2402
2710
  },
2403
2711
  });
2404
2712
 
@@ -2423,6 +2731,7 @@ const XDCApothem = defineChain({
2423
2731
  rpcEndpoints: ['https://erpc.apothem.network'],
2424
2732
  eurcAddress: null,
2425
2733
  usdcAddress: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
2734
+ usdtAddress: null,
2426
2735
  cctp: {
2427
2736
  domain: 18,
2428
2737
  contracts: {
@@ -2465,6 +2774,7 @@ const ZKSyncEra = defineChain({
2465
2774
  rpcEndpoints: ['https://mainnet.era.zksync.io'],
2466
2775
  eurcAddress: null,
2467
2776
  usdcAddress: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
2777
+ usdtAddress: null,
2468
2778
  cctp: null,
2469
2779
  });
2470
2780
 
@@ -2489,6 +2799,7 @@ const ZKSyncEraSepolia = defineChain({
2489
2799
  rpcEndpoints: ['https://sepolia.era.zksync.dev'],
2490
2800
  eurcAddress: null,
2491
2801
  usdcAddress: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
2802
+ usdtAddress: null,
2492
2803
  cctp: null,
2493
2804
  });
2494
2805
 
@@ -2656,10 +2967,12 @@ const baseChainDefinitionSchema = zod.z.object({
2656
2967
  rpcEndpoints: zod.z.array(zod.z.string()),
2657
2968
  eurcAddress: zod.z.string().nullable(),
2658
2969
  usdcAddress: zod.z.string().nullable(),
2970
+ usdtAddress: zod.z.string().nullable(),
2659
2971
  cctp: zod.z.any().nullable(), // We'll accept any CCTP config structure
2660
2972
  kitContracts: zod.z
2661
2973
  .object({
2662
2974
  bridge: zod.z.string().optional(),
2975
+ adapter: zod.z.string().optional(),
2663
2976
  })
2664
2977
  .optional(),
2665
2978
  });
@@ -2774,6 +3087,29 @@ zod.z.union([
2774
3087
  zod.z.nativeEnum(Blockchain),
2775
3088
  chainDefinitionSchema$2,
2776
3089
  ]);
3090
+ /**
3091
+ * Zod schema for validating swap-specific chain identifiers.
3092
+ *
3093
+ * Validates chains based on:
3094
+ * - CCTPv2 support (adapter contract deployed)
3095
+ * - Mainnet only (no testnets)
3096
+ * - At least one supported token available
3097
+ *
3098
+ */
3099
+ zod.z.union([
3100
+ // String blockchain identifier (accepts SwapChain enum values)
3101
+ zod.z.string().refine((val) => val in SwapChain, (val) => ({
3102
+ message: `"${val}" is not a supported swap chain. ` +
3103
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
3104
+ })),
3105
+ // SwapChain enum
3106
+ zod.z.nativeEnum(SwapChain),
3107
+ // ChainDefinition object (checks if chain.chain is in SwapChain)
3108
+ chainDefinitionSchema$2.refine((chain) => chain.chain in SwapChain, (chain) => ({
3109
+ message: `"${chain.chain}" is not a supported swap chain. ` +
3110
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
3111
+ })),
3112
+ ]);
2777
3113
  /**
2778
3114
  * Zod schema for validating bridge chain identifiers.
2779
3115
  *
@@ -2813,6 +3149,38 @@ zod.z.union([
2813
3149
  })),
2814
3150
  ]);
2815
3151
 
3152
+ /**
3153
+ * @packageDocumentation
3154
+ * @module SwapTokenSchemas
3155
+ *
3156
+ * Zod validation schemas for supported swap tokens.
3157
+ */
3158
+ // Internal enum used after input normalization.
3159
+ const swapTokenEnumSchema = zod.z.enum([
3160
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
3161
+ NATIVE_TOKEN,
3162
+ ]);
3163
+ /**
3164
+ * Zod schema for validating supported swap token symbols.
3165
+ *
3166
+ * Accepts any token symbol from the SWAP_TOKEN_REGISTRY plus NATIVE.
3167
+ * Input matching is case-insensitive and normalized to uppercase.
3168
+ *
3169
+ * @example
3170
+ * ```typescript
3171
+ * import { supportedSwapTokenSchema } from '@core/chains'
3172
+ *
3173
+ * const result = supportedSwapTokenSchema.safeParse('USDC')
3174
+ * if (result.success) {
3175
+ * console.log('Valid swap token:', result.data)
3176
+ * }
3177
+ * ```
3178
+ */
3179
+ zod.z
3180
+ .string()
3181
+ .transform((value) => value.toUpperCase())
3182
+ .pipe(swapTokenEnumSchema);
3183
+
2816
3184
  /**
2817
3185
  * Retrieve a chain definition by its blockchain enum value.
2818
3186
  *
@@ -2867,6 +3235,11 @@ const getChainByEnum = (blockchain) => {
2867
3235
  * ```
2868
3236
  */
2869
3237
  function resolveChainIdentifier(chainIdentifier) {
3238
+ // Handle null explicitly (typeof null === 'object' in JavaScript)
3239
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
3240
+ if (chainIdentifier === null) {
3241
+ throw new Error(`Invalid chain identifier type: null. Expected ChainDefinition object, Blockchain enum, or string literal.`);
3242
+ }
2870
3243
  // If it's already a ChainDefinition object, return it unchanged
2871
3244
  if (typeof chainIdentifier === 'object') {
2872
3245
  return chainIdentifier;
@@ -3158,11 +3531,11 @@ const formatComponent = (nameWithVersion) => {
3158
3531
  const createRequestContext = () => {
3159
3532
  const context = {};
3160
3533
  if (typeof globalThis !== 'undefined') {
3161
- if (globalThis.__STABLECOIN_KITS_EXTERNAL_PREFIX__ !== undefined) {
3162
- context.externalPrefix = globalThis.__STABLECOIN_KITS_EXTERNAL_PREFIX__;
3534
+ if (globalThis.__APP_KITS_EXTERNAL_PREFIX__ !== undefined) {
3535
+ context.externalPrefix = globalThis.__APP_KITS_EXTERNAL_PREFIX__;
3163
3536
  }
3164
- if (globalThis.__STABLECOIN_KITS_CURRENT_KIT__ !== undefined) {
3165
- context.kit = globalThis.__STABLECOIN_KITS_CURRENT_KIT__;
3537
+ if (globalThis.__APP_KITS_CURRENT_KIT__ !== undefined) {
3538
+ context.kit = globalThis.__APP_KITS_CURRENT_KIT__;
3166
3539
  }
3167
3540
  }
3168
3541
  return context;
@@ -3268,6 +3641,10 @@ const ERROR_TYPES = {
3268
3641
  RPC: 'RPC',
3269
3642
  /** Internet connectivity, DNS resolution, connection issues */
3270
3643
  NETWORK: 'NETWORK',
3644
+ /** API throttling, request frequency limits errors */
3645
+ RATE_LIMIT: 'RATE_LIMIT',
3646
+ /** Service errors */
3647
+ SERVICE: 'SERVICE',
3271
3648
  /** Catch-all for unrecognized errors (code 0) */
3272
3649
  UNKNOWN: 'UNKNOWN',
3273
3650
  };
@@ -3291,6 +3668,8 @@ const ERROR_CODE_RANGES = [
3291
3668
  { min: 3000, max: 3999, type: 'NETWORK' },
3292
3669
  { min: 4000, max: 4999, type: 'RPC' },
3293
3670
  { min: 5000, max: 5999, type: 'ONCHAIN' },
3671
+ { min: 7000, max: 7999, type: 'RATE_LIMIT' },
3672
+ { min: 8000, max: 8999, type: 'SERVICE' },
3294
3673
  { min: 9000, max: 9999, type: 'BALANCE' },
3295
3674
  ];
3296
3675
  /** Special code for UNKNOWN errors */
@@ -3338,6 +3717,8 @@ const errorDetailsSchema = zod.z.object({
3338
3717
  * - 3000-3999: NETWORK errors - Connectivity issues
3339
3718
  * - 4000-4999: RPC errors - Provider issues, gas estimation
3340
3719
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures
3720
+ * - 7000-7999: RATE_LIMIT errors - API throttling
3721
+ * - 8000-8999: SERVICE errors - Internal service errors
3341
3722
  * - 9000-9999: BALANCE errors - Insufficient funds
3342
3723
  */
3343
3724
  code: zod.z
@@ -3446,11 +3827,11 @@ const AMOUNT_FORMAT_ERROR_MESSAGE = 'Amount must be a numeric string with dot (.
3446
3827
  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
3828
 
3448
3829
  /**
3449
- * Structured error class for Stablecoin Kit operations.
3830
+ * Structured error class for App Kit operations.
3450
3831
  *
3451
3832
  * This class extends the native Error class while implementing the ErrorDetails
3452
3833
  * interface, providing a consistent error format for programmatic handling
3453
- * across the Stablecoin Kits ecosystem. All properties are immutable to ensure
3834
+ * across the App Kits ecosystem. All properties are immutable to ensure
3454
3835
  * error objects cannot be modified after creation.
3455
3836
  *
3456
3837
  * @example
@@ -3460,6 +3841,7 @@ const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (
3460
3841
  * const error = new KitError({
3461
3842
  * code: 1001,
3462
3843
  * name: 'INPUT_NETWORK_MISMATCH',
3844
+ * type: 'INPUT',
3463
3845
  * recoverability: 'FATAL',
3464
3846
  * message: 'Cannot bridge between mainnet and testnet'
3465
3847
  * })
@@ -3477,7 +3859,8 @@ const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (
3477
3859
  * // Error with cause information
3478
3860
  * const error = new KitError({
3479
3861
  * code: 1002,
3480
- * name: 'INVALID_AMOUNT',
3862
+ * name: 'INPUT_INVALID_AMOUNT',
3863
+ * type: 'INPUT',
3481
3864
  * recoverability: 'FATAL',
3482
3865
  * message: 'Amount must be greater than zero',
3483
3866
  * cause: {
@@ -3561,6 +3944,8 @@ class KitError extends Error {
3561
3944
  * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
3562
3945
  * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
3563
3946
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
3947
+ * - 7000-7999: RATE_LIMIT errors - API throttling, request frequency limits
3948
+ * - 8000-8999: SERVICE errors - Internal service errors, server failures
3564
3949
  * - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
3565
3950
  */
3566
3951
  /**
@@ -3621,10 +4006,22 @@ const InputError = {
3621
4006
  name: 'INPUT_INVALID_CHAIN',
3622
4007
  type: 'INPUT',
3623
4008
  },
3624
- /** Invalid or unknown token (symbol not found, missing decimals, etc.) */
3625
- INVALID_TOKEN: {
4009
+ /** Unsupported token for chain */
4010
+ UNSUPPORTED_TOKEN: {
3626
4011
  code: 1006,
3627
- name: 'INPUT_INVALID_TOKEN',
4012
+ name: 'INPUT_UNSUPPORTED_TOKEN',
4013
+ type: 'INPUT',
4014
+ },
4015
+ /** Insufficient swap amount for the token pair */
4016
+ INSUFFICIENT_SWAP_AMOUNT: {
4017
+ code: 1007,
4018
+ name: 'INPUT_INSUFFICIENT_SWAP_AMOUNT',
4019
+ type: 'INPUT',
4020
+ },
4021
+ /** Action not supported by this adapter / ecosystem */
4022
+ UNSUPPORTED_ACTION: {
4023
+ code: 1008,
4024
+ name: 'INPUT_UNSUPPORTED_ACTION',
3628
4025
  type: 'INPUT',
3629
4026
  },
3630
4027
  /** General validation failure for complex validation rules */
@@ -3633,6 +4030,12 @@ const InputError = {
3633
4030
  name: 'INPUT_VALIDATION_FAILED',
3634
4031
  type: 'INPUT',
3635
4032
  },
4033
+ /** User cancelled wallet interaction (signature, transaction, connection) */
4034
+ USER_CANCELLED: {
4035
+ code: 1099,
4036
+ name: 'INPUT_USER_CANCELLED',
4037
+ type: 'INPUT',
4038
+ },
3636
4039
  };
3637
4040
  /**
3638
4041
  * Standardized error definitions for BALANCE type errors.
@@ -3720,8 +4123,7 @@ const NetworkError = {
3720
4123
  code: 3004,
3721
4124
  name: 'NETWORK_RELAYER_PENDING',
3722
4125
  type: 'NETWORK',
3723
- },
3724
- };
4126
+ }};
3725
4127
 
3726
4128
  /**
3727
4129
  * Creates error for network type mismatch between source and destination.
@@ -3808,7 +4210,7 @@ function createInvalidChainError(chain, reason) {
3808
4210
  const errorDetails = {
3809
4211
  ...InputError.INVALID_CHAIN,
3810
4212
  recoverability: 'FATAL',
3811
- message: `Invalid chain '${chain}': ${reason}`,
4213
+ message: `Invalid chain '${chain}': ${reason}.`,
3812
4214
  cause: {
3813
4215
  trace: { chain, reason },
3814
4216
  },
@@ -4157,6 +4559,9 @@ function getErrorMessage(error) {
4157
4559
  if (typeof error === 'string') {
4158
4560
  return error;
4159
4561
  }
4562
+ if (typeof error === 'object' && error !== null && 'message' in error) {
4563
+ return String(error.message);
4564
+ }
4160
4565
  return 'An unknown error occurred';
4161
4566
  }
4162
4567
  /**
@@ -4213,6 +4618,9 @@ function extractErrorInfo(error) {
4213
4618
  if (typeof err.code === 'number') {
4214
4619
  info.code = err.code;
4215
4620
  }
4621
+ if (isKitError(error)) {
4622
+ info.type = error.type;
4623
+ }
4216
4624
  return info;
4217
4625
  }
4218
4626
 
@@ -4335,7 +4743,17 @@ const makeApiRequest = async (url, method, isValidType, config, body) => {
4335
4743
  const response = await fetch(url, requestInit);
4336
4744
  clearTimeout(timeoutId);
4337
4745
  if (!response.ok) {
4338
- throw new Error(`HTTP ${String(response.status)} - ${response.statusText}`);
4746
+ let responseBody;
4747
+ try {
4748
+ responseBody = (await response.json());
4749
+ }
4750
+ catch {
4751
+ // Parse response body for diagnostics; continue even if malformed
4752
+ // so callers always receive HTTP status context
4753
+ }
4754
+ const httpError = new Error(`HTTP ${String(response.status)} - ${response.statusText}`);
4755
+ httpError.responseBody = responseBody;
4756
+ throw httpError;
4339
4757
  }
4340
4758
  const parsedJson = (await response.json());
4341
4759
  if (!isValidType(parsedJson)) {
@@ -4907,7 +5325,7 @@ const convertAddress = (address, targetFormat) => {
4907
5325
  *
4908
5326
  * This function normalizes token values from their blockchain representation (where
4909
5327
  * everything is stored as integers in the smallest denomination) to human-readable
4910
- * decimal format. Uses the battle-tested implementation from @ethersproject/units.
5328
+ * decimal format. Uses the battle-tested implementation from \@ethersproject/units.
4911
5329
  *
4912
5330
  * @param value - The value in smallest units (e.g., "1000000" for 1 USDC with 6 decimals)
4913
5331
  * @param decimals - The number of decimal places for the unit conversion
@@ -4936,116 +5354,837 @@ const formatUnits = (value, decimals) => {
4936
5354
  };
4937
5355
 
4938
5356
  /**
4939
- * Build a complete explorer URL from a chain definition and transaction hash.
4940
- *
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.
4944
- *
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.
4953
- *
4954
- * @example
4955
- * ```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...'
4962
- * ```
5357
+ * Create a structured error for token resolution failures.
4963
5358
  *
4964
- * @example
4965
- * ```typescript
4966
- * import { buildExplorerUrl } from '@core/utils'
4967
- * import { Solana } from '@core/chains'
5359
+ * @remarks
5360
+ * Returns a `KitError` with `INPUT_UNSUPPORTED_TOKEN` code (1006).
5361
+ * The error trace contains the selector and chain context for debugging.
4968
5362
  *
4969
- * // Build URL for Solana transaction
4970
- * const url = buildExplorerUrl(Solana, 'abc123def456...')
4971
- * console.log(url) // 'https://solscan.io/tx/abc123def456...'
4972
- * ```
5363
+ * @param message - Human-readable error description.
5364
+ * @param selector - The token selector that failed to resolve.
5365
+ * @param chainId - The chain being resolved for (optional).
5366
+ * @param cause - The underlying error, if any (optional).
5367
+ * @returns A KitError with INPUT type and FATAL recoverability.
4973
5368
  *
4974
5369
  * @example
4975
5370
  * ```typescript
4976
- * import { buildExplorerUrl } from '@core/utils'
4977
- * import { Aptos } from '@core/chains'
4978
- *
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'
5371
+ * throw createTokenResolutionError(
5372
+ * 'Unknown token symbol: FAKE',
5373
+ * 'FAKE',
5374
+ * 'Ethereum'
5375
+ * )
4982
5376
  * ```
4983
5377
  */
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;
5378
+ function createTokenResolutionError(message, selector, chainId, cause) {
5379
+ const trace = {
5380
+ selector,
5381
+ ...(chainId === undefined ? {} : { chainId }),
5382
+ ...({} ),
5383
+ };
5384
+ return new KitError({
5385
+ ...InputError.UNSUPPORTED_TOKEN,
5386
+ recoverability: 'FATAL',
5387
+ message,
5388
+ cause: { trace },
5389
+ });
4994
5390
  }
4995
5391
 
4996
5392
  /**
4997
- * CCTP forwarding magic bytes prefix.
4998
- *
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.
5001
- */
5002
- const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
5003
- /**
5004
- * CCTP forwarding version number.
5005
- *
5006
- * Version 0 is used for basic forwarding requests.
5007
- */
5008
- const CCTP_FORWARD_VERSION = 0;
5009
- /**
5010
- * Length of Circle-reserved payload for basic forwarding (0 bytes).
5011
- *
5012
- * Set to 0 when no additional Circle-reserved data is needed.
5013
- */
5014
- const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
5015
- /**
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.
5393
+ * USDC token definition with addresses and metadata.
5020
5394
  *
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
5395
+ * @remarks
5396
+ * This is the built-in USDC definition used by the TokenRegistry.
5397
+ * Includes all known USDC addresses across supported chains.
5025
5398
  *
5026
- * @returns A 0x-prefixed hex string representing the 32-byte hookData
5399
+ * Keys use the `Blockchain` enum for type safety. Both enum values
5400
+ * and string literals are supported:
5401
+ * - `Blockchain.Ethereum` or `'Ethereum'`
5027
5402
  *
5028
5403
  * @example
5029
5404
  * ```typescript
5030
- * import { buildForwardingHookData } from '@core/utils'
5031
- *
5032
- * const hookData = buildForwardingHookData()
5033
- * // Returns: '0x636374702d666f72776172640000000000000000000000000000000000000000'
5405
+ * import { USDC } from '@core/tokens'
5406
+ * import { Blockchain } from '@core/chains'
5034
5407
  *
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
- * })
5408
+ * console.log(USDC.symbol) // 'USDC'
5409
+ * console.log(USDC.decimals) // 6
5410
+ * console.log(USDC.locators[Blockchain.Ethereum])
5411
+ * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
5045
5412
  * ```
5046
5413
  */
5047
- // Cached result for memoization (computed once on first call)
5048
- let cachedHookDataHex = null;
5414
+ const USDC = {
5415
+ symbol: 'USDC',
5416
+ decimals: 6,
5417
+ locators: {
5418
+ // =========================================================================
5419
+ // Mainnets (alphabetically sorted)
5420
+ // =========================================================================
5421
+ [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
5422
+ [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
5423
+ [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
5424
+ [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
5425
+ [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
5426
+ [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
5427
+ [Blockchain.Hedera]: '0.0.456858',
5428
+ [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
5429
+ [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
5430
+ [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
5431
+ [Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
5432
+ [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
5433
+ [Blockchain.Noble]: 'uusdc',
5434
+ [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
5435
+ [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
5436
+ [Blockchain.Polkadot_Asset_Hub]: '1337',
5437
+ [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
5438
+ [Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
5439
+ [Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
5440
+ [Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
5441
+ [Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
5442
+ [Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
5443
+ [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
5444
+ [Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
5445
+ [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
5446
+ [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
5447
+ // =========================================================================
5448
+ // Testnets (alphabetically sorted)
5449
+ // =========================================================================
5450
+ [Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
5451
+ [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
5452
+ [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
5453
+ [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
5454
+ [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
5455
+ [Blockchain.Hedera_Testnet]: '0.0.429274',
5456
+ [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
5457
+ [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
5458
+ [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
5459
+ [Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
5460
+ [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
5461
+ [Blockchain.Noble_Testnet]: 'uusdc',
5462
+ [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
5463
+ [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
5464
+ [Blockchain.Polkadot_Westmint]: '31337',
5465
+ [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
5466
+ [Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
5467
+ [Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
5468
+ [Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
5469
+ [Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
5470
+ [Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
5471
+ [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
5472
+ [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
5473
+ [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
5474
+ [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
5475
+ },
5476
+ };
5477
+
5478
+ /**
5479
+ * USDT (Tether) token definition with addresses and metadata.
5480
+ *
5481
+ * @remarks
5482
+ * Built-in USDT definition for the TokenRegistry. Includes chain locators
5483
+ * for swap-supported chains.
5484
+ */
5485
+ const USDT = {
5486
+ symbol: 'USDT',
5487
+ decimals: 6,
5488
+ locators: {
5489
+ [Blockchain.Aptos]: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
5490
+ [Blockchain.Arbitrum]: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
5491
+ [Blockchain.Avalanche]: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7',
5492
+ [Blockchain.Celo]: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
5493
+ [Blockchain.Ethereum]: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
5494
+ [Blockchain.HyperEVM]: '0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb',
5495
+ [Blockchain.Ink]: '0x0200C29006150606B650577BBE7B6248F58470c1',
5496
+ [Blockchain.Linea]: '0xA219439258ca9da29E9Cc4cE5596924745e12B93',
5497
+ [Blockchain.Monad]: '0xe7cd86e13AC4309349F30B3435a9d337750fC82D',
5498
+ [Blockchain.NEAR]: 'usdt.tether-token.near',
5499
+ [Blockchain.Optimism]: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58',
5500
+ [Blockchain.Polkadot_Asset_Hub]: '1984',
5501
+ [Blockchain.Polygon]: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
5502
+ [Blockchain.Sei]: '0x9151434b16b9763660705744891fA906F660EcC5',
5503
+ [Blockchain.Solana]: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
5504
+ [Blockchain.Unichain]: '0x9151434b16b9763660705744891fA906F660EcC5',
5505
+ },
5506
+ };
5507
+
5508
+ /**
5509
+ * EURC (Euro Coin) token definition with addresses and metadata.
5510
+ *
5511
+ * @remarks
5512
+ * Built-in EURC definition for the TokenRegistry. Includes chain locators
5513
+ * for swap-supported chains.
5514
+ */
5515
+ const EURC = {
5516
+ symbol: 'EURC',
5517
+ decimals: 6,
5518
+ locators: {
5519
+ [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
5520
+ [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
5521
+ [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
5522
+ [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
5523
+ [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
5524
+ },
5525
+ };
5526
+
5527
+ /**
5528
+ * DAI (Maker DAO) stablecoin token definition with addresses and metadata.
5529
+ *
5530
+ * @remarks
5531
+ * Built-in DAI definition for the TokenRegistry. Includes chain locators
5532
+ * for swap-supported chains.
5533
+ */
5534
+ const DAI = {
5535
+ symbol: 'DAI',
5536
+ decimals: 18,
5537
+ chainDecimals: {
5538
+ [Blockchain.Solana]: 8,
5539
+ },
5540
+ locators: {
5541
+ [Blockchain.Arbitrum]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5542
+ [Blockchain.Avalanche]: '0xd586E7F844cEa2F87f50152665BCbc2C279D8d70',
5543
+ [Blockchain.Base]: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
5544
+ [Blockchain.Ethereum]: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
5545
+ [Blockchain.Linea]: '0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5',
5546
+ [Blockchain.Optimism]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5547
+ [Blockchain.Polygon]: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
5548
+ [Blockchain.Solana]: 'EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o',
5549
+ },
5550
+ };
5551
+
5552
+ /**
5553
+ * USDe (Ethena) synthetic dollar token definition with addresses and metadata.
5554
+ *
5555
+ * @remarks
5556
+ * Built-in USDE definition for the TokenRegistry. Includes chain locators
5557
+ * for swap-supported chains.
5558
+ */
5559
+ const USDE = {
5560
+ symbol: 'USDe',
5561
+ decimals: 18,
5562
+ chainDecimals: {
5563
+ [Blockchain.Solana]: 9,
5564
+ },
5565
+ locators: {
5566
+ [Blockchain.Arbitrum]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5567
+ [Blockchain.Base]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5568
+ [Blockchain.Ethereum]: '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3',
5569
+ [Blockchain.Linea]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5570
+ [Blockchain.Optimism]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5571
+ [Blockchain.Solana]: 'DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT',
5572
+ },
5573
+ };
5574
+
5575
+ /**
5576
+ * PYUSD (PayPal USD) stablecoin token definition with addresses and metadata.
5577
+ *
5578
+ * @remarks
5579
+ * Built-in PYUSD definition for the TokenRegistry. Includes chain locators
5580
+ * for swap-supported chains.
5581
+ */
5582
+ const PYUSD = {
5583
+ symbol: 'PYUSD',
5584
+ decimals: 6,
5585
+ locators: {
5586
+ [Blockchain.Arbitrum]: '0x46850aD61C2B7d64d08c9C754F45254596696984',
5587
+ [Blockchain.Ethereum]: '0x6c3ea9036406852006290770BEdFcAbA0e23A0e8',
5588
+ [Blockchain.Solana]: '2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo',
5589
+ },
5590
+ };
5591
+
5592
+ /**
5593
+ * WETH (Wrapped Ether) token definition with addresses and metadata.
5594
+ *
5595
+ * @remarks
5596
+ * Built-in WETH definition for the TokenRegistry. Includes chain locators
5597
+ * for swap-supported chains where WETH is available.
5598
+ */
5599
+ const WETH = {
5600
+ symbol: 'WETH',
5601
+ decimals: 18,
5602
+ chainDecimals: {
5603
+ [Blockchain.Solana]: 9,
5604
+ },
5605
+ locators: {
5606
+ [Blockchain.Arbitrum]: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
5607
+ [Blockchain.Avalanche]: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB',
5608
+ [Blockchain.Base]: '0x4200000000000000000000000000000000000006',
5609
+ [Blockchain.Ethereum]: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
5610
+ [Blockchain.Optimism]: '0x4200000000000000000000000000000000000006',
5611
+ [Blockchain.Polygon]: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
5612
+ [Blockchain.Solana]: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs',
5613
+ },
5614
+ };
5615
+
5616
+ /**
5617
+ * WBTC (Wrapped Bitcoin) token definition with addresses and metadata.
5618
+ *
5619
+ * @remarks
5620
+ * Built-in WBTC definition for the TokenRegistry. Includes chain locators
5621
+ * for swap-supported chains where WBTC is available.
5622
+ */
5623
+ const WBTC = {
5624
+ symbol: 'WBTC',
5625
+ decimals: 8,
5626
+ locators: {
5627
+ [Blockchain.Ethereum]: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
5628
+ [Blockchain.Arbitrum]: '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f',
5629
+ [Blockchain.Avalanche]: '0x50b7545627a5162F82A992c33b87aDc75187B218',
5630
+ },
5631
+ };
5632
+
5633
+ /**
5634
+ * WSOL (Wrapped SOL) token definition with addresses and metadata.
5635
+ *
5636
+ * @remarks
5637
+ * Built-in WSOL definition for the TokenRegistry. Includes chain locators
5638
+ * for swap-supported chains where WSOL is available.
5639
+ */
5640
+ const WSOL = {
5641
+ symbol: 'WSOL',
5642
+ decimals: 9,
5643
+ locators: {
5644
+ [Blockchain.Solana]: 'So11111111111111111111111111111111111111112',
5645
+ },
5646
+ };
5647
+
5648
+ /**
5649
+ * WAVAX (Wrapped AVAX) token definition with addresses and metadata.
5650
+ *
5651
+ * @remarks
5652
+ * Built-in WAVAX definition for the TokenRegistry. Includes chain locators
5653
+ * for swap-supported chains where WAVAX is available.
5654
+ */
5655
+ const WAVAX = {
5656
+ symbol: 'WAVAX',
5657
+ decimals: 18,
5658
+ locators: {
5659
+ [Blockchain.Avalanche]: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7',
5660
+ },
5661
+ };
5662
+
5663
+ /**
5664
+ * WPOL (Wrapped POL) token definition with addresses and metadata.
5665
+ *
5666
+ * @remarks
5667
+ * Built-in WPOL definition for the TokenRegistry. Includes chain locators
5668
+ * for swap-supported chains where WPOL is available.
5669
+ */
5670
+ const WPOL = {
5671
+ symbol: 'WPOL',
5672
+ decimals: 18,
5673
+ locators: {
5674
+ [Blockchain.Polygon]: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
5675
+ },
5676
+ };
5677
+
5678
+ /**
5679
+ * ETH (native Ether alias) token definition with metadata.
5680
+ *
5681
+ * @remarks
5682
+ * Built-in ETH definition for the TokenRegistry. Used as a symbol alias
5683
+ * for native ETH (e.g. in swap flows). Chain locators are TBD and will
5684
+ * be added when addresses are available. Use raw selector with
5685
+ * locator + decimals where native gas token is represented as a contract.
5686
+ */
5687
+ const ETH = {
5688
+ symbol: 'ETH',
5689
+ decimals: 18,
5690
+ locators: {},
5691
+ };
5692
+
5693
+ /**
5694
+ * POL (Polygon native token) token definition with addresses and metadata.
5695
+ *
5696
+ * @remarks
5697
+ * Built-in POL definition for the TokenRegistry. Includes chain locators
5698
+ * where POL is available as a bridged/wrapped asset.
5699
+ */
5700
+ const POL = {
5701
+ symbol: 'POL',
5702
+ decimals: 18,
5703
+ locators: {
5704
+ [Blockchain.Ethereum]: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6',
5705
+ },
5706
+ };
5707
+
5708
+ /**
5709
+ * PLUME (Plume network token) token definition with addresses and metadata.
5710
+ *
5711
+ * @remarks
5712
+ * Built-in PLUME definition for the TokenRegistry. Includes chain locators
5713
+ * where PLUME is available.
5714
+ */
5715
+ const PLUME = {
5716
+ symbol: 'PLUME',
5717
+ decimals: 18,
5718
+ locators: {
5719
+ [Blockchain.Ethereum]: '0x4C1746A800D224393fE2470C70A35717eD4eA5F1',
5720
+ },
5721
+ };
5722
+
5723
+ /**
5724
+ * MON token definition with Solana mint metadata.
5725
+ *
5726
+ * @remarks
5727
+ * Built-in MON definition for the TokenRegistry. Includes chain locators
5728
+ * for swap-supported chains.
5729
+ */
5730
+ const MON = {
5731
+ symbol: 'MON',
5732
+ decimals: 18,
5733
+ chainDecimals: {
5734
+ [Blockchain.Solana]: 8,
5735
+ },
5736
+ locators: {
5737
+ [Blockchain.Solana]: 'CrAr4RRJMBVwRsZtT62pEhfA9H5utymC2mVx8e7FreP2',
5738
+ },
5739
+ };
5740
+
5741
+ // Re-export for consumers
5742
+ /**
5743
+ * All default token definitions.
5744
+ *
5745
+ * @remarks
5746
+ * These tokens are automatically included in the TokenRegistry when created
5747
+ * without explicit defaults. Extensions can override these definitions.
5748
+ * Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
5749
+ * WPOL, ETH, POL, PLUME, and MON.
5750
+ *
5751
+ * @example
5752
+ * ```typescript
5753
+ * import { createTokenRegistry } from '@core/tokens'
5754
+ *
5755
+ * // Registry uses these by default
5756
+ * const registry = createTokenRegistry()
5757
+ *
5758
+ * // Add custom tokens (built-ins are still included)
5759
+ * const customRegistry = createTokenRegistry({
5760
+ * tokens: [myCustomToken],
5761
+ * })
5762
+ * ```
5763
+ */
5764
+ const DEFAULT_TOKENS = [
5765
+ USDC,
5766
+ USDT,
5767
+ EURC,
5768
+ DAI,
5769
+ USDE,
5770
+ PYUSD,
5771
+ WETH,
5772
+ WBTC,
5773
+ WSOL,
5774
+ WAVAX,
5775
+ WPOL,
5776
+ ETH,
5777
+ POL,
5778
+ PLUME,
5779
+ MON,
5780
+ ];
5781
+ /**
5782
+ * Uppercased token symbols approved for swap fee collection.
5783
+ *
5784
+ * @remarks
5785
+ * Derived from {@link DEFAULT_TOKENS} so all consumers share a single
5786
+ * allowlist source and stay in sync when defaults change.
5787
+ */
5788
+ new Set(DEFAULT_TOKENS.map((t) => t.symbol.toUpperCase()));
5789
+
5790
+ /**
5791
+ * Resolve the effective decimals for a token on a specific chain.
5792
+ *
5793
+ * Returns the chain-specific override from `chainDecimals` when available,
5794
+ * otherwise falls back to the token's default `decimals`.
5795
+ *
5796
+ * @param token - The token definition to resolve decimals for.
5797
+ * @param chain - Optional chain identifier. When omitted, the default decimals are returned.
5798
+ * @returns The number of decimal places for the token on the given chain.
5799
+ *
5800
+ * @example
5801
+ * ```typescript
5802
+ * import { resolveTokenDecimals } from '\@core/tokens'
5803
+ *
5804
+ * // DAI: 18 decimals on EVM, 8 on Solana
5805
+ * resolveTokenDecimals(DAI) // 18 (default)
5806
+ * resolveTokenDecimals(DAI, 'Solana') // 8 (chain override)
5807
+ * resolveTokenDecimals(DAI, 'Ethereum') // 18 (no override, falls back)
5808
+ * ```
5809
+ */
5810
+ function resolveTokenDecimals(token, chain) {
5811
+ if (chain === undefined) {
5812
+ return token.decimals;
5813
+ }
5814
+ return token.chainDecimals?.[chain] ?? token.decimals;
5815
+ }
5816
+
5817
+ /**
5818
+ * Check if a selector is a raw token selector (object form).
5819
+ *
5820
+ * @param selector - The token selector to check.
5821
+ * @returns True if the selector is a raw token selector.
5822
+ */
5823
+ function isRawSelector(selector) {
5824
+ return typeof selector === 'object' && 'locator' in selector;
5825
+ }
5826
+ /**
5827
+ * Normalize a symbol to uppercase for case-insensitive lookup.
5828
+ *
5829
+ * @param symbol - The symbol to normalize.
5830
+ * @returns The normalized (uppercase) symbol.
5831
+ */
5832
+ function normalizeSymbol(symbol) {
5833
+ return symbol.toUpperCase();
5834
+ }
5835
+ /**
5836
+ * Normalize a locator for stable chain-aware lookup.
5837
+ *
5838
+ * @remarks
5839
+ * EVM-style addresses (`0x...`) are normalized to lowercase for
5840
+ * case-insensitive matching. Non-EVM locators keep original casing.
5841
+ */
5842
+ function normalizeLocator(locator) {
5843
+ if (locator.startsWith('0x') || locator.startsWith('0X')) {
5844
+ return locator.toLowerCase();
5845
+ }
5846
+ return locator;
5847
+ }
5848
+ /**
5849
+ * Build a stable map key for a chain + locator pair.
5850
+ */
5851
+ function buildAddressKey(chainId, locator) {
5852
+ return `${chainId}::${normalizeLocator(locator)}`;
5853
+ }
5854
+ /**
5855
+ * Create a token registry with built-in tokens and optional extensions.
5856
+ *
5857
+ * @remarks
5858
+ * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
5859
+ * Custom tokens are merged on top - use this to add new tokens or override
5860
+ * built-in definitions.
5861
+ *
5862
+ * @param options - Configuration options for the registry.
5863
+ * @returns A token registry instance.
5864
+ *
5865
+ * @example
5866
+ * ```typescript
5867
+ * import { createTokenRegistry } from '@core/tokens'
5868
+ *
5869
+ * // Create registry with built-in tokens (USDC, etc.)
5870
+ * const registry = createTokenRegistry()
5871
+ *
5872
+ * // Resolve USDC on Ethereum
5873
+ * const usdc = registry.resolve('USDC', 'Ethereum')
5874
+ * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
5875
+ * console.log(usdc.decimals) // 6
5876
+ * ```
5877
+ *
5878
+ * @example
5879
+ * ```typescript
5880
+ * // Add custom tokens (built-ins are still included)
5881
+ * const myToken: TokenDefinition = {
5882
+ * symbol: 'MY',
5883
+ * decimals: 18,
5884
+ * locators: { Ethereum: '0x...' },
5885
+ * }
5886
+ *
5887
+ * const registry = createTokenRegistry({ tokens: [myToken] })
5888
+ * registry.resolve('USDC', 'Ethereum') // Still works!
5889
+ * registry.resolve('MY', 'Ethereum') // Also works
5890
+ * ```
5891
+ *
5892
+ * @example
5893
+ * ```typescript
5894
+ * // Override a built-in token
5895
+ * const customUsdc: TokenDefinition = {
5896
+ * symbol: 'USDC',
5897
+ * decimals: 6,
5898
+ * locators: { MyChain: '0xCustomAddress' },
5899
+ * }
5900
+ *
5901
+ * const registry = createTokenRegistry({ tokens: [customUsdc] })
5902
+ * // Now USDC resolves to customUsdc definition
5903
+ * ```
5904
+ *
5905
+ * @example
5906
+ * ```typescript
5907
+ * // Resolve arbitrary tokens by raw locator
5908
+ * const registry = createTokenRegistry()
5909
+ * const token = registry.resolve(
5910
+ * { locator: '0x1234...', decimals: 18 },
5911
+ * 'Ethereum'
5912
+ * )
5913
+ * ```
5914
+ */
5915
+ function createTokenRegistry(options = {}) {
5916
+ const { tokens = [], requireDecimals = false } = options;
5917
+ // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
5918
+ const tokenMap = new Map();
5919
+ // Add built-in tokens first
5920
+ for (const def of DEFAULT_TOKENS) {
5921
+ tokenMap.set(normalizeSymbol(def.symbol), def);
5922
+ }
5923
+ // Custom tokens override built-ins
5924
+ for (const def of tokens) {
5925
+ tokenMap.set(normalizeSymbol(def.symbol), def);
5926
+ }
5927
+ // Build an address index from the resolved token map so overrides win.
5928
+ const addressMap = new Map();
5929
+ for (const token of tokenMap.values()) {
5930
+ for (const [chainId, locator] of Object.entries(token.locators)) {
5931
+ if (typeof locator !== 'string' || locator.trim() === '') {
5932
+ continue;
5933
+ }
5934
+ addressMap.set(buildAddressKey(chainId, locator), token);
5935
+ }
5936
+ }
5937
+ /**
5938
+ * Resolve a symbol selector to token information.
5939
+ */
5940
+ function resolveSymbol(symbol, chainId) {
5941
+ const normalizedSymbol = normalizeSymbol(symbol);
5942
+ const definition = tokenMap.get(normalizedSymbol);
5943
+ if (definition === undefined) {
5944
+ throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
5945
+ }
5946
+ const locator = definition.locators[chainId];
5947
+ if (locator === undefined || locator.trim() === '') {
5948
+ throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
5949
+ }
5950
+ const decimals = resolveTokenDecimals(definition, chainId);
5951
+ return {
5952
+ symbol: definition.symbol,
5953
+ decimals,
5954
+ locator: normalizeLocator(locator),
5955
+ };
5956
+ }
5957
+ /**
5958
+ * Resolve a raw selector to token information.
5959
+ */
5960
+ function resolveRaw(selector, chainId) {
5961
+ const { locator, decimals } = selector;
5962
+ // Validate locator
5963
+ if (!locator || typeof locator !== 'string') {
5964
+ throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
5965
+ }
5966
+ // Decimals are always required for raw selectors
5967
+ if (decimals === undefined) {
5968
+ const message = requireDecimals
5969
+ ? 'Decimals required for raw token selector (requireDecimals: true)'
5970
+ : 'Decimals required for raw token selector. Provide { locator, decimals }.';
5971
+ throw createTokenResolutionError(message, selector, chainId);
5972
+ }
5973
+ // Validate decimals
5974
+ if (typeof decimals !== 'number' ||
5975
+ decimals < 0 ||
5976
+ !Number.isInteger(decimals)) {
5977
+ throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
5978
+ }
5979
+ return {
5980
+ decimals,
5981
+ locator: normalizeLocator(locator),
5982
+ };
5983
+ }
5984
+ return {
5985
+ resolve(selector, chainId) {
5986
+ // Runtime validation of inputs - these checks are for JS consumers
5987
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
5988
+ if (selector === null || selector === undefined) {
5989
+ throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
5990
+ }
5991
+ if (chainId === '' || typeof chainId !== 'string') {
5992
+ throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
5993
+ }
5994
+ // Dispatch based on selector type
5995
+ if (isRawSelector(selector)) {
5996
+ return resolveRaw(selector, chainId);
5997
+ }
5998
+ if (typeof selector === 'string') {
5999
+ return resolveSymbol(selector, chainId);
6000
+ }
6001
+ throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
6002
+ },
6003
+ resolveByAddress(address, chainId) {
6004
+ if (!address || typeof address !== 'string') {
6005
+ throw createTokenResolutionError('Token address is required for address resolution', { locator: address }, chainId);
6006
+ }
6007
+ if (chainId === '' || typeof chainId !== 'string') {
6008
+ throw createTokenResolutionError('Chain ID is required for token resolution', { locator: address }, chainId);
6009
+ }
6010
+ const definition = addressMap.get(buildAddressKey(chainId, address));
6011
+ if (definition === undefined) {
6012
+ throw createTokenResolutionError(`Unknown token address: ${address} for chain ${chainId}`, { locator: address }, chainId);
6013
+ }
6014
+ const locator = definition.locators[chainId];
6015
+ if (locator === undefined || locator.trim() === '') {
6016
+ throw createTokenResolutionError(`Token ${definition.symbol} has no locator for chain ${chainId}`, definition.symbol, chainId);
6017
+ }
6018
+ const decimals = resolveTokenDecimals(definition, chainId);
6019
+ return {
6020
+ symbol: definition.symbol,
6021
+ decimals,
6022
+ locator: normalizeLocator(locator),
6023
+ };
6024
+ },
6025
+ get(symbol) {
6026
+ if (!symbol || typeof symbol !== 'string') {
6027
+ return undefined;
6028
+ }
6029
+ return tokenMap.get(normalizeSymbol(symbol));
6030
+ },
6031
+ has(symbol) {
6032
+ if (!symbol || typeof symbol !== 'string') {
6033
+ return false;
6034
+ }
6035
+ return tokenMap.has(normalizeSymbol(symbol));
6036
+ },
6037
+ symbols() {
6038
+ return Array.from(tokenMap.values()).map((def) => def.symbol);
6039
+ },
6040
+ entries() {
6041
+ return Array.from(tokenMap.values());
6042
+ },
6043
+ };
6044
+ }
6045
+
6046
+ /**
6047
+ * Define a schema for token registry interfaces.
6048
+ *
6049
+ * @remarks
6050
+ * Validate that a registry exposes the `resolve()` API used by adapters.
6051
+ *
6052
+ * @example
6053
+ * ```typescript
6054
+ * import { tokenRegistrySchema } from '@core/tokens'
6055
+ *
6056
+ * const registry = {
6057
+ * resolve: () => ({ locator: '0x0', decimals: 6 }),
6058
+ * }
6059
+ *
6060
+ * tokenRegistrySchema.parse(registry)
6061
+ * ```
6062
+ */
6063
+ zod.z.custom((value) => {
6064
+ if (value === null || typeof value !== 'object') {
6065
+ return false;
6066
+ }
6067
+ const record = value;
6068
+ return (typeof record['resolve'] === 'function' &&
6069
+ typeof record['get'] === 'function' &&
6070
+ typeof record['has'] === 'function' &&
6071
+ typeof record['symbols'] === 'function' &&
6072
+ typeof record['entries'] === 'function');
6073
+ }, {
6074
+ message: 'Invalid token registry',
6075
+ });
6076
+
6077
+ /**
6078
+ * Build a complete explorer URL from a chain definition and transaction hash.
6079
+ *
6080
+ * This function takes a chain definition containing an explorer URL template
6081
+ * and replaces the `{hash}` placeholder with the provided transaction hash
6082
+ * to create a complete, valid explorer URL.
6083
+ *
6084
+ * @param chainDef - The chain definition containing the explorer URL template.
6085
+ * @param txHash - The transaction hash to insert into the URL template.
6086
+ * @returns A complete explorer URL with the transaction hash inserted.
6087
+ * @throws Error if the chain definition is null/undefined.
6088
+ * @throws Error if the transaction hash is null/undefined/empty.
6089
+ * @throws Error if the explorer URL template is missing or empty.
6090
+ * @throws Error if the explorer URL template doesn't contain the {hash} placeholder.
6091
+ * @throws Error if the resulting URL is invalid.
6092
+ *
6093
+ * @example
6094
+ * ```typescript
6095
+ * import { buildExplorerUrl } from '@core/utils'
6096
+ * import { Ethereum } from '@core/chains'
6097
+ *
6098
+ * // Build URL for Ethereum transaction
6099
+ * const url = buildExplorerUrl(Ethereum, '0x1234567890abcdef...')
6100
+ * console.log(url) // 'https://etherscan.io/tx/0x1234567890abcdef...'
6101
+ * ```
6102
+ *
6103
+ * @example
6104
+ * ```typescript
6105
+ * import { buildExplorerUrl } from '@core/utils'
6106
+ * import { Solana } from '@core/chains'
6107
+ *
6108
+ * // Build URL for Solana transaction
6109
+ * const url = buildExplorerUrl(Solana, 'abc123def456...')
6110
+ * console.log(url) // 'https://solscan.io/tx/abc123def456...'
6111
+ * ```
6112
+ *
6113
+ * @example
6114
+ * ```typescript
6115
+ * import { buildExplorerUrl } from '@core/utils'
6116
+ * import { Aptos } from '@core/chains'
6117
+ *
6118
+ * // Build URL for Aptos transaction with query parameters
6119
+ * const url = buildExplorerUrl(Aptos, '0xabc123...')
6120
+ * console.log(url) // 'https://explorer.aptoslabs.com/txn/0xabc123...?network=mainnet'
6121
+ * ```
6122
+ */
6123
+ function buildExplorerUrl(chainDef, txHash) {
6124
+ // Validate input parameters using our standard validation pattern
6125
+ validateOrThrow({ chainDef, txHash }, buildExplorerUrlParamsSchema, 'Invalid buildExplorerUrl parameters');
6126
+ // Parse and transform input parameters (e.g., trim whitespace from txHash)
6127
+ const { chainDef: validChainDef, txHash: validTxHash } = buildExplorerUrlParamsSchema.parse({ chainDef, txHash });
6128
+ // Replace all occurrences of the placeholder with the transaction hash
6129
+ const explorerUrl = validChainDef.explorerUrl.replace(/{hash}/g, validTxHash);
6130
+ // Validate the resulting URL
6131
+ validate(explorerUrl, explorerUrlSchema, 'explorer URL');
6132
+ return explorerUrl;
6133
+ }
6134
+
6135
+ /**
6136
+ * CCTP forwarding magic bytes prefix.
6137
+ *
6138
+ * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
6139
+ * This prefix is right-padded to 24 bytes in the final hookData.
6140
+ */
6141
+ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
6142
+ /**
6143
+ * CCTP forwarding version number.
6144
+ *
6145
+ * Version 0 is used for basic forwarding requests.
6146
+ */
6147
+ const CCTP_FORWARD_VERSION = 0;
6148
+ /**
6149
+ * Length of Circle-reserved payload for basic forwarding (0 bytes).
6150
+ *
6151
+ * Set to 0 when no additional Circle-reserved data is needed.
6152
+ */
6153
+ const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
6154
+ /**
6155
+ * Build the hookData bytes for CCTP forwarding.
6156
+ *
6157
+ * Constructs a 32-byte hookData payload that signals to Circle's Orbit relayer
6158
+ * that the user wants automated attestation fetching and destination mint execution.
6159
+ *
6160
+ * The hookData format is:
6161
+ * - Bytes 0-23: ASCII "cctp-forward" right-padded to 24 bytes with zeros
6162
+ * - Bytes 24-27: uint32 version (big-endian) - currently 0
6163
+ * - Bytes 28-31: uint32 length (big-endian) - 0 for basic forwarding
6164
+ *
6165
+ * @returns A 0x-prefixed hex string representing the 32-byte hookData
6166
+ *
6167
+ * @example
6168
+ * ```typescript
6169
+ * import { buildForwardingHookData } from '@core/utils'
6170
+ *
6171
+ * const hookData = buildForwardingHookData()
6172
+ * // Returns: '0x636374702d666f72776172640000000000000000000000000000000000000000'
6173
+ *
6174
+ * // Use with depositForBurnWithHook action
6175
+ * await adapter.action('cctp.v2.depositForBurnWithHook', {
6176
+ * amount: BigInt('1000000'),
6177
+ * mintRecipient: '0x...',
6178
+ * maxFee: BigInt('50000'),
6179
+ * minFinalityThreshold: 1000,
6180
+ * fromChain: ethereum,
6181
+ * toChain: base,
6182
+ * hookData
6183
+ * })
6184
+ * ```
6185
+ */
6186
+ // Cached result for memoization (computed once on first call)
6187
+ let cachedHookDataHex = null;
5049
6188
  function buildForwardingHookData() {
5050
6189
  // Return cached result if already computed
5051
6190
  if (cachedHookDataHex !== null) {
@@ -5549,6 +6688,21 @@ async function fetchUsdcFastBurnFee(sourceDomain, destinationDomain, isTestnet)
5549
6688
  return minimumFee;
5550
6689
  }
5551
6690
 
6691
+ /**
6692
+ * Well-known SPL Token program ID.
6693
+ * Duplicated here as a raw string to avoid a static import of \@core/adapter-solana
6694
+ * (which would transitively pull \@solana/web3.js into the top-level bundle and
6695
+ * break lazy-loading for EVM-only consumers).
6696
+ *
6697
+ * This MUST match TOKEN_PROGRAM_ID in @core/adapter-solana/src/utils/splTokenUtils.
6698
+ */
6699
+ const TOKEN_PROGRAM_ID_BASE58 = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; // NOSONAR — public Solana program address
6700
+ /**
6701
+ * Well-known SPL Associated Token Account program ID (same rationale as above).
6702
+ *
6703
+ * This MUST match ASSOCIATED_TOKEN_PROGRAM_ID in @core/adapter-solana/src/utils/splTokenUtils.
6704
+ */
6705
+ const ASSOCIATED_TOKEN_PROGRAM_ID_BASE58 = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'; // NOSONAR — public Solana program address
5552
6706
  /**
5553
6707
  * Get the token account address where USDC will be minted for the recipient.
5554
6708
  *
@@ -5592,20 +6746,30 @@ mintAddress) => {
5592
6746
  return rawAddress;
5593
6747
  }
5594
6748
  else {
5595
- // Solana: derive the associated token account
5596
- // Use dynamic import to avoid loading Solana dependencies for EVM-only users
6749
+ // Solana: derive the associated token account.
6750
+ // Both @solana/web3.js and the program-ID constants are resolved lazily
6751
+ // so that EVM-only consumers never load Solana code.
6752
+ const { PublicKey } = await import('@solana/web3.js').catch(() => {
6753
+ throw new KitError({
6754
+ ...InputError.VALIDATION_FAILED,
6755
+ recoverability: 'FATAL',
6756
+ message: 'Failed to load @solana/web3.js. Please ensure it is installed: npm install @solana/web3.js',
6757
+ });
6758
+ });
5597
6759
  try {
5598
- const [{ PublicKey }, { getAssociatedTokenAddressSync }] = await Promise.all([
5599
- import('@solana/web3.js'),
5600
- import('@solana/spl-token'),
5601
- ]);
5602
6760
  const owner = new PublicKey(rawAddress);
5603
6761
  const mintPub = new PublicKey(mintAddress);
5604
- const ata = getAssociatedTokenAddressSync(mintPub, owner);
6762
+ const tokenProgramId = new PublicKey(TOKEN_PROGRAM_ID_BASE58);
6763
+ const ataProgramId = new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID_BASE58);
6764
+ const [ata] = PublicKey.findProgramAddressSync([owner.toBuffer(), tokenProgramId.toBuffer(), mintPub.toBuffer()], ataProgramId);
5605
6765
  return ata.toBase58();
5606
6766
  }
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');
6767
+ catch (error) {
6768
+ throw new KitError({
6769
+ ...InputError.INVALID_ADDRESS,
6770
+ recoverability: 'FATAL',
6771
+ message: `Failed to derive Solana Associated Token Account for recipient "${rawAddress}": ${error instanceof Error ? error.message : String(error)}`,
6772
+ });
5609
6773
  }
5610
6774
  }
5611
6775
  };
@@ -7921,590 +9085,239 @@ function createLogger(options, stream) {
7921
9085
  const finalOptions = redactConfig
7922
9086
  ? { ...pinoOptions, redact: redactConfig }
7923
9087
  : 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 });
9088
+ const pinoInstance = pino__default(finalOptions);
9089
+ return wrapPino(pinoInstance);
8101
9090
  }
8102
9091
 
8103
9092
  /**
8104
- * Extend an invocation context by appending a caller to its call chain.
9093
+ * Factory for creating Runtime instances with defaults.
8105
9094
  *
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.
9095
+ * @packageDocumentation
9096
+ */
9097
+ // ============================================================================
9098
+ // Validation Schema
9099
+ // ============================================================================
9100
+ /**
9101
+ * Schema for validating {@link RuntimeOptions}.
8109
9102
  *
8110
9103
  * @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
- * ```
9104
+ * Used internally by {@link createRuntime} to validate options from JS consumers.
9105
+ * Exported for advanced use cases where manual validation is needed.
8125
9106
  */
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
9107
  zod.z
8148
9108
  .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(),
9109
+ logger: loggerSchema.optional(),
9110
+ metrics: metricsSchema.optional(),
8153
9111
  })
8154
9112
  .passthrough();
8155
-
9113
+ // ============================================================================
9114
+ // Factory
9115
+ // ============================================================================
8156
9116
  /**
8157
- * Create a structured error for token resolution failures.
9117
+ * Create a complete Runtime with sensible defaults.
8158
9118
  *
8159
- * @remarks
8160
- * Returns a `KitError` with `INPUT_INVALID_TOKEN` code (1006).
8161
- * The error trace contains the selector and chain context for debugging.
9119
+ * @param options - Optional configuration to override logger and metrics.
9120
+ * @returns A frozen, immutable Runtime with all services guaranteed present.
9121
+ * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
8162
9122
  *
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.
9123
+ * @remarks
9124
+ * Creates a fully-configured runtime by merging provided options with defaults.
9125
+ * The returned runtime is frozen to enforce immutability.
8168
9126
  *
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.
9127
+ * | Service | Default | Configurable |
9128
+ * |---------|---------|--------------|
9129
+ * | `logger` | pino logger (info level) | Yes |
9130
+ * | `metrics` | No-op metrics | Yes |
9131
+ * | `events` | Internal event bus | No |
9132
+ * | `clock` | `Date.now()` | No |
8194
9133
  *
8195
- * @remarks
8196
- * This is the built-in USDC definition used by the TokenRegistry.
8197
- * Includes all known USDC addresses across supported chains.
9134
+ * **Why only logger and metrics?**
8198
9135
  *
8199
- * Keys use the `Blockchain` enum for type safety. Both enum values
8200
- * and string literals are supported:
8201
- * - `Blockchain.Ethereum` or `'Ethereum'`
9136
+ * - **Logger/Metrics**: Integration points with your infrastructure
9137
+ * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
9138
+ * - **Clock**: Testing concern - use mock factories for tests
8202
9139
  *
8203
9140
  * @example
8204
9141
  * ```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.
9142
+ * import { createRuntime, createLogger } from '@core/runtime'
8279
9143
  *
8280
- * @remarks
8281
- * These tokens are automatically included in the TokenRegistry when created
8282
- * without explicit defaults. Extensions can override these definitions.
9144
+ * // Use all defaults
9145
+ * const runtime = createRuntime()
8283
9146
  *
8284
- * @example
8285
- * ```typescript
8286
- * import { createTokenRegistry } from '@core/tokens'
9147
+ * // Custom logger
9148
+ * const runtime = createRuntime({
9149
+ * logger: createLogger({ level: 'debug' }),
9150
+ * })
8287
9151
  *
8288
- * // Registry uses these by default
8289
- * const registry = createTokenRegistry()
9152
+ * // Custom metrics (e.g., Prometheus)
9153
+ * const runtime = createRuntime({
9154
+ * metrics: myPrometheusMetrics,
9155
+ * })
8290
9156
  *
8291
- * // Add custom tokens (built-ins are still included)
8292
- * const customRegistry = createTokenRegistry({
8293
- * tokens: [myCustomToken],
9157
+ * // Subscribe to events (don't replace the bus)
9158
+ * runtime.events.on('operation.*', (event) => {
9159
+ * console.log('Event:', event.name)
8294
9160
  * })
8295
9161
  * ```
8296
9162
  */
8297
- const DEFAULT_TOKENS = [USDC];
9163
+ function createRuntime(options) {
9164
+ // Resolve logger first (events may need it)
9165
+ const logger = createLogger();
9166
+ // Internal services - not configurable
9167
+ const events = createEventBus({ logger });
9168
+ const clock = defaultClock;
9169
+ // Resolve metrics
9170
+ const metrics = noopMetrics;
9171
+ return Object.freeze({ logger, events, metrics, clock });
9172
+ }
8298
9173
 
8299
9174
  /**
8300
- * Check if a selector is a raw token selector (object form).
9175
+ * Invocation context resolution - resolves the **WHO/HOW** of an operation.
8301
9176
  *
8302
- * @param selector - The token selector to check.
8303
- * @returns True if the selector is a raw token selector.
9177
+ * @packageDocumentation
8304
9178
  */
8305
- function isRawSelector(selector) {
8306
- return typeof selector === 'object' && 'locator' in selector;
8307
- }
9179
+ // ============================================================================
9180
+ // Validation Schemas
9181
+ // ============================================================================
9182
+ /**
9183
+ * Schema for validating Caller.
9184
+ */
9185
+ const callerSchema = zod.z.object({
9186
+ type: zod.z.string(),
9187
+ name: zod.z.string(),
9188
+ version: zod.z.string().optional(),
9189
+ });
9190
+ /**
9191
+ * Schema for validating InvocationMeta input.
9192
+ */
9193
+ const invocationMetaSchema = zod.z
9194
+ .object({
9195
+ traceId: zod.z.string().optional(),
9196
+ runtime: zod.z.object({}).passthrough().optional(),
9197
+ tokens: zod.z.object({}).passthrough().optional(),
9198
+ callers: zod.z.array(callerSchema).optional(),
9199
+ })
9200
+ .strict();
9201
+ // ============================================================================
9202
+ // Invocation Context Resolution
9203
+ // ============================================================================
8308
9204
  /**
8309
- * Normalize a symbol to uppercase for case-insensitive lookup.
9205
+ * Resolve invocation metadata to invocation context.
8310
9206
  *
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.
9207
+ * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
9208
+ * @param defaults - Default runtime and tokens to use if not overridden.
9209
+ * @returns Frozen, immutable invocation context with guaranteed values.
9210
+ * @throws KitError when meta contains invalid properties.
8319
9211
  *
8320
9212
  * @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.
9213
+ * Resolves the **WHO** called and **HOW** to observe:
9214
+ * - TraceId: Uses provided value or generates new one
9215
+ * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
9216
+ * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
9217
+ * - Callers: Uses provided array or empty array
8324
9218
  *
8325
- * @param options - Configuration options for the registry.
8326
- * @returns A token registry instance.
9219
+ * The returned context is frozen to enforce immutability.
8327
9220
  *
8328
9221
  * @example
8329
9222
  * ```typescript
9223
+ * import { resolveInvocationContext, createRuntime } from '@core/runtime'
8330
9224
  * import { createTokenRegistry } from '@core/tokens'
8331
9225
  *
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...' },
9226
+ * const defaults = {
9227
+ * runtime: createRuntime(),
9228
+ * tokens: createTokenRegistry(),
8348
9229
  * }
8349
9230
  *
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
- * }
9231
+ * // Minimal - just using defaults
9232
+ * const ctx = resolveInvocationContext(undefined, defaults)
8363
9233
  *
8364
- * const registry = createTokenRegistry({ tokens: [customUsdc] })
8365
- * // Now USDC resolves to customUsdc definition
8366
- * ```
9234
+ * // With trace ID and caller info
9235
+ * const ctx = resolveInvocationContext(
9236
+ * {
9237
+ * traceId: 'abc-123',
9238
+ * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
9239
+ * },
9240
+ * defaults
9241
+ * )
8367
9242
  *
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'
9243
+ * // With runtime override (complete replacement)
9244
+ * const ctx = resolveInvocationContext(
9245
+ * { runtime: createRuntime({ logger: myLogger }) },
9246
+ * defaults
8375
9247
  * )
8376
9248
  * ```
8377
9249
  */
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);
9250
+ function resolveInvocationContext(meta, defaults) {
9251
+ // Validate meta input if provided
9252
+ if (meta !== undefined) {
9253
+ const result = invocationMetaSchema.safeParse(meta);
9254
+ if (!result.success) {
9255
+ throw createValidationFailedError('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
8430
9256
  }
8431
- return {
8432
- decimals,
8433
- locator,
8434
- };
8435
9257
  }
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
- };
9258
+ // Generate trace ID if not provided
9259
+ const traceId = meta?.traceId ?? createTraceId();
9260
+ // Use meta overrides or fall back to defaults
9261
+ const runtime = meta?.runtime ?? defaults.runtime;
9262
+ const tokens = meta?.tokens ?? defaults.tokens;
9263
+ const callers = meta?.callers ?? [];
9264
+ return Object.freeze({ traceId, runtime, tokens, callers });
8474
9265
  }
8475
9266
 
8476
9267
  /**
8477
- * Define a schema for token registry interfaces.
9268
+ * Extend an invocation context by appending a caller to its call chain.
9269
+ *
9270
+ * @param context - The existing invocation context to extend.
9271
+ * @param caller - The caller to append to the call chain.
9272
+ * @returns A new frozen invocation context with the caller appended.
8478
9273
  *
8479
9274
  * @remarks
8480
- * Validate that a registry exposes the `resolve()` API used by adapters.
9275
+ * This function creates a new immutable context with the caller appended
9276
+ * to the `callers` array while preserving all other context properties
9277
+ * (traceId, runtime, tokens).
9278
+ *
9279
+ * The returned context is frozen to enforce immutability.
8481
9280
  *
8482
9281
  * @example
8483
9282
  * ```typescript
8484
- * import { tokenRegistrySchema } from '@core/tokens'
8485
- *
8486
- * const registry = {
8487
- * resolve: () => ({ locator: '0x0', decimals: 6 }),
8488
- * }
9283
+ * import { extendInvocationContext } from '@core/runtime'
8489
9284
  *
8490
- * tokenRegistrySchema.parse(registry)
9285
+ * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
9286
+ * const extended = extendInvocationContext(existingContext, caller)
9287
+ * // extended.callers === [...existingContext.callers, caller]
8491
9288
  * ```
8492
9289
  */
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
- });
9290
+ function extendInvocationContext(context, caller) {
9291
+ return Object.freeze({
9292
+ traceId: context.traceId,
9293
+ runtime: context.runtime,
9294
+ tokens: context.tokens,
9295
+ callers: [...context.callers, caller],
9296
+ });
9297
+ }
9298
+
9299
+ // Clock - expose defaultClock for backward compatibility
9300
+ /** Clock validation schema (backward compatibility). */
9301
+ zod.z.custom((val) => val !== null &&
9302
+ typeof val === 'object' &&
9303
+ 'now' in val &&
9304
+ typeof val['now'] === 'function');
9305
+ /** EventBus validation schema (backward compatibility). */
9306
+ zod.z.custom((val) => val !== null &&
9307
+ typeof val === 'object' &&
9308
+ 'emit' in val &&
9309
+ typeof val['emit'] === 'function');
9310
+ /** Runtime validation schema (backward compatibility). */
9311
+ zod.z
9312
+ .object({
9313
+ logger: zod.z.any().optional(),
9314
+ events: zod.z.any().optional(),
9315
+ metrics: zod.z.any().optional(),
9316
+ clock: zod.z.any().optional(),
9317
+ })
9318
+ .passthrough();
8506
9319
 
8507
- var version = "1.4.0";
9320
+ var version = "1.4.1";
8508
9321
  var pkg = {
8509
9322
  version: version};
8510
9323
 
@@ -8987,6 +9800,24 @@ const validateNativeBalanceForTransaction = async (params) => {
8987
9800
  }
8988
9801
  };
8989
9802
 
9803
+ /**
9804
+ * Permit signature standards for gasless token approvals.
9805
+ *
9806
+ * Defines the permit types that can be used to approve token spending
9807
+ * without requiring a separate approval transaction.
9808
+ *
9809
+ * @remarks
9810
+ * - NONE: No permit, tokens must be pre-approved via separate transaction
9811
+ * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
9812
+ */
9813
+ var PermitType;
9814
+ (function (PermitType) {
9815
+ /** No permit required - tokens must be pre-approved */
9816
+ PermitType[PermitType["NONE"] = 0] = "NONE";
9817
+ /** EIP-2612 standard permit */
9818
+ PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
9819
+ })(PermitType || (PermitType = {}));
9820
+
8990
9821
  /**
8991
9822
  * CCTP bridge step names that can occur in the bridging flow.
8992
9823
  *