@tria-sdk/hyperliquid-core 0.1.0
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/api.d.ts +279 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +914 -0
- package/dist/api.js.map +1 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +35 -0
- package/dist/client.js.map +1 -0
- package/dist/exchange.d.ts +131 -0
- package/dist/exchange.d.ts.map +1 -0
- package/dist/exchange.js +6 -0
- package/dist/exchange.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +357 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/format.d.ts +62 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +193 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/localization.d.ts +93 -0
- package/dist/utils/localization.d.ts.map +1 -0
- package/dist/utils/localization.js +108 -0
- package/dist/utils/localization.js.map +1 -0
- package/dist/websocket/WebSocketManager.d.ts +41 -0
- package/dist/websocket/WebSocketManager.d.ts.map +1 -0
- package/dist/websocket/WebSocketManager.js +295 -0
- package/dist/websocket/WebSocketManager.js.map +1 -0
- package/dist/websocket/index.d.ts +3 -0
- package/dist/websocket/index.d.ts.map +1 -0
- package/dist/websocket/index.js +3 -0
- package/dist/websocket/index.js.map +1 -0
- package/dist/websocket/types.d.ts +98 -0
- package/dist/websocket/types.d.ts.map +1 -0
- package/dist/websocket/types.js +2 -0
- package/dist/websocket/types.js.map +1 -0
- package/package.json +28 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperliquid API functions
|
|
3
|
+
* Platform-agnostic data fetching - no web or React Native specific code
|
|
4
|
+
*/
|
|
5
|
+
import { SymbolConverter } from "@nktkas/hyperliquid/utils";
|
|
6
|
+
import { getHyperliquidInfoClient, getHyperliquidTransport } from "./client";
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Constants
|
|
9
|
+
// ============================================================================
|
|
10
|
+
const DEFAULT_SPOT_QUOTE = "USDC";
|
|
11
|
+
const CANDLE_LOOKBACK = 200;
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Helper Functions
|
|
14
|
+
// ============================================================================
|
|
15
|
+
const ensureSpotSymbol = (symbol) => {
|
|
16
|
+
const trimmed = symbol?.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
throw new Error("Token name is required");
|
|
19
|
+
}
|
|
20
|
+
return trimmed.includes("/") ? trimmed : `${trimmed}/${DEFAULT_SPOT_QUOTE}`;
|
|
21
|
+
};
|
|
22
|
+
const parseSymbolForBook = (symbol) => {
|
|
23
|
+
const trimmed = symbol?.trim();
|
|
24
|
+
if (!trimmed) {
|
|
25
|
+
throw new Error("Symbol is required");
|
|
26
|
+
}
|
|
27
|
+
if (trimmed.includes("/")) {
|
|
28
|
+
return { coin: trimmed, market: "spot" };
|
|
29
|
+
}
|
|
30
|
+
if (/-PERP$/i.test(trimmed)) {
|
|
31
|
+
return { coin: trimmed.replace(/-PERP$/i, ""), market: "perp" };
|
|
32
|
+
}
|
|
33
|
+
return { coin: trimmed, market: "perp" };
|
|
34
|
+
};
|
|
35
|
+
const intervalToMs = (interval) => {
|
|
36
|
+
const unit = interval.slice(-1);
|
|
37
|
+
const value = parseInt(interval.slice(0, -1), 10);
|
|
38
|
+
if (Number.isNaN(value)) {
|
|
39
|
+
return 60000;
|
|
40
|
+
}
|
|
41
|
+
switch (unit) {
|
|
42
|
+
case "m":
|
|
43
|
+
return value * 60 * 1000;
|
|
44
|
+
case "h":
|
|
45
|
+
return value * 60 * 60 * 1000;
|
|
46
|
+
case "d":
|
|
47
|
+
return value * 24 * 60 * 60 * 1000;
|
|
48
|
+
case "w":
|
|
49
|
+
return value * 7 * 24 * 60 * 60 * 1000;
|
|
50
|
+
case "M":
|
|
51
|
+
return value * 30 * 24 * 60 * 60 * 1000;
|
|
52
|
+
default:
|
|
53
|
+
return 60 * 1000;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const toNumeric = (value) => {
|
|
57
|
+
if (typeof value === "number") {
|
|
58
|
+
return Number.isFinite(value) ? value : null;
|
|
59
|
+
}
|
|
60
|
+
if (typeof value === "string") {
|
|
61
|
+
const parsed = parseFloat(value);
|
|
62
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
const getLevelPrice = (level) => {
|
|
67
|
+
if (!level) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(level)) {
|
|
71
|
+
return toNumeric(level[0]);
|
|
72
|
+
}
|
|
73
|
+
return toNumeric(level.px);
|
|
74
|
+
};
|
|
75
|
+
const getLevelSize = (level) => {
|
|
76
|
+
if (!level) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(level)) {
|
|
80
|
+
return toNumeric(level[1]);
|
|
81
|
+
}
|
|
82
|
+
return toNumeric(level.sz);
|
|
83
|
+
};
|
|
84
|
+
const getLevelCount = (level) => {
|
|
85
|
+
if (!level) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(level)) {
|
|
89
|
+
return typeof level[2] === "number" ? level[2] : null;
|
|
90
|
+
}
|
|
91
|
+
return typeof level.n === "number" ? level.n : null;
|
|
92
|
+
};
|
|
93
|
+
const normalizeOrderBookLevels = (levels, depth) => {
|
|
94
|
+
const normalized = [];
|
|
95
|
+
let runningTotal = 0;
|
|
96
|
+
for (const level of levels) {
|
|
97
|
+
if (normalized.length >= depth) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
const price = getLevelPrice(level);
|
|
101
|
+
const size = getLevelSize(level);
|
|
102
|
+
if (price === null || size === null || size <= 0) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
runningTotal += size;
|
|
106
|
+
normalized.push({
|
|
107
|
+
price,
|
|
108
|
+
size,
|
|
109
|
+
total: runningTotal,
|
|
110
|
+
count: getLevelCount(level),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return normalized;
|
|
114
|
+
};
|
|
115
|
+
const pickValidPrice = (...values) => {
|
|
116
|
+
for (const value of values) {
|
|
117
|
+
if (value == null) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const numeric = parseFloat(value);
|
|
121
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return "0";
|
|
126
|
+
};
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Public API Functions
|
|
129
|
+
// ============================================================================
|
|
130
|
+
/**
|
|
131
|
+
* Retrieves raw spot metadata from Hyperliquid public API
|
|
132
|
+
*/
|
|
133
|
+
export async function fetchSpotMeta(network = "testnet") {
|
|
134
|
+
try {
|
|
135
|
+
const client = getHyperliquidInfoClient(network);
|
|
136
|
+
return await client.spotMeta();
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Fetches token details for a specific token
|
|
144
|
+
*/
|
|
145
|
+
export async function fetchTokenDetails(tokenId, network = "testnet") {
|
|
146
|
+
try {
|
|
147
|
+
const client = getHyperliquidInfoClient(network);
|
|
148
|
+
const details = await client.tokenDetails({ tokenId });
|
|
149
|
+
return {
|
|
150
|
+
name: details.name,
|
|
151
|
+
maxSupply: details.maxSupply,
|
|
152
|
+
totalSupply: details.totalSupply,
|
|
153
|
+
circulatingSupply: details.circulatingSupply,
|
|
154
|
+
szDecimals: details.szDecimals,
|
|
155
|
+
weiDecimals: details.weiDecimals,
|
|
156
|
+
genesis: details.genesis,
|
|
157
|
+
deployer: details.deployer || undefined,
|
|
158
|
+
deployGas: details.deployGas || undefined,
|
|
159
|
+
deployTime: details.deployTime || undefined,
|
|
160
|
+
seededUsdc: details.seededUsdc,
|
|
161
|
+
futureEmissions: details.futureEmissions,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Fetches all mid prices
|
|
170
|
+
*/
|
|
171
|
+
export async function fetchAllMids(network = "testnet") {
|
|
172
|
+
try {
|
|
173
|
+
const client = getHyperliquidInfoClient(network);
|
|
174
|
+
return await client.allMids();
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Fetches spot metadata and asset contexts
|
|
182
|
+
*/
|
|
183
|
+
export async function fetchSpotMetaAndAssetCtxs(network = "testnet") {
|
|
184
|
+
try {
|
|
185
|
+
const client = getHyperliquidInfoClient(network);
|
|
186
|
+
return await client.spotMetaAndAssetCtxs();
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Fetches spot tokens with market data
|
|
194
|
+
*/
|
|
195
|
+
export async function fetchSpotTokensWithMarketData(network = "testnet") {
|
|
196
|
+
try {
|
|
197
|
+
const [[spotMeta, spotCtxs], allMids] = await Promise.all([
|
|
198
|
+
fetchSpotMetaAndAssetCtxs(network),
|
|
199
|
+
fetchAllMids(network),
|
|
200
|
+
]);
|
|
201
|
+
if (!spotMeta?.tokens || !Array.isArray(spotCtxs)) {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
return spotMeta.tokens
|
|
205
|
+
.map((token, index) => {
|
|
206
|
+
const assetCtx = spotCtxs[index];
|
|
207
|
+
const currentPrice = pickValidPrice(allMids[token.name], assetCtx?.midPx, assetCtx?.markPx);
|
|
208
|
+
const lastPrice = pickValidPrice(assetCtx?.markPx, assetCtx?.midPx, assetCtx?.prevDayPx, allMids[token.name]);
|
|
209
|
+
let change24h = "0.00";
|
|
210
|
+
if (assetCtx?.prevDayPx) {
|
|
211
|
+
const prevPx = parseFloat(assetCtx.prevDayPx);
|
|
212
|
+
const nowPx = parseFloat(currentPrice);
|
|
213
|
+
if (prevPx > 0 && Number.isFinite(nowPx)) {
|
|
214
|
+
change24h = (((nowPx - prevPx) / prevPx) * 100).toFixed(2);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
name: token.name,
|
|
219
|
+
szDecimals: token.szDecimals,
|
|
220
|
+
fullName: token.fullName || undefined,
|
|
221
|
+
price: currentPrice,
|
|
222
|
+
lastPrice,
|
|
223
|
+
midPrice: assetCtx?.midPx,
|
|
224
|
+
markPrice: assetCtx?.markPx,
|
|
225
|
+
change24h,
|
|
226
|
+
volume24h: assetCtx?.dayBaseVlm || "0",
|
|
227
|
+
circulatingSupply: assetCtx?.circulatingSupply || "0",
|
|
228
|
+
};
|
|
229
|
+
})
|
|
230
|
+
.filter((token) => {
|
|
231
|
+
const price = parseFloat(token.price || "0");
|
|
232
|
+
const volume = parseFloat(token.volume24h || "0");
|
|
233
|
+
return Number.isFinite(price) && price > 0 && volume > 0;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Fetches spot order book for a token
|
|
242
|
+
*/
|
|
243
|
+
export async function fetchSpotOrderBook(tokenName, network = "testnet") {
|
|
244
|
+
try {
|
|
245
|
+
const client = getHyperliquidInfoClient(network);
|
|
246
|
+
const coin = ensureSpotSymbol(tokenName);
|
|
247
|
+
return await client.l2Book({ coin });
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Fetches order book snapshot with normalized levels
|
|
255
|
+
*/
|
|
256
|
+
export async function fetchOrderBook(symbol, depth = 25, network = "testnet") {
|
|
257
|
+
const { coin, market } = parseSymbolForBook(symbol);
|
|
258
|
+
try {
|
|
259
|
+
const client = getHyperliquidInfoClient(network);
|
|
260
|
+
const response = (await client.l2Book({ coin }));
|
|
261
|
+
const levels = Array.isArray(response?.levels)
|
|
262
|
+
? response.levels
|
|
263
|
+
: [];
|
|
264
|
+
const [rawBids = [], rawAsks = []] = levels;
|
|
265
|
+
const bids = normalizeOrderBookLevels(rawBids, depth);
|
|
266
|
+
const asks = normalizeOrderBookLevels(rawAsks, depth);
|
|
267
|
+
const bestBid = bids[0]?.price ?? null;
|
|
268
|
+
const bestAsk = asks[0]?.price ?? null;
|
|
269
|
+
const midPrice = bestBid !== null && bestAsk !== null
|
|
270
|
+
? (bestBid + bestAsk) / 2
|
|
271
|
+
: bestBid ?? bestAsk ?? null;
|
|
272
|
+
const spread = bestBid !== null && bestAsk !== null
|
|
273
|
+
? Math.max(bestAsk - bestBid, 0)
|
|
274
|
+
: null;
|
|
275
|
+
return {
|
|
276
|
+
symbol,
|
|
277
|
+
coin,
|
|
278
|
+
market,
|
|
279
|
+
bids,
|
|
280
|
+
asks,
|
|
281
|
+
bestBid,
|
|
282
|
+
bestAsk,
|
|
283
|
+
midPrice,
|
|
284
|
+
spread,
|
|
285
|
+
lastUpdated: typeof response?.time === "number" ? response.time : Date.now(),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Fetches recent trades for a coin
|
|
294
|
+
*/
|
|
295
|
+
export async function fetchRecentTrades(coin, limit = 20, network = "testnet") {
|
|
296
|
+
try {
|
|
297
|
+
const client = getHyperliquidInfoClient(network);
|
|
298
|
+
const anyClient = client;
|
|
299
|
+
let trades = [];
|
|
300
|
+
if (typeof anyClient.tradesSnapshot === "function") {
|
|
301
|
+
trades = await anyClient.tradesSnapshot({ coin, limit });
|
|
302
|
+
}
|
|
303
|
+
else if (typeof anyClient.trades === "function") {
|
|
304
|
+
trades = await anyClient.trades({ coin, limit });
|
|
305
|
+
}
|
|
306
|
+
else if (typeof anyClient.tradeHistory === "function") {
|
|
307
|
+
trades = await anyClient.tradeHistory({ coin, limit });
|
|
308
|
+
}
|
|
309
|
+
else if (typeof anyClient.recentTrades === "function") {
|
|
310
|
+
trades = await anyClient.recentTrades({ coin, limit });
|
|
311
|
+
}
|
|
312
|
+
return trades.map((trade) => {
|
|
313
|
+
const price = trade.p ?? trade.px ?? "0";
|
|
314
|
+
const size = trade.s ?? trade.sz ?? "0";
|
|
315
|
+
const timestamp = trade.time ?? Date.now();
|
|
316
|
+
return {
|
|
317
|
+
price: String(price),
|
|
318
|
+
size: String(size),
|
|
319
|
+
side: typeof trade.side === "string" ? trade.side : undefined,
|
|
320
|
+
time: typeof timestamp === "number"
|
|
321
|
+
? timestamp
|
|
322
|
+
: Number(timestamp) || Date.now(),
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Fetches perp metadata
|
|
332
|
+
*/
|
|
333
|
+
export async function fetchPerpMeta(network = "testnet") {
|
|
334
|
+
try {
|
|
335
|
+
const client = getHyperliquidInfoClient(network);
|
|
336
|
+
return await client.meta();
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Fetches perp metadata and asset contexts
|
|
344
|
+
*/
|
|
345
|
+
export async function fetchPerpMetaAndAssetCtxs(network = "testnet") {
|
|
346
|
+
try {
|
|
347
|
+
const client = getHyperliquidInfoClient(network);
|
|
348
|
+
return await client.metaAndAssetCtxs();
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Fetches funding history for a coin
|
|
356
|
+
*/
|
|
357
|
+
export async function fetchFundingHistory(coin, startTime, network = "testnet") {
|
|
358
|
+
try {
|
|
359
|
+
const client = getHyperliquidInfoClient(network);
|
|
360
|
+
return await client.fundingHistory({ coin, startTime });
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Fetches user fills (trade history)
|
|
368
|
+
*/
|
|
369
|
+
export async function fetchUserFills(params) {
|
|
370
|
+
const { user, aggregateByTime = false, limit = 100, network = "testnet", } = params;
|
|
371
|
+
try {
|
|
372
|
+
const client = getHyperliquidInfoClient(network);
|
|
373
|
+
const payload = { user, aggregateByTime };
|
|
374
|
+
let fills = [];
|
|
375
|
+
if (typeof client.userFills === "function") {
|
|
376
|
+
fills = await client.userFills(payload);
|
|
377
|
+
}
|
|
378
|
+
else if (typeof client.userFillsByTime === "function") {
|
|
379
|
+
const now = Date.now();
|
|
380
|
+
fills = await client.userFillsByTime({
|
|
381
|
+
...payload,
|
|
382
|
+
startTime: now - 30 * 24 * 60 * 60 * 1000,
|
|
383
|
+
endTime: now,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return Array.isArray(fills)
|
|
387
|
+
? fills.slice(0, limit).map((fill) => ({
|
|
388
|
+
coin: String(fill.coin ?? ""),
|
|
389
|
+
px: String(fill.px ?? fill.price ?? "0"),
|
|
390
|
+
sz: String(fill.sz ?? fill.size ?? "0"),
|
|
391
|
+
side: fill.side ?? "",
|
|
392
|
+
time: Number(fill.time ?? Date.now()),
|
|
393
|
+
fee: fill.fee ? String(fill.fee) : undefined,
|
|
394
|
+
feeToken: fill.feeToken ? String(fill.feeToken) : undefined,
|
|
395
|
+
hash: fill.hash ? String(fill.hash) : undefined,
|
|
396
|
+
oid: typeof fill.oid === "number" ? fill.oid : undefined,
|
|
397
|
+
tid: typeof fill.tid === "number" ? fill.tid : undefined,
|
|
398
|
+
cloid: fill.cloid ? String(fill.cloid) : undefined,
|
|
399
|
+
startPosition: fill.startPosition
|
|
400
|
+
? String(fill.startPosition)
|
|
401
|
+
: undefined,
|
|
402
|
+
dir: fill.dir ? String(fill.dir) : undefined,
|
|
403
|
+
closedPnl: fill.closedPnl ? String(fill.closedPnl) : undefined,
|
|
404
|
+
crossed: typeof fill.crossed === "boolean" ? fill.crossed : undefined,
|
|
405
|
+
liquidation: fill.liquidation,
|
|
406
|
+
twapId: typeof fill.twapId === "number" ? fill.twapId : undefined,
|
|
407
|
+
}))
|
|
408
|
+
: [];
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Fetches user fills within a time window (order history)
|
|
416
|
+
*/
|
|
417
|
+
export async function fetchUserFillsByTime(params) {
|
|
418
|
+
const { user, startTime, endTime = Date.now(), aggregateByTime = true, limit = 200, network = "testnet", } = params;
|
|
419
|
+
try {
|
|
420
|
+
const client = getHyperliquidInfoClient(network);
|
|
421
|
+
let fills = [];
|
|
422
|
+
if (typeof client.userFillsByTime === "function") {
|
|
423
|
+
fills = await client.userFillsByTime({
|
|
424
|
+
user,
|
|
425
|
+
startTime,
|
|
426
|
+
endTime,
|
|
427
|
+
aggregateByTime,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
else if (typeof client.userFills === "function") {
|
|
431
|
+
fills = await client.userFills({ user, aggregateByTime });
|
|
432
|
+
}
|
|
433
|
+
return Array.isArray(fills)
|
|
434
|
+
? fills
|
|
435
|
+
.filter((fill) => {
|
|
436
|
+
const t = Number(fill.time ?? 0);
|
|
437
|
+
return t >= startTime && t <= endTime;
|
|
438
|
+
})
|
|
439
|
+
.slice(0, limit)
|
|
440
|
+
.map((fill) => ({
|
|
441
|
+
coin: String(fill.coin ?? ""),
|
|
442
|
+
px: String(fill.px ?? fill.price ?? "0"),
|
|
443
|
+
sz: String(fill.sz ?? fill.size ?? "0"),
|
|
444
|
+
side: fill.side ?? "",
|
|
445
|
+
time: Number(fill.time ?? Date.now()),
|
|
446
|
+
fee: fill.fee ? String(fill.fee) : undefined,
|
|
447
|
+
feeToken: fill.feeToken ? String(fill.feeToken) : undefined,
|
|
448
|
+
hash: fill.hash ? String(fill.hash) : undefined,
|
|
449
|
+
oid: typeof fill.oid === "number" ? fill.oid : undefined,
|
|
450
|
+
tid: typeof fill.tid === "number" ? fill.tid : undefined,
|
|
451
|
+
cloid: fill.cloid ? String(fill.cloid) : undefined,
|
|
452
|
+
startPosition: fill.startPosition
|
|
453
|
+
? String(fill.startPosition)
|
|
454
|
+
: undefined,
|
|
455
|
+
dir: fill.dir ? String(fill.dir) : undefined,
|
|
456
|
+
closedPnl: fill.closedPnl ? String(fill.closedPnl) : undefined,
|
|
457
|
+
crossed: typeof fill.crossed === "boolean" ? fill.crossed : undefined,
|
|
458
|
+
liquidation: fill.liquidation,
|
|
459
|
+
twapId: typeof fill.twapId === "number" ? fill.twapId : undefined,
|
|
460
|
+
}))
|
|
461
|
+
: [];
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Fetches user funding ledger updates
|
|
469
|
+
*/
|
|
470
|
+
export async function fetchUserFunding(params) {
|
|
471
|
+
const { user, startTime, endTime = null, limit = 200, network = "testnet", } = params;
|
|
472
|
+
try {
|
|
473
|
+
const client = getHyperliquidInfoClient(network);
|
|
474
|
+
let updates = [];
|
|
475
|
+
if (typeof client.userFunding === "function") {
|
|
476
|
+
updates = await client.userFunding({ user, startTime, endTime });
|
|
477
|
+
}
|
|
478
|
+
return Array.isArray(updates)
|
|
479
|
+
? updates.slice(0, limit).map((update) => ({
|
|
480
|
+
time: Number(update.time ?? Date.now()),
|
|
481
|
+
hash: update.hash ? String(update.hash) : undefined,
|
|
482
|
+
delta: update.delta,
|
|
483
|
+
}))
|
|
484
|
+
: [];
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
return [];
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Fetches user non-funding ledger updates (deposits, withdrawals, transfers)
|
|
492
|
+
*/
|
|
493
|
+
export async function fetchUserNonFundingLedgerUpdates(params) {
|
|
494
|
+
const { user, startTime, endTime = null, limit = 200, network = "testnet", } = params;
|
|
495
|
+
try {
|
|
496
|
+
const client = getHyperliquidInfoClient(network);
|
|
497
|
+
let updates = [];
|
|
498
|
+
if (typeof client.userNonFundingLedgerUpdates === "function") {
|
|
499
|
+
updates = await client.userNonFundingLedgerUpdates({
|
|
500
|
+
user,
|
|
501
|
+
startTime,
|
|
502
|
+
endTime,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return Array.isArray(updates)
|
|
506
|
+
? updates.slice(0, limit).map((update) => ({
|
|
507
|
+
time: Number(update.time ?? Date.now()),
|
|
508
|
+
hash: update.hash ? String(update.hash) : undefined,
|
|
509
|
+
delta: update.delta,
|
|
510
|
+
}))
|
|
511
|
+
: [];
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Fetches perps with market data
|
|
519
|
+
*/
|
|
520
|
+
export async function fetchPerpsWithMarketData(network = "testnet") {
|
|
521
|
+
try {
|
|
522
|
+
const [[perpMeta, perpCtxs], allMids] = await Promise.all([
|
|
523
|
+
fetchPerpMetaAndAssetCtxs(network),
|
|
524
|
+
fetchAllMids(network),
|
|
525
|
+
]);
|
|
526
|
+
if (!perpMeta?.universe || !Array.isArray(perpCtxs)) {
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
return perpMeta.universe.map((asset, index) => {
|
|
530
|
+
const assetCtx = perpCtxs[index];
|
|
531
|
+
const midPrice = allMids[asset.name] || "0";
|
|
532
|
+
let change24h = "0.00";
|
|
533
|
+
if (assetCtx?.prevDayPx && assetCtx?.midPx) {
|
|
534
|
+
const prev = parseFloat(assetCtx.prevDayPx);
|
|
535
|
+
const current = parseFloat(assetCtx.midPx);
|
|
536
|
+
if (prev > 0) {
|
|
537
|
+
change24h = (((current - prev) / prev) * 100).toFixed(2);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
const fundingRate = assetCtx?.funding
|
|
541
|
+
? (parseFloat(assetCtx.funding) * 8 * 100).toFixed(4)
|
|
542
|
+
: "0.0000";
|
|
543
|
+
const now = Date.now();
|
|
544
|
+
const eightHours = 8 * 60 * 60 * 1000;
|
|
545
|
+
const nextFunding = Math.ceil(now / eightHours) * eightHours;
|
|
546
|
+
const hoursUntil = Math.max(0, Math.round((nextFunding - now) / (60 * 60 * 1000)));
|
|
547
|
+
const anyAsset = asset;
|
|
548
|
+
const anyCtx = assetCtx;
|
|
549
|
+
const levFromMeta = typeof anyAsset?.maxLeverage === "number"
|
|
550
|
+
? anyAsset.maxLeverage
|
|
551
|
+
: typeof anyAsset?.maxLev === "number"
|
|
552
|
+
? anyAsset.maxLev
|
|
553
|
+
: undefined;
|
|
554
|
+
const levFromCtx = typeof anyCtx?.risk?.maxLev === "number"
|
|
555
|
+
? anyCtx.risk.maxLev
|
|
556
|
+
: typeof anyCtx?.maxLeverage === "number"
|
|
557
|
+
? anyCtx.maxLeverage
|
|
558
|
+
: undefined;
|
|
559
|
+
const maxLeverage = levFromMeta ?? levFromCtx ?? 10;
|
|
560
|
+
const rawOi = parseFloat(assetCtx?.openInterest || "0");
|
|
561
|
+
const markPx = assetCtx?.markPx ?? assetCtx?.midPx ?? midPrice;
|
|
562
|
+
const markValue = parseFloat(String(markPx) || "0");
|
|
563
|
+
const openInterest = Number.isFinite(rawOi) && Number.isFinite(markValue)
|
|
564
|
+
? (rawOi * markValue).toString()
|
|
565
|
+
: assetCtx?.openInterest || "0";
|
|
566
|
+
return {
|
|
567
|
+
name: asset.name,
|
|
568
|
+
price: midPrice,
|
|
569
|
+
change24h,
|
|
570
|
+
volume24h: assetCtx?.dayNtlVlm || "0",
|
|
571
|
+
openInterest,
|
|
572
|
+
fundingRate: `${fundingRate}%`,
|
|
573
|
+
nextFunding: `${hoursUntil}h`,
|
|
574
|
+
maxLeverage,
|
|
575
|
+
};
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Fetches candlestick data
|
|
584
|
+
*/
|
|
585
|
+
export async function fetchCandlesticks(params) {
|
|
586
|
+
const { coin, interval, startTime, endTime, network = "testnet" } = params;
|
|
587
|
+
try {
|
|
588
|
+
const client = getHyperliquidInfoClient(network);
|
|
589
|
+
const now = Date.now();
|
|
590
|
+
const fromTime = startTime ?? now - CANDLE_LOOKBACK * intervalToMs(interval);
|
|
591
|
+
const toTime = endTime ?? now;
|
|
592
|
+
const candles = await client.candleSnapshot({
|
|
593
|
+
coin,
|
|
594
|
+
interval,
|
|
595
|
+
startTime: fromTime,
|
|
596
|
+
endTime: toTime,
|
|
597
|
+
});
|
|
598
|
+
if (!Array.isArray(candles) || candles.length === 0) {
|
|
599
|
+
throw new Error(`No candlestick data for ${coin}`);
|
|
600
|
+
}
|
|
601
|
+
return candles
|
|
602
|
+
.sort((a, b) => a.t - b.t)
|
|
603
|
+
.map((candle) => ({
|
|
604
|
+
time: Math.floor(candle.t / 1000),
|
|
605
|
+
open: parseFloat(candle.o),
|
|
606
|
+
high: parseFloat(candle.h),
|
|
607
|
+
low: parseFloat(candle.l),
|
|
608
|
+
close: parseFloat(candle.c),
|
|
609
|
+
volume: parseFloat(candle.v || "0"),
|
|
610
|
+
}))
|
|
611
|
+
.filter((candle, index, arr) => index === 0 ? true : candle.time > arr[index - 1].time);
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
throw error;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Fetches spot candles for a token
|
|
619
|
+
*/
|
|
620
|
+
export async function fetchSpotCandles(params) {
|
|
621
|
+
const { tokenName, interval, startTime, endTime, network } = params;
|
|
622
|
+
const coin = ensureSpotSymbol(tokenName);
|
|
623
|
+
return fetchCandlesticks({
|
|
624
|
+
coin,
|
|
625
|
+
interval,
|
|
626
|
+
startTime,
|
|
627
|
+
endTime,
|
|
628
|
+
network,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Fetches latest price for a coin
|
|
633
|
+
*/
|
|
634
|
+
export async function fetchLatestPrice(coin, network = "testnet") {
|
|
635
|
+
try {
|
|
636
|
+
const [[perpMeta, perpCtxs], allMids] = await Promise.all([
|
|
637
|
+
fetchPerpMetaAndAssetCtxs(network),
|
|
638
|
+
fetchAllMids(network),
|
|
639
|
+
]);
|
|
640
|
+
const perpIndex = perpMeta?.universe?.findIndex((asset) => asset.name === coin);
|
|
641
|
+
if (perpIndex !== undefined && perpIndex > -1) {
|
|
642
|
+
const assetCtx = perpCtxs[perpIndex];
|
|
643
|
+
const currentPrice = allMids[coin] || assetCtx?.midPx || assetCtx?.markPx || "0";
|
|
644
|
+
let change24h = "0.00";
|
|
645
|
+
if (assetCtx?.prevDayPx && assetCtx?.midPx) {
|
|
646
|
+
const prev = parseFloat(assetCtx.prevDayPx);
|
|
647
|
+
const current = parseFloat(assetCtx.midPx);
|
|
648
|
+
if (prev > 0) {
|
|
649
|
+
change24h = (((current - prev) / prev) * 100).toFixed(2);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
price: currentPrice,
|
|
654
|
+
change24h,
|
|
655
|
+
high24h: currentPrice,
|
|
656
|
+
low24h: currentPrice,
|
|
657
|
+
volume24h: assetCtx?.dayNtlVlm || "0",
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
const [[spotMeta, spotCtxs]] = await Promise.all([
|
|
661
|
+
fetchSpotMetaAndAssetCtxs(network),
|
|
662
|
+
]);
|
|
663
|
+
const spotIndex = spotMeta?.tokens?.findIndex((token) => token.name === coin);
|
|
664
|
+
if (spotIndex === undefined || spotIndex === -1) {
|
|
665
|
+
throw new Error(`Coin ${coin} not found in Hyperliquid markets`);
|
|
666
|
+
}
|
|
667
|
+
const spotCtx = spotCtxs[spotIndex];
|
|
668
|
+
const currentPrice = allMids[coin] || spotCtx?.midPx || spotCtx?.markPx || "0";
|
|
669
|
+
let change24h = "0.00";
|
|
670
|
+
if (spotCtx?.prevDayPx && spotCtx?.midPx) {
|
|
671
|
+
const prev = parseFloat(spotCtx.prevDayPx);
|
|
672
|
+
const current = parseFloat(spotCtx.midPx);
|
|
673
|
+
if (prev > 0) {
|
|
674
|
+
change24h = (((current - prev) / prev) * 100).toFixed(2);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
price: currentPrice,
|
|
679
|
+
change24h,
|
|
680
|
+
high24h: currentPrice,
|
|
681
|
+
low24h: currentPrice,
|
|
682
|
+
volume24h: spotCtx?.dayBaseVlm || "0",
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
throw error;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Fetches user leverage mode for a specific coin
|
|
691
|
+
*/
|
|
692
|
+
export async function fetchUserLeverageMode(userAddress, coin, network = "testnet") {
|
|
693
|
+
try {
|
|
694
|
+
const client = getHyperliquidInfoClient(network);
|
|
695
|
+
const state = await client.clearinghouseState({
|
|
696
|
+
user: userAddress,
|
|
697
|
+
});
|
|
698
|
+
if (!state?.assetPositions) {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
for (const entry of state.assetPositions) {
|
|
702
|
+
const pos = entry?.position;
|
|
703
|
+
if (pos?.coin !== coin) {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
const isCross = pos?.isCross ?? entry?.isCross ?? true;
|
|
707
|
+
let leverage;
|
|
708
|
+
if (pos?.leverage != null) {
|
|
709
|
+
leverage = Number(pos.leverage);
|
|
710
|
+
}
|
|
711
|
+
else if (pos?.szi &&
|
|
712
|
+
pos?.entryPx &&
|
|
713
|
+
state?.marginSummary?.accountValue) {
|
|
714
|
+
const notional = Math.abs(Number(pos.szi)) * Number(pos.entryPx);
|
|
715
|
+
const accountValue = Number(state.marginSummary.accountValue || 0);
|
|
716
|
+
if (accountValue > 0 && notional > 0) {
|
|
717
|
+
leverage = Math.max(1, notional / (accountValue / 10));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return { mode: isCross ? "cross" : "isolated", leverage };
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Fetches top of book (best bid/ask) for a symbol
|
|
730
|
+
*/
|
|
731
|
+
export async function fetchTopOfBook(symbol, network = "testnet") {
|
|
732
|
+
const { coin } = parseSymbolForBook(symbol);
|
|
733
|
+
try {
|
|
734
|
+
const client = getHyperliquidInfoClient(network);
|
|
735
|
+
const book = (await client.l2Book({ coin }));
|
|
736
|
+
const levels = Array.isArray(book?.levels)
|
|
737
|
+
? book.levels
|
|
738
|
+
: [];
|
|
739
|
+
const [bids = [], asks = []] = levels;
|
|
740
|
+
const bestBid = getLevelPrice(bids[0]);
|
|
741
|
+
const bestAsk = getLevelPrice(asks[0]);
|
|
742
|
+
const midPrice = bestBid !== null && bestAsk !== null
|
|
743
|
+
? (bestBid + bestAsk) / 2
|
|
744
|
+
: bestBid ?? bestAsk ?? null;
|
|
745
|
+
return {
|
|
746
|
+
bestBid,
|
|
747
|
+
bestAsk,
|
|
748
|
+
midPrice,
|
|
749
|
+
lastUpdated: Date.now(),
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
throw error;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
// ============================================================================
|
|
757
|
+
// User-Specific API Functions
|
|
758
|
+
// ============================================================================
|
|
759
|
+
/**
|
|
760
|
+
* Fetches user positions from clearinghouse state
|
|
761
|
+
*/
|
|
762
|
+
export async function fetchUserPositions(userAddress, network = "testnet") {
|
|
763
|
+
try {
|
|
764
|
+
const client = getHyperliquidInfoClient(network);
|
|
765
|
+
const state = await client.clearinghouseState({
|
|
766
|
+
user: userAddress,
|
|
767
|
+
});
|
|
768
|
+
if (!state?.assetPositions || !Array.isArray(state.assetPositions)) {
|
|
769
|
+
return [];
|
|
770
|
+
}
|
|
771
|
+
return state.assetPositions
|
|
772
|
+
.filter((entry) => {
|
|
773
|
+
const pos = entry?.position;
|
|
774
|
+
if (!pos)
|
|
775
|
+
return false;
|
|
776
|
+
const size = parseFloat(pos.szi || "0");
|
|
777
|
+
return size !== 0;
|
|
778
|
+
})
|
|
779
|
+
.map((entry) => {
|
|
780
|
+
const pos = entry.position;
|
|
781
|
+
return {
|
|
782
|
+
coin: pos.coin,
|
|
783
|
+
szi: pos.szi,
|
|
784
|
+
entryPx: pos.entryPx || "0",
|
|
785
|
+
positionValue: pos.positionValue || "0",
|
|
786
|
+
unrealizedPnl: pos.unrealizedPnl || "0",
|
|
787
|
+
returnOnEquity: pos.returnOnEquity || "0",
|
|
788
|
+
liquidationPx: pos.liquidationPx || null,
|
|
789
|
+
marginUsed: pos.marginUsed || "0",
|
|
790
|
+
leverage: entry.leverage?.value || pos.leverage?.value || 1,
|
|
791
|
+
isCross: pos.isCross ?? entry.isCross ?? true,
|
|
792
|
+
};
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Fetches user's clearinghouse state (positions + margin summary)
|
|
801
|
+
*/
|
|
802
|
+
export async function fetchUserState(userAddress, network = "testnet") {
|
|
803
|
+
try {
|
|
804
|
+
const client = getHyperliquidInfoClient(network);
|
|
805
|
+
const state = await client.clearinghouseState({
|
|
806
|
+
user: userAddress,
|
|
807
|
+
});
|
|
808
|
+
if (!state) {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
const positions = (state.assetPositions || [])
|
|
812
|
+
.filter((entry) => {
|
|
813
|
+
const pos = entry?.position;
|
|
814
|
+
if (!pos)
|
|
815
|
+
return false;
|
|
816
|
+
const size = parseFloat(pos.szi || "0");
|
|
817
|
+
return size !== 0;
|
|
818
|
+
})
|
|
819
|
+
.map((entry) => {
|
|
820
|
+
const pos = entry.position;
|
|
821
|
+
return {
|
|
822
|
+
coin: pos.coin,
|
|
823
|
+
szi: pos.szi,
|
|
824
|
+
entryPx: pos.entryPx || "0",
|
|
825
|
+
positionValue: pos.positionValue || "0",
|
|
826
|
+
unrealizedPnl: pos.unrealizedPnl || "0",
|
|
827
|
+
returnOnEquity: pos.returnOnEquity || "0",
|
|
828
|
+
liquidationPx: pos.liquidationPx || null,
|
|
829
|
+
marginUsed: pos.marginUsed || "0",
|
|
830
|
+
leverage: entry.leverage?.value || pos.leverage?.value || 1,
|
|
831
|
+
isCross: pos.isCross ?? entry.isCross ?? true,
|
|
832
|
+
};
|
|
833
|
+
});
|
|
834
|
+
const marginSummary = state.marginSummary || {};
|
|
835
|
+
const crossMarginSummary = state.crossMarginSummary;
|
|
836
|
+
return {
|
|
837
|
+
assetPositions: positions,
|
|
838
|
+
marginSummary: {
|
|
839
|
+
accountValue: marginSummary.accountValue || "0",
|
|
840
|
+
totalNtlPos: marginSummary.totalNtlPos || "0",
|
|
841
|
+
totalRawUsd: marginSummary.totalRawUsd || "0",
|
|
842
|
+
totalMarginUsed: marginSummary.totalMarginUsed || "0",
|
|
843
|
+
withdrawable: marginSummary.withdrawable || "0",
|
|
844
|
+
},
|
|
845
|
+
crossMarginSummary: crossMarginSummary
|
|
846
|
+
? {
|
|
847
|
+
accountValue: crossMarginSummary.accountValue || "0",
|
|
848
|
+
totalNtlPos: crossMarginSummary.totalNtlPos || "0",
|
|
849
|
+
totalRawUsd: crossMarginSummary.totalRawUsd || "0",
|
|
850
|
+
totalMarginUsed: crossMarginSummary.totalMarginUsed || "0",
|
|
851
|
+
withdrawable: crossMarginSummary.withdrawable || "0",
|
|
852
|
+
}
|
|
853
|
+
: undefined,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
throw error;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Fetches user's open orders
|
|
862
|
+
*/
|
|
863
|
+
export async function fetchOpenOrders(userAddress, network = "testnet") {
|
|
864
|
+
try {
|
|
865
|
+
const client = getHyperliquidInfoClient(network);
|
|
866
|
+
const orders = await client.openOrders({
|
|
867
|
+
user: userAddress,
|
|
868
|
+
});
|
|
869
|
+
if (!Array.isArray(orders)) {
|
|
870
|
+
return [];
|
|
871
|
+
}
|
|
872
|
+
return orders.map((order) => ({
|
|
873
|
+
coin: order.coin,
|
|
874
|
+
oid: order.oid,
|
|
875
|
+
side: order.side,
|
|
876
|
+
limitPx: order.limitPx,
|
|
877
|
+
sz: order.sz,
|
|
878
|
+
origSz: order.origSz,
|
|
879
|
+
timestamp: order.timestamp,
|
|
880
|
+
reduceOnly: order.reduceOnly ?? false,
|
|
881
|
+
orderType: order.orderType || "limit",
|
|
882
|
+
triggerPx: order.triggerPx || undefined,
|
|
883
|
+
isPositionTpsl: order.isPositionTpsl || false,
|
|
884
|
+
cloid: order.cloid || undefined,
|
|
885
|
+
}));
|
|
886
|
+
}
|
|
887
|
+
catch (error) {
|
|
888
|
+
throw error;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
// ============================================================================
|
|
892
|
+
// Utility Functions
|
|
893
|
+
// ============================================================================
|
|
894
|
+
/**
|
|
895
|
+
* Resolves asset index for a coin
|
|
896
|
+
*/
|
|
897
|
+
export async function resolveAssetIndex(coin, network = "testnet") {
|
|
898
|
+
const normalizedCoin = (coin || "")
|
|
899
|
+
.trim()
|
|
900
|
+
.replace(/-PERP$/i, "")
|
|
901
|
+
.toUpperCase();
|
|
902
|
+
if (!normalizedCoin) {
|
|
903
|
+
throw new Error("Coin is required to resolve asset index");
|
|
904
|
+
}
|
|
905
|
+
const converter = await SymbolConverter.create({
|
|
906
|
+
transport: getHyperliquidTransport(network),
|
|
907
|
+
});
|
|
908
|
+
const assetId = converter.getAssetId(normalizedCoin);
|
|
909
|
+
if (assetId === undefined) {
|
|
910
|
+
throw new Error(`Unable to resolve Hyperliquid asset index for ${coin}`);
|
|
911
|
+
}
|
|
912
|
+
return assetId;
|
|
913
|
+
}
|
|
914
|
+
//# sourceMappingURL=api.js.map
|