@basedone/core 0.0.6 → 0.0.8

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.
@@ -1,13 +1,27 @@
1
1
  import {
2
2
  HttpTransport,
3
3
  InfoClient,
4
- PerpDex,
5
4
  PerpsAssetCtx,
6
5
  PerpsMeta,
7
6
  SpotMeta,
8
7
  SpotToken,
9
8
  } from "@nktkas/hyperliquid";
10
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
+ }
11
25
 
12
26
  /**
13
27
  * Market information for trading operations
@@ -19,6 +33,9 @@ export interface MarketInfo {
19
33
  type: "perps" | "spot" | "hip3";
20
34
  coin: string; // Format used in info endpoint (e.g., "@107") for spot hype, "HYPE" for perps, "dex:coin" for hip3
21
35
 
36
+ displayName?: string; // Display name for the market
37
+ imageUrl?: string; // Image URL for the market
38
+
22
39
  maxLeverage?: number; // For perps only
23
40
 
24
41
  // Spot-specific fields
@@ -28,6 +45,8 @@ export interface MarketInfo {
28
45
  // HIP-3 specific fields
29
46
  dexName?: string; // DEX name for HIP-3 markets
30
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
31
50
  }
32
51
 
33
52
  export interface DexInfo {
@@ -37,6 +56,9 @@ export interface DexInfo {
37
56
  dexName: string; // DEX name for HIP-3 markets
38
57
  dexIndex: number; // DEX index for asset ID calculation
39
58
  collateralTokenSymbol: string;
59
+ displayName?: string; // Display name override for the DEX
60
+ imageUrl?: string; // Image URL override for the DEX
61
+ accountName?: string; // Account name override for the DEX
40
62
  }
41
63
 
42
64
  export type ExtendedPerpsMeta = PerpsMeta & {
@@ -54,6 +76,25 @@ export interface TokenInfo {
54
76
  tokenId: string;
55
77
  }
56
78
 
79
+ /**
80
+ * Static metadata structure for display overrides
81
+ */
82
+ interface StaticMetadata {
83
+ coins?: {
84
+ [symbol: string]: {
85
+ displayName?: string;
86
+ imageUrl?: string;
87
+ };
88
+ };
89
+ dexs?: {
90
+ [dexName: string]: {
91
+ displayName?: string;
92
+ imageUrl?: string;
93
+ accountName?: string;
94
+ };
95
+ };
96
+ }
97
+
57
98
  /**
58
99
  * Configuration for MetadataClient
59
100
  */
