@basedone/core 0.0.1 → 0.0.6

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.
Files changed (47) hide show
  1. package/dist/chunk-4UEJOM6W.mjs +9 -0
  2. package/dist/index.d.mts +462 -0
  3. package/dist/index.d.ts +462 -2
  4. package/dist/index.js +37465 -2
  5. package/dist/index.mjs +1027 -0
  6. package/dist/lib/cloid.d.ts.map +1 -1
  7. package/dist/lib/cloid.js +11 -1
  8. package/dist/lib/cloid.js.map +1 -1
  9. package/dist/meta-52Q5UUQ4.mjs +1474 -0
  10. package/dist/meta-FTWJX4LV.mjs +1445 -0
  11. package/dist/meta-IKWYLG3Q.mjs +1316 -0
  12. package/dist/meta-UUXKK7IB.mjs +1355 -0
  13. package/dist/perpDexs-PSE3LEVV.mjs +9 -0
  14. package/dist/perpDexs-S3TK25EU.mjs +17 -0
  15. package/dist/perpDexs-TZIQ57IW.mjs +537 -0
  16. package/dist/perpDexs-YNEAJ3R5.mjs +7 -0
  17. package/dist/perpDexs-YS3QQSHW.mjs +338 -0
  18. package/dist/spotMeta-7IJT3W6H.mjs +6442 -0
  19. package/dist/spotMeta-LEO5QFNS.mjs +26392 -0
  20. package/dist/spotMeta-MC5UYLQ7.mjs +6335 -0
  21. package/dist/spotMeta-TXJWYTKI.mjs +26403 -0
  22. package/dist/spotMeta-VAANYV77.mjs +6346 -0
  23. package/dist/spotMeta-ZVBZNUUE.mjs +26559 -0
  24. package/index.ts +6 -0
  25. package/lib/cloid/README.md +233 -0
  26. package/lib/cloid/cloid.ts +368 -0
  27. package/lib/cloid/encoder.ts +60 -0
  28. package/lib/constants/fee.ts +2 -0
  29. package/lib/constants/tokens.ts +28 -0
  30. package/lib/fee.ts +105 -0
  31. package/lib/hip3/market-info.ts +25 -0
  32. package/lib/hip3/utils.ts +9 -0
  33. package/lib/meta/README.md +471 -0
  34. package/lib/meta/data/mainnet/meta.json +1462 -0
  35. package/lib/meta/data/mainnet/perpDexs.json +11 -0
  36. package/lib/meta/data/mainnet/spotMeta.json +6432 -0
  37. package/lib/meta/data/testnet/dexs/rrrrr.json +33 -0
  38. package/lib/meta/data/testnet/meta.json +1343 -0
  39. package/lib/meta/data/testnet/perpDexs.json +531 -0
  40. package/lib/meta/data/testnet/spotMeta.json +26547 -0
  41. package/lib/meta/metadata.ts +600 -0
  42. package/lib/pup/calculator.ts +221 -0
  43. package/lib/pup/index.ts +9 -0
  44. package/lib/pup/types.ts +94 -0
  45. package/lib/utils/formatter.ts +97 -0
  46. package/package.json +21 -17
  47. package/readme.md +0 -0
