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

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