@tria-sdk/hyperliquid-core 0.1.0 → 6.44.0-beta
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 +49 -60
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +695 -575
- package/dist/api.js.map +1 -1
- package/dist/client.js +2 -2
- package/dist/exchange.d.ts +9 -2
- package/dist/exchange.d.ts.map +1 -1
- package/dist/exchange.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +466 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/format.d.ts +17 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +51 -0
- package/dist/utils/format.js.map +1 -1
- package/dist/websocket/WebSocketManager.d.ts +11 -3
- package/dist/websocket/WebSocketManager.d.ts.map +1 -1
- package/dist/websocket/WebSocketManager.js +167 -36
- package/dist/websocket/WebSocketManager.js.map +1 -1
- package/dist/websocket/types.d.ts +59 -2
- package/dist/websocket/types.d.ts.map +1 -1
- package/dist/websocket/types.js +8 -1
- package/dist/websocket/types.js.map +1 -1
- package/package.json +5 -9
package/dist/api.js
CHANGED
|
@@ -12,6 +12,64 @@ const CANDLE_LOOKBACK = 200;
|
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// Helper Functions
|
|
14
14
|
// ============================================================================
|
|
15
|
+
/**
|
|
16
|
+
* Maps a raw asset position entry to a typed HyperliquidPosition.
|
|
17
|
+
* Shared between fetchUserPositions and fetchUserState to avoid duplication.
|
|
18
|
+
*/
|
|
19
|
+
const mapRawAssetPositionToPosition = (entry) => {
|
|
20
|
+
const pos = entry.position;
|
|
21
|
+
const posLeverage = pos.leverage;
|
|
22
|
+
const leverageObj = typeof posLeverage === "number"
|
|
23
|
+
? { value: posLeverage, type: undefined }
|
|
24
|
+
: posLeverage || entry.leverage;
|
|
25
|
+
const leverageType = leverageObj?.type;
|
|
26
|
+
const isCross = leverageType === "cross" || (!leverageType && true);
|
|
27
|
+
return {
|
|
28
|
+
coin: pos.coin ?? "",
|
|
29
|
+
szi: pos.szi ?? "0",
|
|
30
|
+
entryPx: pos.entryPx || "0",
|
|
31
|
+
positionValue: pos.positionValue || "0",
|
|
32
|
+
unrealizedPnl: pos.unrealizedPnl || "0",
|
|
33
|
+
returnOnEquity: pos.returnOnEquity || "0",
|
|
34
|
+
liquidationPx: pos.liquidationPx || null,
|
|
35
|
+
marginUsed: pos.marginUsed || "0",
|
|
36
|
+
leverage: leverageObj?.value || 1,
|
|
37
|
+
isCross,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Maps a raw user fill entry to a typed HyperliquidUserFill.
|
|
42
|
+
* Shared between fetchUserFills and fetchUserFillsByTime to avoid duplication.
|
|
43
|
+
*/
|
|
44
|
+
const mapRawUserFill = (fill) => ({
|
|
45
|
+
coin: String(fill.coin ?? ""),
|
|
46
|
+
px: String(fill.px ?? fill.price ?? "0"),
|
|
47
|
+
sz: String(fill.sz ?? fill.size ?? "0"),
|
|
48
|
+
side: String(fill.side ?? ""),
|
|
49
|
+
time: Number(fill.time ?? Date.now()),
|
|
50
|
+
fee: fill.fee ? String(fill.fee) : undefined,
|
|
51
|
+
feeToken: fill.feeToken ? String(fill.feeToken) : undefined,
|
|
52
|
+
hash: fill.hash ? String(fill.hash) : undefined,
|
|
53
|
+
oid: typeof fill.oid === "number" ? fill.oid : undefined,
|
|
54
|
+
tid: typeof fill.tid === "number" ? fill.tid : undefined,
|
|
55
|
+
cloid: fill.cloid ? String(fill.cloid) : undefined,
|
|
56
|
+
startPosition: fill.startPosition ? String(fill.startPosition) : undefined,
|
|
57
|
+
dir: fill.dir ? String(fill.dir) : undefined,
|
|
58
|
+
closedPnl: fill.closedPnl ? String(fill.closedPnl) : undefined,
|
|
59
|
+
crossed: typeof fill.crossed === "boolean" ? fill.crossed : undefined,
|
|
60
|
+
liquidation: fill.liquidation ?? undefined,
|
|
61
|
+
twapId: typeof fill.twapId === "number" ? fill.twapId : undefined,
|
|
62
|
+
});
|
|
63
|
+
/**
|
|
64
|
+
* Filters raw asset position entries to only include non-zero positions.
|
|
65
|
+
*/
|
|
66
|
+
const filterNonZeroPositions = (entry) => {
|
|
67
|
+
const pos = entry?.position;
|
|
68
|
+
if (!pos)
|
|
69
|
+
return false;
|
|
70
|
+
const size = parseFloat(pos.szi || "0");
|
|
71
|
+
return size !== 0;
|
|
72
|
+
};
|
|
15
73
|
const ensureSpotSymbol = (symbol) => {
|
|
16
74
|
const trimmed = symbol?.trim();
|
|
17
75
|
if (!trimmed) {
|
|
@@ -122,7 +180,7 @@ const pickValidPrice = (...values) => {
|
|
|
122
180
|
return value;
|
|
123
181
|
}
|
|
124
182
|
}
|
|
125
|
-
return
|
|
183
|
+
return null;
|
|
126
184
|
};
|
|
127
185
|
// ============================================================================
|
|
128
186
|
// Public API Functions
|
|
@@ -131,495 +189,494 @@ const pickValidPrice = (...values) => {
|
|
|
131
189
|
* Retrieves raw spot metadata from Hyperliquid public API
|
|
132
190
|
*/
|
|
133
191
|
export async function fetchSpotMeta(network = "testnet") {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return await client.spotMeta();
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
throw error;
|
|
140
|
-
}
|
|
192
|
+
const client = getHyperliquidInfoClient(network);
|
|
193
|
+
return client.spotMeta();
|
|
141
194
|
}
|
|
142
195
|
/**
|
|
143
196
|
* Fetches token details for a specific token
|
|
144
197
|
*/
|
|
145
198
|
export async function fetchTokenDetails(tokenId, network = "testnet") {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
199
|
+
const client = getHyperliquidInfoClient(network);
|
|
200
|
+
const details = await client.tokenDetails({ tokenId });
|
|
201
|
+
return {
|
|
202
|
+
name: details.name,
|
|
203
|
+
maxSupply: details.maxSupply,
|
|
204
|
+
totalSupply: details.totalSupply,
|
|
205
|
+
circulatingSupply: details.circulatingSupply,
|
|
206
|
+
szDecimals: details.szDecimals,
|
|
207
|
+
weiDecimals: details.weiDecimals,
|
|
208
|
+
genesis: details.genesis,
|
|
209
|
+
deployer: details.deployer || undefined,
|
|
210
|
+
deployGas: details.deployGas || undefined,
|
|
211
|
+
deployTime: details.deployTime || undefined,
|
|
212
|
+
seededUsdc: details.seededUsdc,
|
|
213
|
+
futureEmissions: details.futureEmissions,
|
|
214
|
+
};
|
|
167
215
|
}
|
|
168
216
|
/**
|
|
169
217
|
* Fetches all mid prices
|
|
170
218
|
*/
|
|
171
219
|
export async function fetchAllMids(network = "testnet") {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return await client.allMids();
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
throw error;
|
|
178
|
-
}
|
|
220
|
+
const client = getHyperliquidInfoClient(network);
|
|
221
|
+
return client.allMids();
|
|
179
222
|
}
|
|
180
223
|
/**
|
|
181
224
|
* Fetches spot metadata and asset contexts
|
|
182
225
|
*/
|
|
183
226
|
export async function fetchSpotMetaAndAssetCtxs(network = "testnet") {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
227
|
+
const client = getHyperliquidInfoClient(network);
|
|
228
|
+
return client.spotMetaAndAssetCtxs();
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Remaps token symbol for display purposes.
|
|
232
|
+
* Tokens with fullName starting with "Unit " are Unit tokens (e.g., UBTC → BTC)
|
|
233
|
+
* These are wrapped versions on HyperCore, but should display as the underlying asset.
|
|
234
|
+
*/
|
|
235
|
+
function getDisplayName(symbol, fullName) {
|
|
236
|
+
// If fullName starts with "Unit ", this is a Unit token
|
|
237
|
+
// Remove the "U" prefix from the symbol for display (UBTC → BTC, UETH → ETH)
|
|
238
|
+
if (fullName?.startsWith("Unit ") && symbol.startsWith("U")) {
|
|
239
|
+
return symbol.slice(1);
|
|
187
240
|
}
|
|
188
|
-
|
|
189
|
-
|
|
241
|
+
return symbol;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Normalizes quote token names for display.
|
|
245
|
+
* Hyperliquid uses versioned tokens (e.g., USDT0) but we want to display them as USDT.
|
|
246
|
+
*/
|
|
247
|
+
function normalizeQuoteToken(symbol) {
|
|
248
|
+
// USDT0 → USDT (Hyperliquid's versioned USDT)
|
|
249
|
+
if (symbol === "USDT0") {
|
|
250
|
+
return "USDT";
|
|
190
251
|
}
|
|
252
|
+
return symbol;
|
|
191
253
|
}
|
|
192
254
|
/**
|
|
193
255
|
* Fetches spot tokens with market data
|
|
194
256
|
*/
|
|
195
257
|
export async function fetchSpotTokensWithMarketData(network = "testnet") {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
});
|
|
258
|
+
const [[spotMeta, spotCtxs], allMids] = await Promise.all([
|
|
259
|
+
fetchSpotMetaAndAssetCtxs(network),
|
|
260
|
+
fetchAllMids(network),
|
|
261
|
+
]);
|
|
262
|
+
if (!spotMeta?.tokens || !Array.isArray(spotCtxs)) {
|
|
263
|
+
return [];
|
|
235
264
|
}
|
|
236
|
-
|
|
237
|
-
|
|
265
|
+
return spotMeta.tokens
|
|
266
|
+
.map((token, index) => {
|
|
267
|
+
const assetCtx = spotCtxs[index];
|
|
268
|
+
const currentPrice = pickValidPrice(allMids[token.name], assetCtx?.midPx, assetCtx?.markPx);
|
|
269
|
+
const lastPrice = pickValidPrice(assetCtx?.markPx, assetCtx?.midPx, assetCtx?.prevDayPx, allMids[token.name]);
|
|
270
|
+
let change24h = null;
|
|
271
|
+
if (assetCtx?.prevDayPx && currentPrice) {
|
|
272
|
+
const prevPx = parseFloat(assetCtx.prevDayPx);
|
|
273
|
+
const nowPx = parseFloat(currentPrice);
|
|
274
|
+
if (prevPx > 0 && Number.isFinite(nowPx) && nowPx > 0) {
|
|
275
|
+
change24h = (((nowPx - prevPx) / prevPx) * 100).toFixed(2);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
name: token.name,
|
|
280
|
+
szDecimals: token.szDecimals,
|
|
281
|
+
fullName: token.fullName || undefined,
|
|
282
|
+
price: currentPrice,
|
|
283
|
+
lastPrice,
|
|
284
|
+
midPrice: assetCtx?.midPx ?? null,
|
|
285
|
+
markPrice: assetCtx?.markPx ?? null,
|
|
286
|
+
change24h,
|
|
287
|
+
volume24h: assetCtx?.dayBaseVlm ?? null,
|
|
288
|
+
circulatingSupply: assetCtx?.circulatingSupply ?? null,
|
|
289
|
+
};
|
|
290
|
+
})
|
|
291
|
+
.filter((token) => {
|
|
292
|
+
const price = parseFloat(token.price || "0");
|
|
293
|
+
const volume = parseFloat(token.volume24h || "0");
|
|
294
|
+
return Number.isFinite(price) && price > 0 && volume > 0;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Fetches spot trading pairs with market data using universe pairs
|
|
299
|
+
* This properly aligns pair data with asset contexts using pair.index
|
|
300
|
+
*
|
|
301
|
+
* Important: spotCtxs is indexed by pair.index (from universe), NOT by array position
|
|
302
|
+
* Each pair in universe has an 'index' field that corresponds to spotCtxs[index]
|
|
303
|
+
*/
|
|
304
|
+
export async function fetchSpotPairsWithMarketData(network = "testnet") {
|
|
305
|
+
const [spotMeta, spotCtxs] = await fetchSpotMetaAndAssetCtxs(network);
|
|
306
|
+
if (!spotMeta?.universe || !spotMeta?.tokens || !Array.isArray(spotCtxs)) {
|
|
307
|
+
return [];
|
|
238
308
|
}
|
|
309
|
+
// Create a token lookup by token index
|
|
310
|
+
const tokensByIndex = new Map();
|
|
311
|
+
spotMeta.tokens.forEach((token) => {
|
|
312
|
+
if (token.index !== undefined) {
|
|
313
|
+
tokensByIndex.set(token.index, token);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
return spotMeta.universe
|
|
317
|
+
.map((pair) => {
|
|
318
|
+
// CRITICAL: Use pair.index to access spotCtxs, NOT array position
|
|
319
|
+
const pairIndex = pair.index;
|
|
320
|
+
const assetCtx = spotCtxs[pairIndex];
|
|
321
|
+
// Extract base and quote token indices from the pair
|
|
322
|
+
// tokens array is [baseTokenIndex, quoteTokenIndex]
|
|
323
|
+
const [baseTokenIndex, quoteTokenIndex] = pair.tokens || [];
|
|
324
|
+
const baseToken = tokensByIndex.get(baseTokenIndex);
|
|
325
|
+
const quoteToken = tokensByIndex.get(quoteTokenIndex);
|
|
326
|
+
if (!baseToken) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
const baseTokenName = baseToken.name;
|
|
330
|
+
const rawQuoteTokenName = (quoteToken?.name || "USDC");
|
|
331
|
+
// Normalize quote token for display (e.g., USDT0 → USDT)
|
|
332
|
+
const quoteTokenName = normalizeQuoteToken(rawQuoteTokenName);
|
|
333
|
+
const baseFullName = baseToken.fullName;
|
|
334
|
+
// Get display name (remaps Unit tokens like UBTC → BTC)
|
|
335
|
+
const displayName = getDisplayName(baseTokenName, baseFullName);
|
|
336
|
+
// Use pair name from API (e.g., "PURR/USDC" or "@1" for non-canonical)
|
|
337
|
+
// This is the identifier needed for API calls like l2Book and trades
|
|
338
|
+
const pairName = pair.name;
|
|
339
|
+
// Construct the API coin identifier:
|
|
340
|
+
// - For PURR (canonical pair with USDC), use "PURR/USDC"
|
|
341
|
+
// - For all other spot tokens, use the @{index} format
|
|
342
|
+
// NOTE: Use raw quote token name for API calls (USDT0 not USDT)
|
|
343
|
+
const apiCoin = pairName.startsWith("@")
|
|
344
|
+
? pairName
|
|
345
|
+
: `${baseTokenName}/${rawQuoteTokenName}`;
|
|
346
|
+
// For display, construct a readable pair name using remapped display name
|
|
347
|
+
const displayPairName = `${displayName}/${quoteTokenName}`;
|
|
348
|
+
// Price data comes directly from assetCtx which is aligned by pair.index
|
|
349
|
+
const midPrice = assetCtx?.midPx ?? null;
|
|
350
|
+
const markPrice = assetCtx?.markPx ?? null;
|
|
351
|
+
const prevDayPrice = assetCtx?.prevDayPx ?? null;
|
|
352
|
+
// Use assetCtx prices as primary source (they are properly aligned)
|
|
353
|
+
const currentPrice = pickValidPrice(midPrice, markPrice);
|
|
354
|
+
const lastPrice = pickValidPrice(markPrice, midPrice, prevDayPrice);
|
|
355
|
+
// Calculate 24h change only if we have valid prices
|
|
356
|
+
let change24h = null;
|
|
357
|
+
if (prevDayPrice && currentPrice) {
|
|
358
|
+
const prevPx = parseFloat(prevDayPrice);
|
|
359
|
+
const nowPx = parseFloat(currentPrice);
|
|
360
|
+
if (prevPx > 0 && Number.isFinite(nowPx) && nowPx > 0) {
|
|
361
|
+
change24h = (((nowPx - prevPx) / prevPx) * 100).toFixed(2);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Volume - return null if no data
|
|
365
|
+
const volume24h = assetCtx?.dayNtlVlm ?? null;
|
|
366
|
+
// Market cap - requires circulating supply which isn't in bulk API
|
|
367
|
+
// Will be null until we have circulating supply data
|
|
368
|
+
const marketCap = null;
|
|
369
|
+
// Token ID (hex address) and index for explorer links
|
|
370
|
+
const tokenId = baseToken.tokenId;
|
|
371
|
+
const tokenIndex = baseToken.index;
|
|
372
|
+
return {
|
|
373
|
+
pairName: displayPairName,
|
|
374
|
+
apiCoin,
|
|
375
|
+
baseToken: baseTokenName,
|
|
376
|
+
displayName,
|
|
377
|
+
// Return raw quote token name for API calls (USDT0, not USDT)
|
|
378
|
+
quoteToken: rawQuoteTokenName,
|
|
379
|
+
szDecimals: baseToken.szDecimals,
|
|
380
|
+
fullName: baseFullName,
|
|
381
|
+
price: currentPrice,
|
|
382
|
+
lastPrice,
|
|
383
|
+
midPrice,
|
|
384
|
+
markPrice,
|
|
385
|
+
change24h,
|
|
386
|
+
volume24h,
|
|
387
|
+
marketCap,
|
|
388
|
+
isCanonical: pair.isCanonical,
|
|
389
|
+
tokenId,
|
|
390
|
+
index: tokenIndex,
|
|
391
|
+
};
|
|
392
|
+
})
|
|
393
|
+
.filter((pair) => {
|
|
394
|
+
return pair !== null;
|
|
395
|
+
});
|
|
239
396
|
}
|
|
240
397
|
/**
|
|
241
398
|
* Fetches spot order book for a token
|
|
242
399
|
*/
|
|
243
400
|
export async function fetchSpotOrderBook(tokenName, network = "testnet") {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return await client.l2Book({ coin });
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
401
|
+
const client = getHyperliquidInfoClient(network);
|
|
402
|
+
const coin = ensureSpotSymbol(tokenName);
|
|
403
|
+
return client.l2Book({ coin });
|
|
252
404
|
}
|
|
253
405
|
/**
|
|
254
406
|
* Fetches order book snapshot with normalized levels
|
|
255
407
|
*/
|
|
256
408
|
export async function fetchOrderBook(symbol, depth = 25, network = "testnet") {
|
|
257
409
|
const { coin, market } = parseSymbolForBook(symbol);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
catch (error) {
|
|
289
|
-
throw error;
|
|
290
|
-
}
|
|
410
|
+
const client = getHyperliquidInfoClient(network);
|
|
411
|
+
const response = (await client.l2Book({ coin }));
|
|
412
|
+
const levels = Array.isArray(response?.levels)
|
|
413
|
+
? response.levels
|
|
414
|
+
: [];
|
|
415
|
+
const [rawBids = [], rawAsks = []] = levels;
|
|
416
|
+
const bids = normalizeOrderBookLevels(rawBids, depth);
|
|
417
|
+
const asks = normalizeOrderBookLevels(rawAsks, depth);
|
|
418
|
+
const bestBid = bids[0]?.price ?? null;
|
|
419
|
+
const bestAsk = asks[0]?.price ?? null;
|
|
420
|
+
const midPrice = bestBid !== null && bestAsk !== null
|
|
421
|
+
? (bestBid + bestAsk) / 2
|
|
422
|
+
: bestBid ?? bestAsk ?? null;
|
|
423
|
+
const spread = bestBid !== null && bestAsk !== null
|
|
424
|
+
? Math.max(bestAsk - bestBid, 0)
|
|
425
|
+
: null;
|
|
426
|
+
return {
|
|
427
|
+
symbol,
|
|
428
|
+
coin,
|
|
429
|
+
market,
|
|
430
|
+
bids,
|
|
431
|
+
asks,
|
|
432
|
+
bestBid,
|
|
433
|
+
bestAsk,
|
|
434
|
+
midPrice,
|
|
435
|
+
spread,
|
|
436
|
+
lastUpdated: typeof response?.time === "number" ? response.time : Date.now(),
|
|
437
|
+
};
|
|
291
438
|
}
|
|
292
439
|
/**
|
|
293
440
|
* Fetches recent trades for a coin
|
|
294
441
|
*/
|
|
295
442
|
export async function fetchRecentTrades(coin, limit = 20, network = "testnet") {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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 [];
|
|
443
|
+
const transport = getHyperliquidTransport(network);
|
|
444
|
+
const trades = await transport.request("info", {
|
|
445
|
+
type: "recentTrades",
|
|
446
|
+
coin,
|
|
447
|
+
limit,
|
|
448
|
+
});
|
|
449
|
+
if (!Array.isArray(trades)) {
|
|
450
|
+
throw new Error(`Unexpected API response for recentTrades: expected array, got ${typeof trades}`);
|
|
328
451
|
}
|
|
452
|
+
return trades.map((trade) => {
|
|
453
|
+
const price = trade.px ?? trade.p ?? "0";
|
|
454
|
+
const size = trade.sz ?? trade.s ?? "0";
|
|
455
|
+
const timestamp = trade.time ?? Date.now();
|
|
456
|
+
return {
|
|
457
|
+
price: String(price),
|
|
458
|
+
size: String(size),
|
|
459
|
+
side: typeof trade.side === "string" ? trade.side : undefined,
|
|
460
|
+
time: typeof timestamp === "number"
|
|
461
|
+
? timestamp
|
|
462
|
+
: Number(timestamp) || Date.now(),
|
|
463
|
+
};
|
|
464
|
+
});
|
|
329
465
|
}
|
|
330
466
|
/**
|
|
331
467
|
* Fetches perp metadata
|
|
332
468
|
*/
|
|
333
469
|
export async function fetchPerpMeta(network = "testnet") {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
return await client.meta();
|
|
337
|
-
}
|
|
338
|
-
catch (error) {
|
|
339
|
-
throw error;
|
|
340
|
-
}
|
|
470
|
+
const client = getHyperliquidInfoClient(network);
|
|
471
|
+
return client.meta();
|
|
341
472
|
}
|
|
342
473
|
/**
|
|
343
474
|
* Fetches perp metadata and asset contexts
|
|
344
475
|
*/
|
|
345
476
|
export async function fetchPerpMetaAndAssetCtxs(network = "testnet") {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return await client.metaAndAssetCtxs();
|
|
349
|
-
}
|
|
350
|
-
catch (error) {
|
|
351
|
-
throw error;
|
|
352
|
-
}
|
|
477
|
+
const client = getHyperliquidInfoClient(network);
|
|
478
|
+
return client.metaAndAssetCtxs();
|
|
353
479
|
}
|
|
354
480
|
/**
|
|
355
481
|
* Fetches funding history for a coin
|
|
356
482
|
*/
|
|
357
483
|
export async function fetchFundingHistory(coin, startTime, network = "testnet") {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return await client.fundingHistory({ coin, startTime });
|
|
361
|
-
}
|
|
362
|
-
catch (error) {
|
|
363
|
-
throw error;
|
|
364
|
-
}
|
|
484
|
+
const client = getHyperliquidInfoClient(network);
|
|
485
|
+
return client.fundingHistory({ coin, startTime });
|
|
365
486
|
}
|
|
366
487
|
/**
|
|
367
488
|
* Fetches user fills (trade history)
|
|
368
489
|
*/
|
|
369
490
|
export async function fetchUserFills(params) {
|
|
370
491
|
const { user, aggregateByTime = false, limit = 100, network = "testnet", } = params;
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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 [];
|
|
492
|
+
const transport = getHyperliquidTransport(network);
|
|
493
|
+
const now = Date.now();
|
|
494
|
+
const fills = await transport.request("info", {
|
|
495
|
+
type: "userFillsByTime",
|
|
496
|
+
user,
|
|
497
|
+
aggregateByTime,
|
|
498
|
+
startTime: now - 30 * 24 * 60 * 60 * 1000,
|
|
499
|
+
endTime: now,
|
|
500
|
+
});
|
|
501
|
+
if (!Array.isArray(fills)) {
|
|
502
|
+
throw new Error(`Unexpected API response for userFillsByTime: expected array, got ${typeof fills}`);
|
|
412
503
|
}
|
|
504
|
+
return fills.slice(0, limit).map(mapRawUserFill);
|
|
413
505
|
}
|
|
414
506
|
/**
|
|
415
507
|
* Fetches user fills within a time window (order history)
|
|
416
508
|
*/
|
|
417
509
|
export async function fetchUserFillsByTime(params) {
|
|
418
510
|
const { user, startTime, endTime = Date.now(), aggregateByTime = true, limit = 200, network = "testnet", } = params;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
}
|
|
511
|
+
const transport = getHyperliquidTransport(network);
|
|
512
|
+
const fills = await transport.request("info", {
|
|
513
|
+
type: "userFillsByTime",
|
|
514
|
+
user,
|
|
515
|
+
startTime,
|
|
516
|
+
endTime,
|
|
517
|
+
aggregateByTime,
|
|
518
|
+
});
|
|
519
|
+
if (!Array.isArray(fills)) {
|
|
520
|
+
throw new Error(`Unexpected API response for userFillsByTime: expected array, got ${typeof fills}`);
|
|
521
|
+
}
|
|
522
|
+
return fills
|
|
523
|
+
.filter((fill) => {
|
|
524
|
+
const t = Number(fill.time ?? 0);
|
|
525
|
+
return t >= startTime && t <= endTime;
|
|
526
|
+
})
|
|
527
|
+
.slice(0, limit)
|
|
528
|
+
.map(mapRawUserFill);
|
|
466
529
|
}
|
|
467
530
|
/**
|
|
468
531
|
* Fetches user funding ledger updates
|
|
469
532
|
*/
|
|
470
533
|
export async function fetchUserFunding(params) {
|
|
471
534
|
const { user, startTime, endTime = null, limit = 200, network = "testnet", } = params;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
535
|
+
const transport = getHyperliquidTransport(network);
|
|
536
|
+
const updates = await transport.request("info", {
|
|
537
|
+
type: "userFunding",
|
|
538
|
+
user,
|
|
539
|
+
startTime,
|
|
540
|
+
endTime,
|
|
541
|
+
});
|
|
542
|
+
if (!Array.isArray(updates)) {
|
|
543
|
+
throw new Error(`Unexpected API response for userFunding: expected array, got ${typeof updates}`);
|
|
544
|
+
}
|
|
545
|
+
return updates.slice(0, limit).map((update) => ({
|
|
546
|
+
time: Number(update.time ?? Date.now()),
|
|
547
|
+
hash: update.hash ? String(update.hash) : undefined,
|
|
548
|
+
delta: {
|
|
549
|
+
type: "funding",
|
|
550
|
+
coin: update.delta?.coin ?? "",
|
|
551
|
+
usdc: update.delta?.usdc ?? "0",
|
|
552
|
+
szi: update.delta?.szi ?? "0",
|
|
553
|
+
fundingRate: update.delta?.fundingRate ?? "0",
|
|
554
|
+
nSamples: update.delta?.nSamples ?? 0,
|
|
555
|
+
},
|
|
556
|
+
}));
|
|
489
557
|
}
|
|
490
558
|
/**
|
|
491
559
|
* Fetches user non-funding ledger updates (deposits, withdrawals, transfers)
|
|
492
560
|
*/
|
|
493
561
|
export async function fetchUserNonFundingLedgerUpdates(params) {
|
|
494
562
|
const { user, startTime, endTime = null, limit = 200, network = "testnet", } = params;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}))
|
|
511
|
-
: [];
|
|
512
|
-
}
|
|
513
|
-
catch (error) {
|
|
514
|
-
return [];
|
|
515
|
-
}
|
|
563
|
+
const transport = getHyperliquidTransport(network);
|
|
564
|
+
const updates = await transport.request("info", {
|
|
565
|
+
type: "userNonFundingLedgerUpdates",
|
|
566
|
+
user,
|
|
567
|
+
startTime,
|
|
568
|
+
endTime,
|
|
569
|
+
});
|
|
570
|
+
if (!Array.isArray(updates)) {
|
|
571
|
+
throw new Error(`Unexpected API response for userNonFundingLedgerUpdates: expected array, got ${typeof updates}`);
|
|
572
|
+
}
|
|
573
|
+
return updates.slice(0, limit).map((update) => ({
|
|
574
|
+
time: Number(update.time ?? Date.now()),
|
|
575
|
+
hash: update.hash ? String(update.hash) : undefined,
|
|
576
|
+
delta: update.delta ?? { type: "" },
|
|
577
|
+
}));
|
|
516
578
|
}
|
|
517
579
|
/**
|
|
518
580
|
* Fetches perps with market data
|
|
519
581
|
*/
|
|
520
582
|
export async function fetchPerpsWithMarketData(network = "testnet") {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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;
|
|
583
|
+
const [[perpMeta, perpCtxs], allMids] = await Promise.all([
|
|
584
|
+
fetchPerpMetaAndAssetCtxs(network),
|
|
585
|
+
fetchAllMids(network),
|
|
586
|
+
]);
|
|
587
|
+
if (!perpMeta?.universe || !Array.isArray(perpCtxs)) {
|
|
588
|
+
return [];
|
|
580
589
|
}
|
|
590
|
+
return perpMeta.universe.map((asset, index) => {
|
|
591
|
+
const assetCtx = perpCtxs[index];
|
|
592
|
+
const midPrice = allMids[asset.name] || "0";
|
|
593
|
+
let change24h = "0.00";
|
|
594
|
+
if (assetCtx?.prevDayPx && assetCtx?.midPx) {
|
|
595
|
+
const prev = parseFloat(assetCtx.prevDayPx);
|
|
596
|
+
const current = parseFloat(assetCtx.midPx);
|
|
597
|
+
if (prev > 0) {
|
|
598
|
+
change24h = (((current - prev) / prev) * 100).toFixed(2);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// The funding field from the API is the 8-hour rate as a decimal.
|
|
602
|
+
// Multiply by 100 to convert to percentage (e.g., 0.000013 -> 0.0013%)
|
|
603
|
+
const fundingRate = assetCtx?.funding
|
|
604
|
+
? (parseFloat(assetCtx.funding) * 100).toFixed(4)
|
|
605
|
+
: "0.0000";
|
|
606
|
+
const now = Date.now();
|
|
607
|
+
const eightHours = 8 * 60 * 60 * 1000;
|
|
608
|
+
const nextFunding = Math.ceil(now / eightHours) * eightHours;
|
|
609
|
+
const hoursUntil = Math.max(0, Math.round((nextFunding - now) / (60 * 60 * 1000)));
|
|
610
|
+
const levFromMeta = typeof asset?.maxLeverage === "number" ? asset.maxLeverage : undefined;
|
|
611
|
+
const levFromCtx = typeof assetCtx?.risk?.maxLev === "number"
|
|
612
|
+
? assetCtx.risk.maxLev
|
|
613
|
+
: typeof assetCtx?.maxLeverage === "number"
|
|
614
|
+
? assetCtx.maxLeverage
|
|
615
|
+
: undefined;
|
|
616
|
+
const maxLeverage = levFromMeta ?? levFromCtx ?? 10;
|
|
617
|
+
const rawOi = parseFloat(assetCtx?.openInterest || "0");
|
|
618
|
+
const markPx = assetCtx?.markPx ?? assetCtx?.midPx ?? midPrice;
|
|
619
|
+
const markValue = parseFloat(String(markPx) || "0");
|
|
620
|
+
const openInterest = Number.isFinite(rawOi) && Number.isFinite(markValue)
|
|
621
|
+
? (rawOi * markValue).toString()
|
|
622
|
+
: assetCtx?.openInterest || "0";
|
|
623
|
+
return {
|
|
624
|
+
name: asset.name,
|
|
625
|
+
price: midPrice,
|
|
626
|
+
change24h,
|
|
627
|
+
volume24h: assetCtx?.dayNtlVlm || "0",
|
|
628
|
+
openInterest,
|
|
629
|
+
fundingRate: `${fundingRate}%`,
|
|
630
|
+
nextFunding: `${hoursUntil}h`,
|
|
631
|
+
maxLeverage,
|
|
632
|
+
szDecimals: asset.szDecimals ?? 0,
|
|
633
|
+
};
|
|
634
|
+
});
|
|
581
635
|
}
|
|
582
636
|
/**
|
|
583
637
|
* Fetches candlestick data
|
|
584
638
|
*/
|
|
585
639
|
export async function fetchCandlesticks(params) {
|
|
586
640
|
const { coin, interval, startTime, endTime, network = "testnet" } = params;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
.filter((candle, index, arr) => index === 0 ? true : candle.time > arr[index - 1].time);
|
|
612
|
-
}
|
|
613
|
-
catch (error) {
|
|
614
|
-
throw error;
|
|
615
|
-
}
|
|
641
|
+
const client = getHyperliquidInfoClient(network);
|
|
642
|
+
const now = Date.now();
|
|
643
|
+
const fromTime = startTime ?? now - CANDLE_LOOKBACK * intervalToMs(interval);
|
|
644
|
+
const toTime = endTime ?? now;
|
|
645
|
+
const candles = await client.candleSnapshot({
|
|
646
|
+
coin,
|
|
647
|
+
interval,
|
|
648
|
+
startTime: fromTime,
|
|
649
|
+
endTime: toTime,
|
|
650
|
+
});
|
|
651
|
+
if (!Array.isArray(candles) || candles.length === 0) {
|
|
652
|
+
throw new Error(`No candlestick data for ${coin}`);
|
|
653
|
+
}
|
|
654
|
+
return candles
|
|
655
|
+
.sort((a, b) => a.t - b.t)
|
|
656
|
+
.map((candle) => ({
|
|
657
|
+
time: Math.floor(candle.t / 1000),
|
|
658
|
+
open: parseFloat(candle.o),
|
|
659
|
+
high: parseFloat(candle.h),
|
|
660
|
+
low: parseFloat(candle.l),
|
|
661
|
+
close: parseFloat(candle.c),
|
|
662
|
+
volume: parseFloat(candle.v || "0"),
|
|
663
|
+
}))
|
|
664
|
+
.filter((candle, index, arr) => index === 0 ? true : candle.time > arr[index - 1].time);
|
|
616
665
|
}
|
|
617
666
|
/**
|
|
618
667
|
* Fetches spot candles for a token
|
|
668
|
+
* tokenName can be:
|
|
669
|
+
* - "@{index}" format (e.g., "@107") - passed directly to API
|
|
670
|
+
* - "PURR/USDC" format - passed directly to API
|
|
671
|
+
* - "PURR" format - converted to "PURR/USDC"
|
|
619
672
|
*/
|
|
620
673
|
export async function fetchSpotCandles(params) {
|
|
621
674
|
const { tokenName, interval, startTime, endTime, network } = params;
|
|
622
|
-
|
|
675
|
+
// If tokenName starts with "@", it's already in API format - use directly
|
|
676
|
+
// Otherwise, ensure it has the /QUOTE suffix
|
|
677
|
+
const coin = tokenName.startsWith("@")
|
|
678
|
+
? tokenName
|
|
679
|
+
: ensureSpotSymbol(tokenName);
|
|
623
680
|
return fetchCandlesticks({
|
|
624
681
|
coin,
|
|
625
682
|
interval,
|
|
@@ -629,47 +686,24 @@ export async function fetchSpotCandles(params) {
|
|
|
629
686
|
});
|
|
630
687
|
}
|
|
631
688
|
/**
|
|
632
|
-
* Fetches latest price for a coin
|
|
689
|
+
* Fetches latest price for a coin.
|
|
690
|
+
* @remarks
|
|
691
|
+
* `high24h` and `low24h` are returned as null because the Hyperliquid API
|
|
692
|
+
* does not provide actual 24-hour high/low data in this endpoint.
|
|
633
693
|
*/
|
|
634
694
|
export async function fetchLatestPrice(coin, network = "testnet") {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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";
|
|
695
|
+
const [[perpMeta, perpCtxs], allMids] = await Promise.all([
|
|
696
|
+
fetchPerpMetaAndAssetCtxs(network),
|
|
697
|
+
fetchAllMids(network),
|
|
698
|
+
]);
|
|
699
|
+
const perpIndex = perpMeta?.universe?.findIndex((asset) => asset.name === coin);
|
|
700
|
+
if (perpIndex !== undefined && perpIndex > -1) {
|
|
701
|
+
const assetCtx = perpCtxs[perpIndex];
|
|
702
|
+
const currentPrice = allMids[coin] || assetCtx?.midPx || assetCtx?.markPx || "0";
|
|
669
703
|
let change24h = "0.00";
|
|
670
|
-
if (
|
|
671
|
-
const prev = parseFloat(
|
|
672
|
-
const current = parseFloat(
|
|
704
|
+
if (assetCtx?.prevDayPx && assetCtx?.midPx) {
|
|
705
|
+
const prev = parseFloat(assetCtx.prevDayPx);
|
|
706
|
+
const current = parseFloat(assetCtx.midPx);
|
|
673
707
|
if (prev > 0) {
|
|
674
708
|
change24h = (((current - prev) / prev) * 100).toFixed(2);
|
|
675
709
|
}
|
|
@@ -677,17 +711,40 @@ export async function fetchLatestPrice(coin, network = "testnet") {
|
|
|
677
711
|
return {
|
|
678
712
|
price: currentPrice,
|
|
679
713
|
change24h,
|
|
680
|
-
high24h:
|
|
681
|
-
low24h:
|
|
682
|
-
volume24h:
|
|
714
|
+
high24h: null,
|
|
715
|
+
low24h: null,
|
|
716
|
+
volume24h: assetCtx?.dayNtlVlm || "0",
|
|
683
717
|
};
|
|
684
718
|
}
|
|
685
|
-
|
|
686
|
-
|
|
719
|
+
const [[spotMeta, spotCtxs]] = await Promise.all([
|
|
720
|
+
fetchSpotMetaAndAssetCtxs(network),
|
|
721
|
+
]);
|
|
722
|
+
const spotIndex = spotMeta?.tokens?.findIndex((token) => token.name === coin);
|
|
723
|
+
if (spotIndex === undefined || spotIndex === -1) {
|
|
724
|
+
throw new Error(`Coin ${coin} not found in Hyperliquid markets`);
|
|
725
|
+
}
|
|
726
|
+
const spotCtx = spotCtxs[spotIndex];
|
|
727
|
+
const currentPrice = allMids[coin] || spotCtx?.midPx || spotCtx?.markPx || "0";
|
|
728
|
+
let change24h = "0.00";
|
|
729
|
+
if (spotCtx?.prevDayPx && spotCtx?.midPx) {
|
|
730
|
+
const prev = parseFloat(spotCtx.prevDayPx);
|
|
731
|
+
const current = parseFloat(spotCtx.midPx);
|
|
732
|
+
if (prev > 0) {
|
|
733
|
+
change24h = (((current - prev) / prev) * 100).toFixed(2);
|
|
734
|
+
}
|
|
687
735
|
}
|
|
736
|
+
return {
|
|
737
|
+
price: currentPrice,
|
|
738
|
+
change24h,
|
|
739
|
+
high24h: null,
|
|
740
|
+
low24h: null,
|
|
741
|
+
volume24h: spotCtx?.dayBaseVlm || "0",
|
|
742
|
+
};
|
|
688
743
|
}
|
|
689
744
|
/**
|
|
690
|
-
* Fetches user leverage mode for a specific coin
|
|
745
|
+
* Fetches user leverage mode for a specific coin.
|
|
746
|
+
* @throws Error if the API call fails - callers should implement retry logic.
|
|
747
|
+
* @returns The leverage mode, or null if the user has no position for this coin.
|
|
691
748
|
*/
|
|
692
749
|
export async function fetchUserLeverageMode(userAddress, coin, network = "testnet") {
|
|
693
750
|
try {
|
|
@@ -705,8 +762,12 @@ export async function fetchUserLeverageMode(userAddress, coin, network = "testne
|
|
|
705
762
|
}
|
|
706
763
|
const isCross = pos?.isCross ?? entry?.isCross ?? true;
|
|
707
764
|
let leverage;
|
|
708
|
-
|
|
709
|
-
|
|
765
|
+
const posLeverage = pos?.leverage;
|
|
766
|
+
if (posLeverage != null) {
|
|
767
|
+
leverage =
|
|
768
|
+
typeof posLeverage === "number"
|
|
769
|
+
? posLeverage
|
|
770
|
+
: Number(posLeverage.value ?? 1);
|
|
710
771
|
}
|
|
711
772
|
else if (pos?.szi &&
|
|
712
773
|
pos?.entryPx &&
|
|
@@ -722,7 +783,11 @@ export async function fetchUserLeverageMode(userAddress, coin, network = "testne
|
|
|
722
783
|
return null;
|
|
723
784
|
}
|
|
724
785
|
catch (error) {
|
|
725
|
-
|
|
786
|
+
const maskedAddress = userAddress.length > 10
|
|
787
|
+
? `${userAddress.slice(0, 6)}...${userAddress.slice(-4)}`
|
|
788
|
+
: "***";
|
|
789
|
+
console.debug(`[fetchUserLeverageMode] Error fetching leverage mode for coin "${coin}" and user "${maskedAddress}":`, error);
|
|
790
|
+
throw error;
|
|
726
791
|
}
|
|
727
792
|
}
|
|
728
793
|
/**
|
|
@@ -730,28 +795,23 @@ export async function fetchUserLeverageMode(userAddress, coin, network = "testne
|
|
|
730
795
|
*/
|
|
731
796
|
export async function fetchTopOfBook(symbol, network = "testnet") {
|
|
732
797
|
const { coin } = parseSymbolForBook(symbol);
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
catch (error) {
|
|
753
|
-
throw error;
|
|
754
|
-
}
|
|
798
|
+
const client = getHyperliquidInfoClient(network);
|
|
799
|
+
const book = (await client.l2Book({ coin }));
|
|
800
|
+
const levels = Array.isArray(book?.levels)
|
|
801
|
+
? book.levels
|
|
802
|
+
: [];
|
|
803
|
+
const [bids = [], asks = []] = levels;
|
|
804
|
+
const bestBid = getLevelPrice(bids[0]);
|
|
805
|
+
const bestAsk = getLevelPrice(asks[0]);
|
|
806
|
+
const midPrice = bestBid !== null && bestAsk !== null
|
|
807
|
+
? (bestBid + bestAsk) / 2
|
|
808
|
+
: bestBid ?? bestAsk ?? null;
|
|
809
|
+
return {
|
|
810
|
+
bestBid,
|
|
811
|
+
bestAsk,
|
|
812
|
+
midPrice,
|
|
813
|
+
lastUpdated: Date.now(),
|
|
814
|
+
};
|
|
755
815
|
}
|
|
756
816
|
// ============================================================================
|
|
757
817
|
// User-Specific API Functions
|
|
@@ -760,133 +820,83 @@ export async function fetchTopOfBook(symbol, network = "testnet") {
|
|
|
760
820
|
* Fetches user positions from clearinghouse state
|
|
761
821
|
*/
|
|
762
822
|
export async function fetchUserPositions(userAddress, network = "testnet") {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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;
|
|
823
|
+
const client = getHyperliquidInfoClient(network);
|
|
824
|
+
const state = await client.clearinghouseState({
|
|
825
|
+
user: userAddress,
|
|
826
|
+
});
|
|
827
|
+
if (!state?.assetPositions || !Array.isArray(state.assetPositions)) {
|
|
828
|
+
return [];
|
|
797
829
|
}
|
|
830
|
+
return state.assetPositions
|
|
831
|
+
.filter(filterNonZeroPositions)
|
|
832
|
+
.map(mapRawAssetPositionToPosition);
|
|
798
833
|
}
|
|
799
834
|
/**
|
|
800
835
|
* Fetches user's clearinghouse state (positions + margin summary)
|
|
801
836
|
*/
|
|
802
837
|
export async function fetchUserState(userAddress, network = "testnet") {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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;
|
|
838
|
+
const client = getHyperliquidInfoClient(network);
|
|
839
|
+
const state = await client.clearinghouseState({
|
|
840
|
+
user: userAddress,
|
|
841
|
+
});
|
|
842
|
+
if (!state) {
|
|
843
|
+
return null;
|
|
858
844
|
}
|
|
845
|
+
const positions = (state.assetPositions || [])
|
|
846
|
+
.filter(filterNonZeroPositions)
|
|
847
|
+
.map(mapRawAssetPositionToPosition);
|
|
848
|
+
const marginSummary = state.marginSummary || {};
|
|
849
|
+
const crossMarginSummary = state.crossMarginSummary;
|
|
850
|
+
const crossMaintenanceMarginUsed = state.crossMaintenanceMarginUsed;
|
|
851
|
+
return {
|
|
852
|
+
assetPositions: positions,
|
|
853
|
+
marginSummary: {
|
|
854
|
+
accountValue: marginSummary.accountValue || "0",
|
|
855
|
+
totalNtlPos: marginSummary.totalNtlPos || "0",
|
|
856
|
+
totalRawUsd: marginSummary.totalRawUsd || "0",
|
|
857
|
+
totalMarginUsed: marginSummary.totalMarginUsed || "0",
|
|
858
|
+
withdrawable: marginSummary.withdrawable || "0",
|
|
859
|
+
},
|
|
860
|
+
crossMarginSummary: crossMarginSummary
|
|
861
|
+
? {
|
|
862
|
+
accountValue: crossMarginSummary.accountValue || "0",
|
|
863
|
+
totalNtlPos: crossMarginSummary.totalNtlPos || "0",
|
|
864
|
+
totalRawUsd: crossMarginSummary.totalRawUsd || "0",
|
|
865
|
+
totalMarginUsed: crossMarginSummary.totalMarginUsed || "0",
|
|
866
|
+
withdrawable: crossMarginSummary.withdrawable || "0",
|
|
867
|
+
}
|
|
868
|
+
: undefined,
|
|
869
|
+
crossMaintenanceMarginUsed: crossMaintenanceMarginUsed || "0",
|
|
870
|
+
};
|
|
859
871
|
}
|
|
860
872
|
/**
|
|
861
|
-
* Fetches user's open orders
|
|
873
|
+
* Fetches user's open orders with frontend info (includes trigger prices for TP/SL)
|
|
862
874
|
*/
|
|
863
875
|
export async function fetchOpenOrders(userAddress, network = "testnet") {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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;
|
|
876
|
+
const client = getHyperliquidInfoClient(network);
|
|
877
|
+
// Use frontendOpenOrders to get complete order info including triggerPx for TP/SL orders
|
|
878
|
+
const orders = await client.frontendOpenOrders({
|
|
879
|
+
user: userAddress,
|
|
880
|
+
});
|
|
881
|
+
if (!Array.isArray(orders)) {
|
|
882
|
+
return [];
|
|
889
883
|
}
|
|
884
|
+
return orders.map((order) => ({
|
|
885
|
+
coin: String(order.coin ?? ""),
|
|
886
|
+
oid: order.oid ?? 0,
|
|
887
|
+
side: order.side === "B" || order.side === "A" ? order.side : "B",
|
|
888
|
+
limitPx: String(order.limitPx ?? "0"),
|
|
889
|
+
sz: String(order.sz ?? "0"),
|
|
890
|
+
origSz: String(order.origSz ?? order.sz ?? "0"),
|
|
891
|
+
timestamp: order.timestamp ?? Date.now(),
|
|
892
|
+
reduceOnly: order.reduceOnly ?? false,
|
|
893
|
+
orderType: order.orderType || "Limit",
|
|
894
|
+
triggerPx: order.triggerPx || undefined,
|
|
895
|
+
isTrigger: order.isTrigger ?? false,
|
|
896
|
+
triggerCondition: order.triggerCondition || "",
|
|
897
|
+
isPositionTpsl: order.isPositionTpsl || false,
|
|
898
|
+
cloid: order.cloid || undefined,
|
|
899
|
+
}));
|
|
890
900
|
}
|
|
891
901
|
// ============================================================================
|
|
892
902
|
// Utility Functions
|
|
@@ -911,4 +921,114 @@ export async function resolveAssetIndex(coin, network = "testnet") {
|
|
|
911
921
|
}
|
|
912
922
|
return assetId;
|
|
913
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Fetches user fee rates from Hyperliquid
|
|
926
|
+
* Returns taker/maker rates for perps and spot
|
|
927
|
+
*/
|
|
928
|
+
export async function fetchUserFees(params) {
|
|
929
|
+
const { userAddress, network = "testnet" } = params;
|
|
930
|
+
if (!userAddress) {
|
|
931
|
+
throw new Error("User address is required to fetch fees");
|
|
932
|
+
}
|
|
933
|
+
const client = getHyperliquidInfoClient(network);
|
|
934
|
+
const response = await client.userFees({
|
|
935
|
+
user: userAddress,
|
|
936
|
+
});
|
|
937
|
+
return {
|
|
938
|
+
userCrossRate: response.userCrossRate || "0",
|
|
939
|
+
userAddRate: response.userAddRate || "0",
|
|
940
|
+
userSpotCrossRate: response.userSpotCrossRate || "0",
|
|
941
|
+
userSpotAddRate: response.userSpotAddRate || "0",
|
|
942
|
+
activeReferralDiscount: response.activeReferralDiscount || "0",
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Fetches user historical orders from Hyperliquid
|
|
947
|
+
* Returns orders with their current status (open, filled, canceled, etc.)
|
|
948
|
+
*/
|
|
949
|
+
export async function fetchHistoricalOrders(params) {
|
|
950
|
+
const { user, limit = 200, network = "testnet" } = params;
|
|
951
|
+
if (!user) {
|
|
952
|
+
throw new Error("User address is required to fetch historical orders");
|
|
953
|
+
}
|
|
954
|
+
const transport = getHyperliquidTransport(network);
|
|
955
|
+
const response = await transport.request("info", {
|
|
956
|
+
type: "historicalOrders",
|
|
957
|
+
user,
|
|
958
|
+
});
|
|
959
|
+
if (!Array.isArray(response)) {
|
|
960
|
+
return [];
|
|
961
|
+
}
|
|
962
|
+
return response.slice(0, limit).map((item) => {
|
|
963
|
+
const order = item.order ?? {};
|
|
964
|
+
return {
|
|
965
|
+
order: {
|
|
966
|
+
coin: String(order.coin ?? ""),
|
|
967
|
+
side: order.side === "B" || order.side === "A" ? order.side : "B",
|
|
968
|
+
limitPx: String(order.limitPx ?? "0"),
|
|
969
|
+
sz: String(order.sz ?? "0"),
|
|
970
|
+
oid: typeof order.oid === "number" ? order.oid : 0,
|
|
971
|
+
timestamp: typeof order.timestamp === "number" ? order.timestamp : Date.now(),
|
|
972
|
+
origSz: String(order.origSz ?? order.sz ?? "0"),
|
|
973
|
+
triggerCondition: order.triggerCondition
|
|
974
|
+
? String(order.triggerCondition)
|
|
975
|
+
: undefined,
|
|
976
|
+
isTrigger: typeof order.isTrigger === "boolean" ? order.isTrigger : false,
|
|
977
|
+
triggerPx: order.triggerPx ? String(order.triggerPx) : undefined,
|
|
978
|
+
children: Array.isArray(order.children) ? order.children : [],
|
|
979
|
+
isPositionTpsl: typeof order.isPositionTpsl === "boolean"
|
|
980
|
+
? order.isPositionTpsl
|
|
981
|
+
: false,
|
|
982
|
+
reduceOnly: typeof order.reduceOnly === "boolean" ? order.reduceOnly : false,
|
|
983
|
+
orderType: String(order.orderType ?? "Limit"),
|
|
984
|
+
tif: String(order.tif ?? "Gtc"),
|
|
985
|
+
cloid: order.cloid ? String(order.cloid) : undefined,
|
|
986
|
+
},
|
|
987
|
+
status: (item.status ?? "filled"),
|
|
988
|
+
statusTimestamp: typeof item.statusTimestamp === "number"
|
|
989
|
+
? item.statusTimestamp
|
|
990
|
+
: Date.now(),
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Fetches user frontend open orders from Hyperliquid
|
|
996
|
+
* Returns open orders with additional display information (trigger conditions, TP/SL, etc.)
|
|
997
|
+
*/
|
|
998
|
+
export async function fetchFrontendOpenOrders(params) {
|
|
999
|
+
const { user, network = "testnet" } = params;
|
|
1000
|
+
if (!user) {
|
|
1001
|
+
throw new Error("User address is required to fetch open orders");
|
|
1002
|
+
}
|
|
1003
|
+
const transport = getHyperliquidTransport(network);
|
|
1004
|
+
const response = await transport.request("info", {
|
|
1005
|
+
type: "frontendOpenOrders",
|
|
1006
|
+
user,
|
|
1007
|
+
});
|
|
1008
|
+
if (!Array.isArray(response)) {
|
|
1009
|
+
return [];
|
|
1010
|
+
}
|
|
1011
|
+
return response.map((order) => ({
|
|
1012
|
+
coin: String(order.coin ?? ""),
|
|
1013
|
+
side: order.side === "B" || order.side === "A" ? order.side : "B",
|
|
1014
|
+
limitPx: String(order.limitPx ?? "0"),
|
|
1015
|
+
sz: String(order.sz ?? "0"),
|
|
1016
|
+
oid: typeof order.oid === "number" ? order.oid : 0,
|
|
1017
|
+
timestamp: typeof order.timestamp === "number" ? order.timestamp : Date.now(),
|
|
1018
|
+
origSz: String(order.origSz ?? order.sz ?? "0"),
|
|
1019
|
+
triggerCondition: order.triggerCondition
|
|
1020
|
+
? String(order.triggerCondition)
|
|
1021
|
+
: undefined,
|
|
1022
|
+
isTrigger: typeof order.isTrigger === "boolean" ? order.isTrigger : false,
|
|
1023
|
+
triggerPx: order.triggerPx ? String(order.triggerPx) : undefined,
|
|
1024
|
+
children: (Array.isArray(order.children)
|
|
1025
|
+
? order.children
|
|
1026
|
+
: []),
|
|
1027
|
+
isPositionTpsl: typeof order.isPositionTpsl === "boolean" ? order.isPositionTpsl : false,
|
|
1028
|
+
reduceOnly: typeof order.reduceOnly === "boolean" ? order.reduceOnly : false,
|
|
1029
|
+
orderType: String(order.orderType ?? "Limit"),
|
|
1030
|
+
tif: String(order.tif ?? "Gtc"),
|
|
1031
|
+
cloid: order.cloid ? String(order.cloid) : undefined,
|
|
1032
|
+
}));
|
|
1033
|
+
}
|
|
914
1034
|
//# sourceMappingURL=api.js.map
|