@basedone/core 0.0.1 → 0.0.7

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