@pear-protocol/hyperliquid-sdk 0.0.73-beta.2 → 0.0.73-beta.5

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