@@ -84,6 +125,8 @@ interface MetadataClientConfig {
84
125
  * - Spot: 10000 + index
85
126
  * - HIP-3: 100000 + perp_dex_index * 10000 + index_in_meta
86
127
  */
128
+ export const ROOT_DEX = "hyperliquid";
129
+
87
130
  export class MetadataClient {
88
131
  private infoClient: InfoClient;
89
132
  private config: MetadataClientConfig;
@@ -93,6 +136,7 @@ export class MetadataClient {
93
136
  private spotMeta: SpotMeta | null = null;
94
137
  private perpsMeta: PerpsMeta | null = null;
95
138
  private perpDexs: (PerpDex | null)[] = [];
139
+ private staticMeta: StaticMetadata | null = null;
96
140
 
97
141
  // HIP-3 metadata cache
98
142
  private hip3DexsMeta: Map<string, DexInfo> = new Map();
@@ -116,6 +160,7 @@ export class MetadataClient {
116
160
 
117
161
  // Lazy init flag
118
162
  private initialized: boolean = false;
163
+ private initializing: Promise<void> | null = null;
119
164
 
120
165
  constructor(config: MetadataClientConfig = {}) {
121
166
  const transport = new HttpTransport({
@@ -124,6 +169,7 @@ export class MetadataClient {
124
169
  this.infoClient = new InfoClient({ transport });
125
170
  this.config = {
126
171
  ...config,
172
+ hip3Dexs: config.hip3Dexs?.filter((dex) => dex !== ROOT_DEX),
127
173
  useStaticFallback: config.useStaticFallback ?? true,
128
174
  };
129
175
  this.isTestnet = config.isTestnet ?? false;
@@ -135,54 +181,82 @@ export class MetadataClient {
135
181
  */
136
182
  async initialize(): Promise<void> {
137
183
  if (this.initialized) return;
184
+ if (this.initializing) return this.initializing;
185
+ this.initializing = new Promise(async (resolve, reject) => {
186
+ // Always load staticMeta.json regardless of config.useStaticFallback
187
+ await this.loadStaticMetaOverrides();
138
188
 
139
- if (this.config.onlyUseStaticFallback) {
140
- await this.loadStaticMetadata();
141
- this.buildLookupMaps();
189
+ if (this.config.onlyUseStaticFallback) {
190
+ await this.loadStaticMetadata();
191
+ this.buildLookupMaps();
142
192
 
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
- );
193
+ // Fetch HIP-3 metadata if configured
194
+ if (this.config.hip3Dexs && this.config.hip3Dexs.length > 0) {
195
+ await Promise.all(
196
+ this.config.hip3Dexs.map((dex) => this.loadHip3Metadata(dex)),
197
+ );
198
+ }
199
+ resolve();
200
+ this.initialized = true;
201
+ return;
148
202
  }
149
203
 
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
- ]);
204
+ try {
205
+ // Fetch core metadata from API
206
+ [this.spotMeta, this.perpsMeta, this.perpDexs] = await Promise.all([
207
+ this.infoClient.spotMeta(),
208
+ this.infoClient.meta(),
209
+ this.infoClient.perpDexs() as Promise<PerpDex[] | null[]>,
210
+ ]);
161
211
 
162
- // Build optimized lookup maps
163
- this.buildLookupMaps();
212
+ // Build optimized lookup maps
213
+ this.buildLookupMaps();
164
214
 
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
- );
215
+ // Fetch HIP-3 metadata if configured
216
+ if (this.config.hip3Dexs && this.config.hip3Dexs.length > 0) {
217
+ await Promise.all(
218
+ this.config.hip3Dexs.map((dex) => this.loadHip3Metadata(dex)),
219
+ );
220
+ }
221
+ this.initialized = true;
222
+ resolve();
223
+ } catch (error) {
224
+ // Fall back to static data if API fetch fails and fallback is enabled
225
+ if (this.config.useStaticFallback) {
226
+ console.warn(
227
+ "Failed to fetch metadata from API, using static fallback data",
228
+ error,
229
+ );
230
+ await this.loadStaticMetadata();
231
+ this.buildLookupMaps();
232
+ this.initialized = true;
233
+ resolve();
234
+ } else {
235
+ reject(error);
236
+ }
237
+ } finally {
238
+ this.initialized = true;
239
+ resolve();
170
240
  }
241
+ });
242
+ return this.initializing;
243
+ }
171
244
 
172
- this.initialized = true;
245
+ /**
246
+ * Load staticMeta.json for display overrides
247
+ * This is always loaded regardless of config.useStaticFallback
248
+ */
249
+ private async loadStaticMetaOverrides(): Promise<void> {
250
+ const network = this.isTestnet ? "testnet" : "mainnet";
251
+
252
+ try {
253
+ const staticMetaModule = await import(
254
+ `./data/${network}/staticMeta.json`
255
+ );
256
+ this.staticMeta = staticMetaModule.default as StaticMetadata;
173
257
  } 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
- }
258
+ console.warn(`Failed to load staticMeta.json for ${network}:`, error);
259
+ this.staticMeta = null;
186
260
  }
187
261
  }
188
262
 
@@ -194,16 +268,22 @@ export class MetadataClient {
194
268
 
195
269
  try {
196
270
  // 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
- ]);
271
+ const [
272
+ spotMetaModule,
273
+ perpsMetaModule,
274
+ perpDexsModule,
275
+ staticMetaModule,
276
+ ] = await Promise.all([
277
+ import(`./data/${network}/spotMeta.json`),
278
+ import(`./data/${network}/meta.json`),
279
+ import(`./data/${network}/perpDexs.json`),
280
+ import(`./data/${network}/staticMeta.json`),
281
+ ]);
203
282
 
204
283
  this.spotMeta = spotMetaModule.default as SpotMeta;
205
284
  this.perpsMeta = perpsMetaModule.default as PerpsMeta;
206
285
  this.perpDexs = perpDexsModule.default as (PerpDex | null)[];
286
+ this.staticMeta = staticMetaModule.default as StaticMetadata;
207
287
 
208
288
  console.warn(`Using static ${network} metadata`);
209
289
  } catch (error) {
@@ -223,10 +303,16 @@ export class MetadataClient {
223
303
  this.spotPairToMarket.clear();
224
304
  this.baseTokenToMarkets.clear();
225
305
  this.coinToMarket.clear();
226
- this.hip3SymbolToMarket.clear();
227
306
  this.dexNameToIndex.clear();
228
307
  this.quoteAssets = [];
229
308
 
309
+ console.info(
310
+ "[MetadataClient] Building lookup maps",
311
+ this.perpDexs,
312
+ this.perpsMeta,
313
+ this.spotMeta,
314
+ );
315
+
230
316
  // Build dex name to index map
231
317
  if (this.perpDexs) {
232
318
  this.perpDexs.forEach((dex, index) => {
@@ -248,6 +334,17 @@ export class MetadataClient {
248
334
  maxLeverage: market.maxLeverage,
249
335
  };
250
336
 
337
+ // Apply static metadata overrides for perps
338
+ const staticOverrides = this.staticMeta?.coins?.[market.name];
339
+ if (staticOverrides) {
340
+ if (staticOverrides.displayName) {
341
+ marketInfo.displayName = staticOverrides.displayName;
342
+ }
343
+ if (staticOverrides.imageUrl) {
344
+ marketInfo.imageUrl = staticOverrides.imageUrl;
345
+ }
346
+ }
347
+
251
348
  // Case-insensitive lookup
252
349
  this.perpsSymbolToIndex.set(market.name.toUpperCase(), index);
253
350
 
@@ -288,6 +385,17 @@ export class MetadataClient {
288
385
  quoteToken: quoteToken,
289
386
  };
290
387
 
388
+ // Apply static metadata overrides for spot
389
+ const staticOverrides = this.staticMeta?.coins?.[coin];
390
+ if (staticOverrides) {
391
+ if (staticOverrides.displayName) {
392
+ marketInfo.displayName = staticOverrides.displayName;
393
+ }
394
+ if (staticOverrides.imageUrl) {
395
+ marketInfo.imageUrl = staticOverrides.imageUrl;
396
+ }
397
+ }
398
+
291
399
  // Store by full pair (case-insensitive)
292
400
  const pairKey = `${baseToken.name}/${quoteToken.name}`.toUpperCase();
293
401
  this.spotPairToMarket.set(pairKey, marketInfo);
@@ -347,15 +455,29 @@ export class MetadataClient {
347
455
  const collateralTokenSymbol =
348
456
  spotMetaTokens?.[collateralTokenIndex]?.name ?? "USDC";
349
457
 
350
- const dexInfo = {
458
+ const dexInfo: DexInfo = {
351
459
  meta,
352
460
  assetContext: contexts,
353
461
  collateralTokenSymbol,
354
- dexFullName: dex?.full_name ?? dexName,
462
+ dexFullName: dex?.fullName ?? dexName,
355
463
  dexName: dex?.name ?? dexName,
356
464
  dexIndex: dexIndex,
357
465
  };
358
466
 
467
+ // Apply static metadata overrides for DEX
468
+ const staticDexOverrides = this.staticMeta?.dexs?.[dexName];
469
+ if (staticDexOverrides) {
470
+ if (staticDexOverrides.displayName) {
471
+ dexInfo.displayName = staticDexOverrides.displayName;
472
+ }
473
+ if (staticDexOverrides.imageUrl) {
474
+ dexInfo.imageUrl = staticDexOverrides.imageUrl;
475
+ }
476
+ if (staticDexOverrides.accountName) {
477
+ dexInfo.accountName = staticDexOverrides.accountName;
478
+ }
479
+ }
480
+
359
481
  this.hip3DexsMeta.set(dexName, dexInfo);
360
482
 
361
483
  // Build HIP-3 market lookups for this DEX
@@ -374,6 +496,7 @@ export class MetadataClient {
374
496
  private buildHip3MarketsForDex(dexName: string, dexInfo: DexInfo): void {
375
497
  dexInfo.meta.universe.forEach((market, index) => {
376
498
  const symbol = market.name;
499
+
377
500
  const marketInfo: MarketInfo = {
378
501
  coin: symbol,
379
502
  symbol,
@@ -383,8 +506,23 @@ export class MetadataClient {
383
506
  maxLeverage: market.maxLeverage,
384
507
  dexName,
385
508
  dexIndex: dexInfo.dexIndex,
509
+ dexDisplayName: dexInfo.displayName,
510
+ dexImageUrl: dexInfo.imageUrl,
386
511
  };
387
512
 
513
+ // Apply static metadata overrides for HIP-3
514
+ const staticOverrides = this.staticMeta?.coins?.[symbol];
515
+ if (staticOverrides) {
516
+ if (staticOverrides.displayName) {
517
+ marketInfo.displayName = staticOverrides.displayName;
518
+ }
519
+ if (staticOverrides.imageUrl) {
520
+ marketInfo.imageUrl = staticOverrides.imageUrl;
521
+ }
522
+ }
523
+
524
+ console.info("[MetadataClient] caching market", symbol, marketInfo);
525
+
388
526
  this.coinToMarket.set(symbol, marketInfo);
389
527
  this.hip3SymbolToMarket.set(symbol, marketInfo);
390
528
  });
@@ -425,7 +563,7 @@ export class MetadataClient {
425
563
  // If no "/" in symbol, it could be perps or need quote asset
426
564
  if (!symbol.includes("/") && !symbol.includes("@")) {
427
565
  // Try perps lookup first (O(1))
428
- const perpsMarket = this.coinToMarket.get(lookupKey);
566
+ const perpsMarket = this.coinToMarket.get(symbol);
429
567
  if (perpsMarket?.type === "perps") {
430
568
  return perpsMarket;
431
569
  }
@@ -507,6 +645,7 @@ export class MetadataClient {
507
645
  // Try O(1) lookup first (if DEX metadata already loaded)
508
646
  let cachedMarket = this.hip3SymbolToMarket.get(symbol);
509
647
  if (cachedMarket) return cachedMarket;
648
+ console.warn("Missing market", symbol, this.hip3SymbolToMarket);
510
649
 
511
650
  // If not found, check if we need to load DEX metadata
512
651
  const dexMeta = this.hip3DexsMeta.get(dexName);
@@ -584,6 +723,8 @@ export class MetadataClient {
584
723
  perpsMeta: this.perpsMeta,
585
724
  perpDexs: this.perpDexs,
586
725
  hip3DexsMeta: Object.fromEntries(this.hip3DexsMeta),
726
+ coinToMarket: Object.fromEntries(this.coinToMarket),
727
+ hip3SymbolToMarket: Object.fromEntries(this.hip3SymbolToMarket),
587
728
  };
588
729
  }
589
730
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basedone/core",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Core utilities for Based One",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",