@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.mjs CHANGED
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  // Buffer polyfill setup - executes before any other code
20
- // Ensures globalThis.Buffer is available for @solana/spl-token and other Solana libraries
20
+ // Ensures globalThis.Buffer is available for Solana libraries
21
21
  import { Buffer } from 'buffer';
22
22
  if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
23
23
  globalThis.Buffer = Buffer;
@@ -109,6 +109,122 @@ var Blockchain;
109
109
  Blockchain["ZKSync_Era"] = "ZKSync_Era";
110
110
  Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
111
111
  })(Blockchain || (Blockchain = {}));
112
+ /**
113
+ * Enum representing the subset of {@link Blockchain} that supports swap operations.
114
+ *
115
+ * This enum provides compile-time type safety for swap chain selection,
116
+ * ensuring only supported chains are available in IDE autocomplete
117
+ * when building swap parameters.
118
+ *
119
+ * @remarks
120
+ * Unlike the full {@link Blockchain} enum, SwapChain only includes networks
121
+ * where the swap functionality is actively supported by the library.
122
+ * Using this enum prevents runtime errors from attempting unsupported
123
+ * cross-chain swaps.
124
+ *
125
+ * Currently supports:
126
+ * - Ethereum mainnet
127
+ * - Base mainnet
128
+ * - Polygon mainnet
129
+ * - Solana mainnet
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * import { SwapChain, swap, createSwapKitContext } from '@circle-fin/swap-kit'
134
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
135
+ *
136
+ * const context = createSwapKitContext()
137
+ * const adapter = createViemAdapterFromPrivateKey({
138
+ * privateKey: process.env.PRIVATE_KEY
139
+ * })
140
+ *
141
+ * // ✅ Autocomplete shows only swap-supported chains
142
+ * const result = await swap(context, {
143
+ * from: {
144
+ * adapter,
145
+ * chain: SwapChain.Ethereum // Autocomplete: Ethereum, Base, Polygon, Solana
146
+ * },
147
+ * tokenIn: 'USDC',
148
+ * tokenOut: 'USDT',
149
+ * amount: '100.0'
150
+ * })
151
+ * ```
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * // String literals also work (constrained to SwapChain values)
156
+ * const result = await swap(context, {
157
+ * from: {
158
+ * adapter,
159
+ * chain: 'Ethereum' // ✅ Only SwapChain strings allowed
160
+ * },
161
+ * tokenIn: 'USDC',
162
+ * tokenOut: 'NATIVE',
163
+ * amount: '50.0'
164
+ * })
165
+ *
166
+ * // ❌ TypeScript error - Sui not in SwapChain enum
167
+ * const invalidResult = await swap(context, {
168
+ * from: {
169
+ * adapter,
170
+ * chain: 'Sui' // Compile-time error!
171
+ * },
172
+ * tokenIn: 'USDC',
173
+ * tokenOut: 'USDT',
174
+ * amount: '100.0'
175
+ * })
176
+ * ```
177
+ */
178
+ /**
179
+ * Enum representing chains that support same-chain swaps through the Swap Kit.
180
+ *
181
+ * Unlike the full {@link Blockchain} enum, SwapChain includes only mainnet
182
+ * networks where adapter contracts are deployed (CCTPv2 support).
183
+ *
184
+ * Dynamic validation via {@link isSwapSupportedChain} ensures chains
185
+ * automatically work when adapter contracts and supported tokens are deployed.
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * import { SwapChain } from '@core/chains'
190
+ * import { swap } from '@circle-fin/swap-kit'
191
+ *
192
+ * const result = await swap(context, {
193
+ * from: {
194
+ * adapter,
195
+ * chain: SwapChain.Arbitrum // Now supported!
196
+ * },
197
+ * tokenIn: 'USDC',
198
+ * tokenOut: 'WETH',
199
+ * amount: '100.0'
200
+ * })
201
+ * ```
202
+ *
203
+ * @see {@link isSwapSupportedChain} for runtime validation
204
+ * @see {@link getSwapSupportedChains} for all supported chains
205
+ */
206
+ var SwapChain;
207
+ (function (SwapChain) {
208
+ // Original 4 chains
209
+ SwapChain["Ethereum"] = "Ethereum";
210
+ SwapChain["Base"] = "Base";
211
+ SwapChain["Polygon"] = "Polygon";
212
+ SwapChain["Solana"] = "Solana";
213
+ // Additional supported chains
214
+ SwapChain["Arbitrum"] = "Arbitrum";
215
+ SwapChain["Optimism"] = "Optimism";
216
+ SwapChain["Avalanche"] = "Avalanche";
217
+ SwapChain["Linea"] = "Linea";
218
+ SwapChain["Ink"] = "Ink";
219
+ SwapChain["World_Chain"] = "World_Chain";
220
+ SwapChain["Unichain"] = "Unichain";
221
+ SwapChain["Plume"] = "Plume";
222
+ SwapChain["Sei"] = "Sei";
223
+ SwapChain["Sonic"] = "Sonic";
224
+ SwapChain["XDC"] = "XDC";
225
+ SwapChain["HyperEVM"] = "HyperEVM";
226
+ SwapChain["Monad"] = "Monad";
227
+ })(SwapChain || (SwapChain = {}));
112
228
  // -----------------------------------------------------------------------------
113
229
  // Bridge Chain Enum (CCTPv2 Supported Chains)
114
230
  // -----------------------------------------------------------------------------
@@ -260,6 +376,7 @@ const Algorand = defineChain({
260
376
  rpcEndpoints: ['https://mainnet-api.algonode.cloud'],
261
377
  eurcAddress: null,
262
378
  usdcAddress: '31566704',
379
+ usdtAddress: null,
263
380
  cctp: null,
264
381
  });
265
382
 
@@ -283,6 +400,7 @@ const AlgorandTestnet = defineChain({
283
400
  rpcEndpoints: ['https://testnet-api.algonode.cloud'],
284
401
  eurcAddress: null,
285
402
  usdcAddress: '10458941',
403
+ usdtAddress: null,
286
404
  cctp: null,
287
405
  });
288
406
 
