@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.
- package/dist/chunk-4UEJOM6W.mjs +9 -0
- package/dist/index.d.mts +488 -0
- package/dist/index.d.ts +488 -2
- package/dist/index.js +37574 -2
- package/dist/index.mjs +1097 -0
- package/dist/lib/cloid.d.ts.map +1 -1
- package/dist/lib/cloid.js +11 -1
- package/dist/lib/cloid.js.map +1 -1
- package/dist/meta-52Q5UUQ4.mjs +1474 -0
- package/dist/meta-FTWJX4LV.mjs +1445 -0
- package/dist/meta-IKWYLG3Q.mjs +1316 -0
- package/dist/meta-UUXKK7IB.mjs +1355 -0
- package/dist/perpDexs-PSE3LEVV.mjs +9 -0
- package/dist/perpDexs-S3TK25EU.mjs +17 -0
- package/dist/perpDexs-TZIQ57IW.mjs +537 -0
- package/dist/perpDexs-YNEAJ3R5.mjs +7 -0
- package/dist/perpDexs-YS3QQSHW.mjs +338 -0
- package/dist/spotMeta-7IJT3W6H.mjs +6442 -0
- package/dist/spotMeta-LEO5QFNS.mjs +26392 -0
- package/dist/spotMeta-MC5UYLQ7.mjs +6335 -0
- package/dist/spotMeta-TXJWYTKI.mjs +26403 -0
- package/dist/spotMeta-VAANYV77.mjs +6346 -0
- package/dist/spotMeta-ZVBZNUUE.mjs +26559 -0
- package/dist/staticMeta-HRXST42O.mjs +24 -0
- package/dist/staticMeta-QWPQK3MD.mjs +22 -0
- package/index.ts +6 -0
- package/lib/cloid/README.md +233 -0
- package/lib/cloid/cloid.ts +368 -0
- package/lib/cloid/encoder.ts +60 -0
- package/lib/constants/fee.ts +2 -0
- package/lib/constants/tokens.ts +28 -0
- package/lib/fee.ts +105 -0
- package/lib/hip3/market-info.ts +25 -0
- package/lib/hip3/utils.ts +9 -0
- package/lib/meta/README.md +471 -0
- package/lib/meta/data/mainnet/dexs/xyz.json +26 -0
- package/lib/meta/data/mainnet/meta.json +1462 -0
- package/lib/meta/data/mainnet/perpDexs.json +11 -0
- package/lib/meta/data/mainnet/spotMeta.json +6432 -0
- package/lib/meta/data/mainnet/staticMeta.json +14 -0
- package/lib/meta/data/testnet/dexs/rrrrr.json +33 -0
- package/lib/meta/data/testnet/meta.json +1343 -0
- package/lib/meta/data/testnet/perpDexs.json +531 -0
- package/lib/meta/data/testnet/spotMeta.json +26547 -0
- package/lib/meta/data/testnet/staticMeta.json +12 -0
- package/lib/meta/metadata.ts +717 -0
- package/lib/pup/calculator.ts +221 -0
- package/lib/pup/index.ts +9 -0
- package/lib/pup/types.ts +94 -0
- package/lib/utils/formatter.ts +97 -0
- package/package.json +21 -17
- 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
|
+
}
|