@pear-protocol/hyperliquid-sdk 0.0.72 → 0.0.73

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