@@ -306,6 +424,7 @@ const Aptos = defineChain({
306
424
  rpcEndpoints: ['https://fullnode.mainnet.aptoslabs.com/v1'],
307
425
  eurcAddress: null,
308
426
  usdcAddress: '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b',
427
+ usdtAddress: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
309
428
  cctp: {
310
429
  domain: 9,
311
430
  contracts: {
@@ -343,6 +462,7 @@ const AptosTestnet = defineChain({
343
462
  rpcEndpoints: ['https://fullnode.testnet.aptoslabs.com/v1'],
344
463
  eurcAddress: null,
345
464
  usdcAddress: '0x69091fbab5f7d635ee7ac5098cf0c1efbe31d68fec0f2cd565e8d168daf52832',
465
+ usdtAddress: null,
346
466
  cctp: {
347
467
  domain: 9,
348
468
  contracts: {
@@ -360,6 +480,121 @@ const AptosTestnet = defineChain({
360
480
  },
361
481
  });
362
482
 
483
+ /**
484
+ * Complete swap token registry - single source of truth for all swap-supported tokens.
485
+ *
486
+ * @remarks
487
+ * All packages should import from this registry for swap operations.
488
+ * Adding a new swap token requires updating only this registry.
489
+ *
490
+ * The NATIVE token is handled separately as it resolves dynamically based on chain.
491
+ *
492
+ * @example
493
+ * ```typescript
494
+ * import { SWAP_TOKEN_REGISTRY } from '@core/chains'
495
+ *
496
+ * // Get token decimals
497
+ * const decimals = SWAP_TOKEN_REGISTRY.USDC.decimals // 6
498
+ *
499
+ * // Check if token is stablecoin
500
+ * const isStable = SWAP_TOKEN_REGISTRY.DAI.category === 'stablecoin' // true
501
+ * ```
502
+ */
503
+ const SWAP_TOKEN_REGISTRY = {
504
+ // ============================================================================
505
+ // Stablecoins (6 decimals)
506
+ // ============================================================================
507
+ USDC: {
508
+ symbol: 'USDC',
509
+ decimals: 6,
510
+ category: 'stablecoin',
511
+ description: 'USD Coin',
512
+ },
513
+ EURC: {
514
+ symbol: 'EURC',
515
+ decimals: 6,
516
+ category: 'stablecoin',
517
+ description: 'Euro Coin',
518
+ },
519
+ USDT: {
520
+ symbol: 'USDT',
521
+ decimals: 6,
522
+ category: 'stablecoin',
523
+ description: 'Tether USD',
524
+ },
525
+ PYUSD: {
526
+ symbol: 'PYUSD',
527
+ decimals: 6,
528
+ category: 'stablecoin',
529
+ description: 'PayPal USD',
530
+ },
531
+ // ============================================================================
532
+ // Stablecoins (18 decimals)
533
+ // ============================================================================
534
+ DAI: {
535
+ symbol: 'DAI',
536
+ decimals: 18,
537
+ category: 'stablecoin',
538
+ description: 'MakerDAO stablecoin',
539
+ },
540
+ USDE: {
541
+ symbol: 'USDE',
542
+ decimals: 18,
543
+ category: 'stablecoin',
544
+ description: 'Ethena USD (synthetic dollar)',
545
+ },
546
+ // ============================================================================
547
+ // Wrapped Tokens
548
+ // ============================================================================
549
+ WBTC: {
550
+ symbol: 'WBTC',
551
+ decimals: 8,
552
+ category: 'wrapped',
553
+ description: 'Wrapped Bitcoin',
554
+ },
555
+ WETH: {
556
+ symbol: 'WETH',
557
+ decimals: 18,
558
+ category: 'wrapped',
559
+ description: 'Wrapped Ethereum',
560
+ },
561
+ WSOL: {
562
+ symbol: 'WSOL',
563
+ decimals: 9,
564
+ category: 'wrapped',
565
+ description: 'Wrapped Solana',
566
+ },
567
+ WAVAX: {
568
+ symbol: 'WAVAX',
569
+ decimals: 18,
570
+ category: 'wrapped',
571
+ description: 'Wrapped Avalanche',
572
+ },
573
+ WPOL: {
574
+ symbol: 'WPOL',
575
+ decimals: 18,
576
+ category: 'wrapped',
577
+ description: 'Wrapped Polygon',
578
+ },
579
+ };
580
+ /**
581
+ * Special NATIVE token constant for swap operations.
582
+ *
583
+ * @remarks
584
+ * NATIVE is handled separately from SWAP_TOKEN_REGISTRY because it resolves
585
+ * dynamically based on the chain (ETH on Ethereum, SOL on Solana, etc.).
586
+ * Its decimals are chain-specific.
587
+ */
588
+ const NATIVE_TOKEN = 'NATIVE';
589
+ /**
590
+ * Array of all supported swap token symbols including NATIVE.
591
+ * Useful for iteration, validation, and filtering.
592
+ */
593
+ [
594
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
595
+ NATIVE_TOKEN,
596
+ ];
597
+
363
598
  /**
364
599
  * The bridge contract address for EVM testnet networks.
365
600
  *
@@ -376,6 +611,13 @@ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d'
376
611
  * USDC transfers on live networks.
377
612
  */
378
613
  const BRIDGE_CONTRACT_EVM_MAINNET = '0xB3FA262d0fB521cc93bE83d87b322b8A23DAf3F0';
614
+ /**
615
+ * The adapter contract address for EVM mainnet networks.
616
+ *
617
+ * This contract serves as an adapter for integrating with various protocols
618
+ * on EVM-compatible chains. Use this address for mainnet adapter integrations.
619
+ */
620
+ const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845';
379
621
 
380
622
  /**
381
623
  * Arc Testnet chain definition
@@ -405,6 +647,7 @@ const ArcTestnet = defineChain({
405
647
  rpcEndpoints: ['https://rpc.testnet.arc.network/'],
406
648
  eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
407
649
  usdcAddress: '0x3600000000000000000000000000000000000000',
650
+ usdtAddress: null,
408
651
  cctp: {
409
652
  domain: 26,
410
653
  contracts: {
@@ -447,6 +690,7 @@ const Arbitrum = defineChain({
447
690
  rpcEndpoints: ['https://arb1.arbitrum.io/rpc'],
448
691
  eurcAddress: null,
449
692
  usdcAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
693
+ usdtAddress: null,
450
694
  cctp: {
451
695
  domain: 3,
452
696
  contracts: {
@@ -471,6 +715,7 @@ const Arbitrum = defineChain({
471
715
  },
472
716
  kitContracts: {
473
717
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
718
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
474
719
  },
475
720
  });
476
721
 
@@ -495,6 +740,7 @@ const ArbitrumSepolia = defineChain({
495
740
  rpcEndpoints: ['https://sepolia-rollup.arbitrum.io/rpc'],
496
741
  eurcAddress: null,
497
742
  usdcAddress: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
743
+ usdtAddress: null,
498
744
  cctp: {
499
745
  domain: 3,
500
746
  contracts: {
@@ -543,6 +789,7 @@ const Avalanche = defineChain({
543
789
  rpcEndpoints: ['https://api.avax.network/ext/bc/C/rpc'],
544
790
  eurcAddress: '0xc891eb4cbdeff6e073e859e987815ed1505c2acd',
545
791
  usdcAddress: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
792
+ usdtAddress: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7',
546
793
  cctp: {
547
794
  domain: 1,
548
795
  contracts: {
@@ -567,6 +814,7 @@ const Avalanche = defineChain({
567
814
  },
568
815
  kitContracts: {
569
816
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
817
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
570
818
  },
571
819
  });
572
820
 
@@ -590,6 +838,7 @@ const AvalancheFuji = defineChain({
590
838
  explorerUrl: 'https://subnets-test.avax.network/c-chain/tx/{hash}',
591
839
  eurcAddress: '0x5e44db7996c682e92a960b65ac713a54ad815c6b',
592
840
  usdcAddress: '0x5425890298aed601595a70ab815c96711a31bc65',
841
+ usdtAddress: null,
593
842
  cctp: {
594
843
  domain: 1,
595
844
  contracts: {
@@ -639,6 +888,7 @@ const Base = defineChain({
639
888
  rpcEndpoints: ['https://mainnet.base.org', 'https://base.publicnode.com'],
640
889
  eurcAddress: '0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42',
641
890
  usdcAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
891
+ usdtAddress: null,
642
892
  cctp: {
643
893
  domain: 6,
644
894
  contracts: {
@@ -663,6 +913,7 @@ const Base = defineChain({
663
913
  },
664
914
  kitContracts: {
665
915
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
916
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
666
917
  },
667
918
  });
668
919
 
@@ -687,6 +938,7 @@ const BaseSepolia = defineChain({
687
938
  rpcEndpoints: ['https://sepolia.base.org'],
688
939
  eurcAddress: '0x808456652fdb597867f38412077A9182bf77359F',
689
940
  usdcAddress: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
941
+ usdtAddress: null,
690
942
  cctp: {
691
943
  domain: 6,
692
944
  contracts: {
@@ -735,6 +987,7 @@ const Celo = defineChain({
735
987
  rpcEndpoints: ['https://forno.celo.org'],
736
988
  eurcAddress: null,
737
989
  usdcAddress: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
990
+ usdtAddress: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
738
991
  cctp: null,
739
992
  });
740
993
 
@@ -759,6 +1012,7 @@ const CeloAlfajoresTestnet = defineChain({
759
1012
  rpcEndpoints: ['https://alfajores-forno.celo-testnet.org'],
760
1013
  eurcAddress: null,
761
1014
  usdcAddress: '0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B',
1015
+ usdtAddress: null,
762
1016
  cctp: null,
763
1017
  });
764
1018
 
@@ -783,6 +1037,7 @@ const Codex = defineChain({
783
1037
  rpcEndpoints: ['https://rpc.codex.xyz'],
784
1038
  eurcAddress: null,
785
1039
  usdcAddress: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
1040
+ usdtAddress: null,
786
1041
  cctp: {
787
1042
  domain: 12,
788
1043
  contracts: {
@@ -825,6 +1080,7 @@ const CodexTestnet = defineChain({
825
1080
  rpcEndpoints: ['https://rpc.codex-stg.xyz'],
826
1081
  eurcAddress: null,
827
1082
  usdcAddress: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
1083
+ usdtAddress: null,
828
1084
  cctp: {
829
1085
  domain: 12,
830
1086
  contracts: {
@@ -867,6 +1123,7 @@ const Ethereum = defineChain({
867
1123
  rpcEndpoints: ['https://eth.merkle.io', 'https://ethereum.publicnode.com'],
868
1124
  eurcAddress: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
869
1125
  usdcAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
1126
+ usdtAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7',
870
1127
  cctp: {
871
1128
  domain: 0,
872
1129
  contracts: {
@@ -891,6 +1148,7 @@ const Ethereum = defineChain({
891
1148
  },
892
1149
  kitContracts: {
893
1150
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1151
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
894
1152
  },
895
1153
  });
896
1154
 
@@ -915,6 +1173,7 @@ const EthereumSepolia = defineChain({
915
1173
  rpcEndpoints: ['https://sepolia.drpc.org'],
916
1174
  eurcAddress: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
917
1175
  usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
1176
+ usdtAddress: null,
918
1177
  cctp: {
919
1178
  domain: 0,
920
1179
  contracts: {
@@ -962,6 +1221,7 @@ const Hedera = defineChain({
962
1221
  rpcEndpoints: ['https://mainnet.hashio.io/api'],
963
1222
  eurcAddress: null,
964
1223
  usdcAddress: '0.0.456858',
1224
+ usdtAddress: null,
965
1225
  cctp: null,
966
1226
  });
967
1227
 
@@ -985,6 +1245,7 @@ const HederaTestnet = defineChain({
985
1245
  rpcEndpoints: ['https://testnet.hashio.io/api'],
986
1246
  eurcAddress: null,
987
1247
  usdcAddress: '0.0.429274',
1248
+ usdtAddress: null,
988
1249
  cctp: null,
989
1250
  });
990
1251
 
@@ -1011,6 +1272,7 @@ const HyperEVM = defineChain({
1011
1272
  rpcEndpoints: ['https://rpc.hyperliquid.xyz/evm'],
1012
1273
  eurcAddress: null,
1013
1274
  usdcAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
1275
+ usdtAddress: null,
1014
1276
  cctp: {
1015
1277
  domain: 19,
1016
1278
  contracts: {
@@ -1029,6 +1291,7 @@ const HyperEVM = defineChain({
1029
1291
  },
1030
1292
  kitContracts: {
1031
1293
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1294
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1032
1295
  },
1033
1296
  });
1034
1297
 
@@ -1054,6 +1317,7 @@ const HyperEVMTestnet = defineChain({
1054
1317
  rpcEndpoints: ['https://rpc.hyperliquid-testnet.xyz/evm'],
1055
1318
  eurcAddress: null,
1056
1319
  usdcAddress: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
1320
+ usdtAddress: null,
1057
1321
  cctp: {
1058
1322
  domain: 19,
1059
1323
  contracts: {
@@ -1101,6 +1365,7 @@ const Ink = defineChain({
1101
1365
  ],
1102
1366
  eurcAddress: null,
1103
1367
  usdcAddress: '0x2D270e6886d130D724215A266106e6832161EAEd',
1368
+ usdtAddress: null,
1104
1369
  cctp: {
1105
1370
  domain: 21,
1106
1371
  contracts: {
@@ -1119,6 +1384,7 @@ const Ink = defineChain({
1119
1384
  },
1120
1385
  kitContracts: {
1121
1386
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1387
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1122
1388
  },
1123
1389
  });
1124
1390
 
@@ -1147,6 +1413,7 @@ const InkTestnet = defineChain({
1147
1413
  ],
1148
1414
  eurcAddress: null,
1149
1415
  usdcAddress: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
1416
+ usdtAddress: null,
1150
1417
  cctp: {
1151
1418
  domain: 21,
1152
1419
  contracts: {
@@ -1189,6 +1456,7 @@ const Linea = defineChain({
1189
1456
  rpcEndpoints: ['https://rpc.linea.build'],
1190
1457
  eurcAddress: null,
1191
1458
  usdcAddress: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
1459
+ usdtAddress: null,
1192
1460
  cctp: {
1193
1461
  domain: 11,
1194
1462
  contracts: {
@@ -1207,6 +1475,7 @@ const Linea = defineChain({
1207
1475
  },
1208
1476
  kitContracts: {
1209
1477
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1478
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1210
1479
  },
1211
1480
  });
1212
1481
 
@@ -1231,6 +1500,7 @@ const LineaSepolia = defineChain({
1231
1500
  rpcEndpoints: ['https://rpc.sepolia.linea.build'],
1232
1501
  eurcAddress: null,
1233
1502
  usdcAddress: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
1503
+ usdtAddress: null,
1234
1504
  cctp: {
1235
1505
  domain: 11,
1236
1506
  contracts: {
@@ -1275,6 +1545,7 @@ const Monad = defineChain({
1275
1545
  rpcEndpoints: ['https://rpc.monad.xyz'],
1276
1546
  eurcAddress: null,
1277
1547
  usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
1548
+ usdtAddress: null,
1278
1549
  cctp: {
1279
1550
  domain: 15,
1280
1551
  contracts: {
@@ -1293,6 +1564,7 @@ const Monad = defineChain({
1293
1564
  },
1294
1565
  kitContracts: {
1295
1566
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1567
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1296
1568
  },
1297
1569
  });
1298
1570
 
@@ -1319,6 +1591,7 @@ const MonadTestnet = defineChain({
1319
1591
  rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
1320
1592
  eurcAddress: null,
1321
1593
  usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
1594
+ usdtAddress: null,
1322
1595
  cctp: {
1323
1596
  domain: 15,
1324
1597
  contracts: {
@@ -1360,6 +1633,7 @@ const NEAR = defineChain({
1360
1633
  rpcEndpoints: ['https://eth-rpc.mainnet.near.org'],
1361
1634
  eurcAddress: null,
1362
1635
  usdcAddress: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
1636
+ usdtAddress: 'usdt.tether-token.near',
1363
1637
  cctp: null,
1364
1638
  });
1365
1639
 
@@ -1383,6 +1657,7 @@ const NEARTestnet = defineChain({
1383
1657
  rpcEndpoints: ['https://eth-rpc.testnet.near.org'],
1384
1658
  eurcAddress: null,
1385
1659
  usdcAddress: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
1660
+ usdtAddress: null,
1386
1661
  cctp: null,
1387
1662
  });
1388
1663
 
@@ -1406,6 +1681,7 @@ const Noble = defineChain({
1406
1681
  rpcEndpoints: ['https://noble-rpc.polkachu.com'],
1407
1682
  eurcAddress: null,
1408
1683
  usdcAddress: 'uusdc',
1684
+ usdtAddress: null,
1409
1685
  cctp: {
1410
1686
  domain: 4,
1411
1687
  contracts: {
@@ -1442,6 +1718,7 @@ const NobleTestnet = defineChain({
1442
1718
  rpcEndpoints: ['https://noble-testnet-rpc.polkachu.com'],
1443
1719
  eurcAddress: null,
1444
1720
  usdcAddress: 'uusdc',
1721
+ usdtAddress: null,
1445
1722
  cctp: {
1446
1723
  domain: 4,
1447
1724
  contracts: {
@@ -1479,6 +1756,7 @@ const Optimism = defineChain({
1479
1756
  rpcEndpoints: ['https://mainnet.optimism.io'],
1480
1757
  eurcAddress: null,
1481
1758
  usdcAddress: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
1759
+ usdtAddress: null,
1482
1760
  cctp: {
1483
1761
  domain: 2,
1484
1762
  contracts: {
@@ -1503,6 +1781,7 @@ const Optimism = defineChain({
1503
1781
  },
1504
1782
  kitContracts: {
1505
1783
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1784
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1506
1785
  },
1507
1786
  });
1508
1787
 
@@ -1527,6 +1806,7 @@ const OptimismSepolia = defineChain({
1527
1806
  rpcEndpoints: ['https://sepolia.optimism.io'],
1528
1807
  eurcAddress: null,
1529
1808
  usdcAddress: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
1809
+ usdtAddress: null,
1530
1810
  cctp: {
1531
1811
  domain: 2,
1532
1812
  contracts: {
@@ -1577,6 +1857,7 @@ const Plume = defineChain({
1577
1857
  rpcEndpoints: ['https://rpc.plume.org'],
1578
1858
  eurcAddress: null,
1579
1859
  usdcAddress: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
1860
+ usdtAddress: null,
1580
1861
  cctp: {
1581
1862
  domain: 22,
1582
1863
  contracts: {
@@ -1595,6 +1876,7 @@ const Plume = defineChain({
1595
1876
  },
1596
1877
  kitContracts: {
1597
1878
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1879
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1598
1880
  },
1599
1881
  });
1600
1882
 
@@ -1620,6 +1902,7 @@ const PlumeTestnet = defineChain({
1620
1902
  rpcEndpoints: ['https://testnet-rpc.plume.org'],
1621
1903
  eurcAddress: null,
1622
1904
  usdcAddress: '0xcB5f30e335672893c7eb944B374c196392C19D18',
1905
+ usdtAddress: null,
1623
1906
  cctp: {
1624
1907
  domain: 22,
1625
1908
  contracts: {
@@ -1661,6 +1944,7 @@ const PolkadotAssetHub = defineChain({
1661
1944
  rpcEndpoints: ['https://asset-hub-polkadot-rpc.n.dwellir.com'],
1662
1945
  eurcAddress: null,
1663
1946
  usdcAddress: '1337',
1947
+ usdtAddress: '1984',
1664
1948
  cctp: null,
1665
1949
  });
1666
1950
 
@@ -1684,6 +1968,7 @@ const PolkadotWestmint = defineChain({
1684
1968
  rpcEndpoints: ['https://westmint-rpc.polkadot.io'],
1685
1969
  eurcAddress: null,
1686
1970
  usdcAddress: 'Asset ID 31337',
1971
+ usdtAddress: null,
1687
1972
  cctp: null,
1688
1973
  });
1689
1974
 
@@ -1705,9 +1990,10 @@ const Polygon = defineChain({
1705
1990
  chainId: 137,
1706
1991
  isTestnet: false,
1707
1992
  explorerUrl: 'https://polygonscan.com/tx/{hash}',
1708
- rpcEndpoints: ['https://polygon-rpc.com', 'https://polygon.publicnode.com'],
1993
+ rpcEndpoints: ['https://polygon.publicnode.com', 'https://polygon.drpc.org'],
1709
1994
  eurcAddress: null,
1710
1995
  usdcAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
1996
+ usdtAddress: null,
1711
1997
  cctp: {
1712
1998
  domain: 7,
1713
1999
  contracts: {
@@ -1732,6 +2018,7 @@ const Polygon = defineChain({
1732
2018
  },
1733
2019
  kitContracts: {
1734
2020
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2021
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1735
2022
  },
1736
2023
  });
1737
2024
 
@@ -1756,6 +2043,7 @@ const PolygonAmoy = defineChain({
1756
2043
  rpcEndpoints: ['https://rpc-amoy.polygon.technology'],
1757
2044
  eurcAddress: null,
1758
2045
  usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
2046
+ usdtAddress: null,
1759
2047
  cctp: {
1760
2048
  domain: 7,
1761
2049
  contracts: {
@@ -1806,6 +2094,7 @@ const Sei = defineChain({
1806
2094
  rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
1807
2095
  eurcAddress: null,
1808
2096
  usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
2097
+ usdtAddress: null,
1809
2098
  cctp: {
1810
2099
  domain: 16,
1811
2100
  contracts: {
@@ -1824,6 +2113,7 @@ const Sei = defineChain({
1824
2113
  },
1825
2114
  kitContracts: {
1826
2115
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2116
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1827
2117
  },
1828
2118
  });
1829
2119
 
@@ -1849,6 +2139,7 @@ const SeiTestnet = defineChain({
1849
2139
  rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
1850
2140
  eurcAddress: null,
1851
2141
  usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
2142
+ usdtAddress: null,
1852
2143
  cctp: {
1853
2144
  domain: 16,
1854
2145
  contracts: {
@@ -1891,6 +2182,7 @@ const Sonic = defineChain({
1891
2182
  rpcEndpoints: ['https://rpc.soniclabs.com'],
1892
2183
  eurcAddress: null,
1893
2184
  usdcAddress: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
2185
+ usdtAddress: null,
1894
2186
  cctp: {
1895
2187
  domain: 13,
1896
2188
  contracts: {
@@ -1909,6 +2201,7 @@ const Sonic = defineChain({
1909
2201
  },
1910
2202
  kitContracts: {
1911
2203
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2204
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1912
2205
  },
1913
2206
  });
1914
2207
 
@@ -1933,6 +2226,7 @@ const SonicTestnet = defineChain({
1933
2226
  rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
1934
2227
  eurcAddress: null,
1935
2228
  usdcAddress: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
2229
+ usdtAddress: null,
1936
2230
  cctp: {
1937
2231
  domain: 13,
1938
2232
  contracts: {
@@ -1974,6 +2268,7 @@ const Solana = defineChain({
1974
2268
  rpcEndpoints: ['https://api.mainnet-beta.solana.com'],
1975
2269
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
1976
2270
  usdcAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
2271
+ usdtAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
1977
2272
  cctp: {
1978
2273
  domain: 5,
1979
2274
  contracts: {
@@ -2020,6 +2315,7 @@ const SolanaDevnet = defineChain({
2020
2315
  explorerUrl: 'https://solscan.io/tx/{hash}?cluster=devnet',
2021
2316
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
2022
2317
  usdcAddress: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
2318
+ usdtAddress: null,
2023
2319
  cctp: {
2024
2320
  domain: 5,
2025
2321
  contracts: {
@@ -2068,6 +2364,7 @@ const Stellar = defineChain({
2068
2364
  rpcEndpoints: ['https://horizon.stellar.org'],
2069
2365
  eurcAddress: 'EURC-GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2',
2070
2366
  usdcAddress: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
2367
+ usdtAddress: null,
2071
2368
  cctp: null,
2072
2369
  });
2073
2370
 
@@ -2091,6 +2388,7 @@ const StellarTestnet = defineChain({
2091
2388
  rpcEndpoints: ['https://horizon-testnet.stellar.org'],
2092
2389
  eurcAddress: 'EURC-GB3Q6QDZYTHWT7E5PVS3W7FUT5GVAFC5KSZFFLPU25GO7VTC3NM2ZTVO',
2093
2390
  usdcAddress: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
2391
+ usdtAddress: null,
2094
2392
  cctp: null,
2095
2393
  });
2096
2394
 
@@ -2114,6 +2412,7 @@ const Sui = defineChain({
2114
2412
  rpcEndpoints: ['https://fullnode.mainnet.sui.io'],
2115
2413
  eurcAddress: null,
2116
2414
  usdcAddress: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
2415
+ usdtAddress: null,
2117
2416
  cctp: {
2118
2417
  domain: 8,
2119
2418
  contracts: {
@@ -2151,6 +2450,7 @@ const SuiTestnet = defineChain({
2151
2450
  rpcEndpoints: ['https://fullnode.testnet.sui.io'],
2152
2451
  eurcAddress: null,
2153
2452
  usdcAddress: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
2453
+ usdtAddress: null,
2154
2454
  cctp: {
2155
2455
  domain: 8,
2156
2456
  contracts: {
@@ -2189,6 +2489,7 @@ const Unichain = defineChain({
2189
2489
  rpcEndpoints: ['https://mainnet.unichain.org'],
2190
2490
  eurcAddress: null,
2191
2491
  usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
2492
+ usdtAddress: null,
2192
2493
  cctp: {
2193
2494
  domain: 10,
2194
2495
  contracts: {
@@ -2213,6 +2514,7 @@ const Unichain = defineChain({
2213
2514
  },
2214
2515
  kitContracts: {
2215
2516
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2517
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2216
2518
  },
2217
2519
  });
2218
2520
 
@@ -2237,6 +2539,7 @@ const UnichainSepolia = defineChain({
2237
2539
  rpcEndpoints: ['https://sepolia.unichain.org'],
2238
2540
  eurcAddress: null,
2239
2541
  usdcAddress: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
2542
+ usdtAddress: null,
2240
2543
  cctp: {
2241
2544
  domain: 10,
2242
2545
  contracts: {
@@ -2285,6 +2588,7 @@ const WorldChain = defineChain({
2285
2588
  rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
2286
2589
  eurcAddress: null,
2287
2590
  usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
2591
+ usdtAddress: null,
2288
2592
  cctp: {
2289
2593
  domain: 14,
2290
2594
  contracts: {
@@ -2303,6 +2607,7 @@ const WorldChain = defineChain({
2303
2607
  },
2304
2608
  kitContracts: {
2305
2609
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2610
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2306
2611
  },
2307
2612
  });
2308
2613
 
@@ -2330,6 +2635,7 @@ const WorldChainSepolia = defineChain({
2330
2635
  ],
2331
2636
  eurcAddress: null,
2332
2637
  usdcAddress: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
2638
+ usdtAddress: null,
2333
2639
  cctp: {
2334
2640
  domain: 14,
2335
2641
  contracts: {
@@ -2374,6 +2680,7 @@ const XDC = defineChain({
2374
2680
  rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
2375
2681
  eurcAddress: null,
2376
2682
  usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
2683
+ usdtAddress: null,
2377
2684
  cctp: {
2378
2685
  domain: 18,
2379
2686
  contracts: {
@@ -2392,6 +2699,7 @@ const XDC = defineChain({
2392
2699
  },
2393
2700
  kitContracts: {
2394
2701
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2702
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2395
2703
  },
2396
2704
  });
2397
2705
 
@@ -2416,6 +2724,7 @@ const XDCApothem = defineChain({
2416
2724
  rpcEndpoints: ['https://erpc.apothem.network'],
2417
2725
  eurcAddress: null,
2418
2726
  usdcAddress: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
2727
+ usdtAddress: null,
2419
2728
  cctp: {
2420
2729
  domain: 18,
2421
2730
  contracts: {
@@ -2458,6 +2767,7 @@ const ZKSyncEra = defineChain({
2458
2767
  rpcEndpoints: ['https://mainnet.era.zksync.io'],
2459
2768
  eurcAddress: null,
2460
2769
  usdcAddress: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
2770
+ usdtAddress: null,
2461
2771
  cctp: null,
2462
2772
  });
2463
2773
 
@@ -2482,6 +2792,7 @@ const ZKSyncEraSepolia = defineChain({
2482
2792
  rpcEndpoints: ['https://sepolia.era.zksync.dev'],
2483
2793
  eurcAddress: null,
2484
2794
  usdcAddress: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
2795
+ usdtAddress: null,
2485
2796
  cctp: null,
2486
2797
  });
2487
2798
 
@@ -2649,10 +2960,12 @@ const baseChainDefinitionSchema = z.object({
2649
2960
  rpcEndpoints: z.array(z.string()),
2650
2961
  eurcAddress: z.string().nullable(),
2651
2962
  usdcAddress: z.string().nullable(),
2963
+ usdtAddress: z.string().nullable(),
2652
2964
  cctp: z.any().nullable(), // We'll accept any CCTP config structure
2653
2965
  kitContracts: z
2654
2966
  .object({
2655
2967
  bridge: z.string().optional(),
2968
+ adapter: z.string().optional(),
2656
2969
  })
2657
2970
  .optional(),
2658
2971
  });
@@ -2767,6 +3080,29 @@ z.union([
2767
3080
  z.nativeEnum(Blockchain),
2768
3081
  chainDefinitionSchema$2,
2769
3082
  ]);
3083
+ /**
3084
+ * Zod schema for validating swap-specific chain identifiers.
3085
+ *
3086
+ * Validates chains based on:
3087
+ * - CCTPv2 support (adapter contract deployed)
3088
+ * - Mainnet only (no testnets)
3089
+ * - At least one supported token available
3090
+ *
3091
+ */
3092
+ z.union([
3093
+ // String blockchain identifier (accepts SwapChain enum values)
3094
+ z.string().refine((val) => val in SwapChain, (val) => ({
3095
+ message: `"${val}" is not a supported swap chain. ` +
3096
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
3097
+ })),
3098
+ // SwapChain enum
3099
+ z.nativeEnum(SwapChain),
3100
+ // ChainDefinition object (checks if chain.chain is in SwapChain)
3101
+ chainDefinitionSchema$2.refine((chain) => chain.chain in SwapChain, (chain) => ({
3102
+ message: `"${chain.chain}" is not a supported swap chain. ` +
3103
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
3104
+ })),
3105
+ ]);
2770
3106
  /**
2771
3107
  * Zod schema for validating bridge chain identifiers.
2772
3108
  *
@@ -2806,6 +3142,38 @@ z.union([
2806
3142
  })),
2807
3143
  ]);
2808
3144
 
3145
+ /**
3146
+ * @packageDocumentation
3147
+ * @module SwapTokenSchemas
3148
+ *
3149
+ * Zod validation schemas for supported swap tokens.
3150
+ */
3151
+ // Internal enum used after input normalization.
3152
+ const swapTokenEnumSchema = z.enum([
3153
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
3154
+ NATIVE_TOKEN,
3155
+ ]);
3156
+ /**
3157
+ * Zod schema for validating supported swap token symbols.
3158
+ *
3159
+ * Accepts any token symbol from the SWAP_TOKEN_REGISTRY plus NATIVE.
3160
+ * Input matching is case-insensitive and normalized to uppercase.
3161
+ *
3162
+ * @example
3163
+ * ```typescript
3164
+ * import { supportedSwapTokenSchema } from '@core/chains'
3165
+ *
3166
+ * const result = supportedSwapTokenSchema.safeParse('USDC')
3167
+ * if (result.success) {
3168
+ * console.log('Valid swap token:', result.data)
3169
+ * }
3170
+ * ```
3171
+ */
3172
+ z
3173
+ .string()
3174
+ .transform((value) => value.toUpperCase())
3175
+ .pipe(swapTokenEnumSchema);
3176
+
2809
3177
  /**
2810
3178
  * Retrieve a chain definition by its blockchain enum value.
2811
3179
  *
@@ -2860,6 +3228,11 @@ const getChainByEnum = (blockchain) => {
2860
3228
  * ```
2861
3229
  */
2862
3230
  function resolveChainIdentifier(chainIdentifier) {
3231
+ // Handle null explicitly (typeof null === 'object' in JavaScript)
3232
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
3233
+ if (chainIdentifier === null) {
3234
+ throw new Error(`Invalid chain identifier type: null. Expected ChainDefinition object, Blockchain enum, or string literal.`);
3235
+ }
2863
3236
  // If it's already a ChainDefinition object, return it unchanged
2864
3237
  if (typeof chainIdentifier === 'object') {
2865
3238
  return chainIdentifier;
@@ -3151,11 +3524,11 @@ const formatComponent = (nameWithVersion) => {
3151
3524
  const createRequestContext = () => {
3152
3525
  const context = {};
3153
3526
  if (typeof globalThis !== 'undefined') {
3154
- if (globalThis.__STABLECOIN_KITS_EXTERNAL_PREFIX__ !== undefined) {
3155
- context.externalPrefix = globalThis.__STABLECOIN_KITS_EXTERNAL_PREFIX__;
3527
+ if (globalThis.__APP_KITS_EXTERNAL_PREFIX__ !== undefined) {
3528
+ context.externalPrefix = globalThis.__APP_KITS_EXTERNAL_PREFIX__;
3156
3529
  }
3157
- if (globalThis.__STABLECOIN_KITS_CURRENT_KIT__ !== undefined) {
3158
- context.kit = globalThis.__STABLECOIN_KITS_CURRENT_KIT__;
3530
+ if (globalThis.__APP_KITS_CURRENT_KIT__ !== undefined) {
3531
+ context.kit = globalThis.__APP_KITS_CURRENT_KIT__;
3159
3532
  }
3160
3533
  }
3161
3534
  return context;
@@ -3261,6 +3634,10 @@ const ERROR_TYPES = {
3261
3634
  RPC: 'RPC',
3262
3635
  /** Internet connectivity, DNS resolution, connection issues */
3263
3636
  NETWORK: 'NETWORK',
3637
+ /** API throttling, request frequency limits errors */
3638
+ RATE_LIMIT: 'RATE_LIMIT',
3639
+ /** Service errors */
3640
+ SERVICE: 'SERVICE',
3264
3641
  /** Catch-all for unrecognized errors (code 0) */
3265
3642
  UNKNOWN: 'UNKNOWN',
3266
3643
  };
@@ -3284,6 +3661,8 @@ const ERROR_CODE_RANGES = [
3284
3661
  { min: 3000, max: 3999, type: 'NETWORK' },
3285
3662
  { min: 4000, max: 4999, type: 'RPC' },
3286
3663
  { min: 5000, max: 5999, type: 'ONCHAIN' },
3664
+ { min: 7000, max: 7999, type: 'RATE_LIMIT' },
3665
+ { min: 8000, max: 8999, type: 'SERVICE' },
3287
3666
  { min: 9000, max: 9999, type: 'BALANCE' },
3288
3667
  ];
3289
3668
  /** Special code for UNKNOWN errors */
@@ -3331,6 +3710,8 @@ const errorDetailsSchema = z.object({
3331
3710
  * - 3000-3999: NETWORK errors - Connectivity issues
3332
3711
  * - 4000-4999: RPC errors - Provider issues, gas estimation
3333
3712
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures
3713
+ * - 7000-7999: RATE_LIMIT errors - API throttling
3714
+ * - 8000-8999: SERVICE errors - Internal service errors
3334
3715
  * - 9000-9999: BALANCE errors - Insufficient funds
3335
3716
  */
3336
3717
  code: z
@@ -3439,11 +3820,11 @@ const AMOUNT_FORMAT_ERROR_MESSAGE = 'Amount must be a numeric string with dot (.
3439
3820
  const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (.) as decimal separator (e.g., "1", "0.5", ".5", "1.5"), with no thousand separators or comma decimals';
3440
3821
 
3441
3822
  /**
3442
- * Structured error class for Stablecoin Kit operations.
3823
+ * Structured error class for App Kit operations.
3443
3824
  *
3444
3825
  * This class extends the native Error class while implementing the ErrorDetails
3445
3826
  * interface, providing a consistent error format for programmatic handling
3446
- * across the Stablecoin Kits ecosystem. All properties are immutable to ensure
3827
+ * across the App Kits ecosystem. All properties are immutable to ensure
3447
3828
  * error objects cannot be modified after creation.
3448
3829
  *
3449
3830
  * @example
@@ -3453,6 +3834,7 @@ const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (
3453
3834
  * const error = new KitError({
3454
3835
  * code: 1001,
3455
3836
  * name: 'INPUT_NETWORK_MISMATCH',
3837
+ * type: 'INPUT',
3456
3838
  * recoverability: 'FATAL',
3457
3839
  * message: 'Cannot bridge between mainnet and testnet'
3458
3840
  * })
@@ -3470,7 +3852,8 @@ const MAX_FEE_FORMAT_ERROR_MESSAGE = 'maxFee must be a numeric string with dot (
3470
3852
  * // Error with cause information
3471
3853
  * const error = new KitError({
3472
3854
  * code: 1002,
3473
- * name: 'INVALID_AMOUNT',
3855
+ * name: 'INPUT_INVALID_AMOUNT',
3856
+ * type: 'INPUT',
3474
3857
  * recoverability: 'FATAL',
3475
3858
  * message: 'Amount must be greater than zero',
3476
3859
  * cause: {
@@ -3554,6 +3937,8 @@ class KitError extends Error {
3554
3937
  * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
3555
3938
  * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
3556
3939
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
3940
+ * - 7000-7999: RATE_LIMIT errors - API throttling, request frequency limits
3941
+ * - 8000-8999: SERVICE errors - Internal service errors, server failures
3557
3942
  * - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
3558
3943
  */
3559
3944
  /**
@@ -3614,10 +3999,22 @@ const InputError = {
3614
3999
  name: 'INPUT_INVALID_CHAIN',
3615
4000
  type: 'INPUT',
3616
4001
  },
3617
- /** Invalid or unknown token (symbol not found, missing decimals, etc.) */
3618
- INVALID_TOKEN: {
4002
+ /** Unsupported token for chain */
4003
+ UNSUPPORTED_TOKEN: {
3619
4004
  code: 1006,
3620
- name: 'INPUT_INVALID_TOKEN',
4005
+ name: 'INPUT_UNSUPPORTED_TOKEN',
4006
+ type: 'INPUT',
4007
+ },
4008
+ /** Insufficient swap amount for the token pair */
4009
+ INSUFFICIENT_SWAP_AMOUNT: {
4010
+ code: 1007,
4011
+ name: 'INPUT_INSUFFICIENT_SWAP_AMOUNT',
4012
+ type: 'INPUT',
4013
+ },
4014
+ /** Action not supported by this adapter / ecosystem */
4015
+ UNSUPPORTED_ACTION: {
4016
+ code: 1008,
4017
+ name: 'INPUT_UNSUPPORTED_ACTION',
3621
4018
  type: 'INPUT',
3622
4019
  },
3623
4020
  /** General validation failure for complex validation rules */
@@ -3626,6 +4023,12 @@ const InputError = {
3626
4023
  name: 'INPUT_VALIDATION_FAILED',
3627
4024
  type: 'INPUT',
3628
4025
  },
4026
+ /** User cancelled wallet interaction (signature, transaction, connection) */
4027
+ USER_CANCELLED: {
4028
+ code: 1099,
4029
+ name: 'INPUT_USER_CANCELLED',
4030
+ type: 'INPUT',
4031
+ },
3629
4032
  };
3630
4033
  /**
3631
4034
  * Standardized error definitions for BALANCE type errors.
@@ -3713,8 +4116,7 @@ const NetworkError = {
3713
4116
  code: 3004,
3714
4117
  name: 'NETWORK_RELAYER_PENDING',
3715
4118
  type: 'NETWORK',
3716
- },
3717
- };
4119
+ }};
3718
4120
 
3719
4121
  /**
3720
4122
  * Creates error for network type mismatch between source and destination.
@@ -3801,7 +4203,7 @@ function createInvalidChainError(chain, reason) {
3801
4203
  const errorDetails = {
3802
4204
  ...InputError.INVALID_CHAIN,
3803
4205
  recoverability: 'FATAL',
3804
- message: `Invalid chain '${chain}': ${reason}`,
4206
+ message: `Invalid chain '${chain}': ${reason}.`,
3805
4207
  cause: {
3806
4208
  trace: { chain, reason },
3807
4209
  },
@@ -4150,6 +4552,9 @@ function getErrorMessage(error) {
4150
4552
  if (typeof error === 'string') {
4151
4553
  return error;
4152
4554
  }
4555
+ if (typeof error === 'object' && error !== null && 'message' in error) {
4556
+ return String(error.message);
4557
+ }
4153
4558
  return 'An unknown error occurred';
4154
4559
  }
4155
4560
  /**
@@ -4206,6 +4611,9 @@ function extractErrorInfo(error) {
4206
4611
  if (typeof err.code === 'number') {
4207
4612
  info.code = err.code;
4208
4613
  }
4614
+ if (isKitError(error)) {
4615
+ info.type = error.type;
4616
+ }
4209
4617
  return info;
4210
4618
  }
4211
4619
 
@@ -4328,7 +4736,17 @@ const makeApiRequest = async (url, method, isValidType, config, body) => {
4328
4736
  const response = await fetch(url, requestInit);
4329
4737
  clearTimeout(timeoutId);
4330
4738
  if (!response.ok) {
4331
- throw new Error(`HTTP ${String(response.status)} - ${response.statusText}`);
4739
+ let responseBody;
4740
+ try {
4741
+ responseBody = (await response.json());
4742
+ }
4743
+ catch {
4744
+ // Parse response body for diagnostics; continue even if malformed
4745
+ // so callers always receive HTTP status context
4746
+ }
4747
+ const httpError = new Error(`HTTP ${String(response.status)} - ${response.statusText}`);
4748
+ httpError.responseBody = responseBody;
4749
+ throw httpError;
4332
4750
  }
4333
4751
  const parsedJson = (await response.json());
4334
4752
  if (!isValidType(parsedJson)) {
@@ -4900,7 +5318,7 @@ const convertAddress = (address, targetFormat) => {
4900
5318
  *
4901
5319
  * This function normalizes token values from their blockchain representation (where
4902
5320
  * everything is stored as integers in the smallest denomination) to human-readable
4903
- * decimal format. Uses the battle-tested implementation from @ethersproject/units.
5321
+ * decimal format. Uses the battle-tested implementation from \@ethersproject/units.
4904
5322
  *
4905
5323
  * @param value - The value in smallest units (e.g., "1000000" for 1 USDC with 6 decimals)
4906
5324
  * @param decimals - The number of decimal places for the unit conversion
@@ -4929,116 +5347,837 @@ const formatUnits = (value, decimals) => {
4929
5347
  };
4930
5348
 
4931
5349
  /**
4932
- * Build a complete explorer URL from a chain definition and transaction hash.
4933
- *
4934
- * This function takes a chain definition containing an explorer URL template
4935
- * and replaces the `{hash}` placeholder with the provided transaction hash
4936
- * to create a complete, valid explorer URL.
4937
- *
4938
- * @param chainDef - The chain definition containing the explorer URL template.
4939
- * @param txHash - The transaction hash to insert into the URL template.
4940
- * @returns A complete explorer URL with the transaction hash inserted.
4941
- * @throws Error if the chain definition is null/undefined.
4942
- * @throws Error if the transaction hash is null/undefined/empty.
4943
- * @throws Error if the explorer URL template is missing or empty.
4944
- * @throws Error if the explorer URL template doesn't contain the {hash} placeholder.
4945
- * @throws Error if the resulting URL is invalid.
4946
- *
4947
- * @example
4948
- * ```typescript
4949
- * import { buildExplorerUrl } from '@core/utils'
4950
- * import { Ethereum } from '@core/chains'
4951
- *
4952
- * // Build URL for Ethereum transaction
4953
- * const url = buildExplorerUrl(Ethereum, '0x1234567890abcdef...')
4954
- * console.log(url) // 'https://etherscan.io/tx/0x1234567890abcdef...'
4955
- * ```
5350
+ * Create a structured error for token resolution failures.
4956
5351
  *
4957
- * @example
4958
- * ```typescript
4959
- * import { buildExplorerUrl } from '@core/utils'
4960
- * import { Solana } from '@core/chains'
5352
+ * @remarks
5353
+ * Returns a `KitError` with `INPUT_UNSUPPORTED_TOKEN` code (1006).
5354
+ * The error trace contains the selector and chain context for debugging.
4961
5355
  *
4962
- * // Build URL for Solana transaction
4963
- * const url = buildExplorerUrl(Solana, 'abc123def456...')
4964
- * console.log(url) // 'https://solscan.io/tx/abc123def456...'
4965
- * ```
5356
+ * @param message - Human-readable error description.
5357
+ * @param selector - The token selector that failed to resolve.
5358
+ * @param chainId - The chain being resolved for (optional).
5359
+ * @param cause - The underlying error, if any (optional).
5360
+ * @returns A KitError with INPUT type and FATAL recoverability.
4966
5361
  *
4967
5362
  * @example
4968
5363
  * ```typescript
4969
- * import { buildExplorerUrl } from '@core/utils'
4970
- * import { Aptos } from '@core/chains'
4971
- *
4972
- * // Build URL for Aptos transaction with query parameters
4973
- * const url = buildExplorerUrl(Aptos, '0xabc123...')
4974
- * console.log(url) // 'https://explorer.aptoslabs.com/txn/0xabc123...?network=mainnet'
5364
+ * throw createTokenResolutionError(
5365
+ * 'Unknown token symbol: FAKE',
5366
+ * 'FAKE',
5367
+ * 'Ethereum'
5368
+ * )
4975
5369
  * ```
4976
5370
  */
4977
- function buildExplorerUrl(chainDef, txHash) {
4978
- // Validate input parameters using our standard validation pattern
4979
- validateOrThrow({ chainDef, txHash }, buildExplorerUrlParamsSchema, 'Invalid buildExplorerUrl parameters');
4980
- // Parse and transform input parameters (e.g., trim whitespace from txHash)
4981
- const { chainDef: validChainDef, txHash: validTxHash } = buildExplorerUrlParamsSchema.parse({ chainDef, txHash });
4982
- // Replace all occurrences of the placeholder with the transaction hash
4983
- const explorerUrl = validChainDef.explorerUrl.replace(/{hash}/g, validTxHash);
4984
- // Validate the resulting URL
4985
- validate(explorerUrl, explorerUrlSchema, 'explorer URL');
4986
- return explorerUrl;
5371
+ function createTokenResolutionError(message, selector, chainId, cause) {
5372
+ const trace = {
5373
+ selector,
5374
+ ...(chainId === undefined ? {} : { chainId }),
5375
+ ...({} ),
5376
+ };
5377
+ return new KitError({
5378
+ ...InputError.UNSUPPORTED_TOKEN,
5379
+ recoverability: 'FATAL',
5380
+ message,
5381
+ cause: { trace },
5382
+ });
4987
5383
  }
4988
5384
 
4989
5385
  /**
4990
- * CCTP forwarding magic bytes prefix.
4991
- *
4992
- * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
4993
- * This prefix is right-padded to 24 bytes in the final hookData.
4994
- */
4995
- const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
4996
- /**
4997
- * CCTP forwarding version number.
4998
- *
4999
- * Version 0 is used for basic forwarding requests.
5000
- */
5001
- const CCTP_FORWARD_VERSION = 0;
5002
- /**
5003
- * Length of Circle-reserved payload for basic forwarding (0 bytes).
5004
- *
5005
- * Set to 0 when no additional Circle-reserved data is needed.
5006
- */
5007
- const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
5008
- /**
5009
- * Build the hookData bytes for CCTP forwarding.
5010
- *
5011
- * Constructs a 32-byte hookData payload that signals to Circle's Orbit relayer
5012
- * that the user wants automated attestation fetching and destination mint execution.
5386
+ * USDC token definition with addresses and metadata.
5013
5387
  *
5014
- * The hookData format is:
5015
- * - Bytes 0-23: ASCII "cctp-forward" right-padded to 24 bytes with zeros
5016
- * - Bytes 24-27: uint32 version (big-endian) - currently 0
5017
- * - Bytes 28-31: uint32 length (big-endian) - 0 for basic forwarding
5388
+ * @remarks
5389
+ * This is the built-in USDC definition used by the TokenRegistry.
5390
+ * Includes all known USDC addresses across supported chains.
5018
5391
  *
5019
- * @returns A 0x-prefixed hex string representing the 32-byte hookData
5392
+ * Keys use the `Blockchain` enum for type safety. Both enum values
5393
+ * and string literals are supported:
5394
+ * - `Blockchain.Ethereum` or `'Ethereum'`
5020
5395
  *
5021
5396
  * @example
5022
5397
  * ```typescript
5023
- * import { buildForwardingHookData } from '@core/utils'
5024
- *
5025
- * const hookData = buildForwardingHookData()
5026
- * // Returns: '0x636374702d666f72776172640000000000000000000000000000000000000000'
5398
+ * import { USDC } from '@core/tokens'
5399
+ * import { Blockchain } from '@core/chains'
5027
5400
  *
5028
- * // Use with depositForBurnWithHook action
5029
- * await adapter.action('cctp.v2.depositForBurnWithHook', {
5030
- * amount: BigInt('1000000'),
5031
- * mintRecipient: '0x...',
5032
- * maxFee: BigInt('50000'),
5033
- * minFinalityThreshold: 1000,
5034
- * fromChain: ethereum,
5035
- * toChain: base,
5036
- * hookData
5037
- * })
5401
+ * console.log(USDC.symbol) // 'USDC'
5402
+ * console.log(USDC.decimals) // 6
5403
+ * console.log(USDC.locators[Blockchain.Ethereum])
5404
+ * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
5038
5405
  * ```
5039
5406
  */
5040
- // Cached result for memoization (computed once on first call)
5041
- let cachedHookDataHex = null;
5407
+ const USDC = {
5408
+ symbol: 'USDC',
5409
+ decimals: 6,
5410
+ locators: {
5411
+ // =========================================================================
5412
+ // Mainnets (alphabetically sorted)
5413
+ // =========================================================================
5414
+ [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
5415
+ [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
5416
+ [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
5417
+ [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
5418
+ [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
5419
+ [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
5420
+ [Blockchain.Hedera]: '0.0.456858',
5421
+ [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
5422
+ [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
5423
+ [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
5424
+ [Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
5425
+ [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
5426
+ [Blockchain.Noble]: 'uusdc',
5427
+ [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
5428
+ [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
5429
+ [Blockchain.Polkadot_Asset_Hub]: '1337',
5430
+ [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
5431
+ [Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
5432
+ [Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
5433
+ [Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
5434
+ [Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
5435
+ [Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
5436
+ [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
5437
+ [Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
5438
+ [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
5439
+ [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
5440
+ // =========================================================================
5441
+ // Testnets (alphabetically sorted)
5442
+ // =========================================================================
5443
+ [Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
5444
+ [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
5445
+ [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
5446
+ [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
5447
+ [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
5448
+ [Blockchain.Hedera_Testnet]: '0.0.429274',
5449
+ [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
5450
+ [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
5451
+ [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
5452
+ [Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
5453
+ [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
5454
+ [Blockchain.Noble_Testnet]: 'uusdc',
5455
+ [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
5456
+ [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
5457
+ [Blockchain.Polkadot_Westmint]: '31337',
5458
+ [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
5459
+ [Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
5460
+ [Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
5461
+ [Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
5462
+ [Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
5463
+ [Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
5464
+ [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
5465
+ [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
5466
+ [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
5467
+ [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
5468
+ },
5469
+ };
5470
+
5471
+ /**
5472
+ * USDT (Tether) token definition with addresses and metadata.
5473
+ *
5474
+ * @remarks
5475
+ * Built-in USDT definition for the TokenRegistry. Includes chain locators
5476
+ * for swap-supported chains.
5477
+ */
5478
+ const USDT = {
5479
+ symbol: 'USDT',
5480
+ decimals: 6,
5481
+ locators: {
5482
+ [Blockchain.Aptos]: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
5483
+ [Blockchain.Arbitrum]: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
5484
+ [Blockchain.Avalanche]: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7',
5485
+ [Blockchain.Celo]: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
5486
+ [Blockchain.Ethereum]: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
5487
+ [Blockchain.HyperEVM]: '0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb',
5488
+ [Blockchain.Ink]: '0x0200C29006150606B650577BBE7B6248F58470c1',
5489
+ [Blockchain.Linea]: '0xA219439258ca9da29E9Cc4cE5596924745e12B93',
5490
+ [Blockchain.Monad]: '0xe7cd86e13AC4309349F30B3435a9d337750fC82D',
5491
+ [Blockchain.NEAR]: 'usdt.tether-token.near',
5492
+ [Blockchain.Optimism]: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58',
5493
+ [Blockchain.Polkadot_Asset_Hub]: '1984',
5494
+ [Blockchain.Polygon]: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
5495
+ [Blockchain.Sei]: '0x9151434b16b9763660705744891fA906F660EcC5',
5496
+ [Blockchain.Solana]: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
5497
+ [Blockchain.Unichain]: '0x9151434b16b9763660705744891fA906F660EcC5',
5498
+ },
5499
+ };
5500
+
5501
+ /**
5502
+ * EURC (Euro Coin) token definition with addresses and metadata.
5503
+ *
5504
+ * @remarks
5505
+ * Built-in EURC definition for the TokenRegistry. Includes chain locators
5506
+ * for swap-supported chains.
5507
+ */
5508
+ const EURC = {
5509
+ symbol: 'EURC',
5510
+ decimals: 6,
5511
+ locators: {
5512
+ [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
5513
+ [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
5514
+ [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
5515
+ [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
5516
+ [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
5517
+ },
5518
+ };
5519
+
5520
+ /**
5521
+ * DAI (Maker DAO) stablecoin token definition with addresses and metadata.
5522
+ *
5523
+ * @remarks
5524
+ * Built-in DAI definition for the TokenRegistry. Includes chain locators
5525
+ * for swap-supported chains.
5526
+ */
5527
+ const DAI = {
5528
+ symbol: 'DAI',
5529
+ decimals: 18,
5530
+ chainDecimals: {
5531
+ [Blockchain.Solana]: 8,
5532
+ },
5533
+ locators: {
5534
+ [Blockchain.Arbitrum]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5535
+ [Blockchain.Avalanche]: '0xd586E7F844cEa2F87f50152665BCbc2C279D8d70',
5536
+ [Blockchain.Base]: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
5537
+ [Blockchain.Ethereum]: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
5538
+ [Blockchain.Linea]: '0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5',
5539
+ [Blockchain.Optimism]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5540
+ [Blockchain.Polygon]: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
5541
+ [Blockchain.Solana]: 'EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o',
5542
+ },
5543
+ };
5544
+
5545
+ /**
5546
+ * USDe (Ethena) synthetic dollar token definition with addresses and metadata.
5547
+ *
5548
+ * @remarks
5549
+ * Built-in USDE definition for the TokenRegistry. Includes chain locators
5550
+ * for swap-supported chains.
5551
+ */
5552
+ const USDE = {
5553
+ symbol: 'USDe',
5554
+ decimals: 18,
5555
+ chainDecimals: {
5556
+ [Blockchain.Solana]: 9,
5557
+ },
5558
+ locators: {
5559
+ [Blockchain.Arbitrum]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5560
+ [Blockchain.Base]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5561
+ [Blockchain.Ethereum]: '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3',
5562
+ [Blockchain.Linea]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5563
+ [Blockchain.Optimism]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5564
+ [Blockchain.Solana]: 'DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT',
5565
+ },
5566
+ };
5567
+
5568
+ /**
5569
+ * PYUSD (PayPal USD) stablecoin token definition with addresses and metadata.
5570
+ *
5571
+ * @remarks
5572
+ * Built-in PYUSD definition for the TokenRegistry. Includes chain locators
5573
+ * for swap-supported chains.
5574
+ */
5575
+ const PYUSD = {
5576
+ symbol: 'PYUSD',
5577
+ decimals: 6,
5578
+ locators: {
5579
+ [Blockchain.Arbitrum]: '0x46850aD61C2B7d64d08c9C754F45254596696984',
5580
+ [Blockchain.Ethereum]: '0x6c3ea9036406852006290770BEdFcAbA0e23A0e8',
5581
+ [Blockchain.Solana]: '2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo',
5582
+ },
5583
+ };
5584
+
5585
+ /**
5586
+ * WETH (Wrapped Ether) token definition with addresses and metadata.
5587
+ *
5588
+ * @remarks
5589
+ * Built-in WETH definition for the TokenRegistry. Includes chain locators
5590
+ * for swap-supported chains where WETH is available.
5591
+ */
5592
+ const WETH = {
5593
+ symbol: 'WETH',
5594
+ decimals: 18,
5595
+ chainDecimals: {
5596
+ [Blockchain.Solana]: 9,
5597
+ },
5598
+ locators: {
5599
+ [Blockchain.Arbitrum]: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
5600
+ [Blockchain.Avalanche]: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB',
5601
+ [Blockchain.Base]: '0x4200000000000000000000000000000000000006',
5602
+ [Blockchain.Ethereum]: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
5603
+ [Blockchain.Optimism]: '0x4200000000000000000000000000000000000006',
5604
+ [Blockchain.Polygon]: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
5605
+ [Blockchain.Solana]: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs',
5606
+ },
5607
+ };
5608
+
5609
+ /**
5610
+ * WBTC (Wrapped Bitcoin) token definition with addresses and metadata.
5611
+ *
5612
+ * @remarks
5613
+ * Built-in WBTC definition for the TokenRegistry. Includes chain locators
5614
+ * for swap-supported chains where WBTC is available.
5615
+ */
5616
+ const WBTC = {
5617
+ symbol: 'WBTC',
5618
+ decimals: 8,
5619
+ locators: {
5620
+ [Blockchain.Ethereum]: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
5621
+ [Blockchain.Arbitrum]: '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f',
5622
+ [Blockchain.Avalanche]: '0x50b7545627a5162F82A992c33b87aDc75187B218',
5623
+ },
5624
+ };
5625
+
5626
+ /**
5627
+ * WSOL (Wrapped SOL) token definition with addresses and metadata.
5628
+ *
5629
+ * @remarks
5630
+ * Built-in WSOL definition for the TokenRegistry. Includes chain locators
5631
+ * for swap-supported chains where WSOL is available.
5632
+ */
5633
+ const WSOL = {
5634
+ symbol: 'WSOL',
5635
+ decimals: 9,
5636
+ locators: {
5637
+ [Blockchain.Solana]: 'So11111111111111111111111111111111111111112',
5638
+ },
5639
+ };
5640
+
5641
+ /**
5642
+ * WAVAX (Wrapped AVAX) token definition with addresses and metadata.
5643
+ *
5644
+ * @remarks
5645
+ * Built-in WAVAX definition for the TokenRegistry. Includes chain locators
5646
+ * for swap-supported chains where WAVAX is available.
5647
+ */
5648
+ const WAVAX = {
5649
+ symbol: 'WAVAX',
5650
+ decimals: 18,
5651
+ locators: {
5652
+ [Blockchain.Avalanche]: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7',
5653
+ },
5654
+ };
5655
+
5656
+ /**
5657
+ * WPOL (Wrapped POL) token definition with addresses and metadata.
5658
+ *
5659
+ * @remarks
5660
+ * Built-in WPOL definition for the TokenRegistry. Includes chain locators
5661
+ * for swap-supported chains where WPOL is available.
5662
+ */
5663
+ const WPOL = {
5664
+ symbol: 'WPOL',
5665
+ decimals: 18,
5666
+ locators: {
5667
+ [Blockchain.Polygon]: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
5668
+ },
5669
+ };
5670
+
5671
+ /**
5672
+ * ETH (native Ether alias) token definition with metadata.
5673
+ *
5674
+ * @remarks
5675
+ * Built-in ETH definition for the TokenRegistry. Used as a symbol alias
5676
+ * for native ETH (e.g. in swap flows). Chain locators are TBD and will
5677
+ * be added when addresses are available. Use raw selector with
5678
+ * locator + decimals where native gas token is represented as a contract.
5679
+ */
5680
+ const ETH = {
5681
+ symbol: 'ETH',
5682
+ decimals: 18,
5683
+ locators: {},
5684
+ };
5685
+
5686
+ /**
5687
+ * POL (Polygon native token) token definition with addresses and metadata.
5688
+ *
5689
+ * @remarks
5690
+ * Built-in POL definition for the TokenRegistry. Includes chain locators
5691
+ * where POL is available as a bridged/wrapped asset.
5692
+ */
5693
+ const POL = {
5694
+ symbol: 'POL',
5695
+ decimals: 18,
5696
+ locators: {
5697
+ [Blockchain.Ethereum]: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6',
5698
+ },
5699
+ };
5700
+
5701
+ /**
5702
+ * PLUME (Plume network token) token definition with addresses and metadata.
5703
+ *
5704
+ * @remarks
5705
+ * Built-in PLUME definition for the TokenRegistry. Includes chain locators
5706
+ * where PLUME is available.
5707
+ */
5708
+ const PLUME = {
5709
+ symbol: 'PLUME',
5710
+ decimals: 18,
5711
+ locators: {
5712
+ [Blockchain.Ethereum]: '0x4C1746A800D224393fE2470C70A35717eD4eA5F1',
5713
+ },
5714
+ };
5715
+
5716
+ /**
5717
+ * MON token definition with Solana mint metadata.
5718
+ *
5719
+ * @remarks
5720
+ * Built-in MON definition for the TokenRegistry. Includes chain locators
5721
+ * for swap-supported chains.
5722
+ */
5723
+ const MON = {
5724
+ symbol: 'MON',
5725
+ decimals: 18,
5726
+ chainDecimals: {
5727
+ [Blockchain.Solana]: 8,
5728
+ },
5729
+ locators: {
5730
+ [Blockchain.Solana]: 'CrAr4RRJMBVwRsZtT62pEhfA9H5utymC2mVx8e7FreP2',
5731
+ },
5732
+ };
5733
+
5734
+ // Re-export for consumers
5735
+ /**
5736
+ * All default token definitions.
5737
+ *
5738
+ * @remarks
5739
+ * These tokens are automatically included in the TokenRegistry when created
5740
+ * without explicit defaults. Extensions can override these definitions.
5741
+ * Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
5742
+ * WPOL, ETH, POL, PLUME, and MON.
5743
+ *
5744
+ * @example
5745
+ * ```typescript
5746
+ * import { createTokenRegistry } from '@core/tokens'
5747
+ *
5748
+ * // Registry uses these by default
5749
+ * const registry = createTokenRegistry()
5750
+ *
5751
+ * // Add custom tokens (built-ins are still included)
5752
+ * const customRegistry = createTokenRegistry({
5753
+ * tokens: [myCustomToken],
5754
+ * })
5755
+ * ```
5756
+ */
5757
+ const DEFAULT_TOKENS = [
5758
+ USDC,
5759
+ USDT,
5760
+ EURC,
5761
+ DAI,
5762
+ USDE,
5763
+ PYUSD,
5764
+ WETH,
5765
+ WBTC,
5766
+ WSOL,
5767
+ WAVAX,
5768
+ WPOL,
5769
+ ETH,
5770
+ POL,
5771
+ PLUME,
5772
+ MON,
5773
+ ];
5774
+ /**
5775
+ * Uppercased token symbols approved for swap fee collection.
5776
+ *
5777
+ * @remarks
5778
+ * Derived from {@link DEFAULT_TOKENS} so all consumers share a single
5779
+ * allowlist source and stay in sync when defaults change.
5780
+ */
5781
+ new Set(DEFAULT_TOKENS.map((t) => t.symbol.toUpperCase()));
5782
+
5783
+ /**
5784
+ * Resolve the effective decimals for a token on a specific chain.
5785
+ *
5786
+ * Returns the chain-specific override from `chainDecimals` when available,
5787
+ * otherwise falls back to the token's default `decimals`.
5788
+ *
5789
+ * @param token - The token definition to resolve decimals for.
5790
+ * @param chain - Optional chain identifier. When omitted, the default decimals are returned.
5791
+ * @returns The number of decimal places for the token on the given chain.
5792
+ *
5793
+ * @example
5794
+ * ```typescript
5795
+ * import { resolveTokenDecimals } from '\@core/tokens'
5796
+ *
5797
+ * // DAI: 18 decimals on EVM, 8 on Solana
5798
+ * resolveTokenDecimals(DAI) // 18 (default)
5799
+ * resolveTokenDecimals(DAI, 'Solana') // 8 (chain override)
5800
+ * resolveTokenDecimals(DAI, 'Ethereum') // 18 (no override, falls back)
5801
+ * ```
5802
+ */
5803
+ function resolveTokenDecimals(token, chain) {
5804
+ if (chain === undefined) {
5805
+ return token.decimals;
5806
+ }
5807
+ return token.chainDecimals?.[chain] ?? token.decimals;
5808
+ }
5809
+
5810
+ /**
5811
+ * Check if a selector is a raw token selector (object form).
5812
+ *
5813
+ * @param selector - The token selector to check.
5814
+ * @returns True if the selector is a raw token selector.
5815
+ */
5816
+ function isRawSelector(selector) {
5817
+ return typeof selector === 'object' && 'locator' in selector;
5818
+ }
5819
+ /**
5820
+ * Normalize a symbol to uppercase for case-insensitive lookup.
5821
+ *
5822
+ * @param symbol - The symbol to normalize.
5823
+ * @returns The normalized (uppercase) symbol.
5824
+ */
5825
+ function normalizeSymbol(symbol) {
5826
+ return symbol.toUpperCase();
5827
+ }
5828
+ /**
5829
+ * Normalize a locator for stable chain-aware lookup.
5830
+ *
5831
+ * @remarks
5832
+ * EVM-style addresses (`0x...`) are normalized to lowercase for
5833
+ * case-insensitive matching. Non-EVM locators keep original casing.
5834
+ */
5835
+ function normalizeLocator(locator) {
5836
+ if (locator.startsWith('0x') || locator.startsWith('0X')) {
5837
+ return locator.toLowerCase();
5838
+ }
5839
+ return locator;
5840
+ }
5841
+ /**
5842
+ * Build a stable map key for a chain + locator pair.
5843
+ */
5844
+ function buildAddressKey(chainId, locator) {
5845
+ return `${chainId}::${normalizeLocator(locator)}`;
5846
+ }
5847
+ /**
5848
+ * Create a token registry with built-in tokens and optional extensions.
5849
+ *
5850
+ * @remarks
5851
+ * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
5852
+ * Custom tokens are merged on top - use this to add new tokens or override
5853
+ * built-in definitions.
5854
+ *
5855
+ * @param options - Configuration options for the registry.
5856
+ * @returns A token registry instance.
5857
+ *
5858
+ * @example
5859
+ * ```typescript
5860
+ * import { createTokenRegistry } from '@core/tokens'
5861
+ *
5862
+ * // Create registry with built-in tokens (USDC, etc.)
5863
+ * const registry = createTokenRegistry()
5864
+ *
5865
+ * // Resolve USDC on Ethereum
5866
+ * const usdc = registry.resolve('USDC', 'Ethereum')
5867
+ * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
5868
+ * console.log(usdc.decimals) // 6
5869
+ * ```
5870
+ *
5871
+ * @example
5872
+ * ```typescript
5873
+ * // Add custom tokens (built-ins are still included)
5874
+ * const myToken: TokenDefinition = {
5875
+ * symbol: 'MY',
5876
+ * decimals: 18,
5877
+ * locators: { Ethereum: '0x...' },
5878
+ * }
5879
+ *
5880
+ * const registry = createTokenRegistry({ tokens: [myToken] })
5881
+ * registry.resolve('USDC', 'Ethereum') // Still works!
5882
+ * registry.resolve('MY', 'Ethereum') // Also works
5883
+ * ```
5884
+ *
5885
+ * @example
5886
+ * ```typescript
5887
+ * // Override a built-in token
5888
+ * const customUsdc: TokenDefinition = {
5889
+ * symbol: 'USDC',
5890
+ * decimals: 6,
5891
+ * locators: { MyChain: '0xCustomAddress' },
5892
+ * }
5893
+ *
5894
+ * const registry = createTokenRegistry({ tokens: [customUsdc] })
5895
+ * // Now USDC resolves to customUsdc definition
5896
+ * ```
5897
+ *
5898
+ * @example
5899
+ * ```typescript
5900
+ * // Resolve arbitrary tokens by raw locator
5901
+ * const registry = createTokenRegistry()
5902
+ * const token = registry.resolve(
5903
+ * { locator: '0x1234...', decimals: 18 },
5904
+ * 'Ethereum'
5905
+ * )
5906
+ * ```
5907
+ */
5908
+ function createTokenRegistry(options = {}) {
5909
+ const { tokens = [], requireDecimals = false } = options;
5910
+ // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
5911
+ const tokenMap = new Map();
5912
+ // Add built-in tokens first
5913
+ for (const def of DEFAULT_TOKENS) {
5914
+ tokenMap.set(normalizeSymbol(def.symbol), def);
5915
+ }
5916
+ // Custom tokens override built-ins
5917
+ for (const def of tokens) {
5918
+ tokenMap.set(normalizeSymbol(def.symbol), def);
5919
+ }
5920
+ // Build an address index from the resolved token map so overrides win.
5921
+ const addressMap = new Map();
5922
+ for (const token of tokenMap.values()) {
5923
+ for (const [chainId, locator] of Object.entries(token.locators)) {
5924
+ if (typeof locator !== 'string' || locator.trim() === '') {
5925
+ continue;
5926
+ }
5927
+ addressMap.set(buildAddressKey(chainId, locator), token);
5928
+ }
5929
+ }
5930
+ /**
5931
+ * Resolve a symbol selector to token information.
5932
+ */
5933
+ function resolveSymbol(symbol, chainId) {
5934
+ const normalizedSymbol = normalizeSymbol(symbol);
5935
+ const definition = tokenMap.get(normalizedSymbol);
5936
+ if (definition === undefined) {
5937
+ throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
5938
+ }
5939
+ const locator = definition.locators[chainId];
5940
+ if (locator === undefined || locator.trim() === '') {
5941
+ throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
5942
+ }
5943
+ const decimals = resolveTokenDecimals(definition, chainId);
5944
+ return {
5945
+ symbol: definition.symbol,
5946
+ decimals,
5947
+ locator: normalizeLocator(locator),
5948
+ };
5949
+ }
5950
+ /**
5951
+ * Resolve a raw selector to token information.
5952
+ */
5953
+ function resolveRaw(selector, chainId) {
5954
+ const { locator, decimals } = selector;
5955
+ // Validate locator
5956
+ if (!locator || typeof locator !== 'string') {
5957
+ throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
5958
+ }
5959
+ // Decimals are always required for raw selectors
5960
+ if (decimals === undefined) {
5961
+ const message = requireDecimals
5962
+ ? 'Decimals required for raw token selector (requireDecimals: true)'
5963
+ : 'Decimals required for raw token selector. Provide { locator, decimals }.';
5964
+ throw createTokenResolutionError(message, selector, chainId);
5965
+ }
5966
+ // Validate decimals
5967
+ if (typeof decimals !== 'number' ||
5968
+ decimals < 0 ||
5969
+ !Number.isInteger(decimals)) {
5970
+ throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
5971
+ }
5972
+ return {
5973
+ decimals,
5974
+ locator: normalizeLocator(locator),
5975
+ };
5976
+ }
5977
+ return {
5978
+ resolve(selector, chainId) {
5979
+ // Runtime validation of inputs - these checks are for JS consumers
5980
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
5981
+ if (selector === null || selector === undefined) {
5982
+ throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
5983
+ }
5984
+ if (chainId === '' || typeof chainId !== 'string') {
5985
+ throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
5986
+ }
5987
+ // Dispatch based on selector type
5988
+ if (isRawSelector(selector)) {
5989
+ return resolveRaw(selector, chainId);
5990
+ }
5991
+ if (typeof selector === 'string') {
5992
+ return resolveSymbol(selector, chainId);
5993
+ }
5994
+ throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
5995
+ },
5996
+ resolveByAddress(address, chainId) {
5997
+ if (!address || typeof address !== 'string') {
5998
+ throw createTokenResolutionError('Token address is required for address resolution', { locator: address }, chainId);
5999
+ }
6000
+ if (chainId === '' || typeof chainId !== 'string') {
6001
+ throw createTokenResolutionError('Chain ID is required for token resolution', { locator: address }, chainId);
6002
+ }
6003
+ const definition = addressMap.get(buildAddressKey(chainId, address));
6004
+ if (definition === undefined) {
6005
+ throw createTokenResolutionError(`Unknown token address: ${address} for chain ${chainId}`, { locator: address }, chainId);
6006
+ }
6007
+ const locator = definition.locators[chainId];
6008
+ if (locator === undefined || locator.trim() === '') {
6009
+ throw createTokenResolutionError(`Token ${definition.symbol} has no locator for chain ${chainId}`, definition.symbol, chainId);
6010
+ }
6011
+ const decimals = resolveTokenDecimals(definition, chainId);
6012
+ return {
6013
+ symbol: definition.symbol,
6014
+ decimals,
6015
+ locator: normalizeLocator(locator),
6016
+ };
6017
+ },
6018
+ get(symbol) {
6019
+ if (!symbol || typeof symbol !== 'string') {
6020
+ return undefined;
6021
+ }
6022
+ return tokenMap.get(normalizeSymbol(symbol));
6023
+ },
6024
+ has(symbol) {
6025
+ if (!symbol || typeof symbol !== 'string') {
6026
+ return false;
6027
+ }
6028
+ return tokenMap.has(normalizeSymbol(symbol));
6029
+ },
6030
+ symbols() {
6031
+ return Array.from(tokenMap.values()).map((def) => def.symbol);
6032
+ },
6033
+ entries() {
6034
+ return Array.from(tokenMap.values());
6035
+ },
6036
+ };
6037
+ }
6038
+
6039
+ /**
6040
+ * Define a schema for token registry interfaces.
6041
+ *
6042
+ * @remarks
6043
+ * Validate that a registry exposes the `resolve()` API used by adapters.
6044
+ *
6045
+ * @example
6046
+ * ```typescript
6047
+ * import { tokenRegistrySchema } from '@core/tokens'
6048
+ *
6049
+ * const registry = {
6050
+ * resolve: () => ({ locator: '0x0', decimals: 6 }),
6051
+ * }
6052
+ *
6053
+ * tokenRegistrySchema.parse(registry)
6054
+ * ```
6055
+ */
6056
+ z.custom((value) => {
6057
+ if (value === null || typeof value !== 'object') {
6058
+ return false;
6059
+ }
6060
+ const record = value;
6061
+ return (typeof record['resolve'] === 'function' &&
6062
+ typeof record['get'] === 'function' &&
6063
+ typeof record['has'] === 'function' &&
6064
+ typeof record['symbols'] === 'function' &&
6065
+ typeof record['entries'] === 'function');
6066
+ }, {
6067
+ message: 'Invalid token registry',
6068
+ });
6069
+
6070
+ /**
6071
+ * Build a complete explorer URL from a chain definition and transaction hash.
6072
+ *
6073
+ * This function takes a chain definition containing an explorer URL template
6074
+ * and replaces the `{hash}` placeholder with the provided transaction hash
6075
+ * to create a complete, valid explorer URL.
6076
+ *
6077
+ * @param chainDef - The chain definition containing the explorer URL template.
6078
+ * @param txHash - The transaction hash to insert into the URL template.
6079
+ * @returns A complete explorer URL with the transaction hash inserted.
6080
+ * @throws Error if the chain definition is null/undefined.
6081
+ * @throws Error if the transaction hash is null/undefined/empty.
6082
+ * @throws Error if the explorer URL template is missing or empty.
6083
+ * @throws Error if the explorer URL template doesn't contain the {hash} placeholder.
6084
+ * @throws Error if the resulting URL is invalid.
6085
+ *
6086
+ * @example
6087
+ * ```typescript
6088
+ * import { buildExplorerUrl } from '@core/utils'
6089
+ * import { Ethereum } from '@core/chains'
6090
+ *
6091
+ * // Build URL for Ethereum transaction
6092
+ * const url = buildExplorerUrl(Ethereum, '0x1234567890abcdef...')
6093
+ * console.log(url) // 'https://etherscan.io/tx/0x1234567890abcdef...'
6094
+ * ```
6095
+ *
6096
+ * @example
6097
+ * ```typescript
6098
+ * import { buildExplorerUrl } from '@core/utils'
6099
+ * import { Solana } from '@core/chains'
6100
+ *
6101
+ * // Build URL for Solana transaction
6102
+ * const url = buildExplorerUrl(Solana, 'abc123def456...')
6103
+ * console.log(url) // 'https://solscan.io/tx/abc123def456...'
6104
+ * ```
6105
+ *
6106
+ * @example
6107
+ * ```typescript
6108
+ * import { buildExplorerUrl } from '@core/utils'
6109
+ * import { Aptos } from '@core/chains'
6110
+ *
6111
+ * // Build URL for Aptos transaction with query parameters
6112
+ * const url = buildExplorerUrl(Aptos, '0xabc123...')
6113
+ * console.log(url) // 'https://explorer.aptoslabs.com/txn/0xabc123...?network=mainnet'
6114
+ * ```
6115
+ */
6116
+ function buildExplorerUrl(chainDef, txHash) {
6117
+ // Validate input parameters using our standard validation pattern
6118
+ validateOrThrow({ chainDef, txHash }, buildExplorerUrlParamsSchema, 'Invalid buildExplorerUrl parameters');
6119
+ // Parse and transform input parameters (e.g., trim whitespace from txHash)
6120
+ const { chainDef: validChainDef, txHash: validTxHash } = buildExplorerUrlParamsSchema.parse({ chainDef, txHash });
6121
+ // Replace all occurrences of the placeholder with the transaction hash
6122
+ const explorerUrl = validChainDef.explorerUrl.replace(/{hash}/g, validTxHash);
6123
+ // Validate the resulting URL
6124
+ validate(explorerUrl, explorerUrlSchema, 'explorer URL');
6125
+ return explorerUrl;
6126
+ }
6127
+
6128
+ /**
6129
+ * CCTP forwarding magic bytes prefix.
6130
+ *
6131
+ * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
6132
+ * This prefix is right-padded to 24 bytes in the final hookData.
6133
+ */
6134
+ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
6135
+ /**
6136
+ * CCTP forwarding version number.
6137
+ *
6138
+ * Version 0 is used for basic forwarding requests.
6139
+ */
6140
+ const CCTP_FORWARD_VERSION = 0;
6141
+ /**
6142
+ * Length of Circle-reserved payload for basic forwarding (0 bytes).
6143
+ *
6144
+ * Set to 0 when no additional Circle-reserved data is needed.
6145
+ */
6146
+ const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
6147
+ /**
6148
+ * Build the hookData bytes for CCTP forwarding.
6149
+ *
6150
+ * Constructs a 32-byte hookData payload that signals to Circle's Orbit relayer
6151
+ * that the user wants automated attestation fetching and destination mint execution.
6152
+ *
6153
+ * The hookData format is:
6154
+ * - Bytes 0-23: ASCII "cctp-forward" right-padded to 24 bytes with zeros
6155
+ * - Bytes 24-27: uint32 version (big-endian) - currently 0
6156
+ * - Bytes 28-31: uint32 length (big-endian) - 0 for basic forwarding
6157
+ *
6158
+ * @returns A 0x-prefixed hex string representing the 32-byte hookData
6159
+ *
6160
+ * @example
6161
+ * ```typescript
6162
+ * import { buildForwardingHookData } from '@core/utils'
6163
+ *
6164
+ * const hookData = buildForwardingHookData()
6165
+ * // Returns: '0x636374702d666f72776172640000000000000000000000000000000000000000'
6166
+ *
6167
+ * // Use with depositForBurnWithHook action
6168
+ * await adapter.action('cctp.v2.depositForBurnWithHook', {
6169
+ * amount: BigInt('1000000'),
6170
+ * mintRecipient: '0x...',
6171
+ * maxFee: BigInt('50000'),
6172
+ * minFinalityThreshold: 1000,
6173
+ * fromChain: ethereum,
6174
+ * toChain: base,
6175
+ * hookData
6176
+ * })
6177
+ * ```
6178
+ */
6179
+ // Cached result for memoization (computed once on first call)
6180
+ let cachedHookDataHex = null;
5042
6181
  function buildForwardingHookData() {
5043
6182
  // Return cached result if already computed
5044
6183
  if (cachedHookDataHex !== null) {
@@ -5542,6 +6681,21 @@ async function fetchUsdcFastBurnFee(sourceDomain, destinationDomain, isTestnet)
5542
6681
  return minimumFee;
5543
6682
  }
5544
6683
 
6684
+ /**
6685
+ * Well-known SPL Token program ID.
6686
+ * Duplicated here as a raw string to avoid a static import of \@core/adapter-solana
6687
+ * (which would transitively pull \@solana/web3.js into the top-level bundle and
6688
+ * break lazy-loading for EVM-only consumers).
6689
+ *
6690
+ * This MUST match TOKEN_PROGRAM_ID in @core/adapter-solana/src/utils/splTokenUtils.
6691
+ */
6692
+ const TOKEN_PROGRAM_ID_BASE58 = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; // NOSONAR — public Solana program address
6693
+ /**
6694
+ * Well-known SPL Associated Token Account program ID (same rationale as above).
6695
+ *
6696
+ * This MUST match ASSOCIATED_TOKEN_PROGRAM_ID in @core/adapter-solana/src/utils/splTokenUtils.
6697
+ */
6698
+ const ASSOCIATED_TOKEN_PROGRAM_ID_BASE58 = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'; // NOSONAR — public Solana program address
5545
6699
  /**
5546
6700
  * Get the token account address where USDC will be minted for the recipient.
5547
6701
  *
@@ -5585,20 +6739,30 @@ mintAddress) => {
5585
6739
  return rawAddress;
5586
6740
  }
5587
6741
  else {
5588
- // Solana: derive the associated token account
5589
- // Use dynamic import to avoid loading Solana dependencies for EVM-only users
6742
+ // Solana: derive the associated token account.
6743
+ // Both @solana/web3.js and the program-ID constants are resolved lazily
6744
+ // so that EVM-only consumers never load Solana code.
6745
+ const { PublicKey } = await import('@solana/web3.js').catch(() => {
6746
+ throw new KitError({
6747
+ ...InputError.VALIDATION_FAILED,
6748
+ recoverability: 'FATAL',
6749
+ message: 'Failed to load @solana/web3.js. Please ensure it is installed: npm install @solana/web3.js',
6750
+ });
6751
+ });
5590
6752
  try {
5591
- const [{ PublicKey }, { getAssociatedTokenAddressSync }] = await Promise.all([
5592
- import('@solana/web3.js'),
5593
- import('@solana/spl-token'),
5594
- ]);
5595
6753
  const owner = new PublicKey(rawAddress);
5596
6754
  const mintPub = new PublicKey(mintAddress);
5597
- const ata = getAssociatedTokenAddressSync(mintPub, owner);
6755
+ const tokenProgramId = new PublicKey(TOKEN_PROGRAM_ID_BASE58);
6756
+ const ataProgramId = new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID_BASE58);
6757
+ const [ata] = PublicKey.findProgramAddressSync([owner.toBuffer(), tokenProgramId.toBuffer(), mintPub.toBuffer()], ataProgramId);
5598
6758
  return ata.toBase58();
5599
6759
  }
5600
- catch {
5601
- throw new Error('Failed to derive Solana token account. Please ensure @solana/web3.js and @solana/spl-token are installed: npm install @solana/web3.js @solana/spl-token');
6760
+ catch (error) {
6761
+ throw new KitError({
6762
+ ...InputError.INVALID_ADDRESS,
6763
+ recoverability: 'FATAL',
6764
+ message: `Failed to derive Solana Associated Token Account for recipient "${rawAddress}": ${error instanceof Error ? error.message : String(error)}`,
6765
+ });
5602
6766
  }
5603
6767
  }
5604
6768
  };
@@ -7914,590 +9078,239 @@ function createLogger(options, stream) {
7914
9078
  const finalOptions = redactConfig
7915
9079
  ? { ...pinoOptions, redact: redactConfig }
7916
9080
  : pinoOptions;
7917
- const pinoInstance = pino(finalOptions);
7918
- return wrapPino(pinoInstance);
7919
- }
7920
-
7921
- /**
7922
- * Factory for creating Runtime instances with defaults.
7923
- *
7924
- * @packageDocumentation
7925
- */
7926
- // ============================================================================
7927
- // Validation Schema
7928
- // ============================================================================
7929
- /**
7930
- * Schema for validating {@link RuntimeOptions}.
7931
- *
7932
- * @remarks
7933
- * Used internally by {@link createRuntime} to validate options from JS consumers.
7934
- * Exported for advanced use cases where manual validation is needed.
7935
- */
7936
- z
7937
- .object({
7938
- logger: loggerSchema.optional(),
7939
- metrics: metricsSchema.optional(),
7940
- })
7941
- .passthrough();
7942
- // ============================================================================
7943
- // Factory
7944
- // ============================================================================
7945
- /**
7946
- * Create a complete Runtime with sensible defaults.
7947
- *
7948
- * @param options - Optional configuration to override logger and metrics.
7949
- * @returns A frozen, immutable Runtime with all services guaranteed present.
7950
- * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
7951
- *
7952
- * @remarks
7953
- * Creates a fully-configured runtime by merging provided options with defaults.
7954
- * The returned runtime is frozen to enforce immutability.
7955
- *
7956
- * | Service | Default | Configurable |
7957
- * |---------|---------|--------------|
7958
- * | `logger` | pino logger (info level) | Yes |
7959
- * | `metrics` | No-op metrics | Yes |
7960
- * | `events` | Internal event bus | No |
7961
- * | `clock` | `Date.now()` | No |
7962
- *
7963
- * **Why only logger and metrics?**
7964
- *
7965
- * - **Logger/Metrics**: Integration points with your infrastructure
7966
- * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
7967
- * - **Clock**: Testing concern - use mock factories for tests
7968
- *
7969
- * @example
7970
- * ```typescript
7971
- * import { createRuntime, createLogger } from '@core/runtime'
7972
- *
7973
- * // Use all defaults
7974
- * const runtime = createRuntime()
7975
- *
7976
- * // Custom logger
7977
- * const runtime = createRuntime({
7978
- * logger: createLogger({ level: 'debug' }),
7979
- * })
7980
- *
7981
- * // Custom metrics (e.g., Prometheus)
7982
- * const runtime = createRuntime({
7983
- * metrics: myPrometheusMetrics,
7984
- * })
7985
- *
7986
- * // Subscribe to events (don't replace the bus)
7987
- * runtime.events.on('operation.*', (event) => {
7988
- * console.log('Event:', event.name)
7989
- * })
7990
- * ```
7991
- */
7992
- function createRuntime(options) {
7993
- // Resolve logger first (events may need it)
7994
- const logger = createLogger();
7995
- // Internal services - not configurable
7996
- const events = createEventBus({ logger });
7997
- const clock = defaultClock;
7998
- // Resolve metrics
7999
- const metrics = noopMetrics;
8000
- return Object.freeze({ logger, events, metrics, clock });
8001
- }
8002
-
8003
- /**
8004
- * Invocation context resolution - resolves the **WHO/HOW** of an operation.
8005
- *
8006
- * @packageDocumentation
8007
- */
8008
- // ============================================================================
8009
- // Validation Schemas
8010
- // ============================================================================
8011
- /**
8012
- * Schema for validating Caller.
8013
- */
8014
- const callerSchema = z.object({
8015
- type: z.string(),
8016
- name: z.string(),
8017
- version: z.string().optional(),
8018
- });
8019
- /**
8020
- * Schema for validating InvocationMeta input.
8021
- */
8022
- const invocationMetaSchema = z
8023
- .object({
8024
- traceId: z.string().optional(),
8025
- runtime: z.object({}).passthrough().optional(),
8026
- tokens: z.object({}).passthrough().optional(),
8027
- callers: z.array(callerSchema).optional(),
8028
- })
8029
- .strict();
8030
- // ============================================================================
8031
- // Invocation Context Resolution
8032
- // ============================================================================
8033
- /**
8034
- * Resolve invocation metadata to invocation context.
8035
- *
8036
- * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
8037
- * @param defaults - Default runtime and tokens to use if not overridden.
8038
- * @returns Frozen, immutable invocation context with guaranteed values.
8039
- * @throws KitError when meta contains invalid properties.
8040
- *
8041
- * @remarks
8042
- * Resolves the **WHO** called and **HOW** to observe:
8043
- * - TraceId: Uses provided value or generates new one
8044
- * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
8045
- * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
8046
- * - Callers: Uses provided array or empty array
8047
- *
8048
- * The returned context is frozen to enforce immutability.
8049
- *
8050
- * @example
8051
- * ```typescript
8052
- * import { resolveInvocationContext, createRuntime } from '@core/runtime'
8053
- * import { createTokenRegistry } from '@core/tokens'
8054
- *
8055
- * const defaults = {
8056
- * runtime: createRuntime(),
8057
- * tokens: createTokenRegistry(),
8058
- * }
8059
- *
8060
- * // Minimal - just using defaults
8061
- * const ctx = resolveInvocationContext(undefined, defaults)
8062
- *
8063
- * // With trace ID and caller info
8064
- * const ctx = resolveInvocationContext(
8065
- * {
8066
- * traceId: 'abc-123',
8067
- * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
8068
- * },
8069
- * defaults
8070
- * )
8071
- *
8072
- * // With runtime override (complete replacement)
8073
- * const ctx = resolveInvocationContext(
8074
- * { runtime: createRuntime({ logger: myLogger }) },
8075
- * defaults
8076
- * )
8077
- * ```
8078
- */
8079
- function resolveInvocationContext(meta, defaults) {
8080
- // Validate meta input if provided
8081
- if (meta !== undefined) {
8082
- const result = invocationMetaSchema.safeParse(meta);
8083
- if (!result.success) {
8084
- throw createValidationFailedError('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
8085
- }
8086
- }
8087
- // Generate trace ID if not provided
8088
- const traceId = meta?.traceId ?? createTraceId();
8089
- // Use meta overrides or fall back to defaults
8090
- const runtime = meta?.runtime ?? defaults.runtime;
8091
- const tokens = meta?.tokens ?? defaults.tokens;
8092
- const callers = meta?.callers ?? [];
8093
- return Object.freeze({ traceId, runtime, tokens, callers });
9081
+ const pinoInstance = pino(finalOptions);
9082
+ return wrapPino(pinoInstance);
8094
9083
  }
8095
9084
 
8096
9085
  /**
8097
- * Extend an invocation context by appending a caller to its call chain.
9086
+ * Factory for creating Runtime instances with defaults.
8098
9087
  *
8099
- * @param context - The existing invocation context to extend.
8100
- * @param caller - The caller to append to the call chain.
8101
- * @returns A new frozen invocation context with the caller appended.
9088
+ * @packageDocumentation
9089
+ */
9090
+ // ============================================================================
9091
+ // Validation Schema
9092
+ // ============================================================================
9093
+ /**
9094
+ * Schema for validating {@link RuntimeOptions}.
8102
9095
  *
8103
9096
  * @remarks
8104
- * This function creates a new immutable context with the caller appended
8105
- * to the `callers` array while preserving all other context properties
8106
- * (traceId, runtime, tokens).
8107
- *
8108
- * The returned context is frozen to enforce immutability.
8109
- *
8110
- * @example
8111
- * ```typescript
8112
- * import { extendInvocationContext } from '@core/runtime'
8113
- *
8114
- * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
8115
- * const extended = extendInvocationContext(existingContext, caller)
8116
- * // extended.callers === [...existingContext.callers, caller]
8117
- * ```
9097
+ * Used internally by {@link createRuntime} to validate options from JS consumers.
9098
+ * Exported for advanced use cases where manual validation is needed.
8118
9099
  */
8119
- function extendInvocationContext(context, caller) {
8120
- return Object.freeze({
8121
- traceId: context.traceId,
8122
- runtime: context.runtime,
8123
- tokens: context.tokens,
8124
- callers: [...context.callers, caller],
8125
- });
8126
- }
8127
-
8128
- // Clock - expose defaultClock for backward compatibility
8129
- /** Clock validation schema (backward compatibility). */
8130
- z.custom((val) => val !== null &&
8131
- typeof val === 'object' &&
8132
- 'now' in val &&
8133
- typeof val['now'] === 'function');
8134
- /** EventBus validation schema (backward compatibility). */
8135
- z.custom((val) => val !== null &&
8136
- typeof val === 'object' &&
8137
- 'emit' in val &&
8138
- typeof val['emit'] === 'function');
8139
- /** Runtime validation schema (backward compatibility). */
8140
9100
  z
8141
9101
  .object({
8142
- logger: z.any().optional(),
8143
- events: z.any().optional(),
8144
- metrics: z.any().optional(),
8145
- clock: z.any().optional(),
9102
+ logger: loggerSchema.optional(),
9103
+ metrics: metricsSchema.optional(),
8146
9104
  })
8147
9105
  .passthrough();
8148
-
9106
+ // ============================================================================
9107
+ // Factory
9108
+ // ============================================================================
8149
9109
  /**
8150
- * Create a structured error for token resolution failures.
9110
+ * Create a complete Runtime with sensible defaults.
8151
9111
  *
8152
- * @remarks
8153
- * Returns a `KitError` with `INPUT_INVALID_TOKEN` code (1006).
8154
- * The error trace contains the selector and chain context for debugging.
9112
+ * @param options - Optional configuration to override logger and metrics.
9113
+ * @returns A frozen, immutable Runtime with all services guaranteed present.
9114
+ * @throws KitError (INPUT_VALIDATION_FAILED) if options contain invalid services.
8155
9115
  *
8156
- * @param message - Human-readable error description.
8157
- * @param selector - The token selector that failed to resolve.
8158
- * @param chainId - The chain being resolved for (optional).
8159
- * @param cause - The underlying error, if any (optional).
8160
- * @returns A KitError with INPUT type and FATAL recoverability.
9116
+ * @remarks
9117
+ * Creates a fully-configured runtime by merging provided options with defaults.
9118
+ * The returned runtime is frozen to enforce immutability.
8161
9119
  *
8162
- * @example
8163
- * ```typescript
8164
- * throw createTokenResolutionError(
8165
- * 'Unknown token symbol: FAKE',
8166
- * 'FAKE',
8167
- * 'Ethereum'
8168
- * )
8169
- * ```
8170
- */
8171
- function createTokenResolutionError(message, selector, chainId, cause) {
8172
- const trace = {
8173
- selector,
8174
- ...(chainId === undefined ? {} : { chainId }),
8175
- ...({} ),
8176
- };
8177
- return new KitError({
8178
- ...InputError.INVALID_TOKEN,
8179
- recoverability: 'FATAL',
8180
- message,
8181
- cause: { trace },
8182
- });
8183
- }
8184
-
8185
- /**
8186
- * USDC token definition with addresses and metadata.
9120
+ * | Service | Default | Configurable |
9121
+ * |---------|---------|--------------|
9122
+ * | `logger` | pino logger (info level) | Yes |
9123
+ * | `metrics` | No-op metrics | Yes |
9124
+ * | `events` | Internal event bus | No |
9125
+ * | `clock` | `Date.now()` | No |
8187
9126
  *
8188
- * @remarks
8189
- * This is the built-in USDC definition used by the TokenRegistry.
8190
- * Includes all known USDC addresses across supported chains.
9127
+ * **Why only logger and metrics?**
8191
9128
  *
8192
- * Keys use the `Blockchain` enum for type safety. Both enum values
8193
- * and string literals are supported:
8194
- * - `Blockchain.Ethereum` or `'Ethereum'`
9129
+ * - **Logger/Metrics**: Integration points with your infrastructure
9130
+ * - **Events**: Internal pub/sub mechanism - subscribe via `runtime.events.on()`
9131
+ * - **Clock**: Testing concern - use mock factories for tests
8195
9132
  *
8196
9133
  * @example
8197
9134
  * ```typescript
8198
- * import { USDC } from '@core/tokens'
8199
- * import { Blockchain } from '@core/chains'
8200
- *
8201
- * console.log(USDC.symbol) // 'USDC'
8202
- * console.log(USDC.decimals) // 6
8203
- * console.log(USDC.locators[Blockchain.Ethereum])
8204
- * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
8205
- * ```
8206
- */
8207
- const USDC = {
8208
- symbol: 'USDC',
8209
- decimals: 6,
8210
- locators: {
8211
- // =========================================================================
8212
- // Mainnets (alphabetically sorted)
8213
- // =========================================================================
8214
- [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
8215
- [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
8216
- [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
8217
- [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
8218
- [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
8219
- [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
8220
- [Blockchain.Hedera]: '0.0.456858',
8221
- [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
8222
- [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
8223
- [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
8224
- [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
8225
- [Blockchain.Noble]: 'uusdc',
8226
- [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
8227
- [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
8228
- [Blockchain.Polkadot_Asset_Hub]: '1337',
8229
- [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
8230
- [Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
8231
- [Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
8232
- [Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
8233
- [Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
8234
- [Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
8235
- [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
8236
- [Blockchain.World_Chain]: '0x79A02482A880bCe3F13E09da970dC34dB4cD24D1',
8237
- [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
8238
- [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
8239
- // =========================================================================
8240
- // Testnets (alphabetically sorted)
8241
- // =========================================================================
8242
- [Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
8243
- [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
8244
- [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
8245
- [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
8246
- [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
8247
- [Blockchain.Hedera_Testnet]: '0.0.429274',
8248
- [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
8249
- [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
8250
- [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
8251
- [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
8252
- [Blockchain.Noble_Testnet]: 'uusdc',
8253
- [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
8254
- [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
8255
- [Blockchain.Polkadot_Westmint]: '31337',
8256
- [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
8257
- [Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
8258
- [Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
8259
- [Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
8260
- [Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
8261
- [Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
8262
- [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
8263
- [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
8264
- [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
8265
- [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
8266
- },
8267
- };
8268
-
8269
- // Re-export for consumers
8270
- /**
8271
- * All default token definitions.
9135
+ * import { createRuntime, createLogger } from '@core/runtime'
8272
9136
  *
8273
- * @remarks
8274
- * These tokens are automatically included in the TokenRegistry when created
8275
- * without explicit defaults. Extensions can override these definitions.
9137
+ * // Use all defaults
9138
+ * const runtime = createRuntime()
8276
9139
  *
8277
- * @example
8278
- * ```typescript
8279
- * import { createTokenRegistry } from '@core/tokens'
9140
+ * // Custom logger
9141
+ * const runtime = createRuntime({
9142
+ * logger: createLogger({ level: 'debug' }),
9143
+ * })
8280
9144
  *
8281
- * // Registry uses these by default
8282
- * const registry = createTokenRegistry()
9145
+ * // Custom metrics (e.g., Prometheus)
9146
+ * const runtime = createRuntime({
9147
+ * metrics: myPrometheusMetrics,
9148
+ * })
8283
9149
  *
8284
- * // Add custom tokens (built-ins are still included)
8285
- * const customRegistry = createTokenRegistry({
8286
- * tokens: [myCustomToken],
9150
+ * // Subscribe to events (don't replace the bus)
9151
+ * runtime.events.on('operation.*', (event) => {
9152
+ * console.log('Event:', event.name)
8287
9153
  * })
8288
9154
  * ```
8289
9155
  */
8290
- const DEFAULT_TOKENS = [USDC];
9156
+ function createRuntime(options) {
9157
+ // Resolve logger first (events may need it)
9158
+ const logger = createLogger();
9159
+ // Internal services - not configurable
9160
+ const events = createEventBus({ logger });
9161
+ const clock = defaultClock;
9162
+ // Resolve metrics
9163
+ const metrics = noopMetrics;
9164
+ return Object.freeze({ logger, events, metrics, clock });
9165
+ }
8291
9166
 
8292
9167
  /**
8293
- * Check if a selector is a raw token selector (object form).
9168
+ * Invocation context resolution - resolves the **WHO/HOW** of an operation.
8294
9169
  *
8295
- * @param selector - The token selector to check.
8296
- * @returns True if the selector is a raw token selector.
9170
+ * @packageDocumentation
8297
9171
  */
8298
- function isRawSelector(selector) {
8299
- return typeof selector === 'object' && 'locator' in selector;
8300
- }
9172
+ // ============================================================================
9173
+ // Validation Schemas
9174
+ // ============================================================================
9175
+ /**
9176
+ * Schema for validating Caller.
9177
+ */
9178
+ const callerSchema = z.object({
9179
+ type: z.string(),
9180
+ name: z.string(),
9181
+ version: z.string().optional(),
9182
+ });
9183
+ /**
9184
+ * Schema for validating InvocationMeta input.
9185
+ */
9186
+ const invocationMetaSchema = z
9187
+ .object({
9188
+ traceId: z.string().optional(),
9189
+ runtime: z.object({}).passthrough().optional(),
9190
+ tokens: z.object({}).passthrough().optional(),
9191
+ callers: z.array(callerSchema).optional(),
9192
+ })
9193
+ .strict();
9194
+ // ============================================================================
9195
+ // Invocation Context Resolution
9196
+ // ============================================================================
8301
9197
  /**
8302
- * Normalize a symbol to uppercase for case-insensitive lookup.
9198
+ * Resolve invocation metadata to invocation context.
8303
9199
  *
8304
- * @param symbol - The symbol to normalize.
8305
- * @returns The normalized (uppercase) symbol.
8306
- */
8307
- function normalizeSymbol(symbol) {
8308
- return symbol.toUpperCase();
8309
- }
8310
- /**
8311
- * Create a token registry with built-in tokens and optional extensions.
9200
+ * @param meta - User-provided invocation metadata (**WHO/HOW**), optional.
9201
+ * @param defaults - Default runtime and tokens to use if not overridden.
9202
+ * @returns Frozen, immutable invocation context with guaranteed values.
9203
+ * @throws KitError when meta contains invalid properties.
8312
9204
  *
8313
9205
  * @remarks
8314
- * The registry always includes built-in tokens (DEFAULT_TOKENS) like USDC.
8315
- * Custom tokens are merged on top - use this to add new tokens or override
8316
- * built-in definitions.
9206
+ * Resolves the **WHO** called and **HOW** to observe:
9207
+ * - TraceId: Uses provided value or generates new one
9208
+ * - Runtime: Uses meta.runtime if provided, otherwise defaults.runtime
9209
+ * - Tokens: Uses meta.tokens if provided, otherwise defaults.tokens
9210
+ * - Callers: Uses provided array or empty array
8317
9211
  *
8318
- * @param options - Configuration options for the registry.
8319
- * @returns A token registry instance.
9212
+ * The returned context is frozen to enforce immutability.
8320
9213
  *
8321
9214
  * @example
8322
9215
  * ```typescript
9216
+ * import { resolveInvocationContext, createRuntime } from '@core/runtime'
8323
9217
  * import { createTokenRegistry } from '@core/tokens'
8324
9218
  *
8325
- * // Create registry with built-in tokens (USDC, etc.)
8326
- * const registry = createTokenRegistry()
8327
- *
8328
- * // Resolve USDC on Ethereum
8329
- * const usdc = registry.resolve('USDC', 'Ethereum')
8330
- * console.log(usdc.locator) // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
8331
- * console.log(usdc.decimals) // 6
8332
- * ```
8333
- *
8334
- * @example
8335
- * ```typescript
8336
- * // Add custom tokens (built-ins are still included)
8337
- * const myToken: TokenDefinition = {
8338
- * symbol: 'MY',
8339
- * decimals: 18,
8340
- * locators: { Ethereum: '0x...' },
9219
+ * const defaults = {
9220
+ * runtime: createRuntime(),
9221
+ * tokens: createTokenRegistry(),
8341
9222
  * }
8342
9223
  *
8343
- * const registry = createTokenRegistry({ tokens: [myToken] })
8344
- * registry.resolve('USDC', 'Ethereum') // Still works!
8345
- * registry.resolve('MY', 'Ethereum') // Also works
8346
- * ```
8347
- *
8348
- * @example
8349
- * ```typescript
8350
- * // Override a built-in token
8351
- * const customUsdc: TokenDefinition = {
8352
- * symbol: 'USDC',
8353
- * decimals: 6,
8354
- * locators: { MyChain: '0xCustomAddress' },
8355
- * }
9224
+ * // Minimal - just using defaults
9225
+ * const ctx = resolveInvocationContext(undefined, defaults)
8356
9226
  *
8357
- * const registry = createTokenRegistry({ tokens: [customUsdc] })
8358
- * // Now USDC resolves to customUsdc definition
8359
- * ```
9227
+ * // With trace ID and caller info
9228
+ * const ctx = resolveInvocationContext(
9229
+ * {
9230
+ * traceId: 'abc-123',
9231
+ * callers: [{ type: 'kit', name: 'BridgeKit', version: '1.0.0' }],
9232
+ * },
9233
+ * defaults
9234
+ * )
8360
9235
  *
8361
- * @example
8362
- * ```typescript
8363
- * // Resolve arbitrary tokens by raw locator
8364
- * const registry = createTokenRegistry()
8365
- * const token = registry.resolve(
8366
- * { locator: '0x1234...', decimals: 18 },
8367
- * 'Ethereum'
9236
+ * // With runtime override (complete replacement)
9237
+ * const ctx = resolveInvocationContext(
9238
+ * { runtime: createRuntime({ logger: myLogger }) },
9239
+ * defaults
8368
9240
  * )
8369
9241
  * ```
8370
9242
  */
8371
- function createTokenRegistry(options = {}) {
8372
- const { tokens = [], requireDecimals = false } = options;
8373
- // Build the token map: always start with DEFAULT_TOKENS, then add custom tokens
8374
- const tokenMap = new Map();
8375
- // Add built-in tokens first
8376
- for (const def of DEFAULT_TOKENS) {
8377
- tokenMap.set(normalizeSymbol(def.symbol), def);
8378
- }
8379
- // Custom tokens override built-ins
8380
- for (const def of tokens) {
8381
- tokenMap.set(normalizeSymbol(def.symbol), def);
8382
- }
8383
- /**
8384
- * Resolve a symbol selector to token information.
8385
- */
8386
- function resolveSymbol(symbol, chainId) {
8387
- const normalizedSymbol = normalizeSymbol(symbol);
8388
- const definition = tokenMap.get(normalizedSymbol);
8389
- if (definition === undefined) {
8390
- throw createTokenResolutionError(`Unknown token symbol: ${symbol}. Register it via createTokenRegistry({ tokens: [...] }) or use a raw selector.`, symbol, chainId);
8391
- }
8392
- const locator = definition.locators[chainId];
8393
- if (locator === undefined || locator.trim() === '') {
8394
- throw createTokenResolutionError(`Token ${symbol} has no locator for chain ${chainId}`, symbol, chainId);
8395
- }
8396
- return {
8397
- symbol: definition.symbol,
8398
- decimals: definition.decimals,
8399
- locator,
8400
- };
8401
- }
8402
- /**
8403
- * Resolve a raw selector to token information.
8404
- */
8405
- function resolveRaw(selector, chainId) {
8406
- const { locator, decimals } = selector;
8407
- // Validate locator
8408
- if (!locator || typeof locator !== 'string') {
8409
- throw createTokenResolutionError('Raw selector must have a valid locator string', selector, chainId);
8410
- }
8411
- // Decimals are always required for raw selectors
8412
- if (decimals === undefined) {
8413
- const message = requireDecimals
8414
- ? 'Decimals required for raw token selector (requireDecimals: true)'
8415
- : 'Decimals required for raw token selector. Provide { locator, decimals }.';
8416
- throw createTokenResolutionError(message, selector, chainId);
8417
- }
8418
- // Validate decimals
8419
- if (typeof decimals !== 'number' ||
8420
- decimals < 0 ||
8421
- !Number.isInteger(decimals)) {
8422
- throw createTokenResolutionError(`Invalid decimals: ${String(decimals)}. Must be a non-negative integer.`, selector, chainId);
9243
+ function resolveInvocationContext(meta, defaults) {
9244
+ // Validate meta input if provided
9245
+ if (meta !== undefined) {
9246
+ const result = invocationMetaSchema.safeParse(meta);
9247
+ if (!result.success) {
9248
+ throw createValidationFailedError('invocationMeta', meta, result.error.errors.map((e) => e.message).join(', '));
8423
9249
  }
8424
- return {
8425
- decimals,
8426
- locator,
8427
- };
8428
9250
  }
8429
- return {
8430
- resolve(selector, chainId) {
8431
- // Runtime validation of inputs - these checks are for JS consumers
8432
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
8433
- if (selector === null || selector === undefined) {
8434
- throw createTokenResolutionError('Token selector cannot be null or undefined', selector, chainId);
8435
- }
8436
- if (chainId === '' || typeof chainId !== 'string') {
8437
- throw createTokenResolutionError('Chain ID is required for token resolution', selector, chainId);
8438
- }
8439
- // Dispatch based on selector type
8440
- if (isRawSelector(selector)) {
8441
- return resolveRaw(selector, chainId);
8442
- }
8443
- if (typeof selector === 'string') {
8444
- return resolveSymbol(selector, chainId);
8445
- }
8446
- throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
8447
- },
8448
- get(symbol) {
8449
- if (!symbol || typeof symbol !== 'string') {
8450
- return undefined;
8451
- }
8452
- return tokenMap.get(normalizeSymbol(symbol));
8453
- },
8454
- has(symbol) {
8455
- if (!symbol || typeof symbol !== 'string') {
8456
- return false;
8457
- }
8458
- return tokenMap.has(normalizeSymbol(symbol));
8459
- },
8460
- symbols() {
8461
- return Array.from(tokenMap.values()).map((def) => def.symbol);
8462
- },
8463
- entries() {
8464
- return Array.from(tokenMap.values());
8465
- },
8466
- };
9251
+ // Generate trace ID if not provided
9252
+ const traceId = meta?.traceId ?? createTraceId();
9253
+ // Use meta overrides or fall back to defaults
9254
+ const runtime = meta?.runtime ?? defaults.runtime;
9255
+ const tokens = meta?.tokens ?? defaults.tokens;
9256
+ const callers = meta?.callers ?? [];
9257
+ return Object.freeze({ traceId, runtime, tokens, callers });
8467
9258
  }
8468
9259
 
8469
9260
  /**
8470
- * Define a schema for token registry interfaces.
9261
+ * Extend an invocation context by appending a caller to its call chain.
9262
+ *
9263
+ * @param context - The existing invocation context to extend.
9264
+ * @param caller - The caller to append to the call chain.
9265
+ * @returns A new frozen invocation context with the caller appended.
8471
9266
  *
8472
9267
  * @remarks
8473
- * Validate that a registry exposes the `resolve()` API used by adapters.
9268
+ * This function creates a new immutable context with the caller appended
9269
+ * to the `callers` array while preserving all other context properties
9270
+ * (traceId, runtime, tokens).
9271
+ *
9272
+ * The returned context is frozen to enforce immutability.
8474
9273
  *
8475
9274
  * @example
8476
9275
  * ```typescript
8477
- * import { tokenRegistrySchema } from '@core/tokens'
8478
- *
8479
- * const registry = {
8480
- * resolve: () => ({ locator: '0x0', decimals: 6 }),
8481
- * }
9276
+ * import { extendInvocationContext } from '@core/runtime'
8482
9277
  *
8483
- * tokenRegistrySchema.parse(registry)
9278
+ * const caller = { type: 'provider', name: 'CCTPV2', version: '1.0.0' }
9279
+ * const extended = extendInvocationContext(existingContext, caller)
9280
+ * // extended.callers === [...existingContext.callers, caller]
8484
9281
  * ```
8485
9282
  */
8486
- z.custom((value) => {
8487
- if (value === null || typeof value !== 'object') {
8488
- return false;
8489
- }
8490
- const record = value;
8491
- return (typeof record['resolve'] === 'function' &&
8492
- typeof record['get'] === 'function' &&
8493
- typeof record['has'] === 'function' &&
8494
- typeof record['symbols'] === 'function' &&
8495
- typeof record['entries'] === 'function');
8496
- }, {
8497
- message: 'Invalid token registry',
8498
- });
9283
+ function extendInvocationContext(context, caller) {
9284
+ return Object.freeze({
9285
+ traceId: context.traceId,
9286
+ runtime: context.runtime,
9287
+ tokens: context.tokens,
9288
+ callers: [...context.callers, caller],
9289
+ });
9290
+ }
9291
+
9292
+ // Clock - expose defaultClock for backward compatibility
9293
+ /** Clock validation schema (backward compatibility). */
9294
+ z.custom((val) => val !== null &&
9295
+ typeof val === 'object' &&
9296
+ 'now' in val &&
9297
+ typeof val['now'] === 'function');
9298
+ /** EventBus validation schema (backward compatibility). */
9299
+ z.custom((val) => val !== null &&
9300
+ typeof val === 'object' &&
9301
+ 'emit' in val &&
9302
+ typeof val['emit'] === 'function');
9303
+ /** Runtime validation schema (backward compatibility). */
9304
+ z
9305
+ .object({
9306
+ logger: z.any().optional(),
9307
+ events: z.any().optional(),
9308
+ metrics: z.any().optional(),
9309
+ clock: z.any().optional(),
9310
+ })
9311
+ .passthrough();
8499
9312
 
8500
- var version = "1.4.0";
9313
+ var version = "1.4.1";
8501
9314
  var pkg = {
8502
9315
  version: version};
8503
9316
 
@@ -8980,6 +9793,24 @@ const validateNativeBalanceForTransaction = async (params) => {
8980
9793
  }
8981
9794
  };
8982
9795
 
9796
+ /**
9797
+ * Permit signature standards for gasless token approvals.
9798
+ *
9799
+ * Defines the permit types that can be used to approve token spending
9800
+ * without requiring a separate approval transaction.
9801
+ *
9802
+ * @remarks
9803
+ * - NONE: No permit, tokens must be pre-approved via separate transaction
9804
+ * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
9805
+ */
9806
+ var PermitType;
9807
+ (function (PermitType) {
9808
+ /** No permit required - tokens must be pre-approved */
9809
+ PermitType[PermitType["NONE"] = 0] = "NONE";
9810
+ /** EIP-2612 standard permit */
9811
+ PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
9812
+ })(PermitType || (PermitType = {}));
9813
+
8983
9814
  /**
8984
9815
  * CCTP bridge step names that can occur in the bridging flow.
8985
9816
  *