@pear-protocol/hyperliquid-sdk 0.0.72 → 0.0.73
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/clients/hyperliquid.d.ts +1 -1
- package/dist/clients/orders.d.ts +41 -0
- package/dist/clients/positions.d.ts +2 -2
- package/dist/clients/watchlist.d.ts +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useAllUserBalances.d.ts +9 -0
- package/dist/hooks/useAuth.d.ts +3 -3
- package/dist/hooks/useMarketData.d.ts +9 -7
- package/dist/hooks/useSpotOrder.d.ts +13 -0
- package/dist/hooks/useTrading.d.ts +0 -3
- package/dist/hooks/useWebData.d.ts +21 -3
- package/dist/index.d.ts +242 -45
- package/dist/index.js +1306 -194
- package/dist/provider.d.ts +1 -1
- package/dist/store/historicalPriceDataStore.d.ts +12 -1
- package/dist/store/hyperliquidDataStore.d.ts +31 -1
- package/dist/store/marketDataStore.d.ts +10 -1
- package/dist/store/tokenSelectionMetadataStore.d.ts +1 -1
- package/dist/store/userDataStore.d.ts +30 -1
- package/dist/store/userSelection.d.ts +1 -1
- package/dist/types.d.ts +74 -20
- package/dist/utils/symbol-translator.d.ts +32 -3
- package/dist/utils/token-metadata-extractor.d.ts +9 -5
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useState, useRef, useCallback, useEffect, useMemo, useContext, createContext } from 'react';
|
|
3
|
-
import { create } from 'zustand';
|
|
2
|
+
import React, { useState, useRef, useCallback, useEffect, useMemo, useContext, createContext } from 'react';
|
|
4
3
|
|
|
5
4
|
// Browser-compatible WebSocket ready state enum (mirrors native values)
|
|
6
5
|
var ReadyState;
|
|
@@ -11,6 +10,47 @@ var ReadyState;
|
|
|
11
10
|
ReadyState[ReadyState["CLOSED"] = 3] = "CLOSED";
|
|
12
11
|
})(ReadyState || (ReadyState = {}));
|
|
13
12
|
|
|
13
|
+
const createStoreImpl = (createState) => {
|
|
14
|
+
let state;
|
|
15
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
16
|
+
const setState = (partial, replace) => {
|
|
17
|
+
const nextState = typeof partial === "function" ? partial(state) : partial;
|
|
18
|
+
if (!Object.is(nextState, state)) {
|
|
19
|
+
const previousState = state;
|
|
20
|
+
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
|
|
21
|
+
listeners.forEach((listener) => listener(state, previousState));
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const getState = () => state;
|
|
25
|
+
const getInitialState = () => initialState;
|
|
26
|
+
const subscribe = (listener) => {
|
|
27
|
+
listeners.add(listener);
|
|
28
|
+
return () => listeners.delete(listener);
|
|
29
|
+
};
|
|
30
|
+
const api = { setState, getState, getInitialState, subscribe };
|
|
31
|
+
const initialState = state = createState(setState, getState, api);
|
|
32
|
+
return api;
|
|
33
|
+
};
|
|
34
|
+
const createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl);
|
|
35
|
+
|
|
36
|
+
const identity = (arg) => arg;
|
|
37
|
+
function useStore(api, selector = identity) {
|
|
38
|
+
const slice = React.useSyncExternalStore(
|
|
39
|
+
api.subscribe,
|
|
40
|
+
React.useCallback(() => selector(api.getState()), [api, selector]),
|
|
41
|
+
React.useCallback(() => selector(api.getInitialState()), [api, selector])
|
|
42
|
+
);
|
|
43
|
+
React.useDebugValue(slice);
|
|
44
|
+
return slice;
|
|
45
|
+
}
|
|
46
|
+
const createImpl = (createState) => {
|
|
47
|
+
const api = createStore(createState);
|
|
48
|
+
const useBoundStore = (selector) => useStore(api, selector);
|
|
49
|
+
Object.assign(useBoundStore, api);
|
|
50
|
+
return useBoundStore;
|
|
51
|
+
};
|
|
52
|
+
const create = ((createState) => createState ? createImpl(createState) : createImpl);
|
|
53
|
+
|
|
14
54
|
const useUserData = create((set) => ({
|
|
15
55
|
accessToken: null,
|
|
16
56
|
refreshToken: null,
|
|
@@ -23,6 +63,7 @@ const useUserData = create((set) => ({
|
|
|
23
63
|
twapDetails: null,
|
|
24
64
|
notifications: null,
|
|
25
65
|
userExtraAgents: null,
|
|
66
|
+
spotState: null,
|
|
26
67
|
setAccessToken: (token) => set({ accessToken: token }),
|
|
27
68
|
setRefreshToken: (token) => set({ refreshToken: token }),
|
|
28
69
|
setIsAuthenticated: (value) => set({ isAuthenticated: value }),
|
|
@@ -43,6 +84,7 @@ const useUserData = create((set) => ({
|
|
|
43
84
|
setAccountSummary: (value) => set({ accountSummary: value }),
|
|
44
85
|
setTwapDetails: (value) => set({ twapDetails: value }),
|
|
45
86
|
setNotifications: (value) => set({ notifications: value }),
|
|
87
|
+
setSpotState: (value) => set({ spotState: value }),
|
|
46
88
|
clean: () => set({
|
|
47
89
|
accessToken: null,
|
|
48
90
|
refreshToken: null,
|
|
@@ -54,6 +96,7 @@ const useUserData = create((set) => ({
|
|
|
54
96
|
accountSummary: null,
|
|
55
97
|
twapDetails: null,
|
|
56
98
|
notifications: null,
|
|
99
|
+
spotState: null,
|
|
57
100
|
}),
|
|
58
101
|
setUserExtraAgents: (value) => set({ userExtraAgents: value }),
|
|
59
102
|
}));
|
|
@@ -71,18 +114,71 @@ const useMarketData = create((set) => ({
|
|
|
71
114
|
* Convert a full/prefixed symbol (e.g., "xyz:XYZ100") to a display symbol (e.g., "XYZ100").
|
|
72
115
|
*/
|
|
73
116
|
function toDisplaySymbol(symbol) {
|
|
74
|
-
const parts = symbol.split(
|
|
117
|
+
const parts = symbol.split(':');
|
|
75
118
|
return parts.length > 1 ? parts.slice(-1)[0] : symbol;
|
|
76
119
|
}
|
|
77
120
|
/**
|
|
78
121
|
* Convert a display symbol back to backend form using a provided map.
|
|
79
122
|
* If mapping is missing, returns the original symbol.
|
|
80
|
-
*
|
|
81
|
-
* @param
|
|
123
|
+
* For multi-market assets, returns the first available market.
|
|
124
|
+
* @param displaySymbol e.g., "TSLA"
|
|
125
|
+
* @param hip3Assets map of display -> all full market names (e.g., "TSLA" -> ["xyz:TSLA", "flx:TSLA"])
|
|
126
|
+
*/
|
|
127
|
+
function toBackendSymbol(displaySymbol, hip3Assets) {
|
|
128
|
+
const markets = hip3Assets.get(displaySymbol);
|
|
129
|
+
// Return first market if available, otherwise return original symbol
|
|
130
|
+
return markets && markets.length > 0 ? markets[0] : displaySymbol;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Convert a display symbol to backend form for a specific market prefix.
|
|
134
|
+
* This is useful when an asset is available on multiple markets (e.g., xyz:TSLA and flx:TSLA).
|
|
135
|
+
* @param displaySymbol e.g., "TSLA"
|
|
136
|
+
* @param marketPrefix e.g., "xyz" or "flx"
|
|
137
|
+
* @param hip3Assets map of display -> all full market names
|
|
138
|
+
* @returns Full market name if found, null if prefix not specified for multi-market asset, otherwise displaySymbol with prefix
|
|
139
|
+
*/
|
|
140
|
+
function toBackendSymbolWithMarket(displaySymbol, marketPrefix, hip3Assets) {
|
|
141
|
+
const availableMarkets = hip3Assets.get(displaySymbol);
|
|
142
|
+
if (!availableMarkets || availableMarkets.length === 0) {
|
|
143
|
+
// Not a HIP-3 asset, return as-is or with prefix if provided
|
|
144
|
+
return marketPrefix ? `${marketPrefix}:${displaySymbol}` : displaySymbol;
|
|
145
|
+
}
|
|
146
|
+
if (marketPrefix) {
|
|
147
|
+
// Find the market with the specified prefix
|
|
148
|
+
const targetMarket = availableMarkets.find((market) => market.toLowerCase().startsWith(`${marketPrefix.toLowerCase()}:`));
|
|
149
|
+
if (targetMarket) {
|
|
150
|
+
return targetMarket;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// No prefix specified or not found, return null to force explicit market selection
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get all available markets for a display symbol.
|
|
158
|
+
* @param displaySymbol e.g., "TSLA"
|
|
159
|
+
* @param hip3Assets map of display -> all full market names
|
|
160
|
+
* @returns Array of full market names, e.g., ["xyz:TSLA", "flx:TSLA"]
|
|
82
161
|
*/
|
|
83
|
-
function
|
|
162
|
+
function getAvailableMarkets(displaySymbol, hip3Assets) {
|
|
84
163
|
var _a;
|
|
85
|
-
return (_a =
|
|
164
|
+
return (_a = hip3Assets.get(displaySymbol)) !== null && _a !== void 0 ? _a : [];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Extract the market prefix from a full market name.
|
|
168
|
+
* @param fullSymbol e.g., "xyz:TSLA"
|
|
169
|
+
* @returns The prefix (e.g., "xyz") or undefined if no prefix
|
|
170
|
+
*/
|
|
171
|
+
function getMarketPrefix(fullSymbol) {
|
|
172
|
+
const parts = fullSymbol.split(':');
|
|
173
|
+
return parts.length > 1 ? parts[0] : undefined;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Check if a symbol is a HIP-3 market (has a prefix).
|
|
177
|
+
* @param symbol e.g., "xyz:TSLA" or "TSLA"
|
|
178
|
+
* @returns true if the symbol has a market prefix
|
|
179
|
+
*/
|
|
180
|
+
function isHip3Market(symbol) {
|
|
181
|
+
return symbol.includes(':');
|
|
86
182
|
}
|
|
87
183
|
|
|
88
184
|
const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
@@ -99,7 +195,8 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
99
195
|
try {
|
|
100
196
|
const message = JSON.parse(event.data);
|
|
101
197
|
// Handle subscription responses (only if they don't have channel data)
|
|
102
|
-
if (('success' in message || 'error' in message) &&
|
|
198
|
+
if (('success' in message || 'error' in message) &&
|
|
199
|
+
!('channel' in message)) {
|
|
103
200
|
if (message.error) {
|
|
104
201
|
setLastError(message.error);
|
|
105
202
|
}
|
|
@@ -118,12 +215,21 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
118
215
|
switch (dataMessage.channel) {
|
|
119
216
|
case 'trade-histories':
|
|
120
217
|
{
|
|
218
|
+
const mapAsset = (a) => {
|
|
219
|
+
var _a, _b;
|
|
220
|
+
const extractedPrefix = getMarketPrefix(a.coin);
|
|
221
|
+
return {
|
|
222
|
+
...a,
|
|
223
|
+
coin: toDisplaySymbol(a.coin),
|
|
224
|
+
marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
|
|
225
|
+
};
|
|
226
|
+
};
|
|
121
227
|
const list = dataMessage.data.map((item) => {
|
|
122
228
|
var _a, _b;
|
|
123
229
|
return ({
|
|
124
230
|
...item,
|
|
125
|
-
closedLongAssets: item.closedLongAssets.map(
|
|
126
|
-
closedShortAssets: item.closedShortAssets.map(
|
|
231
|
+
closedLongAssets: item.closedLongAssets.map(mapAsset),
|
|
232
|
+
closedShortAssets: item.closedShortAssets.map(mapAsset),
|
|
127
233
|
positionLongAssets: (_a = item.positionLongAssets) === null || _a === void 0 ? void 0 : _a.map((a) => toDisplaySymbol(a)),
|
|
128
234
|
positionShortAssets: (_b = item.positionShortAssets) === null || _b === void 0 ? void 0 : _b.map((a) => toDisplaySymbol(a)),
|
|
129
235
|
});
|
|
@@ -133,10 +239,19 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
133
239
|
break;
|
|
134
240
|
case 'open-positions':
|
|
135
241
|
{
|
|
242
|
+
const enrichAsset = (a) => {
|
|
243
|
+
var _a, _b;
|
|
244
|
+
const extractedPrefix = getMarketPrefix(a.coin);
|
|
245
|
+
return {
|
|
246
|
+
...a,
|
|
247
|
+
coin: toDisplaySymbol(a.coin),
|
|
248
|
+
marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
|
|
249
|
+
};
|
|
250
|
+
};
|
|
136
251
|
const list = dataMessage.data.map((pos) => ({
|
|
137
252
|
...pos,
|
|
138
|
-
longAssets: pos.longAssets.map(
|
|
139
|
-
shortAssets: pos.shortAssets.map(
|
|
253
|
+
longAssets: pos.longAssets.map(enrichAsset),
|
|
254
|
+
shortAssets: pos.shortAssets.map(enrichAsset),
|
|
140
255
|
}));
|
|
141
256
|
setRawOpenPositions(list);
|
|
142
257
|
}
|
|
@@ -145,8 +260,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
145
260
|
{
|
|
146
261
|
const list = dataMessage.data.map((order) => ({
|
|
147
262
|
...order,
|
|
148
|
-
longAssets: order.longAssets.map((a) => ({
|
|
149
|
-
|
|
263
|
+
longAssets: order.longAssets.map((a) => ({
|
|
264
|
+
...a,
|
|
265
|
+
asset: toDisplaySymbol(a.asset),
|
|
266
|
+
})),
|
|
267
|
+
shortAssets: order.shortAssets.map((a) => ({
|
|
268
|
+
...a,
|
|
269
|
+
asset: toDisplaySymbol(a.asset),
|
|
270
|
+
})),
|
|
150
271
|
}));
|
|
151
272
|
setOpenOrders(list);
|
|
152
273
|
}
|
|
@@ -156,10 +277,20 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
156
277
|
break;
|
|
157
278
|
case 'twap-details':
|
|
158
279
|
{
|
|
280
|
+
const mapTwapAsset = (a) => {
|
|
281
|
+
var _a, _b, _c;
|
|
282
|
+
const extractedPrefix = getMarketPrefix(a.asset);
|
|
283
|
+
return {
|
|
284
|
+
...a,
|
|
285
|
+
asset: toDisplaySymbol(a.asset),
|
|
286
|
+
marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
|
|
287
|
+
collateralToken: (_c = a.collateralToken) !== null && _c !== void 0 ? _c : undefined,
|
|
288
|
+
};
|
|
289
|
+
};
|
|
159
290
|
const list = dataMessage.data.map((twap) => ({
|
|
160
291
|
...twap,
|
|
161
|
-
longAssets: twap.longAssets.map(
|
|
162
|
-
shortAssets: twap.shortAssets.map(
|
|
292
|
+
longAssets: twap.longAssets.map(mapTwapAsset),
|
|
293
|
+
shortAssets: twap.shortAssets.map(mapTwapAsset),
|
|
163
294
|
}));
|
|
164
295
|
setTwapDetails(list);
|
|
165
296
|
}
|
|
@@ -172,8 +303,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
172
303
|
const md = dataMessage.data;
|
|
173
304
|
const mapGroup = (g) => ({
|
|
174
305
|
...g,
|
|
175
|
-
longAssets: g.longAssets.map((a) => ({
|
|
176
|
-
|
|
306
|
+
longAssets: g.longAssets.map((a) => ({
|
|
307
|
+
...a,
|
|
308
|
+
asset: toDisplaySymbol(a.asset),
|
|
309
|
+
})),
|
|
310
|
+
shortAssets: g.shortAssets.map((a) => ({
|
|
311
|
+
...a,
|
|
312
|
+
asset: toDisplaySymbol(a.asset),
|
|
313
|
+
})),
|
|
177
314
|
});
|
|
178
315
|
const mapped = {
|
|
179
316
|
active: md.active.map(mapGroup),
|
|
@@ -191,7 +328,15 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
191
328
|
catch (error) {
|
|
192
329
|
setLastError(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`);
|
|
193
330
|
}
|
|
194
|
-
}, [
|
|
331
|
+
}, [
|
|
332
|
+
setTradeHistories,
|
|
333
|
+
setRawOpenPositions,
|
|
334
|
+
setOpenOrders,
|
|
335
|
+
setAccountSummary,
|
|
336
|
+
setTwapDetails,
|
|
337
|
+
setNotifications,
|
|
338
|
+
setMarketData,
|
|
339
|
+
]);
|
|
195
340
|
const connect = useCallback(() => {
|
|
196
341
|
if (!enabled || !wsUrl)
|
|
197
342
|
return;
|
|
@@ -260,7 +405,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
260
405
|
'open-orders',
|
|
261
406
|
'twap-details',
|
|
262
407
|
'fills-checkpoint',
|
|
263
|
-
'notifications'
|
|
408
|
+
'notifications',
|
|
264
409
|
];
|
|
265
410
|
const globalChannels = ['market-data'];
|
|
266
411
|
if (address && address !== lastSubscribedAddress) {
|
|
@@ -269,14 +414,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
269
414
|
sendMessage(JSON.stringify({
|
|
270
415
|
action: 'unsubscribe',
|
|
271
416
|
address: lastSubscribedAddress,
|
|
272
|
-
channels: addressSpecificChannels
|
|
417
|
+
channels: addressSpecificChannels,
|
|
273
418
|
}));
|
|
274
419
|
}
|
|
275
420
|
// Subscribe to all channels (global + address-specific)
|
|
276
421
|
sendMessage(JSON.stringify({
|
|
277
422
|
action: 'subscribe',
|
|
278
423
|
address: address,
|
|
279
|
-
channels: [...globalChannels, ...addressSpecificChannels]
|
|
424
|
+
channels: [...globalChannels, ...addressSpecificChannels],
|
|
280
425
|
}));
|
|
281
426
|
setLastSubscribedAddress(address);
|
|
282
427
|
setLastError(null);
|
|
@@ -286,7 +431,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
286
431
|
sendMessage(JSON.stringify({
|
|
287
432
|
action: 'unsubscribe',
|
|
288
433
|
address: lastSubscribedAddress,
|
|
289
|
-
channels: addressSpecificChannels
|
|
434
|
+
channels: addressSpecificChannels,
|
|
290
435
|
}));
|
|
291
436
|
setLastSubscribedAddress(null);
|
|
292
437
|
}
|
|
@@ -294,7 +439,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
294
439
|
// If no address but connected, subscribe to global channels only
|
|
295
440
|
sendMessage(JSON.stringify({
|
|
296
441
|
action: 'subscribe',
|
|
297
|
-
channels: globalChannels
|
|
442
|
+
channels: globalChannels,
|
|
298
443
|
}));
|
|
299
444
|
}
|
|
300
445
|
}, [isConnected, address, lastSubscribedAddress, sendMessage]);
|
|
@@ -317,11 +462,14 @@ const useHyperliquidData = create((set, get) => ({
|
|
|
317
462
|
finalAssetContexts: null,
|
|
318
463
|
finalAtOICaps: null,
|
|
319
464
|
aggregatedClearingHouseState: null,
|
|
465
|
+
rawClearinghouseStates: null,
|
|
320
466
|
perpMetaAssets: null,
|
|
321
|
-
|
|
467
|
+
allPerpMetaAssets: null,
|
|
468
|
+
hip3Assets: new Map(),
|
|
469
|
+
hip3MarketPrefixes: new Map(),
|
|
322
470
|
setAllMids: (value) => set({ allMids: value }),
|
|
323
471
|
setActiveAssetData: (value) => set((state) => ({
|
|
324
|
-
activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value
|
|
472
|
+
activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value,
|
|
325
473
|
})),
|
|
326
474
|
deleteActiveAssetData: (key) => {
|
|
327
475
|
set((state) => {
|
|
@@ -356,13 +504,16 @@ const useHyperliquidData = create((set, get) => ({
|
|
|
356
504
|
activeAssetData: {
|
|
357
505
|
...state.activeAssetData,
|
|
358
506
|
[key]: value,
|
|
359
|
-
}
|
|
507
|
+
},
|
|
360
508
|
})),
|
|
361
509
|
setFinalAssetContexts: (value) => set({ finalAssetContexts: value }),
|
|
362
510
|
setFinalAtOICaps: (value) => set({ finalAtOICaps: value }),
|
|
363
511
|
setAggregatedClearingHouseState: (value) => set({ aggregatedClearingHouseState: value }),
|
|
512
|
+
setRawClearinghouseStates: (value) => set({ rawClearinghouseStates: value }),
|
|
364
513
|
setPerpMetaAssets: (value) => set({ perpMetaAssets: value }),
|
|
365
|
-
|
|
514
|
+
setAllPerpMetaAssets: (value) => set({ allPerpMetaAssets: value }),
|
|
515
|
+
setHip3Assets: (value) => set({ hip3Assets: value }),
|
|
516
|
+
setHip3MarketPrefixes: (value) => set({ hip3MarketPrefixes: value }),
|
|
366
517
|
}));
|
|
367
518
|
|
|
368
519
|
/**
|
|
@@ -625,7 +776,8 @@ const useUserSelection$1 = create((set, get) => ({
|
|
|
625
776
|
}));
|
|
626
777
|
|
|
627
778
|
const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
628
|
-
const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState, } = useHyperliquidData();
|
|
779
|
+
const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState, setRawClearinghouseStates, } = useHyperliquidData();
|
|
780
|
+
const { setSpotState } = useUserData();
|
|
629
781
|
const { candleInterval } = useUserSelection$1();
|
|
630
782
|
const longTokens = useUserSelection$1((s) => s.longTokens);
|
|
631
783
|
const shortTokens = useUserSelection$1((s) => s.shortTokens);
|
|
@@ -644,9 +796,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
644
796
|
try {
|
|
645
797
|
const message = JSON.parse(event.data);
|
|
646
798
|
// Handle subscription responses
|
|
647
|
-
if (
|
|
799
|
+
if ('success' in message || 'error' in message) {
|
|
648
800
|
if (message.error) {
|
|
649
|
-
console.error(
|
|
801
|
+
console.error('[HyperLiquid WS] Subscription error:', message.error);
|
|
650
802
|
setLastError(message.error);
|
|
651
803
|
}
|
|
652
804
|
else {
|
|
@@ -655,30 +807,44 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
655
807
|
return;
|
|
656
808
|
}
|
|
657
809
|
// Handle channel data messages
|
|
658
|
-
if (
|
|
810
|
+
if ('channel' in message && 'data' in message) {
|
|
659
811
|
const response = message;
|
|
660
812
|
switch (response.channel) {
|
|
661
|
-
case
|
|
813
|
+
case 'webData3':
|
|
662
814
|
const webData3 = response.data;
|
|
663
815
|
// finalAssetContexts now sourced from allDexsAssetCtxs channel
|
|
664
816
|
const finalAtOICaps = webData3.perpDexStates.flatMap((dex) => dex.perpsAtOpenInterestCap);
|
|
665
817
|
setFinalAtOICaps(finalAtOICaps);
|
|
666
818
|
break;
|
|
667
|
-
case
|
|
819
|
+
case 'allDexsAssetCtxs':
|
|
668
820
|
{
|
|
669
821
|
const data = response.data;
|
|
670
|
-
|
|
822
|
+
// Filter out hyna to match perpMetaAssets filtering
|
|
823
|
+
const FILTERED_DEX_PREFIXES = ['hyna'];
|
|
824
|
+
const filtered = (data.ctxs || [])
|
|
825
|
+
.filter(([prefix]) => !FILTERED_DEX_PREFIXES.includes((prefix || '').toLowerCase()))
|
|
826
|
+
.sort((a, b) => {
|
|
827
|
+
// Sort to match perpMetaAssets order: default market first, then alphabetically
|
|
828
|
+
const prefixA = a[0] || '';
|
|
829
|
+
const prefixB = b[0] || '';
|
|
830
|
+
if (prefixA === '' && prefixB !== '')
|
|
831
|
+
return -1;
|
|
832
|
+
if (prefixA !== '' && prefixB === '')
|
|
833
|
+
return 1;
|
|
834
|
+
return prefixA.localeCompare(prefixB);
|
|
835
|
+
});
|
|
836
|
+
const finalAssetContexts = filtered.flatMap(([, ctxs]) => ctxs || []);
|
|
671
837
|
setFinalAssetContexts(finalAssetContexts);
|
|
672
838
|
}
|
|
673
839
|
break;
|
|
674
|
-
case
|
|
840
|
+
case 'allDexsClearinghouseState':
|
|
675
841
|
{
|
|
676
842
|
const data = response.data;
|
|
677
843
|
const states = (data.clearinghouseStates || [])
|
|
678
844
|
.map(([, s]) => s)
|
|
679
845
|
.filter(Boolean);
|
|
680
|
-
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v ||
|
|
681
|
-
const toStr = (n) => Number.isFinite(n) ? n.toString() :
|
|
846
|
+
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || '0') || 0), 0);
|
|
847
|
+
const toStr = (n) => Number.isFinite(n) ? n.toString() : '0';
|
|
682
848
|
const assetPositions = states.flatMap((s) => s.assetPositions || []);
|
|
683
849
|
const crossMaintenanceMarginUsed = toStr(sum(states.map((s) => s.crossMaintenanceMarginUsed)));
|
|
684
850
|
const crossMarginSummary = {
|
|
@@ -704,22 +870,41 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
704
870
|
withdrawable,
|
|
705
871
|
};
|
|
706
872
|
setAggregatedClearingHouseState(aggregatedClearingHouseState);
|
|
873
|
+
setRawClearinghouseStates(data.clearinghouseStates || null);
|
|
707
874
|
}
|
|
708
875
|
break;
|
|
709
|
-
case
|
|
876
|
+
case 'allMids':
|
|
710
877
|
{
|
|
711
878
|
const data = response.data;
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
879
|
+
// Keep BOTH normalized prefixed keys AND display symbol keys
|
|
880
|
+
// This ensures xyz:TSLA and flx:TSLA are stored separately,
|
|
881
|
+
// while also maintaining backward compatibility with non-prefixed lookups
|
|
882
|
+
const mids = {};
|
|
883
|
+
Object.entries(data.mids || {}).forEach(([k, v]) => {
|
|
884
|
+
// Normalize prefixed keys to lowercase prefix (e.g., "XYZ:TSLA" -> "xyz:TSLA")
|
|
885
|
+
// This matches how we look up tokens in the SDK
|
|
886
|
+
let normalizedKey = k;
|
|
887
|
+
if (k.includes(':')) {
|
|
888
|
+
const [prefix, ...rest] = k.split(':');
|
|
889
|
+
normalizedKey = `${prefix.toLowerCase()}:${rest.join(':')}`;
|
|
890
|
+
}
|
|
891
|
+
// Store with normalized key
|
|
892
|
+
mids[normalizedKey] = v;
|
|
893
|
+
// Also store with original key for backward compatibility
|
|
894
|
+
if (k !== normalizedKey) {
|
|
895
|
+
mids[k] = v;
|
|
896
|
+
}
|
|
897
|
+
// Also store with display symbol for backward compatibility
|
|
898
|
+
const displayKey = toDisplaySymbol(k);
|
|
899
|
+
// Only set display key if it doesn't already exist (avoid overwriting market-specific prices)
|
|
900
|
+
if (!(displayKey in mids)) {
|
|
901
|
+
mids[displayKey] = v;
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
setAllMids({ mids });
|
|
720
905
|
}
|
|
721
906
|
break;
|
|
722
|
-
case
|
|
907
|
+
case 'activeAssetData':
|
|
723
908
|
{
|
|
724
909
|
const assetData = response.data;
|
|
725
910
|
const symbol = toDisplaySymbol(assetData.coin);
|
|
@@ -730,14 +915,22 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
730
915
|
upsertActiveAssetData(symbol, normalized);
|
|
731
916
|
}
|
|
732
917
|
break;
|
|
733
|
-
case
|
|
918
|
+
case 'candle':
|
|
734
919
|
{
|
|
735
920
|
const candleDataItem = response.data;
|
|
736
|
-
const symbol = toDisplaySymbol(candleDataItem.s ||
|
|
921
|
+
const symbol = toDisplaySymbol(candleDataItem.s || '');
|
|
737
922
|
const normalized = { ...candleDataItem, s: symbol };
|
|
738
923
|
addCandleData(symbol, normalized);
|
|
739
924
|
}
|
|
740
925
|
break;
|
|
926
|
+
case 'spotState':
|
|
927
|
+
{
|
|
928
|
+
const spotStateData = response.data;
|
|
929
|
+
if (spotStateData === null || spotStateData === void 0 ? void 0 : spotStateData.spotState) {
|
|
930
|
+
setSpotState(spotStateData.spotState);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
break;
|
|
741
934
|
default:
|
|
742
935
|
console.warn(`[HyperLiquid WS] Unknown channel: ${response.channel}`);
|
|
743
936
|
}
|
|
@@ -745,7 +938,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
745
938
|
}
|
|
746
939
|
catch (error) {
|
|
747
940
|
const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
|
|
748
|
-
console.error(
|
|
941
|
+
console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', event.data);
|
|
749
942
|
setLastError(errorMessage);
|
|
750
943
|
}
|
|
751
944
|
}, [
|
|
@@ -755,6 +948,8 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
755
948
|
setFinalAssetContexts,
|
|
756
949
|
setFinalAtOICaps,
|
|
757
950
|
setAggregatedClearingHouseState,
|
|
951
|
+
setRawClearinghouseStates,
|
|
952
|
+
setSpotState,
|
|
758
953
|
]);
|
|
759
954
|
const connect = useCallback(() => {
|
|
760
955
|
if (!enabled)
|
|
@@ -785,7 +980,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
785
980
|
if (!manualCloseRef.current && reconnectAttemptsRef.current < 5) {
|
|
786
981
|
reconnectAttemptsRef.current += 1;
|
|
787
982
|
if (reconnectAttemptsRef.current === 5) {
|
|
788
|
-
console.error(
|
|
983
|
+
console.error('[HyperLiquid WS] Reconnection stopped after 5 attempts');
|
|
789
984
|
}
|
|
790
985
|
setTimeout(() => connect(), 3000);
|
|
791
986
|
}
|
|
@@ -853,6 +1048,17 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
853
1048
|
},
|
|
854
1049
|
};
|
|
855
1050
|
sendJsonMessage(unsubscribeMessage);
|
|
1051
|
+
// Unsubscribe from spotState for previous address
|
|
1052
|
+
if (subscribedAddress !== DEFAULT_ADDRESS) {
|
|
1053
|
+
const unsubscribeSpotState = {
|
|
1054
|
+
method: 'unsubscribe',
|
|
1055
|
+
subscription: {
|
|
1056
|
+
type: 'spotState',
|
|
1057
|
+
user: subscribedAddress,
|
|
1058
|
+
},
|
|
1059
|
+
};
|
|
1060
|
+
sendJsonMessage(unsubscribeSpotState);
|
|
1061
|
+
}
|
|
856
1062
|
const unsubscribeAllDexsClearinghouseState = {
|
|
857
1063
|
method: "unsubscribe",
|
|
858
1064
|
subscription: {
|
|
@@ -896,11 +1102,26 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
896
1102
|
sendJsonMessage(subscribeAllDexsClearinghouseState);
|
|
897
1103
|
sendJsonMessage(subscribeAllMids);
|
|
898
1104
|
sendJsonMessage(subscribeAllDexsAssetCtxs);
|
|
1105
|
+
// Subscribe to spotState for real-time spot balances (USDH, USDC, etc.)
|
|
1106
|
+
// Only subscribe if we have a real user address (not the default)
|
|
1107
|
+
if (userAddress !== DEFAULT_ADDRESS) {
|
|
1108
|
+
const subscribeSpotState = {
|
|
1109
|
+
method: 'subscribe',
|
|
1110
|
+
subscription: {
|
|
1111
|
+
type: 'spotState',
|
|
1112
|
+
user: userAddress,
|
|
1113
|
+
},
|
|
1114
|
+
};
|
|
1115
|
+
sendJsonMessage(subscribeSpotState);
|
|
1116
|
+
}
|
|
899
1117
|
setSubscribedAddress(userAddress);
|
|
900
1118
|
// Clear previous data when address changes
|
|
901
1119
|
if (subscribedAddress && subscribedAddress !== userAddress) {
|
|
902
1120
|
// clear aggregatedClearingHouseState
|
|
903
1121
|
setAggregatedClearingHouseState(null);
|
|
1122
|
+
setRawClearinghouseStates(null);
|
|
1123
|
+
// clear spotState
|
|
1124
|
+
setSpotState(null);
|
|
904
1125
|
}
|
|
905
1126
|
}, [
|
|
906
1127
|
isConnected,
|
|
@@ -908,6 +1129,8 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
908
1129
|
subscribedAddress,
|
|
909
1130
|
sendJsonMessage,
|
|
910
1131
|
setAggregatedClearingHouseState,
|
|
1132
|
+
setRawClearinghouseStates,
|
|
1133
|
+
setSpotState,
|
|
911
1134
|
]);
|
|
912
1135
|
// Handle token subscriptions for activeAssetData
|
|
913
1136
|
useEffect(() => {
|
|
@@ -1104,20 +1327,112 @@ const useAccountSummary = () => {
|
|
|
1104
1327
|
return { data: calculated, isLoading };
|
|
1105
1328
|
};
|
|
1106
1329
|
|
|
1330
|
+
function findAssetMeta$4(coinName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
1331
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1332
|
+
if (!perpMetaAssets) {
|
|
1333
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
1334
|
+
}
|
|
1335
|
+
if (desiredCollateral) {
|
|
1336
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === coinName && a.collateralToken === desiredCollateral);
|
|
1337
|
+
if (collateralMatch) {
|
|
1338
|
+
return {
|
|
1339
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
1340
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
if (coinName.includes(':')) {
|
|
1345
|
+
const [prefix, symbol] = coinName.split(':');
|
|
1346
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
1347
|
+
var _a;
|
|
1348
|
+
return a.name === symbol &&
|
|
1349
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
1350
|
+
});
|
|
1351
|
+
if (exactMatch) {
|
|
1352
|
+
return {
|
|
1353
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
1354
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (knownPrefix) {
|
|
1359
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
1360
|
+
var _a;
|
|
1361
|
+
return a.name === coinName &&
|
|
1362
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
1363
|
+
});
|
|
1364
|
+
if (exactMatch) {
|
|
1365
|
+
return {
|
|
1366
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
1367
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
const exactMatch = perpMetaAssets.find((a) => a.name === coinName && !a.marketPrefix);
|
|
1372
|
+
if (exactMatch) {
|
|
1373
|
+
return {
|
|
1374
|
+
collateralToken: (_g = exactMatch.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
1375
|
+
marketPrefix: (_h = exactMatch.marketPrefix) !== null && _h !== void 0 ? _h : null,
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
const hip3Matches = perpMetaAssets.filter((a) => a.name === coinName && a.marketPrefix);
|
|
1379
|
+
if (hip3Matches.length > 0) {
|
|
1380
|
+
if (desiredCollateral) {
|
|
1381
|
+
const collateralMatch = hip3Matches.find((a) => a.collateralToken === desiredCollateral);
|
|
1382
|
+
if (collateralMatch) {
|
|
1383
|
+
return {
|
|
1384
|
+
collateralToken: (_j = collateralMatch.collateralToken) !== null && _j !== void 0 ? _j : 'USDC',
|
|
1385
|
+
marketPrefix: (_k = collateralMatch.marketPrefix) !== null && _k !== void 0 ? _k : null,
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
const usdHMatch = hip3Matches.find((a) => a.collateralToken === 'USDH');
|
|
1390
|
+
const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Matches[0];
|
|
1391
|
+
return {
|
|
1392
|
+
collateralToken: (_l = chosen.collateralToken) !== null && _l !== void 0 ? _l : 'USDC',
|
|
1393
|
+
marketPrefix: (_m = chosen.marketPrefix) !== null && _m !== void 0 ? _m : null,
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
1397
|
+
}
|
|
1398
|
+
function enrichTradeHistoryAssets(assets, perpMetaAssets) {
|
|
1399
|
+
return assets.map((asset) => {
|
|
1400
|
+
var _a;
|
|
1401
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
1402
|
+
return asset;
|
|
1403
|
+
}
|
|
1404
|
+
const meta = findAssetMeta$4(asset.coin, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
1405
|
+
return {
|
|
1406
|
+
...asset,
|
|
1407
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
1408
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
1409
|
+
};
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
function enrichTradeHistories(histories, perpMetaAssets) {
|
|
1413
|
+
return histories.map((history) => ({
|
|
1414
|
+
...history,
|
|
1415
|
+
closedLongAssets: enrichTradeHistoryAssets(history.closedLongAssets, perpMetaAssets),
|
|
1416
|
+
closedShortAssets: enrichTradeHistoryAssets(history.closedShortAssets, perpMetaAssets),
|
|
1417
|
+
}));
|
|
1418
|
+
}
|
|
1107
1419
|
const useTradeHistories = () => {
|
|
1108
1420
|
const context = useContext(PearHyperliquidContext);
|
|
1109
1421
|
if (!context) {
|
|
1110
1422
|
throw new Error('useTradeHistories must be used within a PearHyperliquidProvider');
|
|
1111
1423
|
}
|
|
1112
1424
|
const tradeHistories = useUserData((state) => state.tradeHistories);
|
|
1425
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
1113
1426
|
const isLoading = useMemo(() => {
|
|
1114
1427
|
return tradeHistories === null && context.isConnected;
|
|
1115
1428
|
}, [tradeHistories, context.isConnected]);
|
|
1116
|
-
|
|
1429
|
+
const enrichedTradeHistories = useMemo(() => {
|
|
1430
|
+
if (!tradeHistories)
|
|
1431
|
+
return null;
|
|
1432
|
+
return enrichTradeHistories(tradeHistories, allPerpMetaAssets);
|
|
1433
|
+
}, [tradeHistories, allPerpMetaAssets]);
|
|
1434
|
+
return { data: enrichedTradeHistories, isLoading };
|
|
1117
1435
|
};
|
|
1118
|
-
/**
|
|
1119
|
-
* Hook to access open orders with loading state
|
|
1120
|
-
*/
|
|
1121
1436
|
const useOpenOrders = () => {
|
|
1122
1437
|
const context = useContext(PearHyperliquidContext);
|
|
1123
1438
|
if (!context) {
|
|
@@ -1146,21 +1461,51 @@ const useWebData = () => {
|
|
|
1146
1461
|
const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
|
|
1147
1462
|
const aggregatedClearinghouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
1148
1463
|
const finalAtOICaps = useHyperliquidData((state) => state.finalAtOICaps);
|
|
1149
|
-
const hip3Assets = useHyperliquidData((state) => state.
|
|
1464
|
+
const hip3Assets = useHyperliquidData((state) => state.hip3Assets);
|
|
1465
|
+
const hip3MarketPrefixes = useHyperliquidData((state) => state.hip3MarketPrefixes);
|
|
1150
1466
|
let marketDataBySymbol = {};
|
|
1151
1467
|
if (finalAssetContexts && perpMetaAssets) {
|
|
1152
1468
|
const result = {};
|
|
1469
|
+
// Build a map of display name -> asset context index (for unique display names)
|
|
1470
|
+
const displayNameToContextIndex = new Map();
|
|
1471
|
+
const seenNames = new Set();
|
|
1472
|
+
let contextIndex = 0;
|
|
1473
|
+
// First pass: map unique display names to their context index
|
|
1153
1474
|
for (let index = 0; index < perpMetaAssets.length; index++) {
|
|
1154
1475
|
const name = perpMetaAssets[index].name;
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1476
|
+
if (!seenNames.has(name)) {
|
|
1477
|
+
seenNames.add(name);
|
|
1478
|
+
if (contextIndex < finalAssetContexts.length) {
|
|
1479
|
+
displayNameToContextIndex.set(name, contextIndex);
|
|
1480
|
+
contextIndex++;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
// Second pass: create nested entries for all market variants
|
|
1485
|
+
for (let index = 0; index < perpMetaAssets.length; index++) {
|
|
1486
|
+
const universeAsset = perpMetaAssets[index];
|
|
1487
|
+
const displayName = universeAsset.name;
|
|
1488
|
+
const marketPrefix = universeAsset.marketPrefix;
|
|
1489
|
+
const ctxIndex = displayNameToContextIndex.get(displayName);
|
|
1490
|
+
if (ctxIndex !== undefined) {
|
|
1491
|
+
const assetContext = finalAssetContexts[ctxIndex];
|
|
1492
|
+
// Initialize the symbol entry if it doesn't exist
|
|
1493
|
+
if (!result[displayName]) {
|
|
1494
|
+
result[displayName] = {};
|
|
1495
|
+
}
|
|
1496
|
+
// Use marketPrefix as key for HIP-3 assets, "default" for regular assets
|
|
1497
|
+
const variantKey = marketPrefix || 'default';
|
|
1498
|
+
result[displayName][variantKey] = {
|
|
1499
|
+
asset: assetContext,
|
|
1500
|
+
universe: universeAsset,
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1159
1503
|
}
|
|
1160
1504
|
marketDataBySymbol = result;
|
|
1161
1505
|
}
|
|
1162
1506
|
return {
|
|
1163
1507
|
hip3Assets,
|
|
1508
|
+
hip3MarketPrefixes,
|
|
1164
1509
|
clearinghouseState: aggregatedClearinghouseState,
|
|
1165
1510
|
perpsAtOpenInterestCap: finalAtOICaps,
|
|
1166
1511
|
marketDataBySymbol,
|
|
@@ -1175,19 +1520,30 @@ const useWebData = () => {
|
|
|
1175
1520
|
class TokenMetadataExtractor {
|
|
1176
1521
|
/**
|
|
1177
1522
|
* Extracts comprehensive token metadata
|
|
1178
|
-
* @param symbol - Token symbol
|
|
1523
|
+
* @param symbol - Token symbol (base symbol without prefix, e.g., "TSLA")
|
|
1179
1524
|
* @param perpMetaAssets - Aggregated universe assets (flattened across dexes)
|
|
1180
1525
|
* @param finalAssetContexts - Aggregated asset contexts (flattened across dexes)
|
|
1181
1526
|
* @param allMids - AllMids data containing current prices
|
|
1182
1527
|
* @param activeAssetData - Optional active asset data containing leverage information
|
|
1528
|
+
* @param marketPrefix - Optional market prefix (e.g., "xyz", "flx") for HIP3 multi-market assets
|
|
1183
1529
|
* @returns TokenMetadata or null if token not found
|
|
1184
1530
|
*/
|
|
1185
|
-
static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
|
|
1531
|
+
static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketPrefix) {
|
|
1186
1532
|
if (!perpMetaAssets || !finalAssetContexts || !allMids) {
|
|
1187
1533
|
return null;
|
|
1188
1534
|
}
|
|
1189
1535
|
// Find token index in aggregated universe
|
|
1190
|
-
|
|
1536
|
+
// For HIP3 assets, match both name AND marketPrefix
|
|
1537
|
+
const universeIndex = perpMetaAssets.findIndex((asset) => {
|
|
1538
|
+
if (asset.name !== symbol)
|
|
1539
|
+
return false;
|
|
1540
|
+
// If marketPrefix is specified, match it; otherwise match assets without prefix
|
|
1541
|
+
if (marketPrefix) {
|
|
1542
|
+
return asset.marketPrefix === marketPrefix;
|
|
1543
|
+
}
|
|
1544
|
+
// No prefix specified - match non-HIP3 asset (no marketPrefix) or first matching asset
|
|
1545
|
+
return !asset.marketPrefix;
|
|
1546
|
+
});
|
|
1191
1547
|
if (universeIndex === -1) {
|
|
1192
1548
|
return null;
|
|
1193
1549
|
}
|
|
@@ -1196,9 +1552,20 @@ class TokenMetadataExtractor {
|
|
|
1196
1552
|
if (!assetCtx) {
|
|
1197
1553
|
return null;
|
|
1198
1554
|
}
|
|
1199
|
-
// Get current price
|
|
1200
|
-
|
|
1201
|
-
const
|
|
1555
|
+
// Get current price - prefer assetCtx.midPx as it's already index-matched,
|
|
1556
|
+
// fall back to allMids lookup if midPx is null
|
|
1557
|
+
const prefixedKeyColon = marketPrefix ? `${marketPrefix}:${symbol}` : null;
|
|
1558
|
+
let currentPrice = 0;
|
|
1559
|
+
// Primary source: assetCtx.midPx (already properly indexed)
|
|
1560
|
+
if (assetCtx.midPx) {
|
|
1561
|
+
currentPrice = parseFloat(assetCtx.midPx);
|
|
1562
|
+
}
|
|
1563
|
+
// Fallback: allMids lookup with multiple key formats for HIP3 markets
|
|
1564
|
+
if (!currentPrice || isNaN(currentPrice)) {
|
|
1565
|
+
const currentPriceStr = (prefixedKeyColon && allMids.mids[prefixedKeyColon]) ||
|
|
1566
|
+
allMids.mids[symbol];
|
|
1567
|
+
currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
|
|
1568
|
+
}
|
|
1202
1569
|
// Get previous day price
|
|
1203
1570
|
const prevDayPrice = parseFloat(assetCtx.prevDayPx);
|
|
1204
1571
|
// Calculate 24h price change
|
|
@@ -1209,7 +1576,11 @@ class TokenMetadataExtractor {
|
|
|
1209
1576
|
const markPrice = parseFloat(assetCtx.markPx);
|
|
1210
1577
|
const oraclePrice = parseFloat(assetCtx.oraclePx);
|
|
1211
1578
|
// Extract leverage info from activeAssetData if available
|
|
1212
|
-
|
|
1579
|
+
// Try prefixed key first (e.g., "xyz:TSLA"), then fall back to plain symbol
|
|
1580
|
+
const activeDataKey = prefixedKeyColon && (activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[prefixedKeyColon])
|
|
1581
|
+
? prefixedKeyColon
|
|
1582
|
+
: symbol;
|
|
1583
|
+
const tokenActiveData = activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[activeDataKey];
|
|
1213
1584
|
const leverage = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.leverage;
|
|
1214
1585
|
const maxTradeSzs = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.maxTradeSzs;
|
|
1215
1586
|
const availableToTrade = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.availableToTrade;
|
|
@@ -1227,21 +1598,27 @@ class TokenMetadataExtractor {
|
|
|
1227
1598
|
leverage,
|
|
1228
1599
|
maxTradeSzs,
|
|
1229
1600
|
availableToTrade,
|
|
1601
|
+
collateralToken: universeAsset.collateralToken,
|
|
1230
1602
|
};
|
|
1231
1603
|
}
|
|
1232
1604
|
/**
|
|
1233
1605
|
* Extracts metadata for multiple tokens
|
|
1234
|
-
* @param
|
|
1606
|
+
* @param tokens - Array of token objects with symbol and optional marketPrefix
|
|
1235
1607
|
* @param perpMetaAssets - Aggregated universe assets
|
|
1236
1608
|
* @param finalAssetContexts - Aggregated asset contexts
|
|
1237
1609
|
* @param allMids - AllMids data
|
|
1238
1610
|
* @param activeAssetData - Optional active asset data containing leverage information
|
|
1239
|
-
* @returns Record of
|
|
1611
|
+
* @returns Record of unique key to TokenMetadata. Key is "{prefix}:{symbol}" for HIP3 assets, or just "{symbol}" otherwise
|
|
1240
1612
|
*/
|
|
1241
|
-
static extractMultipleTokensMetadata(
|
|
1613
|
+
static extractMultipleTokensMetadata(tokens, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
|
|
1242
1614
|
const result = {};
|
|
1243
|
-
for (const
|
|
1244
|
-
|
|
1615
|
+
for (const token of tokens) {
|
|
1616
|
+
// Use a unique key that includes the prefix for HIP3 assets
|
|
1617
|
+
// This ensures xyz:TSLA and flx:TSLA get separate entries
|
|
1618
|
+
const resultKey = token.marketPrefix
|
|
1619
|
+
? `${token.marketPrefix}:${token.symbol}`
|
|
1620
|
+
: token.symbol;
|
|
1621
|
+
result[resultKey] = this.extractTokenMetadata(token.symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, token.marketPrefix);
|
|
1245
1622
|
}
|
|
1246
1623
|
return result;
|
|
1247
1624
|
}
|
|
@@ -1254,10 +1631,30 @@ class TokenMetadataExtractor {
|
|
|
1254
1631
|
static isTokenAvailable(symbol, perpMetaAssets) {
|
|
1255
1632
|
if (!perpMetaAssets)
|
|
1256
1633
|
return false;
|
|
1257
|
-
return perpMetaAssets.some(asset => asset.name === symbol);
|
|
1634
|
+
return perpMetaAssets.some((asset) => asset.name === symbol);
|
|
1258
1635
|
}
|
|
1259
1636
|
}
|
|
1260
1637
|
|
|
1638
|
+
/**
|
|
1639
|
+
* Parse a token string that may have a market prefix (e.g., "xyz:GOOGL" -> { prefix: "xyz", symbol: "GOOGL" })
|
|
1640
|
+
* This allows us to keep the full name (xyz:GOOGL) for URLs/tags while extracting just the symbol for SDK lookups.
|
|
1641
|
+
*/
|
|
1642
|
+
function parseTokenWithPrefix(token) {
|
|
1643
|
+
if (token.includes(":")) {
|
|
1644
|
+
const [prefix, ...rest] = token.split(":");
|
|
1645
|
+
const symbol = rest.join(":").toUpperCase();
|
|
1646
|
+
return {
|
|
1647
|
+
prefix: prefix.toLowerCase(),
|
|
1648
|
+
symbol,
|
|
1649
|
+
fullName: `${prefix.toLowerCase()}:${symbol}`,
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
return {
|
|
1653
|
+
prefix: null,
|
|
1654
|
+
symbol: token.toUpperCase(),
|
|
1655
|
+
fullName: token.toUpperCase(),
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1261
1658
|
const useTokenSelectionMetadataStore = create((set) => ({
|
|
1262
1659
|
isPriceDataReady: false,
|
|
1263
1660
|
isLoading: true,
|
|
@@ -1273,17 +1670,59 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1273
1670
|
maxLeverage: 0,
|
|
1274
1671
|
minMargin: 0,
|
|
1275
1672
|
leverageMatched: true,
|
|
1276
|
-
recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens }) => {
|
|
1277
|
-
const isPriceDataReady = !!(perpMetaAssets &&
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1673
|
+
recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens, }) => {
|
|
1674
|
+
const isPriceDataReady = !!(perpMetaAssets &&
|
|
1675
|
+
finalAssetContexts &&
|
|
1676
|
+
allMids);
|
|
1677
|
+
// Parse tokens - handle prefixed tokens like "xyz:GOOGL" by extracting the symbol and market prefix
|
|
1678
|
+
// The full name (xyz:GOOGL) is kept as the metadata key for UI consistency
|
|
1679
|
+
const parsedLongTokens = longTokens.map((t) => ({
|
|
1680
|
+
...t,
|
|
1681
|
+
parsed: parseTokenWithPrefix(t.symbol),
|
|
1682
|
+
}));
|
|
1683
|
+
const parsedShortTokens = shortTokens.map((t) => ({
|
|
1684
|
+
...t,
|
|
1685
|
+
parsed: parseTokenWithPrefix(t.symbol),
|
|
1686
|
+
}));
|
|
1687
|
+
// Extract base symbols with their market prefixes for SDK lookups
|
|
1688
|
+
// This ensures xyz:TSLA and flx:TSLA get different market data
|
|
1689
|
+
const longTokensForLookup = parsedLongTokens.map((t) => ({
|
|
1690
|
+
symbol: t.parsed.symbol,
|
|
1691
|
+
marketPrefix: t.parsed.prefix,
|
|
1692
|
+
}));
|
|
1693
|
+
const shortTokensForLookup = parsedShortTokens.map((t) => ({
|
|
1694
|
+
symbol: t.parsed.symbol,
|
|
1695
|
+
marketPrefix: t.parsed.prefix,
|
|
1696
|
+
}));
|
|
1697
|
+
// Also extract just the base symbols (without prefix) for lookups that don't support prefixes
|
|
1698
|
+
const longBaseSymbols = longTokensForLookup.map((t) => t.symbol);
|
|
1699
|
+
const shortBaseSymbols = shortTokensForLookup.map((t) => t.symbol);
|
|
1700
|
+
// Get metadata using base symbols with market prefix for proper market differentiation
|
|
1701
|
+
const longBaseMetadata = isPriceDataReady
|
|
1702
|
+
? TokenMetadataExtractor.extractMultipleTokensMetadata(longTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
|
|
1283
1703
|
: {};
|
|
1284
|
-
const
|
|
1285
|
-
? TokenMetadataExtractor.extractMultipleTokensMetadata(
|
|
1704
|
+
const shortBaseMetadata = isPriceDataReady
|
|
1705
|
+
? TokenMetadataExtractor.extractMultipleTokensMetadata(shortTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
|
|
1286
1706
|
: {};
|
|
1707
|
+
// Re-map metadata using original full names (with prefix) as keys for UI consistency
|
|
1708
|
+
// The extractor now keys by "{prefix}:{symbol}" for prefixed tokens, which matches our parsed.fullName
|
|
1709
|
+
const longTokensMetadata = {};
|
|
1710
|
+
parsedLongTokens.forEach((t) => {
|
|
1711
|
+
var _a;
|
|
1712
|
+
// Use the full name (e.g., "xyz:TSLA") as the lookup key since extractor uses the same format
|
|
1713
|
+
const lookupKey = t.parsed.prefix
|
|
1714
|
+
? `${t.parsed.prefix}:${t.parsed.symbol}`
|
|
1715
|
+
: t.parsed.symbol;
|
|
1716
|
+
longTokensMetadata[t.symbol] = (_a = longBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
|
|
1717
|
+
});
|
|
1718
|
+
const shortTokensMetadata = {};
|
|
1719
|
+
parsedShortTokens.forEach((t) => {
|
|
1720
|
+
var _a;
|
|
1721
|
+
const lookupKey = t.parsed.prefix
|
|
1722
|
+
? `${t.parsed.prefix}:${t.parsed.symbol}`
|
|
1723
|
+
: t.parsed.symbol;
|
|
1724
|
+
shortTokensMetadata[t.symbol] = (_a = shortBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
|
|
1725
|
+
});
|
|
1287
1726
|
// Determine loading state
|
|
1288
1727
|
const allTokens = [...longTokens, ...shortTokens];
|
|
1289
1728
|
const isLoading = (() => {
|
|
@@ -1291,26 +1730,33 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1291
1730
|
return true;
|
|
1292
1731
|
if (allTokens.length === 0)
|
|
1293
1732
|
return false;
|
|
1294
|
-
const allMetadata = {
|
|
1733
|
+
const allMetadata = {
|
|
1734
|
+
...longTokensMetadata,
|
|
1735
|
+
...shortTokensMetadata,
|
|
1736
|
+
};
|
|
1295
1737
|
return allTokens.some((token) => !allMetadata[token.symbol]);
|
|
1296
1738
|
})();
|
|
1297
1739
|
// Open interest and volume (from market data for matching asset basket)
|
|
1740
|
+
// Use base symbols (without prefix) for matching against market data
|
|
1298
1741
|
const { openInterest, volume } = (() => {
|
|
1299
1742
|
const empty = { openInterest: "0", volume: "0" };
|
|
1300
1743
|
if (!(marketData === null || marketData === void 0 ? void 0 : marketData.active) || (!longTokens.length && !shortTokens.length))
|
|
1301
1744
|
return empty;
|
|
1302
|
-
const selectedLong =
|
|
1303
|
-
const selectedShort =
|
|
1745
|
+
const selectedLong = longBaseSymbols.slice().sort();
|
|
1746
|
+
const selectedShort = shortBaseSymbols.slice().sort();
|
|
1304
1747
|
const match = marketData.active.find((item) => {
|
|
1305
1748
|
const longs = [...item.longAssets].sort();
|
|
1306
1749
|
const shorts = [...item.shortAssets].sort();
|
|
1307
|
-
if (longs.length !== selectedLong.length ||
|
|
1750
|
+
if (longs.length !== selectedLong.length ||
|
|
1751
|
+
shorts.length !== selectedShort.length)
|
|
1308
1752
|
return false;
|
|
1309
1753
|
const longsEqual = longs.every((s, i) => s.asset === selectedLong[i]);
|
|
1310
1754
|
const shortsEqual = shorts.every((s, i) => s.asset === selectedShort[i]);
|
|
1311
1755
|
return longsEqual && shortsEqual;
|
|
1312
1756
|
});
|
|
1313
|
-
return match
|
|
1757
|
+
return match
|
|
1758
|
+
? { openInterest: match.openInterest, volume: match.volume }
|
|
1759
|
+
: empty;
|
|
1314
1760
|
})();
|
|
1315
1761
|
// Price ratio (only when exactly one long and one short)
|
|
1316
1762
|
const { priceRatio, priceRatio24h } = (() => {
|
|
@@ -1390,17 +1836,27 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1390
1836
|
return totalFunding;
|
|
1391
1837
|
})();
|
|
1392
1838
|
// Max leverage (maximum across all tokens)
|
|
1839
|
+
// Use tokens with their market prefixes for proper lookup in perpMetaAssets
|
|
1393
1840
|
const maxLeverage = (() => {
|
|
1394
1841
|
if (!perpMetaAssets)
|
|
1395
1842
|
return 0;
|
|
1396
|
-
const
|
|
1397
|
-
|
|
1843
|
+
const allTokensForLookup = [
|
|
1844
|
+
...longTokensForLookup,
|
|
1845
|
+
...shortTokensForLookup,
|
|
1846
|
+
];
|
|
1847
|
+
if (allTokensForLookup.length === 0)
|
|
1398
1848
|
return 0;
|
|
1399
1849
|
let maxLev = 0;
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1850
|
+
allTokensForLookup.forEach(({ symbol, marketPrefix }) => {
|
|
1851
|
+
// Match by both name AND marketPrefix for HIP3 assets
|
|
1852
|
+
const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol &&
|
|
1853
|
+
(marketPrefix
|
|
1854
|
+
? u.marketPrefix === marketPrefix
|
|
1855
|
+
: !u.marketPrefix));
|
|
1856
|
+
// Fallback to just matching by name if no exact match
|
|
1857
|
+
const fallbackUniverse = tokenUniverse || perpMetaAssets.find((u) => u.name === symbol);
|
|
1858
|
+
if (fallbackUniverse === null || fallbackUniverse === void 0 ? void 0 : fallbackUniverse.maxLeverage)
|
|
1859
|
+
maxLev = Math.max(maxLev, fallbackUniverse.maxLeverage);
|
|
1404
1860
|
});
|
|
1405
1861
|
return maxLev;
|
|
1406
1862
|
})();
|
|
@@ -1412,7 +1868,10 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1412
1868
|
// Whether all tokens have matching leverage
|
|
1413
1869
|
const leverageMatched = (() => {
|
|
1414
1870
|
const allTokensArr = [...longTokens, ...shortTokens];
|
|
1415
|
-
const allMetadata = {
|
|
1871
|
+
const allMetadata = {
|
|
1872
|
+
...longTokensMetadata,
|
|
1873
|
+
...shortTokensMetadata,
|
|
1874
|
+
};
|
|
1416
1875
|
if (allTokensArr.length === 0)
|
|
1417
1876
|
return true;
|
|
1418
1877
|
const tokensWithLev = allTokensArr.filter((token) => { var _a; return (_a = allMetadata[token.symbol]) === null || _a === void 0 ? void 0 : _a.leverage; });
|
|
@@ -5597,8 +6056,8 @@ function addAuthInterceptors(params) {
|
|
|
5597
6056
|
/**
|
|
5598
6057
|
* Fetch historical candle data from HyperLiquid API
|
|
5599
6058
|
*/
|
|
5600
|
-
const fetchHistoricalCandles = async (coin, startTime, endTime, interval,
|
|
5601
|
-
const backendCoin = toBackendSymbol(coin,
|
|
6059
|
+
const fetchHistoricalCandles = async (coin, startTime, endTime, interval, hip3Assets) => {
|
|
6060
|
+
const backendCoin = toBackendSymbol(coin, hip3Assets);
|
|
5602
6061
|
const request = {
|
|
5603
6062
|
req: { coin: backendCoin, startTime, endTime, interval },
|
|
5604
6063
|
type: 'candleSnapshot',
|
|
@@ -5757,10 +6216,10 @@ const useHistoricalPriceData = () => {
|
|
|
5757
6216
|
setTokenLoading(token.symbol, true);
|
|
5758
6217
|
});
|
|
5759
6218
|
try {
|
|
5760
|
-
const
|
|
6219
|
+
const hip3Assets = useHyperliquidData.getState().hip3Assets;
|
|
5761
6220
|
const fetchPromises = tokensToFetch.map(async (token) => {
|
|
5762
6221
|
try {
|
|
5763
|
-
const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval,
|
|
6222
|
+
const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, hip3Assets);
|
|
5764
6223
|
addHistoricalPriceData(token.symbol, interval, response.data, { start: startTime, end: endTime });
|
|
5765
6224
|
return { symbol: token.symbol, candles: response.data, success: true };
|
|
5766
6225
|
}
|
|
@@ -6449,14 +6908,14 @@ function useAutoSyncFills(options) {
|
|
|
6449
6908
|
* @throws MinimumPositionSizeError if any asset has less than $11 USD value
|
|
6450
6909
|
* @throws MaxAssetsPerLegError if any leg exceeds the maximum allowed assets (15)
|
|
6451
6910
|
*/
|
|
6452
|
-
async function createPosition(baseUrl, payload,
|
|
6911
|
+
async function createPosition(baseUrl, payload, hip3Assets) {
|
|
6453
6912
|
// Validate maximum assets per leg before creating position
|
|
6454
6913
|
validateMaxAssetsPerLeg(payload.longAssets, payload.shortAssets);
|
|
6455
6914
|
// Validate minimum asset size before creating position
|
|
6456
6915
|
validateMinimumAssetSize(payload.usdValue, payload.longAssets, payload.shortAssets);
|
|
6457
6916
|
const url = joinUrl(baseUrl, "/positions");
|
|
6458
6917
|
// Translate display symbols to backend format
|
|
6459
|
-
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
6918
|
+
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6460
6919
|
const translatedPayload = {
|
|
6461
6920
|
...payload,
|
|
6462
6921
|
longAssets: mapAssets(payload.longAssets),
|
|
@@ -6555,9 +7014,9 @@ async function adjustPosition(baseUrl, positionId, payload) {
|
|
|
6555
7014
|
throw toApiError(error);
|
|
6556
7015
|
}
|
|
6557
7016
|
}
|
|
6558
|
-
async function adjustAdvancePosition(baseUrl, positionId, payload,
|
|
7017
|
+
async function adjustAdvancePosition(baseUrl, positionId, payload, hip3Assets) {
|
|
6559
7018
|
const url = joinUrl(baseUrl, `/positions/${positionId}/adjust-advance`);
|
|
6560
|
-
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
7019
|
+
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6561
7020
|
const translatedPayload = (payload || []).map((item) => ({
|
|
6562
7021
|
longAssets: mapAssets(item.longAssets),
|
|
6563
7022
|
shortAssets: mapAssets(item.shortAssets),
|
|
@@ -6640,10 +7099,11 @@ const calculatePositionAsset = (asset, currentPrice, totalInitialPositionSize, l
|
|
|
6640
7099
|
positionValue: currentNotional,
|
|
6641
7100
|
unrealizedPnl: unrealizedPnl,
|
|
6642
7101
|
entryPositionValue: entryNotional,
|
|
6643
|
-
initialWeight: totalInitialPositionSize > 0
|
|
6644
|
-
? entryNotional / totalInitialPositionSize
|
|
6645
|
-
: 0,
|
|
7102
|
+
initialWeight: totalInitialPositionSize > 0 ? entryNotional / totalInitialPositionSize : 0,
|
|
6646
7103
|
fundingPaid: (_a = asset.fundingPaid) !== null && _a !== void 0 ? _a : 0,
|
|
7104
|
+
// Preserve market metadata from raw asset (if provided by backend)
|
|
7105
|
+
marketPrefix: asset.marketPrefix,
|
|
7106
|
+
collateralToken: asset.collateralToken,
|
|
6647
7107
|
};
|
|
6648
7108
|
};
|
|
6649
7109
|
const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
|
|
@@ -6732,36 +7192,108 @@ const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
|
|
|
6732
7192
|
});
|
|
6733
7193
|
};
|
|
6734
7194
|
|
|
7195
|
+
function findAssetMeta$3(coinName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
7196
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
7197
|
+
if (!perpMetaAssets) {
|
|
7198
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7199
|
+
}
|
|
7200
|
+
if (desiredCollateral) {
|
|
7201
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === coinName && a.collateralToken === desiredCollateral);
|
|
7202
|
+
if (collateralMatch) {
|
|
7203
|
+
return {
|
|
7204
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7205
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7206
|
+
};
|
|
7207
|
+
}
|
|
7208
|
+
}
|
|
7209
|
+
if (coinName.includes(':')) {
|
|
7210
|
+
const [prefix, symbol] = coinName.split(':');
|
|
7211
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7212
|
+
var _a;
|
|
7213
|
+
return a.name === symbol &&
|
|
7214
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
7215
|
+
});
|
|
7216
|
+
if (exactMatch) {
|
|
7217
|
+
return {
|
|
7218
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7219
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7220
|
+
};
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
if (knownPrefix) {
|
|
7224
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7225
|
+
var _a;
|
|
7226
|
+
return a.name === coinName &&
|
|
7227
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
7228
|
+
});
|
|
7229
|
+
if (exactMatch) {
|
|
7230
|
+
return {
|
|
7231
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
7232
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
7233
|
+
};
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
const regularAsset = perpMetaAssets.find((a) => a.name === coinName && !a.marketPrefix);
|
|
7237
|
+
if (regularAsset) {
|
|
7238
|
+
return {
|
|
7239
|
+
collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
7240
|
+
marketPrefix: null,
|
|
7241
|
+
};
|
|
7242
|
+
}
|
|
7243
|
+
const hip3Asset = perpMetaAssets.find((a) => a.name === coinName && a.marketPrefix);
|
|
7244
|
+
if (hip3Asset) {
|
|
7245
|
+
return {
|
|
7246
|
+
collateralToken: (_h = hip3Asset.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
|
|
7247
|
+
marketPrefix: (_j = hip3Asset.marketPrefix) !== null && _j !== void 0 ? _j : null,
|
|
7248
|
+
};
|
|
7249
|
+
}
|
|
7250
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7251
|
+
}
|
|
7252
|
+
function enrichPositionAssets(assets, perpMetaAssets) {
|
|
7253
|
+
return assets.map((asset) => {
|
|
7254
|
+
var _a;
|
|
7255
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
7256
|
+
return asset;
|
|
7257
|
+
}
|
|
7258
|
+
const meta = findAssetMeta$3(asset.coin, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
7259
|
+
return {
|
|
7260
|
+
...asset,
|
|
7261
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
7262
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
7263
|
+
};
|
|
7264
|
+
});
|
|
7265
|
+
}
|
|
7266
|
+
function enrichPositions(positions, perpMetaAssets) {
|
|
7267
|
+
return positions.map((position) => ({
|
|
7268
|
+
...position,
|
|
7269
|
+
longAssets: enrichPositionAssets(position.longAssets, perpMetaAssets),
|
|
7270
|
+
shortAssets: enrichPositionAssets(position.shortAssets, perpMetaAssets),
|
|
7271
|
+
}));
|
|
7272
|
+
}
|
|
6735
7273
|
function usePosition() {
|
|
6736
7274
|
const context = useContext(PearHyperliquidContext);
|
|
6737
7275
|
if (!context) {
|
|
6738
7276
|
throw new Error('usePosition must be used within a PearHyperliquidProvider');
|
|
6739
7277
|
}
|
|
6740
7278
|
const { apiBaseUrl, isConnected } = context;
|
|
6741
|
-
const
|
|
6742
|
-
// Create position API action
|
|
7279
|
+
const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
|
|
6743
7280
|
const createPosition$1 = async (payload) => {
|
|
6744
|
-
return createPosition(apiBaseUrl, payload,
|
|
7281
|
+
return createPosition(apiBaseUrl, payload, hip3Assets);
|
|
6745
7282
|
};
|
|
6746
|
-
// Update TP/SL risk parameters for a position
|
|
6747
7283
|
const updateRiskParameters$1 = async (positionId, payload) => {
|
|
6748
7284
|
return updateRiskParameters(apiBaseUrl, positionId, payload);
|
|
6749
7285
|
};
|
|
6750
|
-
// Close a position (MARKET or TWAP)
|
|
6751
7286
|
const closePosition$1 = async (positionId, payload) => {
|
|
6752
7287
|
return closePosition(apiBaseUrl, positionId, payload);
|
|
6753
7288
|
};
|
|
6754
|
-
// Close all positions (MARKET or TWAP)
|
|
6755
7289
|
const closeAllPositions$1 = async (payload) => {
|
|
6756
7290
|
return closeAllPositions(apiBaseUrl, payload);
|
|
6757
7291
|
};
|
|
6758
|
-
// Adjust a position (REDUCE/INCREASE by %; MARKET or LIMIT)
|
|
6759
7292
|
const adjustPosition$1 = async (positionId, payload) => {
|
|
6760
7293
|
return adjustPosition(apiBaseUrl, positionId, payload);
|
|
6761
7294
|
};
|
|
6762
|
-
// Adjust to absolute target sizes per asset, optionally adding new assets
|
|
6763
7295
|
const adjustAdvancePosition$1 = async (positionId, payload) => {
|
|
6764
|
-
return adjustAdvancePosition(apiBaseUrl, positionId, payload,
|
|
7296
|
+
return adjustAdvancePosition(apiBaseUrl, positionId, payload, hip3Assets);
|
|
6765
7297
|
};
|
|
6766
7298
|
const updateLeverage$1 = async (positionId, leverage) => {
|
|
6767
7299
|
return updateLeverage(apiBaseUrl, positionId, { leverage });
|
|
@@ -6770,22 +7302,46 @@ function usePosition() {
|
|
|
6770
7302
|
const userOpenPositions = useUserData((state) => state.rawOpenPositions);
|
|
6771
7303
|
const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
6772
7304
|
const allMids = useHyperliquidData((state) => state.allMids);
|
|
7305
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
6773
7306
|
const isLoading = useMemo(() => {
|
|
6774
7307
|
return userOpenPositions === null && isConnected;
|
|
6775
7308
|
}, [userOpenPositions, isConnected]);
|
|
6776
7309
|
const openPositions = useMemo(() => {
|
|
6777
7310
|
if (!userOpenPositions || !aggregatedClearingHouseState || !allMids)
|
|
6778
7311
|
return null;
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
7312
|
+
const positions = buildPositionValue(userOpenPositions, aggregatedClearingHouseState, allMids);
|
|
7313
|
+
return enrichPositions(positions, allPerpMetaAssets);
|
|
7314
|
+
}, [
|
|
7315
|
+
userOpenPositions,
|
|
7316
|
+
aggregatedClearingHouseState,
|
|
7317
|
+
allMids,
|
|
7318
|
+
allPerpMetaAssets,
|
|
7319
|
+
]);
|
|
7320
|
+
return {
|
|
7321
|
+
createPosition: createPosition$1,
|
|
7322
|
+
updateRiskParameters: updateRiskParameters$1,
|
|
7323
|
+
closePosition: closePosition$1,
|
|
7324
|
+
closeAllPositions: closeAllPositions$1,
|
|
7325
|
+
adjustPosition: adjustPosition$1,
|
|
7326
|
+
adjustAdvancePosition: adjustAdvancePosition$1,
|
|
7327
|
+
updateLeverage: updateLeverage$1,
|
|
7328
|
+
openPositions,
|
|
7329
|
+
isLoading,
|
|
7330
|
+
};
|
|
6782
7331
|
}
|
|
6783
7332
|
|
|
6784
7333
|
async function adjustOrder(baseUrl, orderId, payload) {
|
|
6785
7334
|
const url = joinUrl(baseUrl, `/orders/${orderId}/adjust`);
|
|
6786
7335
|
try {
|
|
6787
|
-
const resp = await apiClient.put(url, payload, {
|
|
6788
|
-
|
|
7336
|
+
const resp = await apiClient.put(url, payload, {
|
|
7337
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7338
|
+
timeout: 60000,
|
|
7339
|
+
});
|
|
7340
|
+
return {
|
|
7341
|
+
data: resp.data,
|
|
7342
|
+
status: resp.status,
|
|
7343
|
+
headers: resp.headers,
|
|
7344
|
+
};
|
|
6789
7345
|
}
|
|
6790
7346
|
catch (error) {
|
|
6791
7347
|
throw toApiError(error);
|
|
@@ -6794,8 +7350,14 @@ async function adjustOrder(baseUrl, orderId, payload) {
|
|
|
6794
7350
|
async function cancelOrder(baseUrl, orderId) {
|
|
6795
7351
|
const url = joinUrl(baseUrl, `/orders/${orderId}/cancel`);
|
|
6796
7352
|
try {
|
|
6797
|
-
const resp = await apiClient.delete(url, {
|
|
6798
|
-
|
|
7353
|
+
const resp = await apiClient.delete(url, {
|
|
7354
|
+
timeout: 60000,
|
|
7355
|
+
});
|
|
7356
|
+
return {
|
|
7357
|
+
data: resp.data,
|
|
7358
|
+
status: resp.status,
|
|
7359
|
+
headers: resp.headers,
|
|
7360
|
+
};
|
|
6799
7361
|
}
|
|
6800
7362
|
catch (error) {
|
|
6801
7363
|
throw toApiError(error);
|
|
@@ -6805,19 +7367,129 @@ async function cancelTwapOrder(baseUrl, orderId) {
|
|
|
6805
7367
|
const url = joinUrl(baseUrl, `/orders/${orderId}/twap/cancel`);
|
|
6806
7368
|
try {
|
|
6807
7369
|
const resp = await apiClient.post(url, {}, { headers: { 'Content-Type': 'application/json' }, timeout: 60000 });
|
|
6808
|
-
return {
|
|
7370
|
+
return {
|
|
7371
|
+
data: resp.data,
|
|
7372
|
+
status: resp.status,
|
|
7373
|
+
headers: resp.headers,
|
|
7374
|
+
};
|
|
7375
|
+
}
|
|
7376
|
+
catch (error) {
|
|
7377
|
+
throw toApiError(error);
|
|
7378
|
+
}
|
|
7379
|
+
}
|
|
7380
|
+
/**
|
|
7381
|
+
* Execute a spot order (swap) using Pear Hyperliquid service
|
|
7382
|
+
* POST /orders/spot
|
|
7383
|
+
*/
|
|
7384
|
+
async function executeSpotOrder(baseUrl, payload) {
|
|
7385
|
+
const url = joinUrl(baseUrl, '/orders/spot');
|
|
7386
|
+
try {
|
|
7387
|
+
const resp = await apiClient.post(url, payload, {
|
|
7388
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7389
|
+
timeout: 60000,
|
|
7390
|
+
});
|
|
7391
|
+
return {
|
|
7392
|
+
data: resp.data,
|
|
7393
|
+
status: resp.status,
|
|
7394
|
+
headers: resp.headers,
|
|
7395
|
+
};
|
|
6809
7396
|
}
|
|
6810
7397
|
catch (error) {
|
|
6811
7398
|
throw toApiError(error);
|
|
6812
7399
|
}
|
|
6813
7400
|
}
|
|
6814
7401
|
|
|
7402
|
+
function findAssetMeta$2(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
7403
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
7404
|
+
if (!perpMetaAssets) {
|
|
7405
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7406
|
+
}
|
|
7407
|
+
if (desiredCollateral) {
|
|
7408
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
|
|
7409
|
+
if (collateralMatch) {
|
|
7410
|
+
return {
|
|
7411
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7412
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7413
|
+
};
|
|
7414
|
+
}
|
|
7415
|
+
}
|
|
7416
|
+
if (assetName.includes(':')) {
|
|
7417
|
+
const [prefix, symbol] = assetName.split(':');
|
|
7418
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7419
|
+
var _a;
|
|
7420
|
+
return a.name === symbol &&
|
|
7421
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
7422
|
+
});
|
|
7423
|
+
if (exactMatch) {
|
|
7424
|
+
return {
|
|
7425
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7426
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7427
|
+
};
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7430
|
+
if (knownPrefix) {
|
|
7431
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7432
|
+
var _a;
|
|
7433
|
+
return a.name === assetName &&
|
|
7434
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
7435
|
+
});
|
|
7436
|
+
if (exactMatch) {
|
|
7437
|
+
return {
|
|
7438
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
7439
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
7440
|
+
};
|
|
7441
|
+
}
|
|
7442
|
+
}
|
|
7443
|
+
const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
|
|
7444
|
+
if (regularAsset) {
|
|
7445
|
+
return {
|
|
7446
|
+
collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
7447
|
+
marketPrefix: null,
|
|
7448
|
+
};
|
|
7449
|
+
}
|
|
7450
|
+
const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
|
|
7451
|
+
if (hip3Assets.length > 0) {
|
|
7452
|
+
if (desiredCollateral) {
|
|
7453
|
+
const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
|
|
7454
|
+
if (collateralMatch) {
|
|
7455
|
+
return {
|
|
7456
|
+
collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
|
|
7457
|
+
marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
|
|
7458
|
+
};
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7461
|
+
const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
|
|
7462
|
+
const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
|
|
7463
|
+
return {
|
|
7464
|
+
collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
|
|
7465
|
+
marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
|
|
7466
|
+
};
|
|
7467
|
+
}
|
|
7468
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7469
|
+
}
|
|
7470
|
+
function enrichOrderAssets$1(assets, perpMetaAssets) {
|
|
7471
|
+
if (!assets)
|
|
7472
|
+
return [];
|
|
7473
|
+
return assets.map((asset) => {
|
|
7474
|
+
var _a;
|
|
7475
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
7476
|
+
return asset;
|
|
7477
|
+
}
|
|
7478
|
+
const meta = findAssetMeta$2(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
7479
|
+
return {
|
|
7480
|
+
...asset,
|
|
7481
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
7482
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
7483
|
+
};
|
|
7484
|
+
});
|
|
7485
|
+
}
|
|
6815
7486
|
function useOrders() {
|
|
6816
7487
|
const context = useContext(PearHyperliquidContext);
|
|
6817
7488
|
if (!context)
|
|
6818
7489
|
throw new Error('useOrders must be used within a PearHyperliquidProvider');
|
|
6819
7490
|
const { apiBaseUrl } = context;
|
|
6820
7491
|
const openOrders = useUserData((state) => state.openOrders);
|
|
7492
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
6821
7493
|
const isLoading = useMemo(() => openOrders === null && context.isConnected, [openOrders, context.isConnected]);
|
|
6822
7494
|
const { openPositions } = usePosition();
|
|
6823
7495
|
const positionsById = useMemo(() => {
|
|
@@ -6836,19 +7508,27 @@ function useOrders() {
|
|
|
6836
7508
|
const isTpSl = ord.orderType === 'TP' || ord.orderType === 'SL';
|
|
6837
7509
|
const hasAssets = ((_b = (_a = ord.longAssets) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 || ((_d = (_c = ord.shortAssets) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0;
|
|
6838
7510
|
const pos = positionsById.get((_e = ord.positionId) !== null && _e !== void 0 ? _e : '');
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
7511
|
+
let enrichedOrd = {
|
|
7512
|
+
...ord,
|
|
7513
|
+
longAssets: enrichOrderAssets$1(ord.longAssets, allPerpMetaAssets),
|
|
7514
|
+
shortAssets: enrichOrderAssets$1(ord.shortAssets, allPerpMetaAssets),
|
|
7515
|
+
};
|
|
7516
|
+
if (isTpSl && !hasAssets && pos) {
|
|
7517
|
+
const mapAssets = (arr) => arr.map((a) => ({
|
|
7518
|
+
asset: a.coin,
|
|
7519
|
+
weight: a.initialWeight,
|
|
7520
|
+
marketPrefix: a.marketPrefix,
|
|
7521
|
+
collateralToken: a.collateralToken,
|
|
7522
|
+
}));
|
|
7523
|
+
enrichedOrd = {
|
|
7524
|
+
...enrichedOrd,
|
|
6845
7525
|
longAssets: mapAssets(pos.longAssets),
|
|
6846
7526
|
shortAssets: mapAssets(pos.shortAssets),
|
|
6847
7527
|
};
|
|
6848
7528
|
}
|
|
6849
|
-
return
|
|
7529
|
+
return enrichedOrd;
|
|
6850
7530
|
});
|
|
6851
|
-
}, [openOrders, positionsById]);
|
|
7531
|
+
}, [openOrders, positionsById, allPerpMetaAssets]);
|
|
6852
7532
|
const adjustOrder$1 = async (orderId, payload) => {
|
|
6853
7533
|
return adjustOrder(apiBaseUrl, orderId, payload);
|
|
6854
7534
|
};
|
|
@@ -6858,16 +7538,156 @@ function useOrders() {
|
|
|
6858
7538
|
const cancelTwapOrder$1 = async (orderId) => {
|
|
6859
7539
|
return cancelTwapOrder(apiBaseUrl, orderId);
|
|
6860
7540
|
};
|
|
6861
|
-
return {
|
|
7541
|
+
return {
|
|
7542
|
+
adjustOrder: adjustOrder$1,
|
|
7543
|
+
cancelOrder: cancelOrder$1,
|
|
7544
|
+
cancelTwapOrder: cancelTwapOrder$1,
|
|
7545
|
+
openOrders: enrichedOpenOrders,
|
|
7546
|
+
isLoading,
|
|
7547
|
+
};
|
|
7548
|
+
}
|
|
7549
|
+
|
|
7550
|
+
/**
|
|
7551
|
+
* Hook for executing spot orders (swaps) on Hyperliquid
|
|
7552
|
+
* Use this to swap between USDC and USDH or other spot assets
|
|
7553
|
+
*/
|
|
7554
|
+
function useSpotOrder() {
|
|
7555
|
+
const context = useContext(PearHyperliquidContext);
|
|
7556
|
+
if (!context) {
|
|
7557
|
+
throw new Error('useSpotOrder must be used within a PearHyperliquidProvider');
|
|
7558
|
+
}
|
|
7559
|
+
const { apiBaseUrl } = context;
|
|
7560
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
7561
|
+
const [error, setError] = useState(null);
|
|
7562
|
+
const resetError = useCallback(() => {
|
|
7563
|
+
setError(null);
|
|
7564
|
+
}, []);
|
|
7565
|
+
const executeSpotOrder$1 = useCallback(async (payload) => {
|
|
7566
|
+
setIsLoading(true);
|
|
7567
|
+
setError(null);
|
|
7568
|
+
try {
|
|
7569
|
+
const response = await executeSpotOrder(apiBaseUrl, payload);
|
|
7570
|
+
return response;
|
|
7571
|
+
}
|
|
7572
|
+
catch (err) {
|
|
7573
|
+
const apiError = err;
|
|
7574
|
+
setError(apiError);
|
|
7575
|
+
throw apiError;
|
|
7576
|
+
}
|
|
7577
|
+
finally {
|
|
7578
|
+
setIsLoading(false);
|
|
7579
|
+
}
|
|
7580
|
+
}, [apiBaseUrl]);
|
|
7581
|
+
return {
|
|
7582
|
+
executeSpotOrder: executeSpotOrder$1,
|
|
7583
|
+
isLoading,
|
|
7584
|
+
error,
|
|
7585
|
+
resetError,
|
|
7586
|
+
};
|
|
6862
7587
|
}
|
|
6863
7588
|
|
|
7589
|
+
function findAssetMeta$1(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
7590
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
7591
|
+
if (!perpMetaAssets) {
|
|
7592
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7593
|
+
}
|
|
7594
|
+
if (desiredCollateral) {
|
|
7595
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
|
|
7596
|
+
if (collateralMatch) {
|
|
7597
|
+
return {
|
|
7598
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7599
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7600
|
+
};
|
|
7601
|
+
}
|
|
7602
|
+
}
|
|
7603
|
+
if (assetName.includes(':')) {
|
|
7604
|
+
const [prefix, symbol] = assetName.split(':');
|
|
7605
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7606
|
+
var _a;
|
|
7607
|
+
return a.name === symbol &&
|
|
7608
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
7609
|
+
});
|
|
7610
|
+
if (exactMatch) {
|
|
7611
|
+
return {
|
|
7612
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7613
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7614
|
+
};
|
|
7615
|
+
}
|
|
7616
|
+
}
|
|
7617
|
+
if (knownPrefix) {
|
|
7618
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7619
|
+
var _a;
|
|
7620
|
+
return a.name === assetName &&
|
|
7621
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
7622
|
+
});
|
|
7623
|
+
if (exactMatch) {
|
|
7624
|
+
return {
|
|
7625
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
7626
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
7627
|
+
};
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
|
|
7631
|
+
if (regularAsset) {
|
|
7632
|
+
return {
|
|
7633
|
+
collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
7634
|
+
marketPrefix: null,
|
|
7635
|
+
};
|
|
7636
|
+
}
|
|
7637
|
+
const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
|
|
7638
|
+
if (hip3Assets.length > 0) {
|
|
7639
|
+
if (desiredCollateral) {
|
|
7640
|
+
const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
|
|
7641
|
+
if (collateralMatch) {
|
|
7642
|
+
return {
|
|
7643
|
+
collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
|
|
7644
|
+
marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
|
|
7645
|
+
};
|
|
7646
|
+
}
|
|
7647
|
+
}
|
|
7648
|
+
const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
|
|
7649
|
+
const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
|
|
7650
|
+
return {
|
|
7651
|
+
collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
|
|
7652
|
+
marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
|
|
7653
|
+
};
|
|
7654
|
+
}
|
|
7655
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7656
|
+
}
|
|
7657
|
+
function enrichOrderAssets(assets, perpMetaAssets) {
|
|
7658
|
+
if (!assets)
|
|
7659
|
+
return [];
|
|
7660
|
+
return assets.map((asset) => {
|
|
7661
|
+
var _a;
|
|
7662
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
7663
|
+
return asset;
|
|
7664
|
+
}
|
|
7665
|
+
const meta = findAssetMeta$1(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
7666
|
+
return {
|
|
7667
|
+
...asset,
|
|
7668
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
7669
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
7670
|
+
};
|
|
7671
|
+
});
|
|
7672
|
+
}
|
|
7673
|
+
function enrichTwapOrders(orders, perpMetaAssets) {
|
|
7674
|
+
return orders.map((order) => ({
|
|
7675
|
+
...order,
|
|
7676
|
+
longAssets: enrichOrderAssets(order.longAssets, perpMetaAssets),
|
|
7677
|
+
shortAssets: enrichOrderAssets(order.shortAssets, perpMetaAssets),
|
|
7678
|
+
}));
|
|
7679
|
+
}
|
|
6864
7680
|
function useTwap() {
|
|
6865
|
-
const twapDetails = useUserData(state => state.twapDetails);
|
|
7681
|
+
const twapDetails = useUserData((state) => state.twapDetails);
|
|
7682
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
6866
7683
|
const context = useContext(PearHyperliquidContext);
|
|
6867
7684
|
if (!context)
|
|
6868
7685
|
throw new Error('useTwap must be used within a PearHyperliquidProvider');
|
|
6869
7686
|
const { apiBaseUrl } = context;
|
|
6870
|
-
const orders = useMemo(() =>
|
|
7687
|
+
const orders = useMemo(() => {
|
|
7688
|
+
const rawOrders = twapDetails !== null && twapDetails !== void 0 ? twapDetails : [];
|
|
7689
|
+
return enrichTwapOrders(rawOrders, allPerpMetaAssets);
|
|
7690
|
+
}, [twapDetails, allPerpMetaAssets]);
|
|
6871
7691
|
const cancelTwap$1 = async (orderId) => {
|
|
6872
7692
|
return cancelTwap(apiBaseUrl, orderId);
|
|
6873
7693
|
};
|
|
@@ -6950,59 +7770,170 @@ function useNotifications() {
|
|
|
6950
7770
|
};
|
|
6951
7771
|
}
|
|
6952
7772
|
|
|
6953
|
-
//
|
|
7773
|
+
// Helper to find asset metadata from perpMetaAssets
|
|
7774
|
+
function findAssetMeta(assetName, perpMetaAssets) {
|
|
7775
|
+
var _a, _b, _c, _d;
|
|
7776
|
+
if (!perpMetaAssets) {
|
|
7777
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7778
|
+
}
|
|
7779
|
+
// Try exact match first (for prefixed assets like "xyz:TSLA")
|
|
7780
|
+
const exactMatch = perpMetaAssets.find((a) => a.name === assetName);
|
|
7781
|
+
if (exactMatch) {
|
|
7782
|
+
return {
|
|
7783
|
+
collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7784
|
+
marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7785
|
+
};
|
|
7786
|
+
}
|
|
7787
|
+
// Try matching by base symbol (for non-prefixed names in data)
|
|
7788
|
+
const baseMatch = perpMetaAssets.find((a) => {
|
|
7789
|
+
const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
|
|
7790
|
+
return baseName === assetName;
|
|
7791
|
+
});
|
|
7792
|
+
if (baseMatch) {
|
|
7793
|
+
return {
|
|
7794
|
+
collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7795
|
+
marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7796
|
+
};
|
|
7797
|
+
}
|
|
7798
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7799
|
+
}
|
|
7800
|
+
// Enrich a single asset with metadata
|
|
7801
|
+
function enrichAsset(asset, perpMetaAssets) {
|
|
7802
|
+
const meta = findAssetMeta(asset.asset, perpMetaAssets);
|
|
7803
|
+
return {
|
|
7804
|
+
...asset,
|
|
7805
|
+
collateralToken: meta.collateralToken,
|
|
7806
|
+
marketPrefix: meta.marketPrefix,
|
|
7807
|
+
};
|
|
7808
|
+
}
|
|
7809
|
+
// Enrich a basket item with collateral info
|
|
7810
|
+
function enrichBasketItem(item, perpMetaAssets) {
|
|
7811
|
+
const enrichedLongs = item.longAssets.map((a) => enrichAsset(a, perpMetaAssets));
|
|
7812
|
+
const enrichedShorts = item.shortAssets.map((a) => enrichAsset(a, perpMetaAssets));
|
|
7813
|
+
// Determine collateral type
|
|
7814
|
+
const allAssets = [...enrichedLongs, ...enrichedShorts];
|
|
7815
|
+
const hasUsdc = allAssets.some((a) => a.collateralToken === 'USDC');
|
|
7816
|
+
const hasUsdh = allAssets.some((a) => a.collateralToken === 'USDH');
|
|
7817
|
+
let collateralType = 'USDC';
|
|
7818
|
+
if (hasUsdc && hasUsdh) {
|
|
7819
|
+
collateralType = 'MIXED';
|
|
7820
|
+
}
|
|
7821
|
+
else if (hasUsdh) {
|
|
7822
|
+
collateralType = 'USDH';
|
|
7823
|
+
}
|
|
7824
|
+
return {
|
|
7825
|
+
...item,
|
|
7826
|
+
longAssets: enrichedLongs,
|
|
7827
|
+
shortAssets: enrichedShorts,
|
|
7828
|
+
collateralType,
|
|
7829
|
+
};
|
|
7830
|
+
}
|
|
7831
|
+
/**
|
|
7832
|
+
* Filter baskets by collateral type
|
|
7833
|
+
* - 'USDC': Only baskets where ALL assets use USDC (collateralType === 'USDC')
|
|
7834
|
+
* - 'USDH': Only baskets where ALL assets use USDH (collateralType === 'USDH')
|
|
7835
|
+
* - 'ALL' or undefined: No filtering, returns all baskets
|
|
7836
|
+
*/
|
|
7837
|
+
function filterByCollateral(baskets, filter) {
|
|
7838
|
+
if (!filter || filter === 'ALL') {
|
|
7839
|
+
return baskets;
|
|
7840
|
+
}
|
|
7841
|
+
return baskets.filter((basket) => {
|
|
7842
|
+
if (filter === 'USDC') {
|
|
7843
|
+
// Include baskets that are purely USDC or have USDC assets
|
|
7844
|
+
return (basket.collateralType === 'USDC' || basket.collateralType === 'MIXED');
|
|
7845
|
+
}
|
|
7846
|
+
if (filter === 'USDH') {
|
|
7847
|
+
// Include baskets that are purely USDH or have USDH assets
|
|
7848
|
+
return (basket.collateralType === 'USDH' || basket.collateralType === 'MIXED');
|
|
7849
|
+
}
|
|
7850
|
+
return true;
|
|
7851
|
+
});
|
|
7852
|
+
}
|
|
7853
|
+
// Base selector for the full market-data payload (raw from WS)
|
|
6954
7854
|
const useMarketDataPayload = () => {
|
|
6955
7855
|
return useMarketData((s) => s.marketData);
|
|
6956
7856
|
};
|
|
6957
|
-
// Full payload for 'market-data-all' channel
|
|
7857
|
+
// Full payload for 'market-data-all' channel (raw from WS)
|
|
6958
7858
|
const useMarketDataAllPayload = () => {
|
|
6959
7859
|
return useMarketData((s) => s.marketDataAll);
|
|
6960
7860
|
};
|
|
6961
|
-
//
|
|
6962
|
-
const
|
|
6963
|
-
|
|
7861
|
+
// Access perpMetaAssets for enrichment
|
|
7862
|
+
const usePerpMetaAssets = () => {
|
|
7863
|
+
return useHyperliquidData((s) => s.perpMetaAssets);
|
|
7864
|
+
};
|
|
7865
|
+
// Active baskets (with collateral and market prefix info)
|
|
7866
|
+
const useActiveBaskets = (collateralFilter) => {
|
|
6964
7867
|
const data = useMarketDataPayload();
|
|
6965
|
-
|
|
7868
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7869
|
+
return useMemo(() => {
|
|
7870
|
+
if (!(data === null || data === void 0 ? void 0 : data.active))
|
|
7871
|
+
return [];
|
|
7872
|
+
const enriched = data.active.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7873
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7874
|
+
}, [data, perpMetaAssets, collateralFilter]);
|
|
6966
7875
|
};
|
|
6967
|
-
// Top gainers (
|
|
6968
|
-
const useTopGainers = (limit) => {
|
|
7876
|
+
// Top gainers (with collateral and market prefix info)
|
|
7877
|
+
const useTopGainers = (limit, collateralFilter) => {
|
|
6969
7878
|
const data = useMarketDataPayload();
|
|
7879
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
6970
7880
|
return useMemo(() => {
|
|
6971
7881
|
var _a;
|
|
6972
7882
|
const list = (_a = data === null || data === void 0 ? void 0 : data.topGainers) !== null && _a !== void 0 ? _a : [];
|
|
6973
|
-
|
|
6974
|
-
|
|
7883
|
+
const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
|
|
7884
|
+
const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7885
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7886
|
+
}, [data, perpMetaAssets, limit, collateralFilter]);
|
|
6975
7887
|
};
|
|
6976
|
-
// Top losers (
|
|
6977
|
-
const useTopLosers = (limit) => {
|
|
7888
|
+
// Top losers (with collateral and market prefix info)
|
|
7889
|
+
const useTopLosers = (limit, collateralFilter) => {
|
|
6978
7890
|
const data = useMarketDataPayload();
|
|
7891
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
6979
7892
|
return useMemo(() => {
|
|
6980
7893
|
var _a;
|
|
6981
7894
|
const list = (_a = data === null || data === void 0 ? void 0 : data.topLosers) !== null && _a !== void 0 ? _a : [];
|
|
6982
|
-
|
|
6983
|
-
|
|
7895
|
+
const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
|
|
7896
|
+
const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7897
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7898
|
+
}, [data, perpMetaAssets, limit, collateralFilter]);
|
|
6984
7899
|
};
|
|
6985
|
-
// Highlighted baskets
|
|
6986
|
-
const useHighlightedBaskets = () => {
|
|
6987
|
-
var _a;
|
|
7900
|
+
// Highlighted baskets (with collateral and market prefix info)
|
|
7901
|
+
const useHighlightedBaskets = (collateralFilter) => {
|
|
6988
7902
|
const data = useMarketDataPayload();
|
|
6989
|
-
|
|
7903
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7904
|
+
return useMemo(() => {
|
|
7905
|
+
if (!(data === null || data === void 0 ? void 0 : data.highlighted))
|
|
7906
|
+
return [];
|
|
7907
|
+
const enriched = data.highlighted.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7908
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7909
|
+
}, [data, perpMetaAssets, collateralFilter]);
|
|
6990
7910
|
};
|
|
6991
|
-
// Watchlist baskets (
|
|
6992
|
-
const useWatchlistBaskets = () => {
|
|
6993
|
-
var _a;
|
|
7911
|
+
// Watchlist baskets (with collateral and market prefix info)
|
|
7912
|
+
const useWatchlistBaskets = (collateralFilter) => {
|
|
6994
7913
|
const data = useMarketDataPayload();
|
|
6995
|
-
|
|
7914
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7915
|
+
return useMemo(() => {
|
|
7916
|
+
if (!(data === null || data === void 0 ? void 0 : data.watchlist))
|
|
7917
|
+
return [];
|
|
7918
|
+
const enriched = data.watchlist.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7919
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7920
|
+
}, [data, perpMetaAssets, collateralFilter]);
|
|
6996
7921
|
};
|
|
6997
|
-
// All baskets (
|
|
6998
|
-
const useAllBaskets = () => {
|
|
6999
|
-
var _a;
|
|
7922
|
+
// All baskets (with collateral and market prefix info)
|
|
7923
|
+
const useAllBaskets = (collateralFilter) => {
|
|
7000
7924
|
const dataAll = useMarketDataAllPayload();
|
|
7001
|
-
|
|
7925
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7926
|
+
return useMemo(() => {
|
|
7927
|
+
if (!(dataAll === null || dataAll === void 0 ? void 0 : dataAll.all))
|
|
7928
|
+
return [];
|
|
7929
|
+
const enriched = dataAll.all.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7930
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7931
|
+
}, [dataAll, perpMetaAssets, collateralFilter]);
|
|
7002
7932
|
};
|
|
7003
7933
|
// Find a basket by its exact asset composition (order-insensitive)
|
|
7004
7934
|
const useFindBasket = (longs, shorts) => {
|
|
7005
7935
|
const data = useMarketDataPayload();
|
|
7936
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7006
7937
|
return useMemo(() => {
|
|
7007
7938
|
if (!data)
|
|
7008
7939
|
return undefined;
|
|
@@ -7016,17 +7947,28 @@ const useFindBasket = (longs, shorts) => {
|
|
|
7016
7947
|
: '';
|
|
7017
7948
|
const lKey = normalize(longs);
|
|
7018
7949
|
const sKey = normalize(shorts);
|
|
7019
|
-
const match = (item) => normalize(item.longAssets) === lKey &&
|
|
7020
|
-
|
|
7021
|
-
|
|
7950
|
+
const match = (item) => normalize(item.longAssets) === lKey &&
|
|
7951
|
+
normalize(item.shortAssets) === sKey;
|
|
7952
|
+
const found = data.active.find(match) || data.highlighted.find(match);
|
|
7953
|
+
return found
|
|
7954
|
+
? enrichBasketItem(found, perpMetaAssets)
|
|
7955
|
+
: undefined;
|
|
7956
|
+
}, [data, longs, shorts, perpMetaAssets]);
|
|
7022
7957
|
};
|
|
7023
7958
|
|
|
7024
|
-
async function toggleWatchlist(baseUrl, longAssets, shortAssets,
|
|
7959
|
+
async function toggleWatchlist(baseUrl, longAssets, shortAssets, hip3Assets) {
|
|
7025
7960
|
const url = joinUrl(baseUrl, '/watchlist');
|
|
7026
|
-
const mapAssets = (arr) => arr.map(a => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
7961
|
+
const mapAssets = (arr) => arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
7027
7962
|
try {
|
|
7028
|
-
const response = await apiClient.post(url, {
|
|
7029
|
-
|
|
7963
|
+
const response = await apiClient.post(url, {
|
|
7964
|
+
longAssets: mapAssets(longAssets),
|
|
7965
|
+
shortAssets: mapAssets(shortAssets),
|
|
7966
|
+
}, { headers: { 'Content-Type': 'application/json' } });
|
|
7967
|
+
return {
|
|
7968
|
+
data: response.data,
|
|
7969
|
+
status: response.status,
|
|
7970
|
+
headers: response.headers,
|
|
7971
|
+
};
|
|
7030
7972
|
}
|
|
7031
7973
|
catch (error) {
|
|
7032
7974
|
throw toApiError(error);
|
|
@@ -7038,11 +7980,11 @@ function useWatchlist() {
|
|
|
7038
7980
|
if (!context)
|
|
7039
7981
|
throw new Error('useWatchlist must be used within a PearHyperliquidProvider');
|
|
7040
7982
|
const { apiBaseUrl, isConnected } = context;
|
|
7041
|
-
const
|
|
7983
|
+
const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
|
|
7042
7984
|
const marketData = useMarketDataPayload();
|
|
7043
7985
|
const isLoading = useMemo(() => !marketData && isConnected, [marketData, isConnected]);
|
|
7044
7986
|
const toggle = async (longAssets, shortAssets) => {
|
|
7045
|
-
const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets,
|
|
7987
|
+
const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, hip3Assets);
|
|
7046
7988
|
// Server will push updated market-data over WS; nothing to set here
|
|
7047
7989
|
return resp;
|
|
7048
7990
|
};
|
|
@@ -7296,16 +8238,111 @@ function useAuth() {
|
|
|
7296
8238
|
};
|
|
7297
8239
|
}
|
|
7298
8240
|
|
|
8241
|
+
const useAllUserBalances = () => {
|
|
8242
|
+
const spotState = useUserData((state) => state.spotState);
|
|
8243
|
+
const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
8244
|
+
const rawClearinghouseStates = useHyperliquidData((state) => state.rawClearinghouseStates);
|
|
8245
|
+
const activeAssetData = useHyperliquidData((state) => state.activeAssetData);
|
|
8246
|
+
const { longTokensMetadata, shortTokensMetadata } = useTokenSelectionMetadata();
|
|
8247
|
+
return useMemo(() => {
|
|
8248
|
+
const isLoading = !spotState || !aggregatedClearingHouseState;
|
|
8249
|
+
// Helper function to truncate to 2 decimal places without rounding
|
|
8250
|
+
const truncateToTwoDecimals = (value) => {
|
|
8251
|
+
return Math.floor(value * 100) / 100;
|
|
8252
|
+
};
|
|
8253
|
+
// Get spot balances from spotState
|
|
8254
|
+
let spotUsdcBal = undefined;
|
|
8255
|
+
let spotUsdhBal = undefined;
|
|
8256
|
+
if (spotState) {
|
|
8257
|
+
const balances = spotState.balances || [];
|
|
8258
|
+
for (const balance of balances) {
|
|
8259
|
+
const total = parseFloat(balance.total || '0');
|
|
8260
|
+
if (balance.coin === 'USDC') {
|
|
8261
|
+
spotUsdcBal = truncateToTwoDecimals(total);
|
|
8262
|
+
}
|
|
8263
|
+
if (balance.coin === 'USDH') {
|
|
8264
|
+
spotUsdhBal = truncateToTwoDecimals(total);
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
}
|
|
8268
|
+
let availableToTradeUsdhFromAsset = 0;
|
|
8269
|
+
// This activeAssetData only contains data for SELECTED tokens (user's long and short Tokens)
|
|
8270
|
+
// It does NOT contain data for all tokens, so we cannot reliably use it for available to trade as used on hl trade page
|
|
8271
|
+
// so intead, we rely on rawClearinghouseStates which provides market-specific data
|
|
8272
|
+
// if (activeAssetData) {
|
|
8273
|
+
// Object.values(activeAssetData).forEach((assetData) => {
|
|
8274
|
+
// if (!assetData.availableToTrade) return;
|
|
8275
|
+
// const coinSymbol = assetData.coin;
|
|
8276
|
+
// const availableValue = truncateToTwoDecimals(
|
|
8277
|
+
// parseFloat(assetData.availableToTrade[0] || '0'),
|
|
8278
|
+
// );
|
|
8279
|
+
// // Determine collateral type based on market prefix
|
|
8280
|
+
// // HIP3 markets have prefix: "xyz:SYMBOL", "flx:SYMBOL", "vntl:SYMBOL", etc.
|
|
8281
|
+
// if (coinSymbol.includes(':')) {
|
|
8282
|
+
// const prefix = coinSymbol.split(':')[0];
|
|
8283
|
+
// if (prefix === 'xyz') {
|
|
8284
|
+
// // xyz markets use USDC
|
|
8285
|
+
// availableToTradeUsdcFromAsset = availableValue;
|
|
8286
|
+
// } else {
|
|
8287
|
+
// // flx, vntl, hyna and other markets use USDH
|
|
8288
|
+
// availableToTradeUsdhFromAsset = availableValue;
|
|
8289
|
+
// }
|
|
8290
|
+
// } else {
|
|
8291
|
+
// // Regular markets without prefix are automatically USDC
|
|
8292
|
+
// availableToTradeUsdcFromAsset = availableValue;
|
|
8293
|
+
// }
|
|
8294
|
+
// });
|
|
8295
|
+
// }
|
|
8296
|
+
// Calculate USDC available to trade
|
|
8297
|
+
// Priority 1: Use value from activeAssetData if available (> 0)
|
|
8298
|
+
// Priority 2: Calculate from USDC-specific clearinghouseState (empty prefix)
|
|
8299
|
+
let availableToTradeUsdcValue = undefined;
|
|
8300
|
+
if (rawClearinghouseStates) {
|
|
8301
|
+
// Find USDC market (empty prefix)
|
|
8302
|
+
const usdcMarket = rawClearinghouseStates.find(([prefix]) => prefix === '');
|
|
8303
|
+
const usdcState = usdcMarket === null || usdcMarket === void 0 ? void 0 : usdcMarket[1];
|
|
8304
|
+
if (usdcState === null || usdcState === void 0 ? void 0 : usdcState.marginSummary) {
|
|
8305
|
+
const accountValue = parseFloat(usdcState.marginSummary.accountValue || '0');
|
|
8306
|
+
const totalMarginUsed = parseFloat(usdcState.marginSummary.totalMarginUsed || '0');
|
|
8307
|
+
const calculatedValue = Math.max(0, accountValue - totalMarginUsed);
|
|
8308
|
+
availableToTradeUsdcValue = truncateToTwoDecimals(calculatedValue);
|
|
8309
|
+
}
|
|
8310
|
+
}
|
|
8311
|
+
// Calculate USDH available to trade
|
|
8312
|
+
// Priority 1: Use value from activeAssetData if available (> 0)
|
|
8313
|
+
// Priority 2: Use spot USDH balance
|
|
8314
|
+
const availableToTradeUsdhValue = availableToTradeUsdhFromAsset > 0
|
|
8315
|
+
? availableToTradeUsdhFromAsset
|
|
8316
|
+
: spotUsdhBal;
|
|
8317
|
+
return {
|
|
8318
|
+
spotUsdcBalance: spotUsdcBal,
|
|
8319
|
+
availableToTradeUsdc: availableToTradeUsdcValue,
|
|
8320
|
+
spotUsdhBalance: spotUsdhBal,
|
|
8321
|
+
availableToTradeUsdh: availableToTradeUsdhValue,
|
|
8322
|
+
isLoading,
|
|
8323
|
+
};
|
|
8324
|
+
}, [
|
|
8325
|
+
spotState,
|
|
8326
|
+
aggregatedClearingHouseState,
|
|
8327
|
+
rawClearinghouseStates,
|
|
8328
|
+
activeAssetData,
|
|
8329
|
+
longTokensMetadata,
|
|
8330
|
+
shortTokensMetadata,
|
|
8331
|
+
]);
|
|
8332
|
+
};
|
|
8333
|
+
|
|
7299
8334
|
const PearHyperliquidContext = createContext(undefined);
|
|
7300
8335
|
/**
|
|
7301
8336
|
* React Provider for PearHyperliquidClient
|
|
7302
8337
|
*/
|
|
7303
|
-
const PearHyperliquidProvider = ({ children, apiBaseUrl =
|
|
8338
|
+
const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearprotocol.io', clientId = 'PEARPROTOCOLUI', wsUrl = 'wss://hl-ui.pearprotocol.io/ws', }) => {
|
|
7304
8339
|
const address = useUserData((s) => s.address);
|
|
7305
8340
|
const setAddress = useUserData((s) => s.setAddress);
|
|
7306
8341
|
const perpsMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
|
|
7307
8342
|
const setPerpMetaAssets = useHyperliquidData((state) => state.setPerpMetaAssets);
|
|
7308
|
-
const
|
|
8343
|
+
const setAllPerpMetaAssets = useHyperliquidData((state) => state.setAllPerpMetaAssets);
|
|
8344
|
+
const setHip3Assets = useHyperliquidData((state) => state.setHip3Assets);
|
|
8345
|
+
const setHip3MarketPrefixes = useHyperliquidData((state) => state.setHip3MarketPrefixes);
|
|
7309
8346
|
const websocketsEnabled = useMemo(() => Array.isArray(perpsMetaAssets) && perpsMetaAssets.length > 0, [perpsMetaAssets]);
|
|
7310
8347
|
const { isConnected, lastError } = useHyperliquidWebSocket({
|
|
7311
8348
|
wsUrl,
|
|
@@ -7320,32 +8357,107 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
|
|
|
7320
8357
|
if (perpsMetaAssets === null) {
|
|
7321
8358
|
fetchAllPerpMetas()
|
|
7322
8359
|
.then((res) => {
|
|
7323
|
-
|
|
7324
|
-
const
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
const
|
|
7329
|
-
const
|
|
7330
|
-
|
|
7331
|
-
const
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
const
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
8360
|
+
const assetToMarkets = new Map();
|
|
8361
|
+
const marketPrefixes = new Map();
|
|
8362
|
+
const FILTERED_PREFIXES = ['hyna'];
|
|
8363
|
+
// Group assets by market prefix to match WebSocket flattening order
|
|
8364
|
+
// WebSocket sends in order: "", "flx", "hyna", "vntl", "xyz" (we filter out hyna)
|
|
8365
|
+
const assetsByPrefix = new Map();
|
|
8366
|
+
const allAssetsByPrefix = new Map();
|
|
8367
|
+
res.data.forEach((item) => {
|
|
8368
|
+
const collateralToken = item.collateralToken === 360 ? 'USDH' : 'USDC';
|
|
8369
|
+
item.universe.forEach((asset) => {
|
|
8370
|
+
var _a;
|
|
8371
|
+
const [maybePrefix, maybeMarket] = asset.name.split(':');
|
|
8372
|
+
if (maybeMarket) {
|
|
8373
|
+
// HIP3 asset with market prefix
|
|
8374
|
+
const prefix = maybePrefix.toLowerCase();
|
|
8375
|
+
const displayName = maybeMarket;
|
|
8376
|
+
const fullName = `${prefix}:${displayName}`;
|
|
8377
|
+
marketPrefixes.set(fullName, prefix);
|
|
8378
|
+
if (!FILTERED_PREFIXES.includes(prefix)) {
|
|
8379
|
+
const existingMarkets = (_a = assetToMarkets.get(displayName)) !== null && _a !== void 0 ? _a : [];
|
|
8380
|
+
if (!existingMarkets.includes(fullName)) {
|
|
8381
|
+
assetToMarkets.set(displayName, [
|
|
8382
|
+
...existingMarkets,
|
|
8383
|
+
fullName,
|
|
8384
|
+
]);
|
|
8385
|
+
}
|
|
8386
|
+
}
|
|
8387
|
+
const assetWithMeta = {
|
|
8388
|
+
...asset,
|
|
8389
|
+
name: displayName,
|
|
8390
|
+
marketPrefix: prefix,
|
|
8391
|
+
collateralToken,
|
|
8392
|
+
};
|
|
8393
|
+
// Group by market prefix
|
|
8394
|
+
const allList = allAssetsByPrefix.get(prefix) || [];
|
|
8395
|
+
allList.push(assetWithMeta);
|
|
8396
|
+
allAssetsByPrefix.set(prefix, allList);
|
|
8397
|
+
if (!FILTERED_PREFIXES.includes(prefix)) {
|
|
8398
|
+
const cleanedList = assetsByPrefix.get(prefix) || [];
|
|
8399
|
+
cleanedList.push(assetWithMeta);
|
|
8400
|
+
assetsByPrefix.set(prefix, cleanedList);
|
|
8401
|
+
}
|
|
8402
|
+
}
|
|
8403
|
+
else {
|
|
8404
|
+
// Default market asset (no prefix)
|
|
8405
|
+
const assetWithMeta = {
|
|
8406
|
+
...asset,
|
|
8407
|
+
collateralToken,
|
|
8408
|
+
};
|
|
8409
|
+
// Add to default market group ("")
|
|
8410
|
+
const defaultList = assetsByPrefix.get('') || [];
|
|
8411
|
+
defaultList.push(assetWithMeta);
|
|
8412
|
+
assetsByPrefix.set('', defaultList);
|
|
8413
|
+
const allDefaultList = allAssetsByPrefix.get('') || [];
|
|
8414
|
+
allDefaultList.push(assetWithMeta);
|
|
8415
|
+
allAssetsByPrefix.set('', allDefaultList);
|
|
8416
|
+
}
|
|
8417
|
+
});
|
|
8418
|
+
});
|
|
8419
|
+
// Flatten in consistent order: default market first, then HIP3 markets alphabetically
|
|
8420
|
+
// This ensures both REST API and WebSocket data align properly
|
|
8421
|
+
const cleanedPrefixes = Array.from(assetsByPrefix.keys()).sort((a, b) => {
|
|
8422
|
+
// Empty prefix (default market) always comes first
|
|
8423
|
+
if (a === '' && b !== '')
|
|
8424
|
+
return -1;
|
|
8425
|
+
if (a !== '' && b === '')
|
|
8426
|
+
return 1;
|
|
8427
|
+
// HIP3 markets sorted alphabetically
|
|
8428
|
+
return a.localeCompare(b);
|
|
7341
8429
|
});
|
|
7342
|
-
|
|
8430
|
+
const allPrefixes = Array.from(allAssetsByPrefix.keys()).sort((a, b) => {
|
|
8431
|
+
if (a === '' && b !== '')
|
|
8432
|
+
return -1;
|
|
8433
|
+
if (a !== '' && b === '')
|
|
8434
|
+
return 1;
|
|
8435
|
+
return a.localeCompare(b);
|
|
8436
|
+
});
|
|
8437
|
+
const cleanedPerpMetas = [];
|
|
8438
|
+
const allPerpMetas = [];
|
|
8439
|
+
cleanedPrefixes.forEach((prefix) => {
|
|
8440
|
+
const assets = assetsByPrefix.get(prefix) || [];
|
|
8441
|
+
cleanedPerpMetas.push(...assets);
|
|
8442
|
+
});
|
|
8443
|
+
allPrefixes.forEach((prefix) => {
|
|
8444
|
+
const assets = allAssetsByPrefix.get(prefix) || [];
|
|
8445
|
+
allPerpMetas.push(...assets);
|
|
8446
|
+
});
|
|
8447
|
+
setHip3Assets(assetToMarkets);
|
|
8448
|
+
setHip3MarketPrefixes(marketPrefixes);
|
|
7343
8449
|
setPerpMetaAssets(cleanedPerpMetas);
|
|
8450
|
+
setAllPerpMetaAssets(allPerpMetas);
|
|
7344
8451
|
})
|
|
7345
8452
|
.catch(() => { });
|
|
7346
8453
|
}
|
|
7347
|
-
}, [
|
|
7348
|
-
|
|
8454
|
+
}, [
|
|
8455
|
+
perpsMetaAssets,
|
|
8456
|
+
setPerpMetaAssets,
|
|
8457
|
+
setAllPerpMetaAssets,
|
|
8458
|
+
setHip3Assets,
|
|
8459
|
+
setHip3MarketPrefixes,
|
|
8460
|
+
]);
|
|
7349
8461
|
useAutoSyncFills({
|
|
7350
8462
|
baseUrl: apiBaseUrl,
|
|
7351
8463
|
address,
|
|
@@ -7383,7 +8495,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
|
|
|
7383
8495
|
function usePearHyperliquid() {
|
|
7384
8496
|
const ctx = useContext(PearHyperliquidContext);
|
|
7385
8497
|
if (!ctx)
|
|
7386
|
-
throw new Error(
|
|
8498
|
+
throw new Error('usePearHyperliquid must be used within a PearHyperliquidProvider');
|
|
7387
8499
|
return ctx;
|
|
7388
8500
|
}
|
|
7389
8501
|
|
|
@@ -7474,4 +8586,4 @@ function mapCandleIntervalToTradingViewInterval(interval) {
|
|
|
7474
8586
|
}
|
|
7475
8587
|
}
|
|
7476
8588
|
|
|
7477
|
-
export { AccountSummaryCalculator, ConflictDetector, MAX_ASSETS_PER_LEG, MINIMUM_ASSET_USD_VALUE, MaxAssetsPerLegError, MinimumPositionSizeError, PearHyperliquidProvider, TokenMetadataExtractor, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, getCompleteTimestamps, getPortfolio, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toggleWatchlist, updateLeverage, updateRiskParameters, useAccountSummary, useActiveBaskets, useAgentWallet, useAllBaskets, useAuth, useAutoSyncFills, useBasketCandles, useFindBasket, useHighlightedBaskets, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMarketData, useMarketDataAllPayload, useMarketDataPayload, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePortfolio, usePosition, useTokenSelectionMetadata, useTopGainers, useTopLosers, useTradeHistories, useTwap, useUserSelection, useWatchlist, useWatchlistBaskets, useWebData, validateMaxAssetsPerLeg, validateMinimumAssetSize, validatePositionSize };
|
|
8589
|
+
export { AccountSummaryCalculator, ConflictDetector, MAX_ASSETS_PER_LEG, MINIMUM_ASSET_USD_VALUE, MaxAssetsPerLegError, MinimumPositionSizeError, PearHyperliquidProvider, TokenMetadataExtractor, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, executeSpotOrder, getAvailableMarkets, getCompleteTimestamps, getMarketPrefix, getPortfolio, isHip3Market, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toBackendSymbol, toBackendSymbolWithMarket, toDisplaySymbol, toggleWatchlist, updateLeverage, updateRiskParameters, useAccountSummary, useActiveBaskets, useAgentWallet, useAllBaskets, useAllUserBalances, useAuth, useAutoSyncFills, useBasketCandles, useFindBasket, useHighlightedBaskets, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMarketData, useMarketDataAllPayload, useMarketDataPayload, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePerpMetaAssets, usePortfolio, usePosition, useSpotOrder, useTokenSelectionMetadata, useTopGainers, useTopLosers, useTradeHistories, useTwap, useUserSelection, useWatchlist, useWatchlistBaskets, useWebData, validateMaxAssetsPerLeg, validateMinimumAssetSize, validatePositionSize };
|