@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.
Files changed (41) hide show
  1. package/dist/api.d.ts +279 -0
  2. package/dist/api.d.ts.map +1 -0
  3. package/dist/api.js +914 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/client.d.ts +6 -0
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/client.js +35 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/exchange.d.ts +131 -0
  10. package/dist/exchange.d.ts.map +1 -0
  11. package/dist/exchange.js +6 -0
  12. package/dist/exchange.js.map +1 -0
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +7 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/types.d.ts +357 -0
  18. package/dist/types.d.ts.map +1 -0
  19. package/dist/types.js +6 -0
  20. package/dist/types.js.map +1 -0
  21. package/dist/utils/format.d.ts +62 -0
  22. package/dist/utils/format.d.ts.map +1 -0
  23. package/dist/utils/format.js +193 -0
  24. package/dist/utils/format.js.map +1 -0
  25. package/dist/utils/localization.d.ts +93 -0
  26. package/dist/utils/localization.d.ts.map +1 -0
  27. package/dist/utils/localization.js +108 -0
  28. package/dist/utils/localization.js.map +1 -0
  29. package/dist/websocket/WebSocketManager.d.ts +41 -0
  30. package/dist/websocket/WebSocketManager.d.ts.map +1 -0
  31. package/dist/websocket/WebSocketManager.js +295 -0
  32. package/dist/websocket/WebSocketManager.js.map +1 -0
  33. package/dist/websocket/index.d.ts +3 -0
  34. package/dist/websocket/index.d.ts.map +1 -0
  35. package/dist/websocket/index.js +3 -0
  36. package/dist/websocket/index.js.map +1 -0
  37. package/dist/websocket/types.d.ts +98 -0
  38. package/dist/websocket/types.d.ts.map +1 -0
  39. package/dist/websocket/types.js +2 -0
  40. package/dist/websocket/types.js.map +1 -0
  41. 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