@pear-protocol/hyperliquid-sdk 0.0.73-beta.2 → 0.0.73-beta.5
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/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 +218 -40
- package/dist/index.js +1264 -199
- package/dist/provider.d.ts +1 -1
- package/dist/store/hyperliquidDataStore.d.ts +9 -3
- package/dist/store/userDataStore.d.ts +3 -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 +2 -2
package/dist/index.js
CHANGED
|
@@ -64,18 +64,12 @@ const useUserData = create((set) => ({
|
|
|
64
64
|
twapDetails: null,
|
|
65
65
|
notifications: null,
|
|
66
66
|
userExtraAgents: null,
|
|
67
|
+
spotState: null,
|
|
67
68
|
setAccessToken: (token) => set({ accessToken: token }),
|
|
68
69
|
setRefreshToken: (token) => set({ refreshToken: token }),
|
|
69
70
|
setIsAuthenticated: (value) => set({ isAuthenticated: value }),
|
|
70
71
|
setIsReady: (value) => set({ isReady: value }),
|
|
71
72
|
setAddress: (address) => {
|
|
72
|
-
// if (typeof window !== "undefined") {
|
|
73
|
-
// if (address) {
|
|
74
|
-
// window.localStorage.setItem("address", address);
|
|
75
|
-
// } else {
|
|
76
|
-
// window.localStorage.removeItem("address");
|
|
77
|
-
// }
|
|
78
|
-
// }
|
|
79
73
|
set({ address });
|
|
80
74
|
},
|
|
81
75
|
setTradeHistories: (value) => set({ tradeHistories: value }),
|
|
@@ -84,6 +78,7 @@ const useUserData = create((set) => ({
|
|
|
84
78
|
setAccountSummary: (value) => set({ accountSummary: value }),
|
|
85
79
|
setTwapDetails: (value) => set({ twapDetails: value }),
|
|
86
80
|
setNotifications: (value) => set({ notifications: value }),
|
|
81
|
+
setSpotState: (value) => set({ spotState: value }),
|
|
87
82
|
clean: () => set({
|
|
88
83
|
// accessToken: null,
|
|
89
84
|
// refreshToken: null,
|
|
@@ -96,6 +91,7 @@ const useUserData = create((set) => ({
|
|
|
96
91
|
accountSummary: null,
|
|
97
92
|
twapDetails: null,
|
|
98
93
|
notifications: null,
|
|
94
|
+
spotState: null,
|
|
99
95
|
}),
|
|
100
96
|
setUserExtraAgents: (value) => set({ userExtraAgents: value }),
|
|
101
97
|
}));
|
|
@@ -113,18 +109,71 @@ const useMarketData = create((set) => ({
|
|
|
113
109
|
* Convert a full/prefixed symbol (e.g., "xyz:XYZ100") to a display symbol (e.g., "XYZ100").
|
|
114
110
|
*/
|
|
115
111
|
function toDisplaySymbol(symbol) {
|
|
116
|
-
const parts = symbol.split(
|
|
112
|
+
const parts = symbol.split(':');
|
|
117
113
|
return parts.length > 1 ? parts.slice(-1)[0] : symbol;
|
|
118
114
|
}
|
|
119
115
|
/**
|
|
120
116
|
* Convert a display symbol back to backend form using a provided map.
|
|
121
117
|
* If mapping is missing, returns the original symbol.
|
|
122
|
-
*
|
|
123
|
-
* @param
|
|
118
|
+
* For multi-market assets, returns the first available market.
|
|
119
|
+
* @param displaySymbol e.g., "TSLA"
|
|
120
|
+
* @param hip3Assets map of display -> all full market names (e.g., "TSLA" -> ["xyz:TSLA", "flx:TSLA"])
|
|
121
|
+
*/
|
|
122
|
+
function toBackendSymbol(displaySymbol, hip3Assets) {
|
|
123
|
+
const markets = hip3Assets.get(displaySymbol);
|
|
124
|
+
// Return first market if available, otherwise return original symbol
|
|
125
|
+
return markets && markets.length > 0 ? markets[0] : displaySymbol;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Convert a display symbol to backend form for a specific market prefix.
|
|
129
|
+
* This is useful when an asset is available on multiple markets (e.g., xyz:TSLA and flx:TSLA).
|
|
130
|
+
* @param displaySymbol e.g., "TSLA"
|
|
131
|
+
* @param marketPrefix e.g., "xyz" or "flx"
|
|
132
|
+
* @param hip3Assets map of display -> all full market names
|
|
133
|
+
* @returns Full market name if found, null if prefix not specified for multi-market asset, otherwise displaySymbol with prefix
|
|
134
|
+
*/
|
|
135
|
+
function toBackendSymbolWithMarket(displaySymbol, marketPrefix, hip3Assets) {
|
|
136
|
+
const availableMarkets = hip3Assets.get(displaySymbol);
|
|
137
|
+
if (!availableMarkets || availableMarkets.length === 0) {
|
|
138
|
+
// Not a HIP-3 asset, return as-is or with prefix if provided
|
|
139
|
+
return marketPrefix ? `${marketPrefix}:${displaySymbol}` : displaySymbol;
|
|
140
|
+
}
|
|
141
|
+
if (marketPrefix) {
|
|
142
|
+
// Find the market with the specified prefix
|
|
143
|
+
const targetMarket = availableMarkets.find((market) => market.toLowerCase().startsWith(`${marketPrefix.toLowerCase()}:`));
|
|
144
|
+
if (targetMarket) {
|
|
145
|
+
return targetMarket;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// No prefix specified or not found, return null to force explicit market selection
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get all available markets for a display symbol.
|
|
153
|
+
* @param displaySymbol e.g., "TSLA"
|
|
154
|
+
* @param hip3Assets map of display -> all full market names
|
|
155
|
+
* @returns Array of full market names, e.g., ["xyz:TSLA", "flx:TSLA"]
|
|
124
156
|
*/
|
|
125
|
-
function
|
|
157
|
+
function getAvailableMarkets(displaySymbol, hip3Assets) {
|
|
126
158
|
var _a;
|
|
127
|
-
return (_a =
|
|
159
|
+
return (_a = hip3Assets.get(displaySymbol)) !== null && _a !== void 0 ? _a : [];
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Extract the market prefix from a full market name.
|
|
163
|
+
* @param fullSymbol e.g., "xyz:TSLA"
|
|
164
|
+
* @returns The prefix (e.g., "xyz") or undefined if no prefix
|
|
165
|
+
*/
|
|
166
|
+
function getMarketPrefix(fullSymbol) {
|
|
167
|
+
const parts = fullSymbol.split(':');
|
|
168
|
+
return parts.length > 1 ? parts[0] : undefined;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check if a symbol is a HIP-3 market (has a prefix).
|
|
172
|
+
* @param symbol e.g., "xyz:TSLA" or "TSLA"
|
|
173
|
+
* @returns true if the symbol has a market prefix
|
|
174
|
+
*/
|
|
175
|
+
function isHip3Market(symbol) {
|
|
176
|
+
return symbol.includes(':');
|
|
128
177
|
}
|
|
129
178
|
|
|
130
179
|
const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
@@ -141,7 +190,8 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
141
190
|
try {
|
|
142
191
|
const message = JSON.parse(event.data);
|
|
143
192
|
// Handle subscription responses (only if they don't have channel data)
|
|
144
|
-
if (('success' in message || 'error' in message) &&
|
|
193
|
+
if (('success' in message || 'error' in message) &&
|
|
194
|
+
!('channel' in message)) {
|
|
145
195
|
if (message.error) {
|
|
146
196
|
setLastError(message.error);
|
|
147
197
|
}
|
|
@@ -160,12 +210,21 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
160
210
|
switch (dataMessage.channel) {
|
|
161
211
|
case 'trade-histories':
|
|
162
212
|
{
|
|
213
|
+
const mapAsset = (a) => {
|
|
214
|
+
var _a, _b;
|
|
215
|
+
const extractedPrefix = getMarketPrefix(a.coin);
|
|
216
|
+
return {
|
|
217
|
+
...a,
|
|
218
|
+
coin: toDisplaySymbol(a.coin),
|
|
219
|
+
marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
|
|
220
|
+
};
|
|
221
|
+
};
|
|
163
222
|
const list = dataMessage.data.map((item) => {
|
|
164
223
|
var _a, _b;
|
|
165
224
|
return ({
|
|
166
225
|
...item,
|
|
167
|
-
closedLongAssets: item.closedLongAssets.map(
|
|
168
|
-
closedShortAssets: item.closedShortAssets.map(
|
|
226
|
+
closedLongAssets: item.closedLongAssets.map(mapAsset),
|
|
227
|
+
closedShortAssets: item.closedShortAssets.map(mapAsset),
|
|
169
228
|
positionLongAssets: (_a = item.positionLongAssets) === null || _a === void 0 ? void 0 : _a.map((a) => toDisplaySymbol(a)),
|
|
170
229
|
positionShortAssets: (_b = item.positionShortAssets) === null || _b === void 0 ? void 0 : _b.map((a) => toDisplaySymbol(a)),
|
|
171
230
|
});
|
|
@@ -175,10 +234,19 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
175
234
|
break;
|
|
176
235
|
case 'open-positions':
|
|
177
236
|
{
|
|
237
|
+
const enrichAsset = (a) => {
|
|
238
|
+
var _a, _b;
|
|
239
|
+
const extractedPrefix = getMarketPrefix(a.coin);
|
|
240
|
+
return {
|
|
241
|
+
...a,
|
|
242
|
+
coin: toDisplaySymbol(a.coin),
|
|
243
|
+
marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
|
|
244
|
+
};
|
|
245
|
+
};
|
|
178
246
|
const list = dataMessage.data.map((pos) => ({
|
|
179
247
|
...pos,
|
|
180
|
-
longAssets: pos.longAssets.map(
|
|
181
|
-
shortAssets: pos.shortAssets.map(
|
|
248
|
+
longAssets: pos.longAssets.map(enrichAsset),
|
|
249
|
+
shortAssets: pos.shortAssets.map(enrichAsset),
|
|
182
250
|
}));
|
|
183
251
|
setRawOpenPositions(list);
|
|
184
252
|
}
|
|
@@ -187,8 +255,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
187
255
|
{
|
|
188
256
|
const list = dataMessage.data.map((order) => ({
|
|
189
257
|
...order,
|
|
190
|
-
longAssets: order.longAssets.map((a) => ({
|
|
191
|
-
|
|
258
|
+
longAssets: order.longAssets.map((a) => ({
|
|
259
|
+
...a,
|
|
260
|
+
asset: toDisplaySymbol(a.asset),
|
|
261
|
+
})),
|
|
262
|
+
shortAssets: order.shortAssets.map((a) => ({
|
|
263
|
+
...a,
|
|
264
|
+
asset: toDisplaySymbol(a.asset),
|
|
265
|
+
})),
|
|
192
266
|
}));
|
|
193
267
|
setOpenOrders(list);
|
|
194
268
|
}
|
|
@@ -198,10 +272,20 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
198
272
|
break;
|
|
199
273
|
case 'twap-details':
|
|
200
274
|
{
|
|
275
|
+
const mapTwapAsset = (a) => {
|
|
276
|
+
var _a, _b, _c;
|
|
277
|
+
const extractedPrefix = getMarketPrefix(a.asset);
|
|
278
|
+
return {
|
|
279
|
+
...a,
|
|
280
|
+
asset: toDisplaySymbol(a.asset),
|
|
281
|
+
marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
|
|
282
|
+
collateralToken: (_c = a.collateralToken) !== null && _c !== void 0 ? _c : undefined,
|
|
283
|
+
};
|
|
284
|
+
};
|
|
201
285
|
const list = dataMessage.data.map((twap) => ({
|
|
202
286
|
...twap,
|
|
203
|
-
longAssets: twap.longAssets.map(
|
|
204
|
-
shortAssets: twap.shortAssets.map(
|
|
287
|
+
longAssets: twap.longAssets.map(mapTwapAsset),
|
|
288
|
+
shortAssets: twap.shortAssets.map(mapTwapAsset),
|
|
205
289
|
}));
|
|
206
290
|
setTwapDetails(list);
|
|
207
291
|
}
|
|
@@ -214,8 +298,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
214
298
|
const md = dataMessage.data;
|
|
215
299
|
const mapGroup = (g) => ({
|
|
216
300
|
...g,
|
|
217
|
-
longAssets: g.longAssets.map((a) => ({
|
|
218
|
-
|
|
301
|
+
longAssets: g.longAssets.map((a) => ({
|
|
302
|
+
...a,
|
|
303
|
+
asset: toDisplaySymbol(a.asset),
|
|
304
|
+
})),
|
|
305
|
+
shortAssets: g.shortAssets.map((a) => ({
|
|
306
|
+
...a,
|
|
307
|
+
asset: toDisplaySymbol(a.asset),
|
|
308
|
+
})),
|
|
219
309
|
});
|
|
220
310
|
const mapped = {
|
|
221
311
|
active: md.active.map(mapGroup),
|
|
@@ -233,7 +323,15 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
233
323
|
catch (error) {
|
|
234
324
|
setLastError(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`);
|
|
235
325
|
}
|
|
236
|
-
}, [
|
|
326
|
+
}, [
|
|
327
|
+
setTradeHistories,
|
|
328
|
+
setRawOpenPositions,
|
|
329
|
+
setOpenOrders,
|
|
330
|
+
setAccountSummary,
|
|
331
|
+
setTwapDetails,
|
|
332
|
+
setNotifications,
|
|
333
|
+
setMarketData,
|
|
334
|
+
]);
|
|
237
335
|
const connect = useCallback(() => {
|
|
238
336
|
if (!enabled || !wsUrl)
|
|
239
337
|
return;
|
|
@@ -302,7 +400,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
302
400
|
'open-orders',
|
|
303
401
|
'twap-details',
|
|
304
402
|
'fills-checkpoint',
|
|
305
|
-
'notifications'
|
|
403
|
+
'notifications',
|
|
306
404
|
];
|
|
307
405
|
const globalChannels = ['market-data'];
|
|
308
406
|
if (address && address !== lastSubscribedAddress) {
|
|
@@ -311,14 +409,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
311
409
|
sendMessage(JSON.stringify({
|
|
312
410
|
action: 'unsubscribe',
|
|
313
411
|
address: lastSubscribedAddress,
|
|
314
|
-
channels: addressSpecificChannels
|
|
412
|
+
channels: addressSpecificChannels,
|
|
315
413
|
}));
|
|
316
414
|
}
|
|
317
415
|
// Subscribe to all channels (global + address-specific)
|
|
318
416
|
sendMessage(JSON.stringify({
|
|
319
417
|
action: 'subscribe',
|
|
320
418
|
address: address,
|
|
321
|
-
channels: [...globalChannels, ...addressSpecificChannels]
|
|
419
|
+
channels: [...globalChannels, ...addressSpecificChannels],
|
|
322
420
|
}));
|
|
323
421
|
setLastSubscribedAddress(address);
|
|
324
422
|
setLastError(null);
|
|
@@ -328,7 +426,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
328
426
|
sendMessage(JSON.stringify({
|
|
329
427
|
action: 'unsubscribe',
|
|
330
428
|
address: lastSubscribedAddress,
|
|
331
|
-
channels: addressSpecificChannels
|
|
429
|
+
channels: addressSpecificChannels,
|
|
332
430
|
}));
|
|
333
431
|
setLastSubscribedAddress(null);
|
|
334
432
|
}
|
|
@@ -336,7 +434,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
|
|
|
336
434
|
// If no address but connected, subscribe to global channels only
|
|
337
435
|
sendMessage(JSON.stringify({
|
|
338
436
|
action: 'subscribe',
|
|
339
|
-
channels: globalChannels
|
|
437
|
+
channels: globalChannels,
|
|
340
438
|
}));
|
|
341
439
|
}
|
|
342
440
|
}, [isConnected, address, lastSubscribedAddress, sendMessage]);
|
|
@@ -359,11 +457,14 @@ const useHyperliquidData = create((set, get) => ({
|
|
|
359
457
|
finalAssetContexts: null,
|
|
360
458
|
finalAtOICaps: null,
|
|
361
459
|
aggregatedClearingHouseState: null,
|
|
460
|
+
rawClearinghouseStates: null,
|
|
362
461
|
perpMetaAssets: null,
|
|
363
|
-
|
|
462
|
+
allPerpMetaAssets: null,
|
|
463
|
+
hip3Assets: new Map(),
|
|
464
|
+
hip3MarketPrefixes: new Map(),
|
|
364
465
|
setAllMids: (value) => set({ allMids: value }),
|
|
365
466
|
setActiveAssetData: (value) => set((state) => ({
|
|
366
|
-
activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value
|
|
467
|
+
activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value,
|
|
367
468
|
})),
|
|
368
469
|
deleteActiveAssetData: (key) => {
|
|
369
470
|
set((state) => {
|
|
@@ -398,13 +499,16 @@ const useHyperliquidData = create((set, get) => ({
|
|
|
398
499
|
activeAssetData: {
|
|
399
500
|
...state.activeAssetData,
|
|
400
501
|
[key]: value,
|
|
401
|
-
}
|
|
502
|
+
},
|
|
402
503
|
})),
|
|
403
504
|
setFinalAssetContexts: (value) => set({ finalAssetContexts: value }),
|
|
404
505
|
setFinalAtOICaps: (value) => set({ finalAtOICaps: value }),
|
|
405
506
|
setAggregatedClearingHouseState: (value) => set({ aggregatedClearingHouseState: value }),
|
|
507
|
+
setRawClearinghouseStates: (value) => set({ rawClearinghouseStates: value }),
|
|
406
508
|
setPerpMetaAssets: (value) => set({ perpMetaAssets: value }),
|
|
407
|
-
|
|
509
|
+
setAllPerpMetaAssets: (value) => set({ allPerpMetaAssets: value }),
|
|
510
|
+
setHip3Assets: (value) => set({ hip3Assets: value }),
|
|
511
|
+
setHip3MarketPrefixes: (value) => set({ hip3MarketPrefixes: value }),
|
|
408
512
|
}));
|
|
409
513
|
|
|
410
514
|
/**
|
|
@@ -667,7 +771,8 @@ const useUserSelection$1 = create((set, get) => ({
|
|
|
667
771
|
}));
|
|
668
772
|
|
|
669
773
|
const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
670
|
-
const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState, } = useHyperliquidData();
|
|
774
|
+
const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState, setRawClearinghouseStates, } = useHyperliquidData();
|
|
775
|
+
const { setSpotState } = useUserData();
|
|
671
776
|
const { candleInterval } = useUserSelection$1();
|
|
672
777
|
const longTokens = useUserSelection$1((s) => s.longTokens);
|
|
673
778
|
const shortTokens = useUserSelection$1((s) => s.shortTokens);
|
|
@@ -686,9 +791,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
686
791
|
try {
|
|
687
792
|
const message = JSON.parse(event.data);
|
|
688
793
|
// Handle subscription responses
|
|
689
|
-
if (
|
|
794
|
+
if ('success' in message || 'error' in message) {
|
|
690
795
|
if (message.error) {
|
|
691
|
-
console.error(
|
|
796
|
+
console.error('[HyperLiquid WS] Subscription error:', message.error);
|
|
692
797
|
setLastError(message.error);
|
|
693
798
|
}
|
|
694
799
|
else {
|
|
@@ -697,30 +802,44 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
697
802
|
return;
|
|
698
803
|
}
|
|
699
804
|
// Handle channel data messages
|
|
700
|
-
if (
|
|
805
|
+
if ('channel' in message && 'data' in message) {
|
|
701
806
|
const response = message;
|
|
702
807
|
switch (response.channel) {
|
|
703
|
-
case
|
|
808
|
+
case 'webData3':
|
|
704
809
|
const webData3 = response.data;
|
|
705
810
|
// finalAssetContexts now sourced from allDexsAssetCtxs channel
|
|
706
811
|
const finalAtOICaps = webData3.perpDexStates.flatMap((dex) => dex.perpsAtOpenInterestCap);
|
|
707
812
|
setFinalAtOICaps(finalAtOICaps);
|
|
708
813
|
break;
|
|
709
|
-
case
|
|
814
|
+
case 'allDexsAssetCtxs':
|
|
710
815
|
{
|
|
711
816
|
const data = response.data;
|
|
712
|
-
|
|
817
|
+
// Filter out hyna to match perpMetaAssets filtering
|
|
818
|
+
const FILTERED_DEX_PREFIXES = ['hyna'];
|
|
819
|
+
const filtered = (data.ctxs || [])
|
|
820
|
+
.filter(([prefix]) => !FILTERED_DEX_PREFIXES.includes((prefix || '').toLowerCase()))
|
|
821
|
+
.sort((a, b) => {
|
|
822
|
+
// Sort to match perpMetaAssets order: default market first, then alphabetically
|
|
823
|
+
const prefixA = a[0] || '';
|
|
824
|
+
const prefixB = b[0] || '';
|
|
825
|
+
if (prefixA === '' && prefixB !== '')
|
|
826
|
+
return -1;
|
|
827
|
+
if (prefixA !== '' && prefixB === '')
|
|
828
|
+
return 1;
|
|
829
|
+
return prefixA.localeCompare(prefixB);
|
|
830
|
+
});
|
|
831
|
+
const finalAssetContexts = filtered.flatMap(([, ctxs]) => ctxs || []);
|
|
713
832
|
setFinalAssetContexts(finalAssetContexts);
|
|
714
833
|
}
|
|
715
834
|
break;
|
|
716
|
-
case
|
|
835
|
+
case 'allDexsClearinghouseState':
|
|
717
836
|
{
|
|
718
837
|
const data = response.data;
|
|
719
838
|
const states = (data.clearinghouseStates || [])
|
|
720
839
|
.map(([, s]) => s)
|
|
721
840
|
.filter(Boolean);
|
|
722
|
-
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v ||
|
|
723
|
-
const toStr = (n) => Number.isFinite(n) ? n.toString() :
|
|
841
|
+
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || '0') || 0), 0);
|
|
842
|
+
const toStr = (n) => Number.isFinite(n) ? n.toString() : '0';
|
|
724
843
|
const assetPositions = states.flatMap((s) => s.assetPositions || []);
|
|
725
844
|
const crossMaintenanceMarginUsed = toStr(sum(states.map((s) => s.crossMaintenanceMarginUsed)));
|
|
726
845
|
const crossMarginSummary = {
|
|
@@ -746,22 +865,41 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
746
865
|
withdrawable,
|
|
747
866
|
};
|
|
748
867
|
setAggregatedClearingHouseState(aggregatedClearingHouseState);
|
|
868
|
+
setRawClearinghouseStates(data.clearinghouseStates || null);
|
|
749
869
|
}
|
|
750
870
|
break;
|
|
751
|
-
case
|
|
871
|
+
case 'allMids':
|
|
752
872
|
{
|
|
753
873
|
const data = response.data;
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
874
|
+
// Keep BOTH normalized prefixed keys AND display symbol keys
|
|
875
|
+
// This ensures xyz:TSLA and flx:TSLA are stored separately,
|
|
876
|
+
// while also maintaining backward compatibility with non-prefixed lookups
|
|
877
|
+
const mids = {};
|
|
878
|
+
Object.entries(data.mids || {}).forEach(([k, v]) => {
|
|
879
|
+
// Normalize prefixed keys to lowercase prefix (e.g., "XYZ:TSLA" -> "xyz:TSLA")
|
|
880
|
+
// This matches how we look up tokens in the SDK
|
|
881
|
+
let normalizedKey = k;
|
|
882
|
+
if (k.includes(':')) {
|
|
883
|
+
const [prefix, ...rest] = k.split(':');
|
|
884
|
+
normalizedKey = `${prefix.toLowerCase()}:${rest.join(':')}`;
|
|
885
|
+
}
|
|
886
|
+
// Store with normalized key
|
|
887
|
+
mids[normalizedKey] = v;
|
|
888
|
+
// Also store with original key for backward compatibility
|
|
889
|
+
if (k !== normalizedKey) {
|
|
890
|
+
mids[k] = v;
|
|
891
|
+
}
|
|
892
|
+
// Also store with display symbol for backward compatibility
|
|
893
|
+
const displayKey = toDisplaySymbol(k);
|
|
894
|
+
// Only set display key if it doesn't already exist (avoid overwriting market-specific prices)
|
|
895
|
+
if (!(displayKey in mids)) {
|
|
896
|
+
mids[displayKey] = v;
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
setAllMids({ mids });
|
|
762
900
|
}
|
|
763
901
|
break;
|
|
764
|
-
case
|
|
902
|
+
case 'activeAssetData':
|
|
765
903
|
{
|
|
766
904
|
const assetData = response.data;
|
|
767
905
|
const symbol = toDisplaySymbol(assetData.coin);
|
|
@@ -772,14 +910,22 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
772
910
|
upsertActiveAssetData(symbol, normalized);
|
|
773
911
|
}
|
|
774
912
|
break;
|
|
775
|
-
case
|
|
913
|
+
case 'candle':
|
|
776
914
|
{
|
|
777
915
|
const candleDataItem = response.data;
|
|
778
|
-
const symbol = toDisplaySymbol(candleDataItem.s ||
|
|
916
|
+
const symbol = toDisplaySymbol(candleDataItem.s || '');
|
|
779
917
|
const normalized = { ...candleDataItem, s: symbol };
|
|
780
918
|
addCandleData(symbol, normalized);
|
|
781
919
|
}
|
|
782
920
|
break;
|
|
921
|
+
case 'spotState':
|
|
922
|
+
{
|
|
923
|
+
const spotStateData = response.data;
|
|
924
|
+
if (spotStateData === null || spotStateData === void 0 ? void 0 : spotStateData.spotState) {
|
|
925
|
+
setSpotState(spotStateData.spotState);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
break;
|
|
783
929
|
default:
|
|
784
930
|
console.warn(`[HyperLiquid WS] Unknown channel: ${response.channel}`);
|
|
785
931
|
}
|
|
@@ -787,7 +933,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
787
933
|
}
|
|
788
934
|
catch (error) {
|
|
789
935
|
const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
|
|
790
|
-
console.error(
|
|
936
|
+
console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', event.data);
|
|
791
937
|
setLastError(errorMessage);
|
|
792
938
|
}
|
|
793
939
|
}, [
|
|
@@ -797,6 +943,8 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
797
943
|
setFinalAssetContexts,
|
|
798
944
|
setFinalAtOICaps,
|
|
799
945
|
setAggregatedClearingHouseState,
|
|
946
|
+
setRawClearinghouseStates,
|
|
947
|
+
setSpotState,
|
|
800
948
|
]);
|
|
801
949
|
const connect = useCallback(() => {
|
|
802
950
|
if (!enabled)
|
|
@@ -827,7 +975,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
827
975
|
if (!manualCloseRef.current && reconnectAttemptsRef.current < 5) {
|
|
828
976
|
reconnectAttemptsRef.current += 1;
|
|
829
977
|
if (reconnectAttemptsRef.current === 5) {
|
|
830
|
-
console.error(
|
|
978
|
+
console.error('[HyperLiquid WS] Reconnection stopped after 5 attempts');
|
|
831
979
|
}
|
|
832
980
|
setTimeout(() => connect(), 3000);
|
|
833
981
|
}
|
|
@@ -895,6 +1043,17 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
895
1043
|
},
|
|
896
1044
|
};
|
|
897
1045
|
sendJsonMessage(unsubscribeMessage);
|
|
1046
|
+
// Unsubscribe from spotState for previous address
|
|
1047
|
+
if (subscribedAddress !== DEFAULT_ADDRESS) {
|
|
1048
|
+
const unsubscribeSpotState = {
|
|
1049
|
+
method: 'unsubscribe',
|
|
1050
|
+
subscription: {
|
|
1051
|
+
type: 'spotState',
|
|
1052
|
+
user: subscribedAddress,
|
|
1053
|
+
},
|
|
1054
|
+
};
|
|
1055
|
+
sendJsonMessage(unsubscribeSpotState);
|
|
1056
|
+
}
|
|
898
1057
|
const unsubscribeAllDexsClearinghouseState = {
|
|
899
1058
|
method: "unsubscribe",
|
|
900
1059
|
subscription: {
|
|
@@ -938,11 +1097,26 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
938
1097
|
sendJsonMessage(subscribeAllDexsClearinghouseState);
|
|
939
1098
|
sendJsonMessage(subscribeAllMids);
|
|
940
1099
|
sendJsonMessage(subscribeAllDexsAssetCtxs);
|
|
1100
|
+
// Subscribe to spotState for real-time spot balances (USDH, USDC, etc.)
|
|
1101
|
+
// Only subscribe if we have a real user address (not the default)
|
|
1102
|
+
if (userAddress !== DEFAULT_ADDRESS) {
|
|
1103
|
+
const subscribeSpotState = {
|
|
1104
|
+
method: 'subscribe',
|
|
1105
|
+
subscription: {
|
|
1106
|
+
type: 'spotState',
|
|
1107
|
+
user: userAddress,
|
|
1108
|
+
},
|
|
1109
|
+
};
|
|
1110
|
+
sendJsonMessage(subscribeSpotState);
|
|
1111
|
+
}
|
|
941
1112
|
setSubscribedAddress(userAddress);
|
|
942
1113
|
// Clear previous data when address changes
|
|
943
1114
|
if (subscribedAddress && subscribedAddress !== userAddress) {
|
|
944
1115
|
// clear aggregatedClearingHouseState
|
|
945
1116
|
setAggregatedClearingHouseState(null);
|
|
1117
|
+
setRawClearinghouseStates(null);
|
|
1118
|
+
// clear spotState
|
|
1119
|
+
setSpotState(null);
|
|
946
1120
|
}
|
|
947
1121
|
}, [
|
|
948
1122
|
isConnected,
|
|
@@ -950,6 +1124,8 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
|
|
|
950
1124
|
subscribedAddress,
|
|
951
1125
|
sendJsonMessage,
|
|
952
1126
|
setAggregatedClearingHouseState,
|
|
1127
|
+
setRawClearinghouseStates,
|
|
1128
|
+
setSpotState,
|
|
953
1129
|
]);
|
|
954
1130
|
// Handle token subscriptions for activeAssetData
|
|
955
1131
|
useEffect(() => {
|
|
@@ -1146,20 +1322,112 @@ const useAccountSummary = () => {
|
|
|
1146
1322
|
return { data: calculated, isLoading };
|
|
1147
1323
|
};
|
|
1148
1324
|
|
|
1325
|
+
function findAssetMeta$4(coinName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
1326
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1327
|
+
if (!perpMetaAssets) {
|
|
1328
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
1329
|
+
}
|
|
1330
|
+
if (desiredCollateral) {
|
|
1331
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === coinName && a.collateralToken === desiredCollateral);
|
|
1332
|
+
if (collateralMatch) {
|
|
1333
|
+
return {
|
|
1334
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
1335
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (coinName.includes(':')) {
|
|
1340
|
+
const [prefix, symbol] = coinName.split(':');
|
|
1341
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
1342
|
+
var _a;
|
|
1343
|
+
return a.name === symbol &&
|
|
1344
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
1345
|
+
});
|
|
1346
|
+
if (exactMatch) {
|
|
1347
|
+
return {
|
|
1348
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
1349
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
if (knownPrefix) {
|
|
1354
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
1355
|
+
var _a;
|
|
1356
|
+
return a.name === coinName &&
|
|
1357
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
1358
|
+
});
|
|
1359
|
+
if (exactMatch) {
|
|
1360
|
+
return {
|
|
1361
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
1362
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
const exactMatch = perpMetaAssets.find((a) => a.name === coinName && !a.marketPrefix);
|
|
1367
|
+
if (exactMatch) {
|
|
1368
|
+
return {
|
|
1369
|
+
collateralToken: (_g = exactMatch.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
1370
|
+
marketPrefix: (_h = exactMatch.marketPrefix) !== null && _h !== void 0 ? _h : null,
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
const hip3Matches = perpMetaAssets.filter((a) => a.name === coinName && a.marketPrefix);
|
|
1374
|
+
if (hip3Matches.length > 0) {
|
|
1375
|
+
if (desiredCollateral) {
|
|
1376
|
+
const collateralMatch = hip3Matches.find((a) => a.collateralToken === desiredCollateral);
|
|
1377
|
+
if (collateralMatch) {
|
|
1378
|
+
return {
|
|
1379
|
+
collateralToken: (_j = collateralMatch.collateralToken) !== null && _j !== void 0 ? _j : 'USDC',
|
|
1380
|
+
marketPrefix: (_k = collateralMatch.marketPrefix) !== null && _k !== void 0 ? _k : null,
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
const usdHMatch = hip3Matches.find((a) => a.collateralToken === 'USDH');
|
|
1385
|
+
const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Matches[0];
|
|
1386
|
+
return {
|
|
1387
|
+
collateralToken: (_l = chosen.collateralToken) !== null && _l !== void 0 ? _l : 'USDC',
|
|
1388
|
+
marketPrefix: (_m = chosen.marketPrefix) !== null && _m !== void 0 ? _m : null,
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
1392
|
+
}
|
|
1393
|
+
function enrichTradeHistoryAssets(assets, perpMetaAssets) {
|
|
1394
|
+
return assets.map((asset) => {
|
|
1395
|
+
var _a;
|
|
1396
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
1397
|
+
return asset;
|
|
1398
|
+
}
|
|
1399
|
+
const meta = findAssetMeta$4(asset.coin, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
1400
|
+
return {
|
|
1401
|
+
...asset,
|
|
1402
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
1403
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
1404
|
+
};
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
function enrichTradeHistories(histories, perpMetaAssets) {
|
|
1408
|
+
return histories.map((history) => ({
|
|
1409
|
+
...history,
|
|
1410
|
+
closedLongAssets: enrichTradeHistoryAssets(history.closedLongAssets, perpMetaAssets),
|
|
1411
|
+
closedShortAssets: enrichTradeHistoryAssets(history.closedShortAssets, perpMetaAssets),
|
|
1412
|
+
}));
|
|
1413
|
+
}
|
|
1149
1414
|
const useTradeHistories = () => {
|
|
1150
1415
|
const context = useContext(PearHyperliquidContext);
|
|
1151
1416
|
if (!context) {
|
|
1152
1417
|
throw new Error('useTradeHistories must be used within a PearHyperliquidProvider');
|
|
1153
1418
|
}
|
|
1154
1419
|
const tradeHistories = useUserData((state) => state.tradeHistories);
|
|
1420
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
1155
1421
|
const isLoading = useMemo(() => {
|
|
1156
1422
|
return tradeHistories === null && context.isConnected;
|
|
1157
1423
|
}, [tradeHistories, context.isConnected]);
|
|
1158
|
-
|
|
1424
|
+
const enrichedTradeHistories = useMemo(() => {
|
|
1425
|
+
if (!tradeHistories)
|
|
1426
|
+
return null;
|
|
1427
|
+
return enrichTradeHistories(tradeHistories, allPerpMetaAssets);
|
|
1428
|
+
}, [tradeHistories, allPerpMetaAssets]);
|
|
1429
|
+
return { data: enrichedTradeHistories, isLoading };
|
|
1159
1430
|
};
|
|
1160
|
-
/**
|
|
1161
|
-
* Hook to access open orders with loading state
|
|
1162
|
-
*/
|
|
1163
1431
|
const useOpenOrders = () => {
|
|
1164
1432
|
const context = useContext(PearHyperliquidContext);
|
|
1165
1433
|
if (!context) {
|
|
@@ -1188,21 +1456,51 @@ const useWebData = () => {
|
|
|
1188
1456
|
const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
|
|
1189
1457
|
const aggregatedClearinghouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
1190
1458
|
const finalAtOICaps = useHyperliquidData((state) => state.finalAtOICaps);
|
|
1191
|
-
const hip3Assets = useHyperliquidData((state) => state.
|
|
1459
|
+
const hip3Assets = useHyperliquidData((state) => state.hip3Assets);
|
|
1460
|
+
const hip3MarketPrefixes = useHyperliquidData((state) => state.hip3MarketPrefixes);
|
|
1192
1461
|
let marketDataBySymbol = {};
|
|
1193
1462
|
if (finalAssetContexts && perpMetaAssets) {
|
|
1194
1463
|
const result = {};
|
|
1464
|
+
// Build a map of display name -> asset context index (for unique display names)
|
|
1465
|
+
const displayNameToContextIndex = new Map();
|
|
1466
|
+
const seenNames = new Set();
|
|
1467
|
+
let contextIndex = 0;
|
|
1468
|
+
// First pass: map unique display names to their context index
|
|
1195
1469
|
for (let index = 0; index < perpMetaAssets.length; index++) {
|
|
1196
1470
|
const name = perpMetaAssets[index].name;
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1471
|
+
if (!seenNames.has(name)) {
|
|
1472
|
+
seenNames.add(name);
|
|
1473
|
+
if (contextIndex < finalAssetContexts.length) {
|
|
1474
|
+
displayNameToContextIndex.set(name, contextIndex);
|
|
1475
|
+
contextIndex++;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
// Second pass: create nested entries for all market variants
|
|
1480
|
+
for (let index = 0; index < perpMetaAssets.length; index++) {
|
|
1481
|
+
const universeAsset = perpMetaAssets[index];
|
|
1482
|
+
const displayName = universeAsset.name;
|
|
1483
|
+
const marketPrefix = universeAsset.marketPrefix;
|
|
1484
|
+
const ctxIndex = displayNameToContextIndex.get(displayName);
|
|
1485
|
+
if (ctxIndex !== undefined) {
|
|
1486
|
+
const assetContext = finalAssetContexts[ctxIndex];
|
|
1487
|
+
// Initialize the symbol entry if it doesn't exist
|
|
1488
|
+
if (!result[displayName]) {
|
|
1489
|
+
result[displayName] = {};
|
|
1490
|
+
}
|
|
1491
|
+
// Use marketPrefix as key for HIP-3 assets, "default" for regular assets
|
|
1492
|
+
const variantKey = marketPrefix || 'default';
|
|
1493
|
+
result[displayName][variantKey] = {
|
|
1494
|
+
asset: assetContext,
|
|
1495
|
+
universe: universeAsset,
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1201
1498
|
}
|
|
1202
1499
|
marketDataBySymbol = result;
|
|
1203
1500
|
}
|
|
1204
1501
|
return {
|
|
1205
1502
|
hip3Assets,
|
|
1503
|
+
hip3MarketPrefixes,
|
|
1206
1504
|
clearinghouseState: aggregatedClearinghouseState,
|
|
1207
1505
|
perpsAtOpenInterestCap: finalAtOICaps,
|
|
1208
1506
|
marketDataBySymbol,
|
|
@@ -1217,19 +1515,30 @@ const useWebData = () => {
|
|
|
1217
1515
|
class TokenMetadataExtractor {
|
|
1218
1516
|
/**
|
|
1219
1517
|
* Extracts comprehensive token metadata
|
|
1220
|
-
* @param symbol - Token symbol
|
|
1518
|
+
* @param symbol - Token symbol (base symbol without prefix, e.g., "TSLA")
|
|
1221
1519
|
* @param perpMetaAssets - Aggregated universe assets (flattened across dexes)
|
|
1222
1520
|
* @param finalAssetContexts - Aggregated asset contexts (flattened across dexes)
|
|
1223
1521
|
* @param allMids - AllMids data containing current prices
|
|
1224
1522
|
* @param activeAssetData - Optional active asset data containing leverage information
|
|
1523
|
+
* @param marketPrefix - Optional market prefix (e.g., "xyz", "flx") for HIP3 multi-market assets
|
|
1225
1524
|
* @returns TokenMetadata or null if token not found
|
|
1226
1525
|
*/
|
|
1227
|
-
static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
|
|
1526
|
+
static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketPrefix) {
|
|
1228
1527
|
if (!perpMetaAssets || !finalAssetContexts || !allMids) {
|
|
1229
1528
|
return null;
|
|
1230
1529
|
}
|
|
1231
1530
|
// Find token index in aggregated universe
|
|
1232
|
-
|
|
1531
|
+
// For HIP3 assets, match both name AND marketPrefix
|
|
1532
|
+
const universeIndex = perpMetaAssets.findIndex((asset) => {
|
|
1533
|
+
if (asset.name !== symbol)
|
|
1534
|
+
return false;
|
|
1535
|
+
// If marketPrefix is specified, match it; otherwise match assets without prefix
|
|
1536
|
+
if (marketPrefix) {
|
|
1537
|
+
return asset.marketPrefix === marketPrefix;
|
|
1538
|
+
}
|
|
1539
|
+
// No prefix specified - match non-HIP3 asset (no marketPrefix) or first matching asset
|
|
1540
|
+
return !asset.marketPrefix;
|
|
1541
|
+
});
|
|
1233
1542
|
if (universeIndex === -1) {
|
|
1234
1543
|
return null;
|
|
1235
1544
|
}
|
|
@@ -1238,9 +1547,20 @@ class TokenMetadataExtractor {
|
|
|
1238
1547
|
if (!assetCtx) {
|
|
1239
1548
|
return null;
|
|
1240
1549
|
}
|
|
1241
|
-
// Get current price
|
|
1242
|
-
|
|
1243
|
-
const
|
|
1550
|
+
// Get current price - prefer assetCtx.midPx as it's already index-matched,
|
|
1551
|
+
// fall back to allMids lookup if midPx is null
|
|
1552
|
+
const prefixedKeyColon = marketPrefix ? `${marketPrefix}:${symbol}` : null;
|
|
1553
|
+
let currentPrice = 0;
|
|
1554
|
+
// Primary source: assetCtx.midPx (already properly indexed)
|
|
1555
|
+
if (assetCtx.midPx) {
|
|
1556
|
+
currentPrice = parseFloat(assetCtx.midPx);
|
|
1557
|
+
}
|
|
1558
|
+
// Fallback: allMids lookup with multiple key formats for HIP3 markets
|
|
1559
|
+
if (!currentPrice || isNaN(currentPrice)) {
|
|
1560
|
+
const currentPriceStr = (prefixedKeyColon && allMids.mids[prefixedKeyColon]) ||
|
|
1561
|
+
allMids.mids[symbol];
|
|
1562
|
+
currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
|
|
1563
|
+
}
|
|
1244
1564
|
// Get previous day price
|
|
1245
1565
|
const prevDayPrice = parseFloat(assetCtx.prevDayPx);
|
|
1246
1566
|
// Calculate 24h price change
|
|
@@ -1251,7 +1571,11 @@ class TokenMetadataExtractor {
|
|
|
1251
1571
|
const markPrice = parseFloat(assetCtx.markPx);
|
|
1252
1572
|
const oraclePrice = parseFloat(assetCtx.oraclePx);
|
|
1253
1573
|
// Extract leverage info from activeAssetData if available
|
|
1254
|
-
|
|
1574
|
+
// Try prefixed key first (e.g., "xyz:TSLA"), then fall back to plain symbol
|
|
1575
|
+
const activeDataKey = prefixedKeyColon && (activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[prefixedKeyColon])
|
|
1576
|
+
? prefixedKeyColon
|
|
1577
|
+
: symbol;
|
|
1578
|
+
const tokenActiveData = activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[activeDataKey];
|
|
1255
1579
|
const leverage = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.leverage;
|
|
1256
1580
|
const maxTradeSzs = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.maxTradeSzs;
|
|
1257
1581
|
const availableToTrade = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.availableToTrade;
|
|
@@ -1269,21 +1593,27 @@ class TokenMetadataExtractor {
|
|
|
1269
1593
|
leverage,
|
|
1270
1594
|
maxTradeSzs,
|
|
1271
1595
|
availableToTrade,
|
|
1596
|
+
collateralToken: universeAsset.collateralToken,
|
|
1272
1597
|
};
|
|
1273
1598
|
}
|
|
1274
1599
|
/**
|
|
1275
1600
|
* Extracts metadata for multiple tokens
|
|
1276
|
-
* @param
|
|
1601
|
+
* @param tokens - Array of token objects with symbol and optional marketPrefix
|
|
1277
1602
|
* @param perpMetaAssets - Aggregated universe assets
|
|
1278
1603
|
* @param finalAssetContexts - Aggregated asset contexts
|
|
1279
1604
|
* @param allMids - AllMids data
|
|
1280
1605
|
* @param activeAssetData - Optional active asset data containing leverage information
|
|
1281
|
-
* @returns Record of
|
|
1606
|
+
* @returns Record of unique key to TokenMetadata. Key is "{prefix}:{symbol}" for HIP3 assets, or just "{symbol}" otherwise
|
|
1282
1607
|
*/
|
|
1283
|
-
static extractMultipleTokensMetadata(
|
|
1608
|
+
static extractMultipleTokensMetadata(tokens, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
|
|
1284
1609
|
const result = {};
|
|
1285
|
-
for (const
|
|
1286
|
-
|
|
1610
|
+
for (const token of tokens) {
|
|
1611
|
+
// Use a unique key that includes the prefix for HIP3 assets
|
|
1612
|
+
// This ensures xyz:TSLA and flx:TSLA get separate entries
|
|
1613
|
+
const resultKey = token.marketPrefix
|
|
1614
|
+
? `${token.marketPrefix}:${token.symbol}`
|
|
1615
|
+
: token.symbol;
|
|
1616
|
+
result[resultKey] = this.extractTokenMetadata(token.symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, token.marketPrefix);
|
|
1287
1617
|
}
|
|
1288
1618
|
return result;
|
|
1289
1619
|
}
|
|
@@ -1296,10 +1626,30 @@ class TokenMetadataExtractor {
|
|
|
1296
1626
|
static isTokenAvailable(symbol, perpMetaAssets) {
|
|
1297
1627
|
if (!perpMetaAssets)
|
|
1298
1628
|
return false;
|
|
1299
|
-
return perpMetaAssets.some(asset => asset.name === symbol);
|
|
1629
|
+
return perpMetaAssets.some((asset) => asset.name === symbol);
|
|
1300
1630
|
}
|
|
1301
1631
|
}
|
|
1302
1632
|
|
|
1633
|
+
/**
|
|
1634
|
+
* Parse a token string that may have a market prefix (e.g., "xyz:GOOGL" -> { prefix: "xyz", symbol: "GOOGL" })
|
|
1635
|
+
* This allows us to keep the full name (xyz:GOOGL) for URLs/tags while extracting just the symbol for SDK lookups.
|
|
1636
|
+
*/
|
|
1637
|
+
function parseTokenWithPrefix(token) {
|
|
1638
|
+
if (token.includes(":")) {
|
|
1639
|
+
const [prefix, ...rest] = token.split(":");
|
|
1640
|
+
const symbol = rest.join(":").toUpperCase();
|
|
1641
|
+
return {
|
|
1642
|
+
prefix: prefix.toLowerCase(),
|
|
1643
|
+
symbol,
|
|
1644
|
+
fullName: `${prefix.toLowerCase()}:${symbol}`,
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
return {
|
|
1648
|
+
prefix: null,
|
|
1649
|
+
symbol: token.toUpperCase(),
|
|
1650
|
+
fullName: token.toUpperCase(),
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1303
1653
|
const useTokenSelectionMetadataStore = create((set) => ({
|
|
1304
1654
|
isPriceDataReady: false,
|
|
1305
1655
|
isLoading: true,
|
|
@@ -1315,17 +1665,59 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1315
1665
|
maxLeverage: 0,
|
|
1316
1666
|
minMargin: 0,
|
|
1317
1667
|
leverageMatched: true,
|
|
1318
|
-
recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens }) => {
|
|
1319
|
-
const isPriceDataReady = !!(perpMetaAssets &&
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1668
|
+
recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens, }) => {
|
|
1669
|
+
const isPriceDataReady = !!(perpMetaAssets &&
|
|
1670
|
+
finalAssetContexts &&
|
|
1671
|
+
allMids);
|
|
1672
|
+
// Parse tokens - handle prefixed tokens like "xyz:GOOGL" by extracting the symbol and market prefix
|
|
1673
|
+
// The full name (xyz:GOOGL) is kept as the metadata key for UI consistency
|
|
1674
|
+
const parsedLongTokens = longTokens.map((t) => ({
|
|
1675
|
+
...t,
|
|
1676
|
+
parsed: parseTokenWithPrefix(t.symbol),
|
|
1677
|
+
}));
|
|
1678
|
+
const parsedShortTokens = shortTokens.map((t) => ({
|
|
1679
|
+
...t,
|
|
1680
|
+
parsed: parseTokenWithPrefix(t.symbol),
|
|
1681
|
+
}));
|
|
1682
|
+
// Extract base symbols with their market prefixes for SDK lookups
|
|
1683
|
+
// This ensures xyz:TSLA and flx:TSLA get different market data
|
|
1684
|
+
const longTokensForLookup = parsedLongTokens.map((t) => ({
|
|
1685
|
+
symbol: t.parsed.symbol,
|
|
1686
|
+
marketPrefix: t.parsed.prefix,
|
|
1687
|
+
}));
|
|
1688
|
+
const shortTokensForLookup = parsedShortTokens.map((t) => ({
|
|
1689
|
+
symbol: t.parsed.symbol,
|
|
1690
|
+
marketPrefix: t.parsed.prefix,
|
|
1691
|
+
}));
|
|
1692
|
+
// Also extract just the base symbols (without prefix) for lookups that don't support prefixes
|
|
1693
|
+
const longBaseSymbols = longTokensForLookup.map((t) => t.symbol);
|
|
1694
|
+
const shortBaseSymbols = shortTokensForLookup.map((t) => t.symbol);
|
|
1695
|
+
// Get metadata using base symbols with market prefix for proper market differentiation
|
|
1696
|
+
const longBaseMetadata = isPriceDataReady
|
|
1697
|
+
? TokenMetadataExtractor.extractMultipleTokensMetadata(longTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
|
|
1325
1698
|
: {};
|
|
1326
|
-
const
|
|
1327
|
-
? TokenMetadataExtractor.extractMultipleTokensMetadata(
|
|
1699
|
+
const shortBaseMetadata = isPriceDataReady
|
|
1700
|
+
? TokenMetadataExtractor.extractMultipleTokensMetadata(shortTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
|
|
1328
1701
|
: {};
|
|
1702
|
+
// Re-map metadata using original full names (with prefix) as keys for UI consistency
|
|
1703
|
+
// The extractor now keys by "{prefix}:{symbol}" for prefixed tokens, which matches our parsed.fullName
|
|
1704
|
+
const longTokensMetadata = {};
|
|
1705
|
+
parsedLongTokens.forEach((t) => {
|
|
1706
|
+
var _a;
|
|
1707
|
+
// Use the full name (e.g., "xyz:TSLA") as the lookup key since extractor uses the same format
|
|
1708
|
+
const lookupKey = t.parsed.prefix
|
|
1709
|
+
? `${t.parsed.prefix}:${t.parsed.symbol}`
|
|
1710
|
+
: t.parsed.symbol;
|
|
1711
|
+
longTokensMetadata[t.symbol] = (_a = longBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
|
|
1712
|
+
});
|
|
1713
|
+
const shortTokensMetadata = {};
|
|
1714
|
+
parsedShortTokens.forEach((t) => {
|
|
1715
|
+
var _a;
|
|
1716
|
+
const lookupKey = t.parsed.prefix
|
|
1717
|
+
? `${t.parsed.prefix}:${t.parsed.symbol}`
|
|
1718
|
+
: t.parsed.symbol;
|
|
1719
|
+
shortTokensMetadata[t.symbol] = (_a = shortBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
|
|
1720
|
+
});
|
|
1329
1721
|
// Determine loading state
|
|
1330
1722
|
const allTokens = [...longTokens, ...shortTokens];
|
|
1331
1723
|
const isLoading = (() => {
|
|
@@ -1333,26 +1725,33 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1333
1725
|
return true;
|
|
1334
1726
|
if (allTokens.length === 0)
|
|
1335
1727
|
return false;
|
|
1336
|
-
const allMetadata = {
|
|
1728
|
+
const allMetadata = {
|
|
1729
|
+
...longTokensMetadata,
|
|
1730
|
+
...shortTokensMetadata,
|
|
1731
|
+
};
|
|
1337
1732
|
return allTokens.some((token) => !allMetadata[token.symbol]);
|
|
1338
1733
|
})();
|
|
1339
1734
|
// Open interest and volume (from market data for matching asset basket)
|
|
1735
|
+
// Use base symbols (without prefix) for matching against market data
|
|
1340
1736
|
const { openInterest, volume } = (() => {
|
|
1341
1737
|
const empty = { openInterest: "0", volume: "0" };
|
|
1342
1738
|
if (!(marketData === null || marketData === void 0 ? void 0 : marketData.active) || (!longTokens.length && !shortTokens.length))
|
|
1343
1739
|
return empty;
|
|
1344
|
-
const selectedLong =
|
|
1345
|
-
const selectedShort =
|
|
1740
|
+
const selectedLong = longBaseSymbols.slice().sort();
|
|
1741
|
+
const selectedShort = shortBaseSymbols.slice().sort();
|
|
1346
1742
|
const match = marketData.active.find((item) => {
|
|
1347
1743
|
const longs = [...item.longAssets].sort();
|
|
1348
1744
|
const shorts = [...item.shortAssets].sort();
|
|
1349
|
-
if (longs.length !== selectedLong.length ||
|
|
1745
|
+
if (longs.length !== selectedLong.length ||
|
|
1746
|
+
shorts.length !== selectedShort.length)
|
|
1350
1747
|
return false;
|
|
1351
1748
|
const longsEqual = longs.every((s, i) => s.asset === selectedLong[i]);
|
|
1352
1749
|
const shortsEqual = shorts.every((s, i) => s.asset === selectedShort[i]);
|
|
1353
1750
|
return longsEqual && shortsEqual;
|
|
1354
1751
|
});
|
|
1355
|
-
return match
|
|
1752
|
+
return match
|
|
1753
|
+
? { openInterest: match.openInterest, volume: match.volume }
|
|
1754
|
+
: empty;
|
|
1356
1755
|
})();
|
|
1357
1756
|
// Price ratio (only when exactly one long and one short)
|
|
1358
1757
|
const { priceRatio, priceRatio24h } = (() => {
|
|
@@ -1432,17 +1831,27 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1432
1831
|
return totalFunding;
|
|
1433
1832
|
})();
|
|
1434
1833
|
// Max leverage (maximum across all tokens)
|
|
1834
|
+
// Use tokens with their market prefixes for proper lookup in perpMetaAssets
|
|
1435
1835
|
const maxLeverage = (() => {
|
|
1436
1836
|
if (!perpMetaAssets)
|
|
1437
1837
|
return 0;
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1838
|
+
const allTokensForLookup = [
|
|
1839
|
+
...longTokensForLookup,
|
|
1840
|
+
...shortTokensForLookup,
|
|
1841
|
+
];
|
|
1842
|
+
if (allTokensForLookup.length === 0)
|
|
1440
1843
|
return 0;
|
|
1441
1844
|
let maxLev = 0;
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1845
|
+
allTokensForLookup.forEach(({ symbol, marketPrefix }) => {
|
|
1846
|
+
// Match by both name AND marketPrefix for HIP3 assets
|
|
1847
|
+
const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol &&
|
|
1848
|
+
(marketPrefix
|
|
1849
|
+
? u.marketPrefix === marketPrefix
|
|
1850
|
+
: !u.marketPrefix));
|
|
1851
|
+
// Fallback to just matching by name if no exact match
|
|
1852
|
+
const fallbackUniverse = tokenUniverse || perpMetaAssets.find((u) => u.name === symbol);
|
|
1853
|
+
if (fallbackUniverse === null || fallbackUniverse === void 0 ? void 0 : fallbackUniverse.maxLeverage)
|
|
1854
|
+
maxLev = Math.max(maxLev, fallbackUniverse.maxLeverage);
|
|
1446
1855
|
});
|
|
1447
1856
|
return maxLev;
|
|
1448
1857
|
})();
|
|
@@ -1454,7 +1863,10 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1454
1863
|
// Whether all tokens have matching leverage
|
|
1455
1864
|
const leverageMatched = (() => {
|
|
1456
1865
|
const allTokensArr = [...longTokens, ...shortTokens];
|
|
1457
|
-
const allMetadata = {
|
|
1866
|
+
const allMetadata = {
|
|
1867
|
+
...longTokensMetadata,
|
|
1868
|
+
...shortTokensMetadata,
|
|
1869
|
+
};
|
|
1458
1870
|
if (allTokensArr.length === 0)
|
|
1459
1871
|
return true;
|
|
1460
1872
|
const tokensWithLev = allTokensArr.filter((token) => { var _a; return (_a = allMetadata[token.symbol]) === null || _a === void 0 ? void 0 : _a.leverage; });
|
|
@@ -5639,8 +6051,8 @@ function addAuthInterceptors(params) {
|
|
|
5639
6051
|
/**
|
|
5640
6052
|
* Fetch historical candle data from HyperLiquid API
|
|
5641
6053
|
*/
|
|
5642
|
-
const fetchHistoricalCandles = async (coin, startTime, endTime, interval,
|
|
5643
|
-
const backendCoin = toBackendSymbol(coin,
|
|
6054
|
+
const fetchHistoricalCandles = async (coin, startTime, endTime, interval, hip3Assets) => {
|
|
6055
|
+
const backendCoin = toBackendSymbol(coin, hip3Assets);
|
|
5644
6056
|
const request = {
|
|
5645
6057
|
req: { coin: backendCoin, startTime, endTime, interval },
|
|
5646
6058
|
type: 'candleSnapshot',
|
|
@@ -5799,10 +6211,10 @@ const useHistoricalPriceData = () => {
|
|
|
5799
6211
|
setTokenLoading(token.symbol, true);
|
|
5800
6212
|
});
|
|
5801
6213
|
try {
|
|
5802
|
-
const
|
|
6214
|
+
const hip3Assets = useHyperliquidData.getState().hip3Assets;
|
|
5803
6215
|
const fetchPromises = tokensToFetch.map(async (token) => {
|
|
5804
6216
|
try {
|
|
5805
|
-
const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval,
|
|
6217
|
+
const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, hip3Assets);
|
|
5806
6218
|
addHistoricalPriceData(token.symbol, interval, response.data, { start: startTime, end: endTime });
|
|
5807
6219
|
return { symbol: token.symbol, candles: response.data, success: true };
|
|
5808
6220
|
}
|
|
@@ -6491,14 +6903,14 @@ function useAutoSyncFills(options) {
|
|
|
6491
6903
|
* @throws MinimumPositionSizeError if any asset has less than $11 USD value
|
|
6492
6904
|
* @throws MaxAssetsPerLegError if any leg exceeds the maximum allowed assets (15)
|
|
6493
6905
|
*/
|
|
6494
|
-
async function createPosition(baseUrl, payload,
|
|
6906
|
+
async function createPosition(baseUrl, payload, hip3Assets) {
|
|
6495
6907
|
// Validate maximum assets per leg before creating position
|
|
6496
6908
|
validateMaxAssetsPerLeg(payload.longAssets, payload.shortAssets);
|
|
6497
6909
|
// Validate minimum asset size before creating position
|
|
6498
6910
|
validateMinimumAssetSize(payload.usdValue, payload.longAssets, payload.shortAssets);
|
|
6499
6911
|
const url = joinUrl(baseUrl, "/positions");
|
|
6500
6912
|
// Translate display symbols to backend format
|
|
6501
|
-
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
6913
|
+
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6502
6914
|
const translatedPayload = {
|
|
6503
6915
|
...payload,
|
|
6504
6916
|
longAssets: mapAssets(payload.longAssets),
|
|
@@ -6597,9 +7009,9 @@ async function adjustPosition(baseUrl, positionId, payload) {
|
|
|
6597
7009
|
throw toApiError(error);
|
|
6598
7010
|
}
|
|
6599
7011
|
}
|
|
6600
|
-
async function adjustAdvancePosition(baseUrl, positionId, payload,
|
|
7012
|
+
async function adjustAdvancePosition(baseUrl, positionId, payload, hip3Assets) {
|
|
6601
7013
|
const url = joinUrl(baseUrl, `/positions/${positionId}/adjust-advance`);
|
|
6602
|
-
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
7014
|
+
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6603
7015
|
const translatedPayload = (payload || []).map((item) => ({
|
|
6604
7016
|
longAssets: mapAssets(item.longAssets),
|
|
6605
7017
|
shortAssets: mapAssets(item.shortAssets),
|
|
@@ -6682,10 +7094,11 @@ const calculatePositionAsset = (asset, currentPrice, totalInitialPositionSize, l
|
|
|
6682
7094
|
positionValue: currentNotional,
|
|
6683
7095
|
unrealizedPnl: unrealizedPnl,
|
|
6684
7096
|
entryPositionValue: entryNotional,
|
|
6685
|
-
initialWeight: totalInitialPositionSize > 0
|
|
6686
|
-
? entryNotional / totalInitialPositionSize
|
|
6687
|
-
: 0,
|
|
7097
|
+
initialWeight: totalInitialPositionSize > 0 ? entryNotional / totalInitialPositionSize : 0,
|
|
6688
7098
|
fundingPaid: (_a = asset.fundingPaid) !== null && _a !== void 0 ? _a : 0,
|
|
7099
|
+
// Preserve market metadata from raw asset (if provided by backend)
|
|
7100
|
+
marketPrefix: asset.marketPrefix,
|
|
7101
|
+
collateralToken: asset.collateralToken,
|
|
6689
7102
|
};
|
|
6690
7103
|
};
|
|
6691
7104
|
const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
|
|
@@ -6774,36 +7187,108 @@ const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
|
|
|
6774
7187
|
});
|
|
6775
7188
|
};
|
|
6776
7189
|
|
|
7190
|
+
function findAssetMeta$3(coinName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
7191
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
7192
|
+
if (!perpMetaAssets) {
|
|
7193
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7194
|
+
}
|
|
7195
|
+
if (desiredCollateral) {
|
|
7196
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === coinName && a.collateralToken === desiredCollateral);
|
|
7197
|
+
if (collateralMatch) {
|
|
7198
|
+
return {
|
|
7199
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7200
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7201
|
+
};
|
|
7202
|
+
}
|
|
7203
|
+
}
|
|
7204
|
+
if (coinName.includes(':')) {
|
|
7205
|
+
const [prefix, symbol] = coinName.split(':');
|
|
7206
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7207
|
+
var _a;
|
|
7208
|
+
return a.name === symbol &&
|
|
7209
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
7210
|
+
});
|
|
7211
|
+
if (exactMatch) {
|
|
7212
|
+
return {
|
|
7213
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7214
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7215
|
+
};
|
|
7216
|
+
}
|
|
7217
|
+
}
|
|
7218
|
+
if (knownPrefix) {
|
|
7219
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7220
|
+
var _a;
|
|
7221
|
+
return a.name === coinName &&
|
|
7222
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
7223
|
+
});
|
|
7224
|
+
if (exactMatch) {
|
|
7225
|
+
return {
|
|
7226
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
7227
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
7228
|
+
};
|
|
7229
|
+
}
|
|
7230
|
+
}
|
|
7231
|
+
const regularAsset = perpMetaAssets.find((a) => a.name === coinName && !a.marketPrefix);
|
|
7232
|
+
if (regularAsset) {
|
|
7233
|
+
return {
|
|
7234
|
+
collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
7235
|
+
marketPrefix: null,
|
|
7236
|
+
};
|
|
7237
|
+
}
|
|
7238
|
+
const hip3Asset = perpMetaAssets.find((a) => a.name === coinName && a.marketPrefix);
|
|
7239
|
+
if (hip3Asset) {
|
|
7240
|
+
return {
|
|
7241
|
+
collateralToken: (_h = hip3Asset.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
|
|
7242
|
+
marketPrefix: (_j = hip3Asset.marketPrefix) !== null && _j !== void 0 ? _j : null,
|
|
7243
|
+
};
|
|
7244
|
+
}
|
|
7245
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7246
|
+
}
|
|
7247
|
+
function enrichPositionAssets(assets, perpMetaAssets) {
|
|
7248
|
+
return assets.map((asset) => {
|
|
7249
|
+
var _a;
|
|
7250
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
7251
|
+
return asset;
|
|
7252
|
+
}
|
|
7253
|
+
const meta = findAssetMeta$3(asset.coin, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
7254
|
+
return {
|
|
7255
|
+
...asset,
|
|
7256
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
7257
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
7258
|
+
};
|
|
7259
|
+
});
|
|
7260
|
+
}
|
|
7261
|
+
function enrichPositions(positions, perpMetaAssets) {
|
|
7262
|
+
return positions.map((position) => ({
|
|
7263
|
+
...position,
|
|
7264
|
+
longAssets: enrichPositionAssets(position.longAssets, perpMetaAssets),
|
|
7265
|
+
shortAssets: enrichPositionAssets(position.shortAssets, perpMetaAssets),
|
|
7266
|
+
}));
|
|
7267
|
+
}
|
|
6777
7268
|
function usePosition() {
|
|
6778
7269
|
const context = useContext(PearHyperliquidContext);
|
|
6779
7270
|
if (!context) {
|
|
6780
7271
|
throw new Error('usePosition must be used within a PearHyperliquidProvider');
|
|
6781
7272
|
}
|
|
6782
7273
|
const { apiBaseUrl, isConnected } = context;
|
|
6783
|
-
const
|
|
6784
|
-
// Create position API action
|
|
7274
|
+
const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
|
|
6785
7275
|
const createPosition$1 = async (payload) => {
|
|
6786
|
-
return createPosition(apiBaseUrl, payload,
|
|
7276
|
+
return createPosition(apiBaseUrl, payload, hip3Assets);
|
|
6787
7277
|
};
|
|
6788
|
-
// Update TP/SL risk parameters for a position
|
|
6789
7278
|
const updateRiskParameters$1 = async (positionId, payload) => {
|
|
6790
7279
|
return updateRiskParameters(apiBaseUrl, positionId, payload);
|
|
6791
7280
|
};
|
|
6792
|
-
// Close a position (MARKET or TWAP)
|
|
6793
7281
|
const closePosition$1 = async (positionId, payload) => {
|
|
6794
7282
|
return closePosition(apiBaseUrl, positionId, payload);
|
|
6795
7283
|
};
|
|
6796
|
-
// Close all positions (MARKET or TWAP)
|
|
6797
7284
|
const closeAllPositions$1 = async (payload) => {
|
|
6798
7285
|
return closeAllPositions(apiBaseUrl, payload);
|
|
6799
7286
|
};
|
|
6800
|
-
// Adjust a position (REDUCE/INCREASE by %; MARKET or LIMIT)
|
|
6801
7287
|
const adjustPosition$1 = async (positionId, payload) => {
|
|
6802
7288
|
return adjustPosition(apiBaseUrl, positionId, payload);
|
|
6803
7289
|
};
|
|
6804
|
-
// Adjust to absolute target sizes per asset, optionally adding new assets
|
|
6805
7290
|
const adjustAdvancePosition$1 = async (positionId, payload) => {
|
|
6806
|
-
return adjustAdvancePosition(apiBaseUrl, positionId, payload,
|
|
7291
|
+
return adjustAdvancePosition(apiBaseUrl, positionId, payload, hip3Assets);
|
|
6807
7292
|
};
|
|
6808
7293
|
const updateLeverage$1 = async (positionId, leverage) => {
|
|
6809
7294
|
return updateLeverage(apiBaseUrl, positionId, { leverage });
|
|
@@ -6812,22 +7297,46 @@ function usePosition() {
|
|
|
6812
7297
|
const userOpenPositions = useUserData((state) => state.rawOpenPositions);
|
|
6813
7298
|
const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
6814
7299
|
const allMids = useHyperliquidData((state) => state.allMids);
|
|
7300
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
6815
7301
|
const isLoading = useMemo(() => {
|
|
6816
7302
|
return userOpenPositions === null && isConnected;
|
|
6817
7303
|
}, [userOpenPositions, isConnected]);
|
|
6818
7304
|
const openPositions = useMemo(() => {
|
|
6819
7305
|
if (!userOpenPositions || !aggregatedClearingHouseState || !allMids)
|
|
6820
7306
|
return null;
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
7307
|
+
const positions = buildPositionValue(userOpenPositions, aggregatedClearingHouseState, allMids);
|
|
7308
|
+
return enrichPositions(positions, allPerpMetaAssets);
|
|
7309
|
+
}, [
|
|
7310
|
+
userOpenPositions,
|
|
7311
|
+
aggregatedClearingHouseState,
|
|
7312
|
+
allMids,
|
|
7313
|
+
allPerpMetaAssets,
|
|
7314
|
+
]);
|
|
7315
|
+
return {
|
|
7316
|
+
createPosition: createPosition$1,
|
|
7317
|
+
updateRiskParameters: updateRiskParameters$1,
|
|
7318
|
+
closePosition: closePosition$1,
|
|
7319
|
+
closeAllPositions: closeAllPositions$1,
|
|
7320
|
+
adjustPosition: adjustPosition$1,
|
|
7321
|
+
adjustAdvancePosition: adjustAdvancePosition$1,
|
|
7322
|
+
updateLeverage: updateLeverage$1,
|
|
7323
|
+
openPositions,
|
|
7324
|
+
isLoading,
|
|
7325
|
+
};
|
|
6824
7326
|
}
|
|
6825
7327
|
|
|
6826
7328
|
async function adjustOrder(baseUrl, orderId, payload) {
|
|
6827
7329
|
const url = joinUrl(baseUrl, `/orders/${orderId}/adjust`);
|
|
6828
7330
|
try {
|
|
6829
|
-
const resp = await apiClient.put(url, payload, {
|
|
6830
|
-
|
|
7331
|
+
const resp = await apiClient.put(url, payload, {
|
|
7332
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7333
|
+
timeout: 60000,
|
|
7334
|
+
});
|
|
7335
|
+
return {
|
|
7336
|
+
data: resp.data,
|
|
7337
|
+
status: resp.status,
|
|
7338
|
+
headers: resp.headers,
|
|
7339
|
+
};
|
|
6831
7340
|
}
|
|
6832
7341
|
catch (error) {
|
|
6833
7342
|
throw toApiError(error);
|
|
@@ -6836,8 +7345,14 @@ async function adjustOrder(baseUrl, orderId, payload) {
|
|
|
6836
7345
|
async function cancelOrder(baseUrl, orderId) {
|
|
6837
7346
|
const url = joinUrl(baseUrl, `/orders/${orderId}/cancel`);
|
|
6838
7347
|
try {
|
|
6839
|
-
const resp = await apiClient.delete(url, {
|
|
6840
|
-
|
|
7348
|
+
const resp = await apiClient.delete(url, {
|
|
7349
|
+
timeout: 60000,
|
|
7350
|
+
});
|
|
7351
|
+
return {
|
|
7352
|
+
data: resp.data,
|
|
7353
|
+
status: resp.status,
|
|
7354
|
+
headers: resp.headers,
|
|
7355
|
+
};
|
|
6841
7356
|
}
|
|
6842
7357
|
catch (error) {
|
|
6843
7358
|
throw toApiError(error);
|
|
@@ -6847,19 +7362,129 @@ async function cancelTwapOrder(baseUrl, orderId) {
|
|
|
6847
7362
|
const url = joinUrl(baseUrl, `/orders/${orderId}/twap/cancel`);
|
|
6848
7363
|
try {
|
|
6849
7364
|
const resp = await apiClient.post(url, {}, { headers: { 'Content-Type': 'application/json' }, timeout: 60000 });
|
|
6850
|
-
return {
|
|
7365
|
+
return {
|
|
7366
|
+
data: resp.data,
|
|
7367
|
+
status: resp.status,
|
|
7368
|
+
headers: resp.headers,
|
|
7369
|
+
};
|
|
7370
|
+
}
|
|
7371
|
+
catch (error) {
|
|
7372
|
+
throw toApiError(error);
|
|
7373
|
+
}
|
|
7374
|
+
}
|
|
7375
|
+
/**
|
|
7376
|
+
* Execute a spot order (swap) using Pear Hyperliquid service
|
|
7377
|
+
* POST /orders/spot
|
|
7378
|
+
*/
|
|
7379
|
+
async function executeSpotOrder(baseUrl, payload) {
|
|
7380
|
+
const url = joinUrl(baseUrl, '/orders/spot');
|
|
7381
|
+
try {
|
|
7382
|
+
const resp = await apiClient.post(url, payload, {
|
|
7383
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7384
|
+
timeout: 60000,
|
|
7385
|
+
});
|
|
7386
|
+
return {
|
|
7387
|
+
data: resp.data,
|
|
7388
|
+
status: resp.status,
|
|
7389
|
+
headers: resp.headers,
|
|
7390
|
+
};
|
|
6851
7391
|
}
|
|
6852
7392
|
catch (error) {
|
|
6853
7393
|
throw toApiError(error);
|
|
6854
7394
|
}
|
|
6855
7395
|
}
|
|
6856
7396
|
|
|
7397
|
+
function findAssetMeta$2(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
7398
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
7399
|
+
if (!perpMetaAssets) {
|
|
7400
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7401
|
+
}
|
|
7402
|
+
if (desiredCollateral) {
|
|
7403
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
|
|
7404
|
+
if (collateralMatch) {
|
|
7405
|
+
return {
|
|
7406
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7407
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7408
|
+
};
|
|
7409
|
+
}
|
|
7410
|
+
}
|
|
7411
|
+
if (assetName.includes(':')) {
|
|
7412
|
+
const [prefix, symbol] = assetName.split(':');
|
|
7413
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7414
|
+
var _a;
|
|
7415
|
+
return a.name === symbol &&
|
|
7416
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
7417
|
+
});
|
|
7418
|
+
if (exactMatch) {
|
|
7419
|
+
return {
|
|
7420
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7421
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7422
|
+
};
|
|
7423
|
+
}
|
|
7424
|
+
}
|
|
7425
|
+
if (knownPrefix) {
|
|
7426
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7427
|
+
var _a;
|
|
7428
|
+
return a.name === assetName &&
|
|
7429
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
7430
|
+
});
|
|
7431
|
+
if (exactMatch) {
|
|
7432
|
+
return {
|
|
7433
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
7434
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
7435
|
+
};
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
|
|
7439
|
+
if (regularAsset) {
|
|
7440
|
+
return {
|
|
7441
|
+
collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
7442
|
+
marketPrefix: null,
|
|
7443
|
+
};
|
|
7444
|
+
}
|
|
7445
|
+
const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
|
|
7446
|
+
if (hip3Assets.length > 0) {
|
|
7447
|
+
if (desiredCollateral) {
|
|
7448
|
+
const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
|
|
7449
|
+
if (collateralMatch) {
|
|
7450
|
+
return {
|
|
7451
|
+
collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
|
|
7452
|
+
marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
|
|
7453
|
+
};
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
7456
|
+
const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
|
|
7457
|
+
const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
|
|
7458
|
+
return {
|
|
7459
|
+
collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
|
|
7460
|
+
marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
|
|
7461
|
+
};
|
|
7462
|
+
}
|
|
7463
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7464
|
+
}
|
|
7465
|
+
function enrichOrderAssets$1(assets, perpMetaAssets) {
|
|
7466
|
+
if (!assets)
|
|
7467
|
+
return [];
|
|
7468
|
+
return assets.map((asset) => {
|
|
7469
|
+
var _a;
|
|
7470
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
7471
|
+
return asset;
|
|
7472
|
+
}
|
|
7473
|
+
const meta = findAssetMeta$2(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
7474
|
+
return {
|
|
7475
|
+
...asset,
|
|
7476
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
7477
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
7478
|
+
};
|
|
7479
|
+
});
|
|
7480
|
+
}
|
|
6857
7481
|
function useOrders() {
|
|
6858
7482
|
const context = useContext(PearHyperliquidContext);
|
|
6859
7483
|
if (!context)
|
|
6860
7484
|
throw new Error('useOrders must be used within a PearHyperliquidProvider');
|
|
6861
7485
|
const { apiBaseUrl } = context;
|
|
6862
7486
|
const openOrders = useUserData((state) => state.openOrders);
|
|
7487
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
6863
7488
|
const isLoading = useMemo(() => openOrders === null && context.isConnected, [openOrders, context.isConnected]);
|
|
6864
7489
|
const { openPositions } = usePosition();
|
|
6865
7490
|
const positionsById = useMemo(() => {
|
|
@@ -6878,19 +7503,27 @@ function useOrders() {
|
|
|
6878
7503
|
const isTpSl = ord.orderType === 'TP' || ord.orderType === 'SL';
|
|
6879
7504
|
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;
|
|
6880
7505
|
const pos = positionsById.get((_e = ord.positionId) !== null && _e !== void 0 ? _e : '');
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
7506
|
+
let enrichedOrd = {
|
|
7507
|
+
...ord,
|
|
7508
|
+
longAssets: enrichOrderAssets$1(ord.longAssets, allPerpMetaAssets),
|
|
7509
|
+
shortAssets: enrichOrderAssets$1(ord.shortAssets, allPerpMetaAssets),
|
|
7510
|
+
};
|
|
7511
|
+
if (isTpSl && !hasAssets && pos) {
|
|
7512
|
+
const mapAssets = (arr) => arr.map((a) => ({
|
|
7513
|
+
asset: a.coin,
|
|
7514
|
+
weight: a.initialWeight,
|
|
7515
|
+
marketPrefix: a.marketPrefix,
|
|
7516
|
+
collateralToken: a.collateralToken,
|
|
7517
|
+
}));
|
|
7518
|
+
enrichedOrd = {
|
|
7519
|
+
...enrichedOrd,
|
|
6887
7520
|
longAssets: mapAssets(pos.longAssets),
|
|
6888
7521
|
shortAssets: mapAssets(pos.shortAssets),
|
|
6889
7522
|
};
|
|
6890
7523
|
}
|
|
6891
|
-
return
|
|
7524
|
+
return enrichedOrd;
|
|
6892
7525
|
});
|
|
6893
|
-
}, [openOrders, positionsById]);
|
|
7526
|
+
}, [openOrders, positionsById, allPerpMetaAssets]);
|
|
6894
7527
|
const adjustOrder$1 = async (orderId, payload) => {
|
|
6895
7528
|
return adjustOrder(apiBaseUrl, orderId, payload);
|
|
6896
7529
|
};
|
|
@@ -6900,16 +7533,156 @@ function useOrders() {
|
|
|
6900
7533
|
const cancelTwapOrder$1 = async (orderId) => {
|
|
6901
7534
|
return cancelTwapOrder(apiBaseUrl, orderId);
|
|
6902
7535
|
};
|
|
6903
|
-
return {
|
|
7536
|
+
return {
|
|
7537
|
+
adjustOrder: adjustOrder$1,
|
|
7538
|
+
cancelOrder: cancelOrder$1,
|
|
7539
|
+
cancelTwapOrder: cancelTwapOrder$1,
|
|
7540
|
+
openOrders: enrichedOpenOrders,
|
|
7541
|
+
isLoading,
|
|
7542
|
+
};
|
|
6904
7543
|
}
|
|
6905
7544
|
|
|
7545
|
+
/**
|
|
7546
|
+
* Hook for executing spot orders (swaps) on Hyperliquid
|
|
7547
|
+
* Use this to swap between USDC and USDH or other spot assets
|
|
7548
|
+
*/
|
|
7549
|
+
function useSpotOrder() {
|
|
7550
|
+
const context = useContext(PearHyperliquidContext);
|
|
7551
|
+
if (!context) {
|
|
7552
|
+
throw new Error('useSpotOrder must be used within a PearHyperliquidProvider');
|
|
7553
|
+
}
|
|
7554
|
+
const { apiBaseUrl } = context;
|
|
7555
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
7556
|
+
const [error, setError] = useState(null);
|
|
7557
|
+
const resetError = useCallback(() => {
|
|
7558
|
+
setError(null);
|
|
7559
|
+
}, []);
|
|
7560
|
+
const executeSpotOrder$1 = useCallback(async (payload) => {
|
|
7561
|
+
setIsLoading(true);
|
|
7562
|
+
setError(null);
|
|
7563
|
+
try {
|
|
7564
|
+
const response = await executeSpotOrder(apiBaseUrl, payload);
|
|
7565
|
+
return response;
|
|
7566
|
+
}
|
|
7567
|
+
catch (err) {
|
|
7568
|
+
const apiError = err;
|
|
7569
|
+
setError(apiError);
|
|
7570
|
+
throw apiError;
|
|
7571
|
+
}
|
|
7572
|
+
finally {
|
|
7573
|
+
setIsLoading(false);
|
|
7574
|
+
}
|
|
7575
|
+
}, [apiBaseUrl]);
|
|
7576
|
+
return {
|
|
7577
|
+
executeSpotOrder: executeSpotOrder$1,
|
|
7578
|
+
isLoading,
|
|
7579
|
+
error,
|
|
7580
|
+
resetError,
|
|
7581
|
+
};
|
|
7582
|
+
}
|
|
7583
|
+
|
|
7584
|
+
function findAssetMeta$1(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
|
|
7585
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
7586
|
+
if (!perpMetaAssets) {
|
|
7587
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7588
|
+
}
|
|
7589
|
+
if (desiredCollateral) {
|
|
7590
|
+
const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
|
|
7591
|
+
if (collateralMatch) {
|
|
7592
|
+
return {
|
|
7593
|
+
collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7594
|
+
marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7595
|
+
};
|
|
7596
|
+
}
|
|
7597
|
+
}
|
|
7598
|
+
if (assetName.includes(':')) {
|
|
7599
|
+
const [prefix, symbol] = assetName.split(':');
|
|
7600
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7601
|
+
var _a;
|
|
7602
|
+
return a.name === symbol &&
|
|
7603
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
|
|
7604
|
+
});
|
|
7605
|
+
if (exactMatch) {
|
|
7606
|
+
return {
|
|
7607
|
+
collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7608
|
+
marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7609
|
+
};
|
|
7610
|
+
}
|
|
7611
|
+
}
|
|
7612
|
+
if (knownPrefix) {
|
|
7613
|
+
const exactMatch = perpMetaAssets.find((a) => {
|
|
7614
|
+
var _a;
|
|
7615
|
+
return a.name === assetName &&
|
|
7616
|
+
((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
|
|
7617
|
+
});
|
|
7618
|
+
if (exactMatch) {
|
|
7619
|
+
return {
|
|
7620
|
+
collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
|
|
7621
|
+
marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
|
|
7622
|
+
};
|
|
7623
|
+
}
|
|
7624
|
+
}
|
|
7625
|
+
const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
|
|
7626
|
+
if (regularAsset) {
|
|
7627
|
+
return {
|
|
7628
|
+
collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
|
|
7629
|
+
marketPrefix: null,
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7632
|
+
const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
|
|
7633
|
+
if (hip3Assets.length > 0) {
|
|
7634
|
+
if (desiredCollateral) {
|
|
7635
|
+
const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
|
|
7636
|
+
if (collateralMatch) {
|
|
7637
|
+
return {
|
|
7638
|
+
collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
|
|
7639
|
+
marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
|
|
7640
|
+
};
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
|
|
7644
|
+
const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
|
|
7645
|
+
return {
|
|
7646
|
+
collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
|
|
7647
|
+
marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
|
|
7648
|
+
};
|
|
7649
|
+
}
|
|
7650
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7651
|
+
}
|
|
7652
|
+
function enrichOrderAssets(assets, perpMetaAssets) {
|
|
7653
|
+
if (!assets)
|
|
7654
|
+
return [];
|
|
7655
|
+
return assets.map((asset) => {
|
|
7656
|
+
var _a;
|
|
7657
|
+
if (asset.marketPrefix && asset.collateralToken) {
|
|
7658
|
+
return asset;
|
|
7659
|
+
}
|
|
7660
|
+
const meta = findAssetMeta$1(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
|
|
7661
|
+
return {
|
|
7662
|
+
...asset,
|
|
7663
|
+
marketPrefix: asset.marketPrefix || meta.marketPrefix,
|
|
7664
|
+
collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
|
|
7665
|
+
};
|
|
7666
|
+
});
|
|
7667
|
+
}
|
|
7668
|
+
function enrichTwapOrders(orders, perpMetaAssets) {
|
|
7669
|
+
return orders.map((order) => ({
|
|
7670
|
+
...order,
|
|
7671
|
+
longAssets: enrichOrderAssets(order.longAssets, perpMetaAssets),
|
|
7672
|
+
shortAssets: enrichOrderAssets(order.shortAssets, perpMetaAssets),
|
|
7673
|
+
}));
|
|
7674
|
+
}
|
|
6906
7675
|
function useTwap() {
|
|
6907
|
-
const twapDetails = useUserData(state => state.twapDetails);
|
|
7676
|
+
const twapDetails = useUserData((state) => state.twapDetails);
|
|
7677
|
+
const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
|
|
6908
7678
|
const context = useContext(PearHyperliquidContext);
|
|
6909
7679
|
if (!context)
|
|
6910
7680
|
throw new Error('useTwap must be used within a PearHyperliquidProvider');
|
|
6911
7681
|
const { apiBaseUrl } = context;
|
|
6912
|
-
const orders = useMemo(() =>
|
|
7682
|
+
const orders = useMemo(() => {
|
|
7683
|
+
const rawOrders = twapDetails !== null && twapDetails !== void 0 ? twapDetails : [];
|
|
7684
|
+
return enrichTwapOrders(rawOrders, allPerpMetaAssets);
|
|
7685
|
+
}, [twapDetails, allPerpMetaAssets]);
|
|
6913
7686
|
const cancelTwap$1 = async (orderId) => {
|
|
6914
7687
|
return cancelTwap(apiBaseUrl, orderId);
|
|
6915
7688
|
};
|
|
@@ -6992,59 +7765,170 @@ function useNotifications() {
|
|
|
6992
7765
|
};
|
|
6993
7766
|
}
|
|
6994
7767
|
|
|
6995
|
-
//
|
|
7768
|
+
// Helper to find asset metadata from perpMetaAssets
|
|
7769
|
+
function findAssetMeta(assetName, perpMetaAssets) {
|
|
7770
|
+
var _a, _b, _c, _d;
|
|
7771
|
+
if (!perpMetaAssets) {
|
|
7772
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7773
|
+
}
|
|
7774
|
+
// Try exact match first (for prefixed assets like "xyz:TSLA")
|
|
7775
|
+
const exactMatch = perpMetaAssets.find((a) => a.name === assetName);
|
|
7776
|
+
if (exactMatch) {
|
|
7777
|
+
return {
|
|
7778
|
+
collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
|
|
7779
|
+
marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
|
|
7780
|
+
};
|
|
7781
|
+
}
|
|
7782
|
+
// Try matching by base symbol (for non-prefixed names in data)
|
|
7783
|
+
const baseMatch = perpMetaAssets.find((a) => {
|
|
7784
|
+
const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
|
|
7785
|
+
return baseName === assetName;
|
|
7786
|
+
});
|
|
7787
|
+
if (baseMatch) {
|
|
7788
|
+
return {
|
|
7789
|
+
collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
|
|
7790
|
+
marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
|
|
7791
|
+
};
|
|
7792
|
+
}
|
|
7793
|
+
return { collateralToken: 'USDC', marketPrefix: null };
|
|
7794
|
+
}
|
|
7795
|
+
// Enrich a single asset with metadata
|
|
7796
|
+
function enrichAsset(asset, perpMetaAssets) {
|
|
7797
|
+
const meta = findAssetMeta(asset.asset, perpMetaAssets);
|
|
7798
|
+
return {
|
|
7799
|
+
...asset,
|
|
7800
|
+
collateralToken: meta.collateralToken,
|
|
7801
|
+
marketPrefix: meta.marketPrefix,
|
|
7802
|
+
};
|
|
7803
|
+
}
|
|
7804
|
+
// Enrich a basket item with collateral info
|
|
7805
|
+
function enrichBasketItem(item, perpMetaAssets) {
|
|
7806
|
+
const enrichedLongs = item.longAssets.map((a) => enrichAsset(a, perpMetaAssets));
|
|
7807
|
+
const enrichedShorts = item.shortAssets.map((a) => enrichAsset(a, perpMetaAssets));
|
|
7808
|
+
// Determine collateral type
|
|
7809
|
+
const allAssets = [...enrichedLongs, ...enrichedShorts];
|
|
7810
|
+
const hasUsdc = allAssets.some((a) => a.collateralToken === 'USDC');
|
|
7811
|
+
const hasUsdh = allAssets.some((a) => a.collateralToken === 'USDH');
|
|
7812
|
+
let collateralType = 'USDC';
|
|
7813
|
+
if (hasUsdc && hasUsdh) {
|
|
7814
|
+
collateralType = 'MIXED';
|
|
7815
|
+
}
|
|
7816
|
+
else if (hasUsdh) {
|
|
7817
|
+
collateralType = 'USDH';
|
|
7818
|
+
}
|
|
7819
|
+
return {
|
|
7820
|
+
...item,
|
|
7821
|
+
longAssets: enrichedLongs,
|
|
7822
|
+
shortAssets: enrichedShorts,
|
|
7823
|
+
collateralType,
|
|
7824
|
+
};
|
|
7825
|
+
}
|
|
7826
|
+
/**
|
|
7827
|
+
* Filter baskets by collateral type
|
|
7828
|
+
* - 'USDC': Only baskets where ALL assets use USDC (collateralType === 'USDC')
|
|
7829
|
+
* - 'USDH': Only baskets where ALL assets use USDH (collateralType === 'USDH')
|
|
7830
|
+
* - 'ALL' or undefined: No filtering, returns all baskets
|
|
7831
|
+
*/
|
|
7832
|
+
function filterByCollateral(baskets, filter) {
|
|
7833
|
+
if (!filter || filter === 'ALL') {
|
|
7834
|
+
return baskets;
|
|
7835
|
+
}
|
|
7836
|
+
return baskets.filter((basket) => {
|
|
7837
|
+
if (filter === 'USDC') {
|
|
7838
|
+
// Include baskets that are purely USDC or have USDC assets
|
|
7839
|
+
return (basket.collateralType === 'USDC' || basket.collateralType === 'MIXED');
|
|
7840
|
+
}
|
|
7841
|
+
if (filter === 'USDH') {
|
|
7842
|
+
// Include baskets that are purely USDH or have USDH assets
|
|
7843
|
+
return (basket.collateralType === 'USDH' || basket.collateralType === 'MIXED');
|
|
7844
|
+
}
|
|
7845
|
+
return true;
|
|
7846
|
+
});
|
|
7847
|
+
}
|
|
7848
|
+
// Base selector for the full market-data payload (raw from WS)
|
|
6996
7849
|
const useMarketDataPayload = () => {
|
|
6997
7850
|
return useMarketData((s) => s.marketData);
|
|
6998
7851
|
};
|
|
6999
|
-
// Full payload for 'market-data-all' channel
|
|
7852
|
+
// Full payload for 'market-data-all' channel (raw from WS)
|
|
7000
7853
|
const useMarketDataAllPayload = () => {
|
|
7001
7854
|
return useMarketData((s) => s.marketDataAll);
|
|
7002
7855
|
};
|
|
7003
|
-
//
|
|
7004
|
-
const
|
|
7005
|
-
|
|
7856
|
+
// Access perpMetaAssets for enrichment
|
|
7857
|
+
const usePerpMetaAssets = () => {
|
|
7858
|
+
return useHyperliquidData((s) => s.perpMetaAssets);
|
|
7859
|
+
};
|
|
7860
|
+
// Active baskets (with collateral and market prefix info)
|
|
7861
|
+
const useActiveBaskets = (collateralFilter) => {
|
|
7006
7862
|
const data = useMarketDataPayload();
|
|
7007
|
-
|
|
7863
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7864
|
+
return useMemo(() => {
|
|
7865
|
+
if (!(data === null || data === void 0 ? void 0 : data.active))
|
|
7866
|
+
return [];
|
|
7867
|
+
const enriched = data.active.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7868
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7869
|
+
}, [data, perpMetaAssets, collateralFilter]);
|
|
7008
7870
|
};
|
|
7009
|
-
// Top gainers (
|
|
7010
|
-
const useTopGainers = (limit) => {
|
|
7871
|
+
// Top gainers (with collateral and market prefix info)
|
|
7872
|
+
const useTopGainers = (limit, collateralFilter) => {
|
|
7011
7873
|
const data = useMarketDataPayload();
|
|
7874
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7012
7875
|
return useMemo(() => {
|
|
7013
7876
|
var _a;
|
|
7014
7877
|
const list = (_a = data === null || data === void 0 ? void 0 : data.topGainers) !== null && _a !== void 0 ? _a : [];
|
|
7015
|
-
|
|
7016
|
-
|
|
7878
|
+
const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
|
|
7879
|
+
const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7880
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7881
|
+
}, [data, perpMetaAssets, limit, collateralFilter]);
|
|
7017
7882
|
};
|
|
7018
|
-
// Top losers (
|
|
7019
|
-
const useTopLosers = (limit) => {
|
|
7883
|
+
// Top losers (with collateral and market prefix info)
|
|
7884
|
+
const useTopLosers = (limit, collateralFilter) => {
|
|
7020
7885
|
const data = useMarketDataPayload();
|
|
7886
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7021
7887
|
return useMemo(() => {
|
|
7022
7888
|
var _a;
|
|
7023
7889
|
const list = (_a = data === null || data === void 0 ? void 0 : data.topLosers) !== null && _a !== void 0 ? _a : [];
|
|
7024
|
-
|
|
7025
|
-
|
|
7890
|
+
const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
|
|
7891
|
+
const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7892
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7893
|
+
}, [data, perpMetaAssets, limit, collateralFilter]);
|
|
7026
7894
|
};
|
|
7027
|
-
// Highlighted baskets
|
|
7028
|
-
const useHighlightedBaskets = () => {
|
|
7029
|
-
var _a;
|
|
7895
|
+
// Highlighted baskets (with collateral and market prefix info)
|
|
7896
|
+
const useHighlightedBaskets = (collateralFilter) => {
|
|
7030
7897
|
const data = useMarketDataPayload();
|
|
7031
|
-
|
|
7898
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7899
|
+
return useMemo(() => {
|
|
7900
|
+
if (!(data === null || data === void 0 ? void 0 : data.highlighted))
|
|
7901
|
+
return [];
|
|
7902
|
+
const enriched = data.highlighted.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7903
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7904
|
+
}, [data, perpMetaAssets, collateralFilter]);
|
|
7032
7905
|
};
|
|
7033
|
-
// Watchlist baskets (
|
|
7034
|
-
const useWatchlistBaskets = () => {
|
|
7035
|
-
var _a;
|
|
7906
|
+
// Watchlist baskets (with collateral and market prefix info)
|
|
7907
|
+
const useWatchlistBaskets = (collateralFilter) => {
|
|
7036
7908
|
const data = useMarketDataPayload();
|
|
7037
|
-
|
|
7909
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7910
|
+
return useMemo(() => {
|
|
7911
|
+
if (!(data === null || data === void 0 ? void 0 : data.watchlist))
|
|
7912
|
+
return [];
|
|
7913
|
+
const enriched = data.watchlist.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7914
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7915
|
+
}, [data, perpMetaAssets, collateralFilter]);
|
|
7038
7916
|
};
|
|
7039
|
-
// All baskets (
|
|
7040
|
-
const useAllBaskets = () => {
|
|
7041
|
-
var _a;
|
|
7917
|
+
// All baskets (with collateral and market prefix info)
|
|
7918
|
+
const useAllBaskets = (collateralFilter) => {
|
|
7042
7919
|
const dataAll = useMarketDataAllPayload();
|
|
7043
|
-
|
|
7920
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7921
|
+
return useMemo(() => {
|
|
7922
|
+
if (!(dataAll === null || dataAll === void 0 ? void 0 : dataAll.all))
|
|
7923
|
+
return [];
|
|
7924
|
+
const enriched = dataAll.all.map((item) => enrichBasketItem(item, perpMetaAssets));
|
|
7925
|
+
return filterByCollateral(enriched, collateralFilter);
|
|
7926
|
+
}, [dataAll, perpMetaAssets, collateralFilter]);
|
|
7044
7927
|
};
|
|
7045
7928
|
// Find a basket by its exact asset composition (order-insensitive)
|
|
7046
7929
|
const useFindBasket = (longs, shorts) => {
|
|
7047
7930
|
const data = useMarketDataPayload();
|
|
7931
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
7048
7932
|
return useMemo(() => {
|
|
7049
7933
|
if (!data)
|
|
7050
7934
|
return undefined;
|
|
@@ -7058,17 +7942,28 @@ const useFindBasket = (longs, shorts) => {
|
|
|
7058
7942
|
: '';
|
|
7059
7943
|
const lKey = normalize(longs);
|
|
7060
7944
|
const sKey = normalize(shorts);
|
|
7061
|
-
const match = (item) => normalize(item.longAssets) === lKey &&
|
|
7062
|
-
|
|
7063
|
-
|
|
7945
|
+
const match = (item) => normalize(item.longAssets) === lKey &&
|
|
7946
|
+
normalize(item.shortAssets) === sKey;
|
|
7947
|
+
const found = data.active.find(match) || data.highlighted.find(match);
|
|
7948
|
+
return found
|
|
7949
|
+
? enrichBasketItem(found, perpMetaAssets)
|
|
7950
|
+
: undefined;
|
|
7951
|
+
}, [data, longs, shorts, perpMetaAssets]);
|
|
7064
7952
|
};
|
|
7065
7953
|
|
|
7066
|
-
async function toggleWatchlist(baseUrl, longAssets, shortAssets,
|
|
7954
|
+
async function toggleWatchlist(baseUrl, longAssets, shortAssets, hip3Assets) {
|
|
7067
7955
|
const url = joinUrl(baseUrl, '/watchlist');
|
|
7068
|
-
const mapAssets = (arr) => arr.map(a => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
7956
|
+
const mapAssets = (arr) => arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
7069
7957
|
try {
|
|
7070
|
-
const response = await apiClient.post(url, {
|
|
7071
|
-
|
|
7958
|
+
const response = await apiClient.post(url, {
|
|
7959
|
+
longAssets: mapAssets(longAssets),
|
|
7960
|
+
shortAssets: mapAssets(shortAssets),
|
|
7961
|
+
}, { headers: { 'Content-Type': 'application/json' } });
|
|
7962
|
+
return {
|
|
7963
|
+
data: response.data,
|
|
7964
|
+
status: response.status,
|
|
7965
|
+
headers: response.headers,
|
|
7966
|
+
};
|
|
7072
7967
|
}
|
|
7073
7968
|
catch (error) {
|
|
7074
7969
|
throw toApiError(error);
|
|
@@ -7080,11 +7975,11 @@ function useWatchlist() {
|
|
|
7080
7975
|
if (!context)
|
|
7081
7976
|
throw new Error('useWatchlist must be used within a PearHyperliquidProvider');
|
|
7082
7977
|
const { apiBaseUrl, isConnected } = context;
|
|
7083
|
-
const
|
|
7978
|
+
const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
|
|
7084
7979
|
const marketData = useMarketDataPayload();
|
|
7085
7980
|
const isLoading = useMemo(() => !marketData && isConnected, [marketData, isConnected]);
|
|
7086
7981
|
const toggle = async (longAssets, shortAssets) => {
|
|
7087
|
-
const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets,
|
|
7982
|
+
const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, hip3Assets);
|
|
7088
7983
|
// Server will push updated market-data over WS; nothing to set here
|
|
7089
7984
|
return resp;
|
|
7090
7985
|
};
|
|
@@ -7360,15 +8255,110 @@ function useAuth() {
|
|
|
7360
8255
|
};
|
|
7361
8256
|
}
|
|
7362
8257
|
|
|
8258
|
+
const useAllUserBalances = () => {
|
|
8259
|
+
const spotState = useUserData((state) => state.spotState);
|
|
8260
|
+
const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
8261
|
+
const rawClearinghouseStates = useHyperliquidData((state) => state.rawClearinghouseStates);
|
|
8262
|
+
const activeAssetData = useHyperliquidData((state) => state.activeAssetData);
|
|
8263
|
+
const { longTokensMetadata, shortTokensMetadata } = useTokenSelectionMetadata();
|
|
8264
|
+
return useMemo(() => {
|
|
8265
|
+
const isLoading = !spotState || !aggregatedClearingHouseState;
|
|
8266
|
+
// Helper function to truncate to 2 decimal places without rounding
|
|
8267
|
+
const truncateToTwoDecimals = (value) => {
|
|
8268
|
+
return Math.floor(value * 100) / 100;
|
|
8269
|
+
};
|
|
8270
|
+
// Get spot balances from spotState
|
|
8271
|
+
let spotUsdcBal = undefined;
|
|
8272
|
+
let spotUsdhBal = undefined;
|
|
8273
|
+
if (spotState) {
|
|
8274
|
+
const balances = spotState.balances || [];
|
|
8275
|
+
for (const balance of balances) {
|
|
8276
|
+
const total = parseFloat(balance.total || '0');
|
|
8277
|
+
if (balance.coin === 'USDC') {
|
|
8278
|
+
spotUsdcBal = truncateToTwoDecimals(total);
|
|
8279
|
+
}
|
|
8280
|
+
if (balance.coin === 'USDH') {
|
|
8281
|
+
spotUsdhBal = truncateToTwoDecimals(total);
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
let availableToTradeUsdhFromAsset = 0;
|
|
8286
|
+
// This activeAssetData only contains data for SELECTED tokens (user's long and short Tokens)
|
|
8287
|
+
// It does NOT contain data for all tokens, so we cannot reliably use it for available to trade as used on hl trade page
|
|
8288
|
+
// so intead, we rely on rawClearinghouseStates which provides market-specific data
|
|
8289
|
+
// if (activeAssetData) {
|
|
8290
|
+
// Object.values(activeAssetData).forEach((assetData) => {
|
|
8291
|
+
// if (!assetData.availableToTrade) return;
|
|
8292
|
+
// const coinSymbol = assetData.coin;
|
|
8293
|
+
// const availableValue = truncateToTwoDecimals(
|
|
8294
|
+
// parseFloat(assetData.availableToTrade[0] || '0'),
|
|
8295
|
+
// );
|
|
8296
|
+
// // Determine collateral type based on market prefix
|
|
8297
|
+
// // HIP3 markets have prefix: "xyz:SYMBOL", "flx:SYMBOL", "vntl:SYMBOL", etc.
|
|
8298
|
+
// if (coinSymbol.includes(':')) {
|
|
8299
|
+
// const prefix = coinSymbol.split(':')[0];
|
|
8300
|
+
// if (prefix === 'xyz') {
|
|
8301
|
+
// // xyz markets use USDC
|
|
8302
|
+
// availableToTradeUsdcFromAsset = availableValue;
|
|
8303
|
+
// } else {
|
|
8304
|
+
// // flx, vntl, hyna and other markets use USDH
|
|
8305
|
+
// availableToTradeUsdhFromAsset = availableValue;
|
|
8306
|
+
// }
|
|
8307
|
+
// } else {
|
|
8308
|
+
// // Regular markets without prefix are automatically USDC
|
|
8309
|
+
// availableToTradeUsdcFromAsset = availableValue;
|
|
8310
|
+
// }
|
|
8311
|
+
// });
|
|
8312
|
+
// }
|
|
8313
|
+
// Calculate USDC available to trade
|
|
8314
|
+
// Priority 1: Use value from activeAssetData if available (> 0)
|
|
8315
|
+
// Priority 2: Calculate from USDC-specific clearinghouseState (empty prefix)
|
|
8316
|
+
let availableToTradeUsdcValue = undefined;
|
|
8317
|
+
if (rawClearinghouseStates) {
|
|
8318
|
+
// Find USDC market (empty prefix)
|
|
8319
|
+
const usdcMarket = rawClearinghouseStates.find(([prefix]) => prefix === '');
|
|
8320
|
+
const usdcState = usdcMarket === null || usdcMarket === void 0 ? void 0 : usdcMarket[1];
|
|
8321
|
+
if (usdcState === null || usdcState === void 0 ? void 0 : usdcState.marginSummary) {
|
|
8322
|
+
const accountValue = parseFloat(usdcState.marginSummary.accountValue || '0');
|
|
8323
|
+
const totalMarginUsed = parseFloat(usdcState.marginSummary.totalMarginUsed || '0');
|
|
8324
|
+
const calculatedValue = Math.max(0, accountValue - totalMarginUsed);
|
|
8325
|
+
availableToTradeUsdcValue = truncateToTwoDecimals(calculatedValue);
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8328
|
+
// Calculate USDH available to trade
|
|
8329
|
+
// Priority 1: Use value from activeAssetData if available (> 0)
|
|
8330
|
+
// Priority 2: Use spot USDH balance
|
|
8331
|
+
const availableToTradeUsdhValue = availableToTradeUsdhFromAsset > 0
|
|
8332
|
+
? availableToTradeUsdhFromAsset
|
|
8333
|
+
: spotUsdhBal;
|
|
8334
|
+
return {
|
|
8335
|
+
spotUsdcBalance: spotUsdcBal,
|
|
8336
|
+
availableToTradeUsdc: availableToTradeUsdcValue,
|
|
8337
|
+
spotUsdhBalance: spotUsdhBal,
|
|
8338
|
+
availableToTradeUsdh: availableToTradeUsdhValue,
|
|
8339
|
+
isLoading,
|
|
8340
|
+
};
|
|
8341
|
+
}, [
|
|
8342
|
+
spotState,
|
|
8343
|
+
aggregatedClearingHouseState,
|
|
8344
|
+
rawClearinghouseStates,
|
|
8345
|
+
activeAssetData,
|
|
8346
|
+
longTokensMetadata,
|
|
8347
|
+
shortTokensMetadata,
|
|
8348
|
+
]);
|
|
8349
|
+
};
|
|
8350
|
+
|
|
7363
8351
|
const PearHyperliquidContext = createContext(undefined);
|
|
7364
8352
|
/**
|
|
7365
8353
|
* React Provider for PearHyperliquidClient
|
|
7366
8354
|
*/
|
|
7367
|
-
const PearHyperliquidProvider = ({ children, apiBaseUrl =
|
|
8355
|
+
const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearprotocol.io', clientId = 'PEARPROTOCOLUI', wsUrl = 'wss://hl-ui.pearprotocol.io/ws', }) => {
|
|
7368
8356
|
const address = useUserData((s) => s.address);
|
|
7369
8357
|
const perpsMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
|
|
7370
8358
|
const setPerpMetaAssets = useHyperliquidData((state) => state.setPerpMetaAssets);
|
|
7371
|
-
const
|
|
8359
|
+
const setAllPerpMetaAssets = useHyperliquidData((state) => state.setAllPerpMetaAssets);
|
|
8360
|
+
const setHip3Assets = useHyperliquidData((state) => state.setHip3Assets);
|
|
8361
|
+
const setHip3MarketPrefixes = useHyperliquidData((state) => state.setHip3MarketPrefixes);
|
|
7372
8362
|
const websocketsEnabled = useMemo(() => Array.isArray(perpsMetaAssets) && perpsMetaAssets.length > 0, [perpsMetaAssets]);
|
|
7373
8363
|
const { isConnected, lastError } = useHyperliquidWebSocket({
|
|
7374
8364
|
wsUrl,
|
|
@@ -7383,32 +8373,107 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
|
|
|
7383
8373
|
if (perpsMetaAssets === null) {
|
|
7384
8374
|
fetchAllPerpMetas()
|
|
7385
8375
|
.then((res) => {
|
|
7386
|
-
|
|
7387
|
-
const
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
const
|
|
7392
|
-
const
|
|
7393
|
-
|
|
7394
|
-
const
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
const
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
8376
|
+
const assetToMarkets = new Map();
|
|
8377
|
+
const marketPrefixes = new Map();
|
|
8378
|
+
const FILTERED_PREFIXES = ['hyna'];
|
|
8379
|
+
// Group assets by market prefix to match WebSocket flattening order
|
|
8380
|
+
// WebSocket sends in order: "", "flx", "hyna", "vntl", "xyz" (we filter out hyna)
|
|
8381
|
+
const assetsByPrefix = new Map();
|
|
8382
|
+
const allAssetsByPrefix = new Map();
|
|
8383
|
+
res.data.forEach((item) => {
|
|
8384
|
+
const collateralToken = item.collateralToken === 360 ? 'USDH' : 'USDC';
|
|
8385
|
+
item.universe.forEach((asset) => {
|
|
8386
|
+
var _a;
|
|
8387
|
+
const [maybePrefix, maybeMarket] = asset.name.split(':');
|
|
8388
|
+
if (maybeMarket) {
|
|
8389
|
+
// HIP3 asset with market prefix
|
|
8390
|
+
const prefix = maybePrefix.toLowerCase();
|
|
8391
|
+
const displayName = maybeMarket;
|
|
8392
|
+
const fullName = `${prefix}:${displayName}`;
|
|
8393
|
+
marketPrefixes.set(fullName, prefix);
|
|
8394
|
+
if (!FILTERED_PREFIXES.includes(prefix)) {
|
|
8395
|
+
const existingMarkets = (_a = assetToMarkets.get(displayName)) !== null && _a !== void 0 ? _a : [];
|
|
8396
|
+
if (!existingMarkets.includes(fullName)) {
|
|
8397
|
+
assetToMarkets.set(displayName, [
|
|
8398
|
+
...existingMarkets,
|
|
8399
|
+
fullName,
|
|
8400
|
+
]);
|
|
8401
|
+
}
|
|
8402
|
+
}
|
|
8403
|
+
const assetWithMeta = {
|
|
8404
|
+
...asset,
|
|
8405
|
+
name: displayName,
|
|
8406
|
+
marketPrefix: prefix,
|
|
8407
|
+
collateralToken,
|
|
8408
|
+
};
|
|
8409
|
+
// Group by market prefix
|
|
8410
|
+
const allList = allAssetsByPrefix.get(prefix) || [];
|
|
8411
|
+
allList.push(assetWithMeta);
|
|
8412
|
+
allAssetsByPrefix.set(prefix, allList);
|
|
8413
|
+
if (!FILTERED_PREFIXES.includes(prefix)) {
|
|
8414
|
+
const cleanedList = assetsByPrefix.get(prefix) || [];
|
|
8415
|
+
cleanedList.push(assetWithMeta);
|
|
8416
|
+
assetsByPrefix.set(prefix, cleanedList);
|
|
8417
|
+
}
|
|
8418
|
+
}
|
|
8419
|
+
else {
|
|
8420
|
+
// Default market asset (no prefix)
|
|
8421
|
+
const assetWithMeta = {
|
|
8422
|
+
...asset,
|
|
8423
|
+
collateralToken,
|
|
8424
|
+
};
|
|
8425
|
+
// Add to default market group ("")
|
|
8426
|
+
const defaultList = assetsByPrefix.get('') || [];
|
|
8427
|
+
defaultList.push(assetWithMeta);
|
|
8428
|
+
assetsByPrefix.set('', defaultList);
|
|
8429
|
+
const allDefaultList = allAssetsByPrefix.get('') || [];
|
|
8430
|
+
allDefaultList.push(assetWithMeta);
|
|
8431
|
+
allAssetsByPrefix.set('', allDefaultList);
|
|
8432
|
+
}
|
|
8433
|
+
});
|
|
8434
|
+
});
|
|
8435
|
+
// Flatten in consistent order: default market first, then HIP3 markets alphabetically
|
|
8436
|
+
// This ensures both REST API and WebSocket data align properly
|
|
8437
|
+
const cleanedPrefixes = Array.from(assetsByPrefix.keys()).sort((a, b) => {
|
|
8438
|
+
// Empty prefix (default market) always comes first
|
|
8439
|
+
if (a === '' && b !== '')
|
|
8440
|
+
return -1;
|
|
8441
|
+
if (a !== '' && b === '')
|
|
8442
|
+
return 1;
|
|
8443
|
+
// HIP3 markets sorted alphabetically
|
|
8444
|
+
return a.localeCompare(b);
|
|
7404
8445
|
});
|
|
7405
|
-
|
|
8446
|
+
const allPrefixes = Array.from(allAssetsByPrefix.keys()).sort((a, b) => {
|
|
8447
|
+
if (a === '' && b !== '')
|
|
8448
|
+
return -1;
|
|
8449
|
+
if (a !== '' && b === '')
|
|
8450
|
+
return 1;
|
|
8451
|
+
return a.localeCompare(b);
|
|
8452
|
+
});
|
|
8453
|
+
const cleanedPerpMetas = [];
|
|
8454
|
+
const allPerpMetas = [];
|
|
8455
|
+
cleanedPrefixes.forEach((prefix) => {
|
|
8456
|
+
const assets = assetsByPrefix.get(prefix) || [];
|
|
8457
|
+
cleanedPerpMetas.push(...assets);
|
|
8458
|
+
});
|
|
8459
|
+
allPrefixes.forEach((prefix) => {
|
|
8460
|
+
const assets = allAssetsByPrefix.get(prefix) || [];
|
|
8461
|
+
allPerpMetas.push(...assets);
|
|
8462
|
+
});
|
|
8463
|
+
setHip3Assets(assetToMarkets);
|
|
8464
|
+
setHip3MarketPrefixes(marketPrefixes);
|
|
7406
8465
|
setPerpMetaAssets(cleanedPerpMetas);
|
|
8466
|
+
setAllPerpMetaAssets(allPerpMetas);
|
|
7407
8467
|
})
|
|
7408
8468
|
.catch(() => { });
|
|
7409
8469
|
}
|
|
7410
|
-
}, [
|
|
7411
|
-
|
|
8470
|
+
}, [
|
|
8471
|
+
perpsMetaAssets,
|
|
8472
|
+
setPerpMetaAssets,
|
|
8473
|
+
setAllPerpMetaAssets,
|
|
8474
|
+
setHip3Assets,
|
|
8475
|
+
setHip3MarketPrefixes,
|
|
8476
|
+
]);
|
|
7412
8477
|
useAutoSyncFills({
|
|
7413
8478
|
baseUrl: apiBaseUrl,
|
|
7414
8479
|
address,
|
|
@@ -7444,7 +8509,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
|
|
|
7444
8509
|
function usePearHyperliquid() {
|
|
7445
8510
|
const ctx = useContext(PearHyperliquidContext);
|
|
7446
8511
|
if (!ctx)
|
|
7447
|
-
throw new Error(
|
|
8512
|
+
throw new Error('usePearHyperliquid must be used within a PearHyperliquidProvider');
|
|
7448
8513
|
return ctx;
|
|
7449
8514
|
}
|
|
7450
8515
|
|
|
@@ -7535,4 +8600,4 @@ function mapCandleIntervalToTradingViewInterval(interval) {
|
|
|
7535
8600
|
}
|
|
7536
8601
|
}
|
|
7537
8602
|
|
|
7538
|
-
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 };
|
|
8603
|
+
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 };
|