@@ -0,0 +1,600 @@
1
+ import {
2
+ HttpTransport,
3
+ InfoClient,
4
+ PerpDex,
5
+ PerpsAssetCtx,
6
+ PerpsMeta,
7
+ SpotMeta,
8
+ SpotToken,
9
+ } from "@nktkas/hyperliquid";
10
+ import { isHip3Symbol } from "../hip3/utils";
11
+
12
+ /**
13
+ * Market information for trading operations
14
+ */
15
+ export interface MarketInfo {
16
+ symbol: string; // Human-readable symbol (e.g., "BTC", "PURR/USDC")
17
+ assetId: number; // Trading asset ID (perps: index, spot: 10000+index, hip3: 100000+dex*10000+index)
18
+ szDecimals: number; // Size decimals for order amounts
19
+ type: "perps" | "spot" | "hip3";
20
+ coin: string; // Format used in info endpoint (e.g., "@107") for spot hype, "HYPE" for perps, "dex:coin" for hip3
21
+
22
+ maxLeverage?: number; // For perps only
23
+
24
+ // Spot-specific fields
25
+ baseToken?: SpotToken; // Base token symbol (e.g., "PURR")
26
+ quoteToken?: SpotToken; // Quote token symbol (e.g., "USDC")
27
+
28
+ // HIP-3 specific fields
29
+ dexName?: string; // DEX name for HIP-3 markets
30
+ dexIndex?: number; // DEX index for asset ID calculation
31
+ }
32
+
33
+ export interface DexInfo {
34
+ meta: PerpsMeta;
35
+ assetContext: PerpsAssetCtx[];
36
+ dexFullName: string; // DEX full name for HIP-3 markets
37
+ dexName: string; // DEX name for HIP-3 markets
38
+ dexIndex: number; // DEX index for asset ID calculation
39
+ collateralTokenSymbol: string;
40
+ }
41
+
42
+ export type ExtendedPerpsMeta = PerpsMeta & {
43
+ collateralToken?: number; // spot token index
44
+ };
45
+
46
+ /**
47
+ * Spot token information
48
+ */
49
+ export interface TokenInfo {
50
+ name: string;
51
+ index: number;
52
+ szDecimals: number;
53
+ weiDecimals: number;
54
+ tokenId: string;
55
+ }
56
+
57
+ /**
58
+ * Configuration for MetadataClient
59
+ */
60
+ interface MetadataClientConfig {
61
+ /** List of HIP-3 DEX names to load metadata for */
62
+ hip3Dexs?: string[];
63
+ /** Whether to lazily initialize (fetch metadata on first use) */
64
+ lazyInit?: boolean;
65
+ /** Whether to use testnet (default: false for mainnet) */
66
+ isTestnet?: boolean;
67
+ /** Use static fallback data only, no API fetch */
68
+ onlyUseStaticFallback?: boolean;
69
+ /** Whether to use static fallback data if API fetch fails (default: true) */
70
+ useStaticFallback?: boolean;
71
+ }
72
+
73
+ /**
74
+ * MetadataClient - Handles Hyperliquid metadata for trading operations
75
+ *
76
+ * Provides a clean interface to:
77
+ * - Convert symbols to asset IDs and decimals
78
+ * - Handle spot markets with multiple quote assets (USDC, USDT0, USDH)
79
+ * - Support perpetuals and HIP-3 deployed DEXs
80
+ * - Calculate correct asset IDs per Hyperliquid spec
81
+ *
82
+ * Asset ID Calculation:
83
+ * - Perps: index in meta universe
84
+ * - Spot: 10000 + index
85
+ * - HIP-3: 100000 + perp_dex_index * 10000 + index_in_meta
86
+ */
87
+ export class MetadataClient {
88
+ private infoClient: InfoClient;
89
+ private config: MetadataClientConfig;
90
+ private isTestnet: boolean;
91
+
92
+ // Core metadata
93
+ private spotMeta: SpotMeta | null = null;
94
+ private perpsMeta: PerpsMeta | null = null;
95
+ private perpDexs: (PerpDex | null)[] = [];
96
+
97
+ // HIP-3 metadata cache
98
+ private hip3DexsMeta: Map<string, DexInfo> = new Map();
99
+
100
+ // Pre-computed lookup maps (populated on initialize)
101
+ private perpsSymbolToIndex: Map<string, number> = new Map();
102
+ private spotTokenNameToIndex: Map<string, number> = new Map();
103
+ private spotPairToMarket: Map<string, MarketInfo> = new Map();
104
+ private baseTokenToMarkets: Map<string, MarketInfo[]> = new Map();
105
+ private quoteAssets: string[] = [];
106
+
107
+ // Unified symbol lookup (used by getMarketBySymbol for O(1) access)
108
+ // Maps symbol to MarketInfo for quick lookups
109
+ private coinToMarket: Map<string, MarketInfo> = new Map();
110
+
111
+ // HIP-3 optimized lookups
112
+ // Maps "dex:coin" symbol to MarketInfo
113
+ private hip3SymbolToMarket: Map<string, MarketInfo> = new Map();
114
+ // Maps dex name to dex index for quick lookups
115
+ private dexNameToIndex: Map<string, number> = new Map();
116
+
117
+ // Lazy init flag
118
+ private initialized: boolean = false;
119
+
120
+ constructor(config: MetadataClientConfig = {}) {
121
+ const transport = new HttpTransport({
122
+ isTestnet: config.isTestnet ?? false,
123
+ });
124
+ this.infoClient = new InfoClient({ transport });
125
+ this.config = {
126
+ ...config,
127
+ useStaticFallback: config.useStaticFallback ?? true,
128
+ };
129
+ this.isTestnet = config.isTestnet ?? false;
130
+ this.initialize();
131
+ }
132
+
133
+ /**
134
+ * Initialize metadata by fetching from Hyperliquid
135
+ */
136
+ async initialize(): Promise<void> {
137
+ if (this.initialized) return;
138
+
139
+ if (this.config.onlyUseStaticFallback) {
140
+ await this.loadStaticMetadata();
141
+ this.buildLookupMaps();
142
+
143
+ // Fetch HIP-3 metadata if configured
144
+ if (this.config.hip3Dexs && this.config.hip3Dexs.length > 0) {
145
+ await Promise.all(
146
+ this.config.hip3Dexs.map((dex) => this.loadHip3Metadata(dex)),
147
+ );
148
+ }
149
+
150
+ this.initialized = true;
151
+ return;
152
+ }
153
+
154
+ try {
155
+ // Fetch core metadata from API
156
+ [this.spotMeta, this.perpsMeta, this.perpDexs] = await Promise.all([
157
+ this.infoClient.spotMeta(),
158
+ this.infoClient.meta(),
159
+ this.infoClient.perpDexs(),
160
+ ]);
161
+
162
+ // Build optimized lookup maps
163
+ this.buildLookupMaps();
164
+
165
+ // Fetch HIP-3 metadata if configured
166
+ if (this.config.hip3Dexs && this.config.hip3Dexs.length > 0) {
167
+ await Promise.all(
168
+ this.config.hip3Dexs.map((dex) => this.loadHip3Metadata(dex)),
169
+ );
170
+ }
171
+
172
+ this.initialized = true;
173
+ } catch (error) {
174
+ // Fall back to static data if API fetch fails and fallback is enabled
175
+ if (this.config.useStaticFallback) {
176
+ console.warn(
177
+ "Failed to fetch metadata from API, using static fallback data",
178
+ error,
179
+ );
180
+ await this.loadStaticMetadata();
181
+ this.buildLookupMaps();
182
+ this.initialized = true;
183
+ } else {
184
+ throw error;
185
+ }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Load static metadata from bundled JSON files
191
+ */
192
+ private async loadStaticMetadata(): Promise<void> {
193
+ const network = this.isTestnet ? "testnet" : "mainnet";
194
+
195
+ try {
196
+ // Dynamic imports for static data
197
+ const [spotMetaModule, perpsMetaModule, perpDexsModule] =
198
+ await Promise.all([
199
+ import(`./data/${network}/spotMeta.json`),
200
+ import(`./data/${network}/meta.json`),
201
+ import(`./data/${network}/perpDexs.json`),
202
+ ]);
203
+
204
+ this.spotMeta = spotMetaModule.default as SpotMeta;
205
+ this.perpsMeta = perpsMetaModule.default as PerpsMeta;
206
+ this.perpDexs = perpDexsModule.default as (PerpDex | null)[];
207
+
208
+ console.warn(`Using static ${network} metadata`);
209
+ } catch (error) {
210
+ console.error(`Failed to load static ${network} metadata:`, error);
211
+ throw new Error(`Could not load metadata for ${network}`);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Build optimized lookup maps from raw metadata
217
+ * Called after metadata is loaded (from API or static files)
218
+ */
219
+ private buildLookupMaps(): void {
220
+ // Clear existing maps
221
+ this.perpsSymbolToIndex.clear();
222
+ this.spotTokenNameToIndex.clear();
223
+ this.spotPairToMarket.clear();
224
+ this.baseTokenToMarkets.clear();
225
+ this.coinToMarket.clear();
226
+ this.hip3SymbolToMarket.clear();
227
+ this.dexNameToIndex.clear();
228
+ this.quoteAssets = [];
229
+
230
+ // Build dex name to index map
231
+ if (this.perpDexs) {
232
+ this.perpDexs.forEach((dex, index) => {
233
+ if (dex && dex.name) {
234
+ this.dexNameToIndex.set(dex.name.toLowerCase(), index);
235
+ }
236
+ });
237
+ }
238
+
239
+ // Build perps symbol to index map
240
+ if (this.perpsMeta) {
241
+ this.perpsMeta.universe.forEach((market, index) => {
242
+ const marketInfo: MarketInfo = {
243
+ symbol: market.name,
244
+ coin: market.name,
245
+ assetId: index,
246
+ szDecimals: market.szDecimals,
247
+ type: "perps",
248
+ maxLeverage: market.maxLeverage,
249
+ };
250
+
251
+ // Case-insensitive lookup
252
+ this.perpsSymbolToIndex.set(market.name.toUpperCase(), index);
253
+
254
+ // Add to unified symbol lookup
255
+ this.coinToMarket.set(market.name, marketInfo);
256
+ });
257
+ }
258
+
259
+ // Build spot token name to index map and collect quote assets
260
+ if (this.spotMeta) {
261
+ this.spotMeta.tokens.forEach((token) => {
262
+ // Case-insensitive lookup
263
+ this.spotTokenNameToIndex.set(token.name.toUpperCase(), token.index);
264
+ });
265
+
266
+ // Track unique quote assets
267
+ const quoteIndices = new Set<number>();
268
+
269
+ // Build spot pair to market map and base token to markets map
270
+ this.spotMeta.universe.forEach((universe) => {
271
+ const baseToken = this.spotMeta!.tokens[universe.tokens[0]];
272
+ const quoteToken = this.spotMeta!.tokens[universe.tokens[1]];
273
+
274
+ if (!baseToken || !quoteToken) return;
275
+
276
+ const coin = universe.name;
277
+
278
+ // Track quote asset
279
+ quoteIndices.add(quoteToken.index);
280
+
281
+ const marketInfo: MarketInfo = {
282
+ coin,
283
+ symbol: `${baseToken.name}/${quoteToken.name}`,
284
+ assetId: 10000 + universe.index,
285
+ szDecimals: baseToken.szDecimals,
286
+ type: "spot",
287
+ baseToken: baseToken,
288
+ quoteToken: quoteToken,
289
+ };
290
+
291
+ // Store by full pair (case-insensitive)
292
+ const pairKey = `${baseToken.name}/${quoteToken.name}`.toUpperCase();
293
+ this.spotPairToMarket.set(pairKey, marketInfo);
294
+
295
+ // Add to unified symbol lookup
296
+ this.coinToMarket.set(pairKey, marketInfo);
297
+
298
+ this.coinToMarket.set(coin, marketInfo);
299
+
300
+ // Group by base token (case-insensitive)
301
+ const baseKey = baseToken.name.toUpperCase();
302
+ const existing = this.baseTokenToMarkets.get(baseKey) || [];
303
+ existing.push(marketInfo);
304
+ this.baseTokenToMarkets.set(baseKey, existing);
305
+ });
306
+
307
+ // Pre-compute sorted quote assets list
308
+ this.quoteAssets = Array.from(quoteIndices)
309
+ .map((idx) => this.spotMeta!.tokens[idx].name)
310
+ .sort();
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Load metadata for a specific HIP-3 DEX
316
+ * Also builds optimized lookups for this DEX's markets
317
+ */
318
+ async loadHip3Metadata(dexName: string): Promise<DexInfo> {
319
+ if (this.hip3DexsMeta.has(dexName)) return this.hip3DexsMeta.get(dexName)!;
320
+
321
+ try {
322
+ const [meta, contexts] = await this.infoClient.metaAndAssetCtxs({
323
+ dex: dexName,
324
+ });
325
+
326
+ // O(1) lookup using pre-computed map
327
+ let dexIndex = this.dexNameToIndex.get(dexName.toLowerCase());
328
+
329
+ // Fallback to array search if not in map (shouldn't happen after initialization)
330
+ if (dexIndex === undefined) {
331
+ dexIndex = this.perpDexs.findIndex(
332
+ (d) => d && d.name.toLowerCase() === dexName.toLowerCase(),
333
+ );
334
+ }
335
+
336
+ if (dexIndex === -1 || dexIndex === undefined) {
337
+ throw new Error(`DEX ${dexName} not found`);
338
+ }
339
+
340
+ const dex = this.perpDexs[dexIndex!];
341
+
342
+ const collateralTokenIndex =
343
+ (meta as ExtendedPerpsMeta).collateralToken ?? 0;
344
+
345
+ const spotMetaTokens = this.spotMeta?.tokens;
346
+
347
+ const collateralTokenSymbol =
348
+ spotMetaTokens?.[collateralTokenIndex]?.name ?? "USDC";
349
+
350
+ const dexInfo = {
351
+ meta,
352
+ assetContext: contexts,
353
+ collateralTokenSymbol,
354
+ dexFullName: dex?.full_name ?? dexName,
355
+ dexName: dex?.name ?? dexName,
356
+ dexIndex: dexIndex,
357
+ };
358
+
359
+ this.hip3DexsMeta.set(dexName, dexInfo);
360
+
361
+ // Build HIP-3 market lookups for this DEX
362
+ this.buildHip3MarketsForDex(dexName, dexInfo);
363
+
364
+ return dexInfo;
365
+ } catch (error) {
366
+ console.error(`Failed to load HIP-3 metadata for ${dexName}:`, error);
367
+ throw error;
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Build optimized lookups for HIP-3 markets of a specific DEX
373
+ */
374
+ private buildHip3MarketsForDex(dexName: string, dexInfo: DexInfo): void {
375
+ dexInfo.meta.universe.forEach((market, index) => {
376
+ const symbol = market.name;
377
+ const marketInfo: MarketInfo = {
378
+ coin: symbol,
379
+ symbol,
380
+ assetId: 100000 + dexInfo.dexIndex * 10000 + index,
381
+ szDecimals: market.szDecimals,
382
+ type: "hip3",
383
+ maxLeverage: market.maxLeverage,
384
+ dexName,
385
+ dexIndex: dexInfo.dexIndex,
386
+ };
387
+
388
+ this.coinToMarket.set(symbol, marketInfo);
389
+ this.hip3SymbolToMarket.set(symbol, marketInfo);
390
+ });
391
+ }
392
+
393
+ /**
394
+ * Ensure metadata is loaded (for lazy init)
395
+ */
396
+ private async ensureInitialized(): Promise<void> {
397
+ if (!this.initialized) {
398
+ await this.initialize();
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Get market information by symbol
404
+ * Optimized: O(1) direct map lookup for most cases
405
+ * Lazily initialize hip-3 metadata if not already initialized
406
+ *
407
+ * @param symbol - Market symbol (e.g., "BTC", "PURR/USDC", "vntls:ABC")
408
+ * @param quoteAsset - Quote asset for spot markets (default: "USDC")
409
+ * @returns Market information or null if not found
410
+ */
411
+ async getMarketBySymbolAsync(
412
+ symbol: string,
413
+ quoteAsset: string = "USDC",
414
+ ): Promise<MarketInfo | null> {
415
+ await this.ensureInitialized();
416
+
417
+ // Check if HIP-3 market (format: "dex:coin")
418
+ if (symbol.includes(":")) {
419
+ return this.getHip3Market(symbol);
420
+ }
421
+
422
+ // Try O(1) direct lookup first
423
+ let lookupKey = symbol.toUpperCase();
424
+
425
+ // If no "/" in symbol, it could be perps or need quote asset
426
+ if (!symbol.includes("/") && !symbol.includes("@")) {
427
+ // Try perps lookup first (O(1))
428
+ const perpsMarket = this.coinToMarket.get(lookupKey);
429
+ if (perpsMarket?.type === "perps") {
430
+ return perpsMarket;
431
+ }
432
+
433
+ // Construct spot pair with quote asset
434
+ lookupKey = `${symbol}/${quoteAsset}`.toUpperCase();
435
+ }
436
+
437
+ // O(1) lookup in unified map
438
+ return this.coinToMarket.get(lookupKey) || null;
439
+ }
440
+
441
+ getMarketByCoin(coin: string): MarketInfo | null {
442
+ return this.coinToMarket.get(coin) || null;
443
+ }
444
+
445
+ /**
446
+ * Get perpetuals market information
447
+ * Optimized: O(1) map lookup instead of O(n) array search
448
+ */
449
+ getPerpsMarket(symbol: string): MarketInfo | null {
450
+ if (!this.perpsMeta) return null;
451
+
452
+ if (isHip3Symbol(symbol)) {
453
+ const [dexName, coinName] = symbol.split(":");
454
+ if (!dexName || !coinName) return null;
455
+
456
+ // Try O(1) lookup first (if DEX metadata already loaded)
457
+ let cachedMarket = this.hip3SymbolToMarket.get(symbol);
458
+ return cachedMarket || null;
459
+ }
460
+
461
+ // O(1) lookup using pre-computed map
462
+ const index = this.perpsSymbolToIndex.get(symbol.toUpperCase());
463
+ if (index === undefined) return null;
464
+
465
+ const market = this.perpsMeta.universe[index];
466
+
467
+ return {
468
+ coin: symbol,
469
+ symbol: market.name,
470
+ assetId: index, // Perps asset ID is just the index
471
+ szDecimals: market.szDecimals,
472
+ type: "perps",
473
+ maxLeverage: market.maxLeverage,
474
+ };
475
+ }
476
+
477
+ /**
478
+ * Get spot market information
479
+ * Optimized: O(1) map lookup instead of O(n) array searches
480
+ *
481
+ * @param baseSymbol - Base token symbol (e.g., "PURR", "UHYPE")
482
+ * @param quoteSymbol - Quote token symbol (default: "USDC")
483
+ */
484
+ getSpotMarket(
485
+ baseSymbol: string,
486
+ quoteSymbol: string = "USDC",
487
+ ): MarketInfo | null {
488
+ if (!this.spotMeta) return null;
489
+
490
+ // O(1) lookup using pre-computed map
491
+ const pairKey = `${baseSymbol}/${quoteSymbol}`.toUpperCase();
492
+ const market = this.spotPairToMarket.get(pairKey);
493
+
494
+ return market || null;
495
+ }
496
+
497
+ /**
498
+ * Get HIP-3 market information
499
+ * Optimized: O(1) map lookup after DEX metadata is loaded
500
+ *
501
+ * @param symbol - HIP-3 market symbol (format: "dex:coin")
502
+ */
503
+ async getHip3Market(symbol: string): Promise<MarketInfo | null> {
504
+ const [dexName, coinName] = symbol.split(":");
505
+ if (!dexName || !coinName) return null;
506
+
507
+ // Try O(1) lookup first (if DEX metadata already loaded)
508
+ let cachedMarket = this.hip3SymbolToMarket.get(symbol);
509
+ if (cachedMarket) return cachedMarket;
510
+
511
+ // If not found, check if we need to load DEX metadata
512
+ const dexMeta = this.hip3DexsMeta.get(dexName);
513
+ if (!dexMeta && this.config.lazyInit) {
514
+ // Load and build lookups for this DEX
515
+ await this.loadHip3Metadata(dexName);
516
+ // Try lookup again after loading
517
+ cachedMarket = this.hip3SymbolToMarket.get(symbol);
518
+ return cachedMarket || null;
519
+ }
520
+
521
+ // If DEX is loaded but symbol not found in map, return null
522
+ return null;
523
+ }
524
+
525
+ async getHip3Dex(dexName: string): Promise<DexInfo | null> {
526
+ await this.ensureInitialized();
527
+ let dexInfo = this.hip3DexsMeta.get(dexName) ?? null;
528
+ if (this.config.lazyInit && !dexInfo) {
529
+ dexInfo = await this.loadHip3Metadata(dexName);
530
+ }
531
+
532
+ return dexInfo;
533
+ }
534
+
535
+ /**
536
+ * Get all available markets for a base token
537
+ * Optimized: O(1) map lookup instead of O(n) filter + map
538
+ * Useful for showing all quote asset options
539
+ */
540
+ getAllMarketsForBase(baseSymbol: string): MarketInfo[] {
541
+ if (!this.spotMeta) return [];
542
+
543
+ // O(1) lookup using pre-computed map
544
+ const baseKey = baseSymbol.toUpperCase();
545
+ return this.baseTokenToMarkets.get(baseKey) || [];
546
+ }
547
+
548
+ /**
549
+ * Get spot token information
550
+ * Optimized: O(1) map lookup instead of O(n) array search
551
+ */
552
+ getSpotTokenInfo(tokenSymbol: string): TokenInfo | null {
553
+ if (!this.spotMeta) return null;
554
+
555
+ // O(1) lookup using pre-computed map
556
+ const tokenIndex = this.spotTokenNameToIndex.get(tokenSymbol.toUpperCase());
557
+ if (tokenIndex === undefined) return null;
558
+
559
+ const token = this.spotMeta.tokens[tokenIndex];
560
+
561
+ return {
562
+ name: token.name,
563
+ index: token.index,
564
+ szDecimals: token.szDecimals,
565
+ weiDecimals: token.weiDecimals,
566
+ tokenId: token.tokenId,
567
+ };
568
+ }
569
+
570
+ /**
571
+ * Get all available quote assets
572
+ * Optimized: O(1) pre-computed array instead of O(n) computation
573
+ */
574
+ getAvailableQuoteAssets(): string[] {
575
+ return this.quoteAssets;
576
+ }
577
+
578
+ /**
579
+ * Get raw metadata (for advanced use cases)
580
+ */
581
+ getRawMetadata() {
582
+ return {
583
+ spotMeta: this.spotMeta,
584
+ perpsMeta: this.perpsMeta,
585
+ perpDexs: this.perpDexs,
586
+ hip3DexsMeta: Object.fromEntries(this.hip3DexsMeta),
587
+ };
588
+ }
589
+
590
+ /**
591
+ * Get network configuration
592
+ */
593
+ getNetworkInfo() {
594
+ return {
595
+ isTestnet: this.isTestnet,
596
+ useStaticFallback: this.config.useStaticFallback,
597
+ initialized: this.initialized,
598
+ };
599
+ }
600
+ }