@pear-protocol/hyperliquid-sdk 0.0.72 → 0.0.73-beta-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { useState, useRef, useCallback, useEffect, useMemo, useContext, createContext } from 'react';
3
- import { create } from 'zustand';
2
+ import React, { useState, useRef, useCallback, useEffect, useMemo, useContext, createContext } from 'react';
4
3
 
5
4
  // Browser-compatible WebSocket ready state enum (mirrors native values)
6
5
  var ReadyState;
@@ -11,6 +10,47 @@ var ReadyState;
11
10
  ReadyState[ReadyState["CLOSED"] = 3] = "CLOSED";
12
11
  })(ReadyState || (ReadyState = {}));
13
12
 
13
+ const createStoreImpl = (createState) => {
14
+ let state;
15
+ const listeners = /* @__PURE__ */ new Set();
16
+ const setState = (partial, replace) => {
17
+ const nextState = typeof partial === "function" ? partial(state) : partial;
18
+ if (!Object.is(nextState, state)) {
19
+ const previousState = state;
20
+ state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
21
+ listeners.forEach((listener) => listener(state, previousState));
22
+ }
23
+ };
24
+ const getState = () => state;
25
+ const getInitialState = () => initialState;
26
+ const subscribe = (listener) => {
27
+ listeners.add(listener);
28
+ return () => listeners.delete(listener);
29
+ };
30
+ const api = { setState, getState, getInitialState, subscribe };
31
+ const initialState = state = createState(setState, getState, api);
32
+ return api;
33
+ };
34
+ const createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl);
35
+
36
+ const identity = (arg) => arg;
37
+ function useStore(api, selector = identity) {
38
+ const slice = React.useSyncExternalStore(
39
+ api.subscribe,
40
+ React.useCallback(() => selector(api.getState()), [api, selector]),
41
+ React.useCallback(() => selector(api.getInitialState()), [api, selector])
42
+ );
43
+ React.useDebugValue(slice);
44
+ return slice;
45
+ }
46
+ const createImpl = (createState) => {
47
+ const api = createStore(createState);
48
+ const useBoundStore = (selector) => useStore(api, selector);
49
+ Object.assign(useBoundStore, api);
50
+ return useBoundStore;
51
+ };
52
+ const create = ((createState) => createState ? createImpl(createState) : createImpl);
53
+
14
54
  const useUserData = create((set) => ({
15
55
  accessToken: null,
16
56
  refreshToken: null,
@@ -23,6 +63,7 @@ const useUserData = create((set) => ({
23
63
  twapDetails: null,
24
64
  notifications: null,
25
65
  userExtraAgents: null,
66
+ spotState: null,
26
67
  setAccessToken: (token) => set({ accessToken: token }),
27
68
  setRefreshToken: (token) => set({ refreshToken: token }),
28
69
  setIsAuthenticated: (value) => set({ isAuthenticated: value }),
@@ -43,6 +84,7 @@ const useUserData = create((set) => ({
43
84
  setAccountSummary: (value) => set({ accountSummary: value }),
44
85
  setTwapDetails: (value) => set({ twapDetails: value }),
45
86
  setNotifications: (value) => set({ notifications: value }),
87
+ setSpotState: (value) => set({ spotState: value }),
46
88
  clean: () => set({
47
89
  accessToken: null,
48
90
  refreshToken: null,
@@ -54,6 +96,7 @@ const useUserData = create((set) => ({
54
96
  accountSummary: null,
55
97
  twapDetails: null,
56
98
  notifications: null,
99
+ spotState: null,
57
100
  }),
58
101
  setUserExtraAgents: (value) => set({ userExtraAgents: value }),
59
102
  }));
@@ -71,18 +114,71 @@ const useMarketData = create((set) => ({
71
114
  * Convert a full/prefixed symbol (e.g., "xyz:XYZ100") to a display symbol (e.g., "XYZ100").
72
115
  */
73
116
  function toDisplaySymbol(symbol) {
74
- const parts = symbol.split(":");
117
+ const parts = symbol.split(':');
75
118
  return parts.length > 1 ? parts.slice(-1)[0] : symbol;
76
119
  }
77
120
  /**
78
121
  * Convert a display symbol back to backend form using a provided map.
79
122
  * If mapping is missing, returns the original symbol.
80
- * @param displaySymbol e.g., "XYZ100"
81
- * @param displayToFull map of display -> full (e.g., "XYZ100" -> "xyz:XYZ100")
123
+ * For multi-market assets, returns the first available market.
124
+ * @param displaySymbol e.g., "TSLA"
125
+ * @param hip3Assets map of display -> all full market names (e.g., "TSLA" -> ["xyz:TSLA", "flx:TSLA"])
126
+ */
127
+ function toBackendSymbol(displaySymbol, hip3Assets) {
128
+ const markets = hip3Assets.get(displaySymbol);
129
+ // Return first market if available, otherwise return original symbol
130
+ return markets && markets.length > 0 ? markets[0] : displaySymbol;
131
+ }
132
+ /**
133
+ * Convert a display symbol to backend form for a specific market prefix.
134
+ * This is useful when an asset is available on multiple markets (e.g., xyz:TSLA and flx:TSLA).
135
+ * @param displaySymbol e.g., "TSLA"
136
+ * @param marketPrefix e.g., "xyz" or "flx"
137
+ * @param hip3Assets map of display -> all full market names
138
+ * @returns Full market name if found, null if prefix not specified for multi-market asset, otherwise displaySymbol with prefix
139
+ */
140
+ function toBackendSymbolWithMarket(displaySymbol, marketPrefix, hip3Assets) {
141
+ const availableMarkets = hip3Assets.get(displaySymbol);
142
+ if (!availableMarkets || availableMarkets.length === 0) {
143
+ // Not a HIP-3 asset, return as-is or with prefix if provided
144
+ return marketPrefix ? `${marketPrefix}:${displaySymbol}` : displaySymbol;
145
+ }
146
+ if (marketPrefix) {
147
+ // Find the market with the specified prefix
148
+ const targetMarket = availableMarkets.find((market) => market.toLowerCase().startsWith(`${marketPrefix.toLowerCase()}:`));
149
+ if (targetMarket) {
150
+ return targetMarket;
151
+ }
152
+ }
153
+ // No prefix specified or not found, return null to force explicit market selection
154
+ return null;
155
+ }
156
+ /**
157
+ * Get all available markets for a display symbol.
158
+ * @param displaySymbol e.g., "TSLA"
159
+ * @param hip3Assets map of display -> all full market names
160
+ * @returns Array of full market names, e.g., ["xyz:TSLA", "flx:TSLA"]
82
161
  */
83
- function toBackendSymbol(displaySymbol, displayToFull) {
162
+ function getAvailableMarkets(displaySymbol, hip3Assets) {
84
163
  var _a;
85
- return (_a = displayToFull.get(displaySymbol)) !== null && _a !== void 0 ? _a : displaySymbol;
164
+ return (_a = hip3Assets.get(displaySymbol)) !== null && _a !== void 0 ? _a : [];
165
+ }
166
+ /**
167
+ * Extract the market prefix from a full market name.
168
+ * @param fullSymbol e.g., "xyz:TSLA"
169
+ * @returns The prefix (e.g., "xyz") or undefined if no prefix
170
+ */
171
+ function getMarketPrefix(fullSymbol) {
172
+ const parts = fullSymbol.split(':');
173
+ return parts.length > 1 ? parts[0] : undefined;
174
+ }
175
+ /**
176
+ * Check if a symbol is a HIP-3 market (has a prefix).
177
+ * @param symbol e.g., "xyz:TSLA" or "TSLA"
178
+ * @returns true if the symbol has a market prefix
179
+ */
180
+ function isHip3Market(symbol) {
181
+ return symbol.includes(':');
86
182
  }
87
183
 
88
184
  const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
@@ -99,7 +195,8 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
99
195
  try {
100
196
  const message = JSON.parse(event.data);
101
197
  // Handle subscription responses (only if they don't have channel data)
102
- if (('success' in message || 'error' in message) && !('channel' in message)) {
198
+ if (('success' in message || 'error' in message) &&
199
+ !('channel' in message)) {
103
200
  if (message.error) {
104
201
  setLastError(message.error);
105
202
  }
@@ -118,12 +215,21 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
118
215
  switch (dataMessage.channel) {
119
216
  case 'trade-histories':
120
217
  {
218
+ const mapAsset = (a) => {
219
+ var _a, _b;
220
+ const extractedPrefix = getMarketPrefix(a.coin);
221
+ return {
222
+ ...a,
223
+ coin: toDisplaySymbol(a.coin),
224
+ marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
225
+ };
226
+ };
121
227
  const list = dataMessage.data.map((item) => {
122
228
  var _a, _b;
123
229
  return ({
124
230
  ...item,
125
- closedLongAssets: item.closedLongAssets.map((a) => ({ ...a, coin: toDisplaySymbol(a.coin) })),
126
- closedShortAssets: item.closedShortAssets.map((a) => ({ ...a, coin: toDisplaySymbol(a.coin) })),
231
+ closedLongAssets: item.closedLongAssets.map(mapAsset),
232
+ closedShortAssets: item.closedShortAssets.map(mapAsset),
127
233
  positionLongAssets: (_a = item.positionLongAssets) === null || _a === void 0 ? void 0 : _a.map((a) => toDisplaySymbol(a)),
128
234
  positionShortAssets: (_b = item.positionShortAssets) === null || _b === void 0 ? void 0 : _b.map((a) => toDisplaySymbol(a)),
129
235
  });
@@ -133,10 +239,19 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
133
239
  break;
134
240
  case 'open-positions':
135
241
  {
242
+ const enrichAsset = (a) => {
243
+ var _a, _b;
244
+ const extractedPrefix = getMarketPrefix(a.coin);
245
+ return {
246
+ ...a,
247
+ coin: toDisplaySymbol(a.coin),
248
+ marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
249
+ };
250
+ };
136
251
  const list = dataMessage.data.map((pos) => ({
137
252
  ...pos,
138
- longAssets: pos.longAssets.map((a) => ({ ...a, coin: toDisplaySymbol(a.coin) })),
139
- shortAssets: pos.shortAssets.map((a) => ({ ...a, coin: toDisplaySymbol(a.coin) })),
253
+ longAssets: pos.longAssets.map(enrichAsset),
254
+ shortAssets: pos.shortAssets.map(enrichAsset),
140
255
  }));
141
256
  setRawOpenPositions(list);
142
257
  }
@@ -145,8 +260,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
145
260
  {
146
261
  const list = dataMessage.data.map((order) => ({
147
262
  ...order,
148
- longAssets: order.longAssets.map((a) => ({ ...a, asset: toDisplaySymbol(a.asset) })),
149
- shortAssets: order.shortAssets.map((a) => ({ ...a, asset: toDisplaySymbol(a.asset) })),
263
+ longAssets: order.longAssets.map((a) => ({
264
+ ...a,
265
+ asset: toDisplaySymbol(a.asset),
266
+ })),
267
+ shortAssets: order.shortAssets.map((a) => ({
268
+ ...a,
269
+ asset: toDisplaySymbol(a.asset),
270
+ })),
150
271
  }));
151
272
  setOpenOrders(list);
152
273
  }
@@ -156,10 +277,20 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
156
277
  break;
157
278
  case 'twap-details':
158
279
  {
280
+ const mapTwapAsset = (a) => {
281
+ var _a, _b, _c;
282
+ const extractedPrefix = getMarketPrefix(a.asset);
283
+ return {
284
+ ...a,
285
+ asset: toDisplaySymbol(a.asset),
286
+ marketPrefix: (_b = (_a = a.marketPrefix) !== null && _a !== void 0 ? _a : extractedPrefix) !== null && _b !== void 0 ? _b : null,
287
+ collateralToken: (_c = a.collateralToken) !== null && _c !== void 0 ? _c : undefined,
288
+ };
289
+ };
159
290
  const list = dataMessage.data.map((twap) => ({
160
291
  ...twap,
161
- longAssets: twap.longAssets.map((a) => ({ ...a, asset: toDisplaySymbol(a.asset) })),
162
- shortAssets: twap.shortAssets.map((a) => ({ ...a, asset: toDisplaySymbol(a.asset) })),
292
+ longAssets: twap.longAssets.map(mapTwapAsset),
293
+ shortAssets: twap.shortAssets.map(mapTwapAsset),
163
294
  }));
164
295
  setTwapDetails(list);
165
296
  }
@@ -172,8 +303,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
172
303
  const md = dataMessage.data;
173
304
  const mapGroup = (g) => ({
174
305
  ...g,
175
- longAssets: g.longAssets.map((a) => ({ ...a, asset: toDisplaySymbol(a.asset) })),
176
- shortAssets: g.shortAssets.map((a) => ({ ...a, asset: toDisplaySymbol(a.asset) })),
306
+ longAssets: g.longAssets.map((a) => ({
307
+ ...a,
308
+ asset: toDisplaySymbol(a.asset),
309
+ })),
310
+ shortAssets: g.shortAssets.map((a) => ({
311
+ ...a,
312
+ asset: toDisplaySymbol(a.asset),
313
+ })),
177
314
  });
178
315
  const mapped = {
179
316
  active: md.active.map(mapGroup),
@@ -191,7 +328,15 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
191
328
  catch (error) {
192
329
  setLastError(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`);
193
330
  }
194
- }, [setTradeHistories, setRawOpenPositions, setOpenOrders, setAccountSummary, setTwapDetails, setNotifications, setMarketData]);
331
+ }, [
332
+ setTradeHistories,
333
+ setRawOpenPositions,
334
+ setOpenOrders,
335
+ setAccountSummary,
336
+ setTwapDetails,
337
+ setNotifications,
338
+ setMarketData,
339
+ ]);
195
340
  const connect = useCallback(() => {
196
341
  if (!enabled || !wsUrl)
197
342
  return;
@@ -260,7 +405,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
260
405
  'open-orders',
261
406
  'twap-details',
262
407
  'fills-checkpoint',
263
- 'notifications'
408
+ 'notifications',
264
409
  ];
265
410
  const globalChannels = ['market-data'];
266
411
  if (address && address !== lastSubscribedAddress) {
@@ -269,14 +414,14 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
269
414
  sendMessage(JSON.stringify({
270
415
  action: 'unsubscribe',
271
416
  address: lastSubscribedAddress,
272
- channels: addressSpecificChannels
417
+ channels: addressSpecificChannels,
273
418
  }));
274
419
  }
275
420
  // Subscribe to all channels (global + address-specific)
276
421
  sendMessage(JSON.stringify({
277
422
  action: 'subscribe',
278
423
  address: address,
279
- channels: [...globalChannels, ...addressSpecificChannels]
424
+ channels: [...globalChannels, ...addressSpecificChannels],
280
425
  }));
281
426
  setLastSubscribedAddress(address);
282
427
  setLastError(null);
@@ -286,7 +431,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
286
431
  sendMessage(JSON.stringify({
287
432
  action: 'unsubscribe',
288
433
  address: lastSubscribedAddress,
289
- channels: addressSpecificChannels
434
+ channels: addressSpecificChannels,
290
435
  }));
291
436
  setLastSubscribedAddress(null);
292
437
  }
@@ -294,7 +439,7 @@ const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
294
439
  // If no address but connected, subscribe to global channels only
295
440
  sendMessage(JSON.stringify({
296
441
  action: 'subscribe',
297
- channels: globalChannels
442
+ channels: globalChannels,
298
443
  }));
299
444
  }
300
445
  }, [isConnected, address, lastSubscribedAddress, sendMessage]);
@@ -317,11 +462,14 @@ const useHyperliquidData = create((set, get) => ({
317
462
  finalAssetContexts: null,
318
463
  finalAtOICaps: null,
319
464
  aggregatedClearingHouseState: null,
465
+ rawClearinghouseStates: null,
320
466
  perpMetaAssets: null,
321
- hip3DisplayToFull: new Map(),
467
+ allPerpMetaAssets: null,
468
+ hip3Assets: new Map(),
469
+ hip3MarketPrefixes: new Map(),
322
470
  setAllMids: (value) => set({ allMids: value }),
323
471
  setActiveAssetData: (value) => set((state) => ({
324
- activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value
472
+ activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value,
325
473
  })),
326
474
  deleteActiveAssetData: (key) => {
327
475
  set((state) => {
@@ -356,13 +504,16 @@ const useHyperliquidData = create((set, get) => ({
356
504
  activeAssetData: {
357
505
  ...state.activeAssetData,
358
506
  [key]: value,
359
- }
507
+ },
360
508
  })),
361
509
  setFinalAssetContexts: (value) => set({ finalAssetContexts: value }),
362
510
  setFinalAtOICaps: (value) => set({ finalAtOICaps: value }),
363
511
  setAggregatedClearingHouseState: (value) => set({ aggregatedClearingHouseState: value }),
512
+ setRawClearinghouseStates: (value) => set({ rawClearinghouseStates: value }),
364
513
  setPerpMetaAssets: (value) => set({ perpMetaAssets: value }),
365
- setHip3DisplayToFull: (value) => set({ hip3DisplayToFull: value })
514
+ setAllPerpMetaAssets: (value) => set({ allPerpMetaAssets: value }),
515
+ setHip3Assets: (value) => set({ hip3Assets: value }),
516
+ setHip3MarketPrefixes: (value) => set({ hip3MarketPrefixes: value }),
366
517
  }));
367
518
 
368
519
  /**
@@ -624,8 +775,9 @@ const useUserSelection$1 = create((set, get) => ({
624
775
  resetToDefaults: () => set((prev) => ({ ...prev, ...DEFAULT_STATE })),
625
776
  }));
626
777
 
627
- const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
628
- const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState, } = useHyperliquidData();
778
+ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }) => {
779
+ const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState, setRawClearinghouseStates, } = useHyperliquidData();
780
+ const { setSpotState } = useUserData();
629
781
  const { candleInterval } = useUserSelection$1();
630
782
  const longTokens = useUserSelection$1((s) => s.longTokens);
631
783
  const shortTokens = useUserSelection$1((s) => s.shortTokens);
@@ -644,9 +796,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
644
796
  try {
645
797
  const message = JSON.parse(event.data);
646
798
  // Handle subscription responses
647
- if ("success" in message || "error" in message) {
799
+ if ('success' in message || 'error' in message) {
648
800
  if (message.error) {
649
- console.error("[HyperLiquid WS] Subscription error:", message.error);
801
+ console.error('[HyperLiquid WS] Subscription error:', message.error);
650
802
  setLastError(message.error);
651
803
  }
652
804
  else {
@@ -655,30 +807,52 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
655
807
  return;
656
808
  }
657
809
  // Handle channel data messages
658
- if ("channel" in message && "data" in message) {
810
+ if ('channel' in message && 'data' in message) {
659
811
  const response = message;
660
812
  switch (response.channel) {
661
- case "webData3":
813
+ case 'userFills':
814
+ {
815
+ const maybePromise = onUserFills === null || onUserFills === void 0 ? void 0 : onUserFills();
816
+ if (maybePromise instanceof Promise) {
817
+ maybePromise.catch((err) => console.error('[HyperLiquid WS] userFills callback error', err));
818
+ }
819
+ }
820
+ break;
821
+ case 'webData3':
662
822
  const webData3 = response.data;
663
823
  // finalAssetContexts now sourced from allDexsAssetCtxs channel
664
824
  const finalAtOICaps = webData3.perpDexStates.flatMap((dex) => dex.perpsAtOpenInterestCap);
665
825
  setFinalAtOICaps(finalAtOICaps);
666
826
  break;
667
- case "allDexsAssetCtxs":
827
+ case 'allDexsAssetCtxs':
668
828
  {
669
829
  const data = response.data;
670
- const finalAssetContexts = (data.ctxs || []).flatMap(([, ctxs]) => ctxs || []);
830
+ // Filter out hyna to match perpMetaAssets filtering
831
+ const FILTERED_DEX_PREFIXES = ['hyna'];
832
+ const filtered = (data.ctxs || [])
833
+ .filter(([prefix]) => !FILTERED_DEX_PREFIXES.includes((prefix || '').toLowerCase()))
834
+ .sort((a, b) => {
835
+ // Sort to match perpMetaAssets order: default market first, then alphabetically
836
+ const prefixA = a[0] || '';
837
+ const prefixB = b[0] || '';
838
+ if (prefixA === '' && prefixB !== '')
839
+ return -1;
840
+ if (prefixA !== '' && prefixB === '')
841
+ return 1;
842
+ return prefixA.localeCompare(prefixB);
843
+ });
844
+ const finalAssetContexts = filtered.flatMap(([, ctxs]) => ctxs || []);
671
845
  setFinalAssetContexts(finalAssetContexts);
672
846
  }
673
847
  break;
674
- case "allDexsClearinghouseState":
848
+ case 'allDexsClearinghouseState':
675
849
  {
676
850
  const data = response.data;
677
851
  const states = (data.clearinghouseStates || [])
678
852
  .map(([, s]) => s)
679
853
  .filter(Boolean);
680
- const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || "0") || 0), 0);
681
- const toStr = (n) => Number.isFinite(n) ? n.toString() : "0";
854
+ const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || '0') || 0), 0);
855
+ const toStr = (n) => Number.isFinite(n) ? n.toString() : '0';
682
856
  const assetPositions = states.flatMap((s) => s.assetPositions || []);
683
857
  const crossMaintenanceMarginUsed = toStr(sum(states.map((s) => s.crossMaintenanceMarginUsed)));
684
858
  const crossMarginSummary = {
@@ -704,22 +878,41 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
704
878
  withdrawable,
705
879
  };
706
880
  setAggregatedClearingHouseState(aggregatedClearingHouseState);
881
+ setRawClearinghouseStates(data.clearinghouseStates || null);
707
882
  }
708
883
  break;
709
- case "allMids":
884
+ case 'allMids':
710
885
  {
711
886
  const data = response.data;
712
- const remapped = {
713
- mids: Object.fromEntries(
714
- // only support non hip-3 and xyz market
715
- Object.entries(data.mids || {})
716
- .filter(([k, v]) => !k.includes(":") || k.includes("xyz:"))
717
- .map(([k, v]) => [toDisplaySymbol(k), v])),
718
- };
719
- setAllMids(remapped);
887
+ // Keep BOTH normalized prefixed keys AND display symbol keys
888
+ // This ensures xyz:TSLA and flx:TSLA are stored separately,
889
+ // while also maintaining backward compatibility with non-prefixed lookups
890
+ const mids = {};
891
+ Object.entries(data.mids || {}).forEach(([k, v]) => {
892
+ // Normalize prefixed keys to lowercase prefix (e.g., "XYZ:TSLA" -> "xyz:TSLA")
893
+ // This matches how we look up tokens in the SDK
894
+ let normalizedKey = k;
895
+ if (k.includes(':')) {
896
+ const [prefix, ...rest] = k.split(':');
897
+ normalizedKey = `${prefix.toLowerCase()}:${rest.join(':')}`;
898
+ }
899
+ // Store with normalized key
900
+ mids[normalizedKey] = v;
901
+ // Also store with original key for backward compatibility
902
+ if (k !== normalizedKey) {
903
+ mids[k] = v;
904
+ }
905
+ // Also store with display symbol for backward compatibility
906
+ const displayKey = toDisplaySymbol(k);
907
+ // Only set display key if it doesn't already exist (avoid overwriting market-specific prices)
908
+ if (!(displayKey in mids)) {
909
+ mids[displayKey] = v;
910
+ }
911
+ });
912
+ setAllMids({ mids });
720
913
  }
721
914
  break;
722
- case "activeAssetData":
915
+ case 'activeAssetData':
723
916
  {
724
917
  const assetData = response.data;
725
918
  const symbol = toDisplaySymbol(assetData.coin);
@@ -730,14 +923,22 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
730
923
  upsertActiveAssetData(symbol, normalized);
731
924
  }
732
925
  break;
733
- case "candle":
926
+ case 'candle':
734
927
  {
735
928
  const candleDataItem = response.data;
736
- const symbol = toDisplaySymbol(candleDataItem.s || "");
929
+ const symbol = toDisplaySymbol(candleDataItem.s || '');
737
930
  const normalized = { ...candleDataItem, s: symbol };
738
931
  addCandleData(symbol, normalized);
739
932
  }
740
933
  break;
934
+ case 'spotState':
935
+ {
936
+ const spotStateData = response.data;
937
+ if (spotStateData === null || spotStateData === void 0 ? void 0 : spotStateData.spotState) {
938
+ setSpotState(spotStateData.spotState);
939
+ }
940
+ }
941
+ break;
741
942
  default:
742
943
  console.warn(`[HyperLiquid WS] Unknown channel: ${response.channel}`);
743
944
  }
@@ -745,7 +946,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
745
946
  }
746
947
  catch (error) {
747
948
  const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
748
- console.error("[HyperLiquid WS] Parse error:", errorMessage, "Raw message:", event.data);
949
+ console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', event.data);
749
950
  setLastError(errorMessage);
750
951
  }
751
952
  }, [
@@ -755,6 +956,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
755
956
  setFinalAssetContexts,
756
957
  setFinalAtOICaps,
757
958
  setAggregatedClearingHouseState,
959
+ setRawClearinghouseStates,
960
+ setSpotState,
961
+ onUserFills,
758
962
  ]);
759
963
  const connect = useCallback(() => {
760
964
  if (!enabled)
@@ -785,7 +989,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
785
989
  if (!manualCloseRef.current && reconnectAttemptsRef.current < 5) {
786
990
  reconnectAttemptsRef.current += 1;
787
991
  if (reconnectAttemptsRef.current === 5) {
788
- console.error("[HyperLiquid WS] Reconnection stopped after 5 attempts");
992
+ console.error('[HyperLiquid WS] Reconnection stopped after 5 attempts');
789
993
  }
790
994
  setTimeout(() => connect(), 3000);
791
995
  }
@@ -853,6 +1057,17 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
853
1057
  },
854
1058
  };
855
1059
  sendJsonMessage(unsubscribeMessage);
1060
+ // Unsubscribe from spotState for previous address
1061
+ if (subscribedAddress !== DEFAULT_ADDRESS) {
1062
+ const unsubscribeSpotState = {
1063
+ method: 'unsubscribe',
1064
+ subscription: {
1065
+ type: 'spotState',
1066
+ user: subscribedAddress,
1067
+ },
1068
+ };
1069
+ sendJsonMessage(unsubscribeSpotState);
1070
+ }
856
1071
  const unsubscribeAllDexsClearinghouseState = {
857
1072
  method: "unsubscribe",
858
1073
  subscription: {
@@ -861,6 +1076,14 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
861
1076
  },
862
1077
  };
863
1078
  sendJsonMessage(unsubscribeAllDexsClearinghouseState);
1079
+ const unsubscribeUserFills = {
1080
+ method: 'unsubscribe',
1081
+ subscription: {
1082
+ type: 'userFills',
1083
+ user: subscribedAddress,
1084
+ },
1085
+ };
1086
+ sendJsonMessage(unsubscribeUserFills);
864
1087
  }
865
1088
  const subscribeWebData3 = {
866
1089
  method: "subscribe",
@@ -892,15 +1115,38 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
892
1115
  type: "allDexsAssetCtxs",
893
1116
  },
894
1117
  };
1118
+ const subscribeUserFills = {
1119
+ method: 'subscribe',
1120
+ subscription: {
1121
+ type: 'userFills',
1122
+ user: userAddress,
1123
+ },
1124
+ };
895
1125
  sendJsonMessage(subscribeWebData3);
896
1126
  sendJsonMessage(subscribeAllDexsClearinghouseState);
897
1127
  sendJsonMessage(subscribeAllMids);
898
1128
  sendJsonMessage(subscribeAllDexsAssetCtxs);
1129
+ sendJsonMessage(subscribeUserFills);
1130
+ // Subscribe to spotState for real-time spot balances (USDH, USDC, etc.)
1131
+ // Only subscribe if we have a real user address (not the default)
1132
+ if (userAddress !== DEFAULT_ADDRESS) {
1133
+ const subscribeSpotState = {
1134
+ method: 'subscribe',
1135
+ subscription: {
1136
+ type: 'spotState',
1137
+ user: userAddress,
1138
+ },
1139
+ };
1140
+ sendJsonMessage(subscribeSpotState);
1141
+ }
899
1142
  setSubscribedAddress(userAddress);
900
1143
  // Clear previous data when address changes
901
1144
  if (subscribedAddress && subscribedAddress !== userAddress) {
902
1145
  // clear aggregatedClearingHouseState
903
1146
  setAggregatedClearingHouseState(null);
1147
+ setRawClearinghouseStates(null);
1148
+ // clear spotState
1149
+ setSpotState(null);
904
1150
  }
905
1151
  }, [
906
1152
  isConnected,
@@ -908,6 +1154,8 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
908
1154
  subscribedAddress,
909
1155
  sendJsonMessage,
910
1156
  setAggregatedClearingHouseState,
1157
+ setRawClearinghouseStates,
1158
+ setSpotState,
911
1159
  ]);
912
1160
  // Handle token subscriptions for activeAssetData
913
1161
  useEffect(() => {
@@ -1104,20 +1352,112 @@ const useAccountSummary = () => {
1104
1352
  return { data: calculated, isLoading };
1105
1353
  };
1106
1354
 
1355
+ function findAssetMeta$4(coinName, perpMetaAssets, knownPrefix, desiredCollateral) {
1356
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1357
+ if (!perpMetaAssets) {
1358
+ return { collateralToken: 'USDC', marketPrefix: null };
1359
+ }
1360
+ if (desiredCollateral) {
1361
+ const collateralMatch = perpMetaAssets.find((a) => a.name === coinName && a.collateralToken === desiredCollateral);
1362
+ if (collateralMatch) {
1363
+ return {
1364
+ collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
1365
+ marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
1366
+ };
1367
+ }
1368
+ }
1369
+ if (coinName.includes(':')) {
1370
+ const [prefix, symbol] = coinName.split(':');
1371
+ const exactMatch = perpMetaAssets.find((a) => {
1372
+ var _a;
1373
+ return a.name === symbol &&
1374
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
1375
+ });
1376
+ if (exactMatch) {
1377
+ return {
1378
+ collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
1379
+ marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
1380
+ };
1381
+ }
1382
+ }
1383
+ if (knownPrefix) {
1384
+ const exactMatch = perpMetaAssets.find((a) => {
1385
+ var _a;
1386
+ return a.name === coinName &&
1387
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
1388
+ });
1389
+ if (exactMatch) {
1390
+ return {
1391
+ collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
1392
+ marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
1393
+ };
1394
+ }
1395
+ }
1396
+ const exactMatch = perpMetaAssets.find((a) => a.name === coinName && !a.marketPrefix);
1397
+ if (exactMatch) {
1398
+ return {
1399
+ collateralToken: (_g = exactMatch.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
1400
+ marketPrefix: (_h = exactMatch.marketPrefix) !== null && _h !== void 0 ? _h : null,
1401
+ };
1402
+ }
1403
+ const hip3Matches = perpMetaAssets.filter((a) => a.name === coinName && a.marketPrefix);
1404
+ if (hip3Matches.length > 0) {
1405
+ if (desiredCollateral) {
1406
+ const collateralMatch = hip3Matches.find((a) => a.collateralToken === desiredCollateral);
1407
+ if (collateralMatch) {
1408
+ return {
1409
+ collateralToken: (_j = collateralMatch.collateralToken) !== null && _j !== void 0 ? _j : 'USDC',
1410
+ marketPrefix: (_k = collateralMatch.marketPrefix) !== null && _k !== void 0 ? _k : null,
1411
+ };
1412
+ }
1413
+ }
1414
+ const usdHMatch = hip3Matches.find((a) => a.collateralToken === 'USDH');
1415
+ const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Matches[0];
1416
+ return {
1417
+ collateralToken: (_l = chosen.collateralToken) !== null && _l !== void 0 ? _l : 'USDC',
1418
+ marketPrefix: (_m = chosen.marketPrefix) !== null && _m !== void 0 ? _m : null,
1419
+ };
1420
+ }
1421
+ return { collateralToken: 'USDC', marketPrefix: null };
1422
+ }
1423
+ function enrichTradeHistoryAssets(assets, perpMetaAssets) {
1424
+ return assets.map((asset) => {
1425
+ var _a;
1426
+ if (asset.marketPrefix && asset.collateralToken) {
1427
+ return asset;
1428
+ }
1429
+ const meta = findAssetMeta$4(asset.coin, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
1430
+ return {
1431
+ ...asset,
1432
+ marketPrefix: asset.marketPrefix || meta.marketPrefix,
1433
+ collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
1434
+ };
1435
+ });
1436
+ }
1437
+ function enrichTradeHistories(histories, perpMetaAssets) {
1438
+ return histories.map((history) => ({
1439
+ ...history,
1440
+ closedLongAssets: enrichTradeHistoryAssets(history.closedLongAssets, perpMetaAssets),
1441
+ closedShortAssets: enrichTradeHistoryAssets(history.closedShortAssets, perpMetaAssets),
1442
+ }));
1443
+ }
1107
1444
  const useTradeHistories = () => {
1108
1445
  const context = useContext(PearHyperliquidContext);
1109
1446
  if (!context) {
1110
1447
  throw new Error('useTradeHistories must be used within a PearHyperliquidProvider');
1111
1448
  }
1112
1449
  const tradeHistories = useUserData((state) => state.tradeHistories);
1450
+ const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
1113
1451
  const isLoading = useMemo(() => {
1114
1452
  return tradeHistories === null && context.isConnected;
1115
1453
  }, [tradeHistories, context.isConnected]);
1116
- return { data: tradeHistories, isLoading };
1454
+ const enrichedTradeHistories = useMemo(() => {
1455
+ if (!tradeHistories)
1456
+ return null;
1457
+ return enrichTradeHistories(tradeHistories, allPerpMetaAssets);
1458
+ }, [tradeHistories, allPerpMetaAssets]);
1459
+ return { data: enrichedTradeHistories, isLoading };
1117
1460
  };
1118
- /**
1119
- * Hook to access open orders with loading state
1120
- */
1121
1461
  const useOpenOrders = () => {
1122
1462
  const context = useContext(PearHyperliquidContext);
1123
1463
  if (!context) {
@@ -1146,21 +1486,51 @@ const useWebData = () => {
1146
1486
  const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
1147
1487
  const aggregatedClearinghouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
1148
1488
  const finalAtOICaps = useHyperliquidData((state) => state.finalAtOICaps);
1149
- const hip3Assets = useHyperliquidData((state) => state.hip3DisplayToFull);
1489
+ const hip3Assets = useHyperliquidData((state) => state.hip3Assets);
1490
+ const hip3MarketPrefixes = useHyperliquidData((state) => state.hip3MarketPrefixes);
1150
1491
  let marketDataBySymbol = {};
1151
1492
  if (finalAssetContexts && perpMetaAssets) {
1152
1493
  const result = {};
1494
+ // Build a map of display name -> asset context index (for unique display names)
1495
+ const displayNameToContextIndex = new Map();
1496
+ const seenNames = new Set();
1497
+ let contextIndex = 0;
1498
+ // First pass: map unique display names to their context index
1153
1499
  for (let index = 0; index < perpMetaAssets.length; index++) {
1154
1500
  const name = perpMetaAssets[index].name;
1155
- result[name] = {
1156
- asset: finalAssetContexts[index],
1157
- universe: perpMetaAssets[index],
1158
- };
1501
+ if (!seenNames.has(name)) {
1502
+ seenNames.add(name);
1503
+ if (contextIndex < finalAssetContexts.length) {
1504
+ displayNameToContextIndex.set(name, contextIndex);
1505
+ contextIndex++;
1506
+ }
1507
+ }
1508
+ }
1509
+ // Second pass: create nested entries for all market variants
1510
+ for (let index = 0; index < perpMetaAssets.length; index++) {
1511
+ const universeAsset = perpMetaAssets[index];
1512
+ const displayName = universeAsset.name;
1513
+ const marketPrefix = universeAsset.marketPrefix;
1514
+ const ctxIndex = displayNameToContextIndex.get(displayName);
1515
+ if (ctxIndex !== undefined) {
1516
+ const assetContext = finalAssetContexts[ctxIndex];
1517
+ // Initialize the symbol entry if it doesn't exist
1518
+ if (!result[displayName]) {
1519
+ result[displayName] = {};
1520
+ }
1521
+ // Use marketPrefix as key for HIP-3 assets, "default" for regular assets
1522
+ const variantKey = marketPrefix || 'default';
1523
+ result[displayName][variantKey] = {
1524
+ asset: assetContext,
1525
+ universe: universeAsset,
1526
+ };
1527
+ }
1159
1528
  }
1160
1529
  marketDataBySymbol = result;
1161
1530
  }
1162
1531
  return {
1163
1532
  hip3Assets,
1533
+ hip3MarketPrefixes,
1164
1534
  clearinghouseState: aggregatedClearinghouseState,
1165
1535
  perpsAtOpenInterestCap: finalAtOICaps,
1166
1536
  marketDataBySymbol,
@@ -1175,19 +1545,30 @@ const useWebData = () => {
1175
1545
  class TokenMetadataExtractor {
1176
1546
  /**
1177
1547
  * Extracts comprehensive token metadata
1178
- * @param symbol - Token symbol
1548
+ * @param symbol - Token symbol (base symbol without prefix, e.g., "TSLA")
1179
1549
  * @param perpMetaAssets - Aggregated universe assets (flattened across dexes)
1180
1550
  * @param finalAssetContexts - Aggregated asset contexts (flattened across dexes)
1181
1551
  * @param allMids - AllMids data containing current prices
1182
1552
  * @param activeAssetData - Optional active asset data containing leverage information
1553
+ * @param marketPrefix - Optional market prefix (e.g., "xyz", "flx") for HIP3 multi-market assets
1183
1554
  * @returns TokenMetadata or null if token not found
1184
1555
  */
1185
- static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
1556
+ static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketPrefix) {
1186
1557
  if (!perpMetaAssets || !finalAssetContexts || !allMids) {
1187
1558
  return null;
1188
1559
  }
1189
1560
  // Find token index in aggregated universe
1190
- const universeIndex = perpMetaAssets.findIndex(asset => asset.name === symbol);
1561
+ // For HIP3 assets, match both name AND marketPrefix
1562
+ const universeIndex = perpMetaAssets.findIndex((asset) => {
1563
+ if (asset.name !== symbol)
1564
+ return false;
1565
+ // If marketPrefix is specified, match it; otherwise match assets without prefix
1566
+ if (marketPrefix) {
1567
+ return asset.marketPrefix === marketPrefix;
1568
+ }
1569
+ // No prefix specified - match non-HIP3 asset (no marketPrefix) or first matching asset
1570
+ return !asset.marketPrefix;
1571
+ });
1191
1572
  if (universeIndex === -1) {
1192
1573
  return null;
1193
1574
  }
@@ -1196,9 +1577,20 @@ class TokenMetadataExtractor {
1196
1577
  if (!assetCtx) {
1197
1578
  return null;
1198
1579
  }
1199
- // Get current price from allMids
1200
- const currentPriceStr = allMids.mids[symbol];
1201
- const currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
1580
+ // Get current price - prefer assetCtx.midPx as it's already index-matched,
1581
+ // fall back to allMids lookup if midPx is null
1582
+ const prefixedKeyColon = marketPrefix ? `${marketPrefix}:${symbol}` : null;
1583
+ let currentPrice = 0;
1584
+ // Primary source: assetCtx.midPx (already properly indexed)
1585
+ if (assetCtx.midPx) {
1586
+ currentPrice = parseFloat(assetCtx.midPx);
1587
+ }
1588
+ // Fallback: allMids lookup with multiple key formats for HIP3 markets
1589
+ if (!currentPrice || isNaN(currentPrice)) {
1590
+ const currentPriceStr = (prefixedKeyColon && allMids.mids[prefixedKeyColon]) ||
1591
+ allMids.mids[symbol];
1592
+ currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
1593
+ }
1202
1594
  // Get previous day price
1203
1595
  const prevDayPrice = parseFloat(assetCtx.prevDayPx);
1204
1596
  // Calculate 24h price change
@@ -1209,7 +1601,11 @@ class TokenMetadataExtractor {
1209
1601
  const markPrice = parseFloat(assetCtx.markPx);
1210
1602
  const oraclePrice = parseFloat(assetCtx.oraclePx);
1211
1603
  // Extract leverage info from activeAssetData if available
1212
- const tokenActiveData = activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[symbol];
1604
+ // Try prefixed key first (e.g., "xyz:TSLA"), then fall back to plain symbol
1605
+ const activeDataKey = prefixedKeyColon && (activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[prefixedKeyColon])
1606
+ ? prefixedKeyColon
1607
+ : symbol;
1608
+ const tokenActiveData = activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[activeDataKey];
1213
1609
  const leverage = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.leverage;
1214
1610
  const maxTradeSzs = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.maxTradeSzs;
1215
1611
  const availableToTrade = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.availableToTrade;
@@ -1227,21 +1623,27 @@ class TokenMetadataExtractor {
1227
1623
  leverage,
1228
1624
  maxTradeSzs,
1229
1625
  availableToTrade,
1626
+ collateralToken: universeAsset.collateralToken,
1230
1627
  };
1231
1628
  }
1232
1629
  /**
1233
1630
  * Extracts metadata for multiple tokens
1234
- * @param symbols - Array of token symbols
1631
+ * @param tokens - Array of token objects with symbol and optional marketPrefix
1235
1632
  * @param perpMetaAssets - Aggregated universe assets
1236
1633
  * @param finalAssetContexts - Aggregated asset contexts
1237
1634
  * @param allMids - AllMids data
1238
1635
  * @param activeAssetData - Optional active asset data containing leverage information
1239
- * @returns Record of symbol to TokenMetadata
1636
+ * @returns Record of unique key to TokenMetadata. Key is "{prefix}:{symbol}" for HIP3 assets, or just "{symbol}" otherwise
1240
1637
  */
1241
- static extractMultipleTokensMetadata(symbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
1638
+ static extractMultipleTokensMetadata(tokens, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
1242
1639
  const result = {};
1243
- for (const symbol of symbols) {
1244
- result[symbol] = this.extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData);
1640
+ for (const token of tokens) {
1641
+ // Use a unique key that includes the prefix for HIP3 assets
1642
+ // This ensures xyz:TSLA and flx:TSLA get separate entries
1643
+ const resultKey = token.marketPrefix
1644
+ ? `${token.marketPrefix}:${token.symbol}`
1645
+ : token.symbol;
1646
+ result[resultKey] = this.extractTokenMetadata(token.symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, token.marketPrefix);
1245
1647
  }
1246
1648
  return result;
1247
1649
  }
@@ -1254,10 +1656,30 @@ class TokenMetadataExtractor {
1254
1656
  static isTokenAvailable(symbol, perpMetaAssets) {
1255
1657
  if (!perpMetaAssets)
1256
1658
  return false;
1257
- return perpMetaAssets.some(asset => asset.name === symbol);
1659
+ return perpMetaAssets.some((asset) => asset.name === symbol);
1258
1660
  }
1259
1661
  }
1260
1662
 
1663
+ /**
1664
+ * Parse a token string that may have a market prefix (e.g., "xyz:GOOGL" -> { prefix: "xyz", symbol: "GOOGL" })
1665
+ * This allows us to keep the full name (xyz:GOOGL) for URLs/tags while extracting just the symbol for SDK lookups.
1666
+ */
1667
+ function parseTokenWithPrefix(token) {
1668
+ if (token.includes(":")) {
1669
+ const [prefix, ...rest] = token.split(":");
1670
+ const symbol = rest.join(":").toUpperCase();
1671
+ return {
1672
+ prefix: prefix.toLowerCase(),
1673
+ symbol,
1674
+ fullName: `${prefix.toLowerCase()}:${symbol}`,
1675
+ };
1676
+ }
1677
+ return {
1678
+ prefix: null,
1679
+ symbol: token.toUpperCase(),
1680
+ fullName: token.toUpperCase(),
1681
+ };
1682
+ }
1261
1683
  const useTokenSelectionMetadataStore = create((set) => ({
1262
1684
  isPriceDataReady: false,
1263
1685
  isLoading: true,
@@ -1273,17 +1695,59 @@ const useTokenSelectionMetadataStore = create((set) => ({
1273
1695
  maxLeverage: 0,
1274
1696
  minMargin: 0,
1275
1697
  leverageMatched: true,
1276
- recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens }) => {
1277
- const isPriceDataReady = !!(perpMetaAssets && finalAssetContexts && allMids);
1278
- // Compute metadata when ready
1279
- const longSymbols = longTokens.map((t) => t.symbol);
1280
- const shortSymbols = shortTokens.map((t) => t.symbol);
1281
- const longTokensMetadata = isPriceDataReady
1282
- ? TokenMetadataExtractor.extractMultipleTokensMetadata(longSymbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1698
+ recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens, }) => {
1699
+ const isPriceDataReady = !!(perpMetaAssets &&
1700
+ finalAssetContexts &&
1701
+ allMids);
1702
+ // Parse tokens - handle prefixed tokens like "xyz:GOOGL" by extracting the symbol and market prefix
1703
+ // The full name (xyz:GOOGL) is kept as the metadata key for UI consistency
1704
+ const parsedLongTokens = longTokens.map((t) => ({
1705
+ ...t,
1706
+ parsed: parseTokenWithPrefix(t.symbol),
1707
+ }));
1708
+ const parsedShortTokens = shortTokens.map((t) => ({
1709
+ ...t,
1710
+ parsed: parseTokenWithPrefix(t.symbol),
1711
+ }));
1712
+ // Extract base symbols with their market prefixes for SDK lookups
1713
+ // This ensures xyz:TSLA and flx:TSLA get different market data
1714
+ const longTokensForLookup = parsedLongTokens.map((t) => ({
1715
+ symbol: t.parsed.symbol,
1716
+ marketPrefix: t.parsed.prefix,
1717
+ }));
1718
+ const shortTokensForLookup = parsedShortTokens.map((t) => ({
1719
+ symbol: t.parsed.symbol,
1720
+ marketPrefix: t.parsed.prefix,
1721
+ }));
1722
+ // Also extract just the base symbols (without prefix) for lookups that don't support prefixes
1723
+ const longBaseSymbols = longTokensForLookup.map((t) => t.symbol);
1724
+ const shortBaseSymbols = shortTokensForLookup.map((t) => t.symbol);
1725
+ // Get metadata using base symbols with market prefix for proper market differentiation
1726
+ const longBaseMetadata = isPriceDataReady
1727
+ ? TokenMetadataExtractor.extractMultipleTokensMetadata(longTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1283
1728
  : {};
1284
- const shortTokensMetadata = isPriceDataReady
1285
- ? TokenMetadataExtractor.extractMultipleTokensMetadata(shortSymbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1729
+ const shortBaseMetadata = isPriceDataReady
1730
+ ? TokenMetadataExtractor.extractMultipleTokensMetadata(shortTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1286
1731
  : {};
1732
+ // Re-map metadata using original full names (with prefix) as keys for UI consistency
1733
+ // The extractor now keys by "{prefix}:{symbol}" for prefixed tokens, which matches our parsed.fullName
1734
+ const longTokensMetadata = {};
1735
+ parsedLongTokens.forEach((t) => {
1736
+ var _a;
1737
+ // Use the full name (e.g., "xyz:TSLA") as the lookup key since extractor uses the same format
1738
+ const lookupKey = t.parsed.prefix
1739
+ ? `${t.parsed.prefix}:${t.parsed.symbol}`
1740
+ : t.parsed.symbol;
1741
+ longTokensMetadata[t.symbol] = (_a = longBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
1742
+ });
1743
+ const shortTokensMetadata = {};
1744
+ parsedShortTokens.forEach((t) => {
1745
+ var _a;
1746
+ const lookupKey = t.parsed.prefix
1747
+ ? `${t.parsed.prefix}:${t.parsed.symbol}`
1748
+ : t.parsed.symbol;
1749
+ shortTokensMetadata[t.symbol] = (_a = shortBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
1750
+ });
1287
1751
  // Determine loading state
1288
1752
  const allTokens = [...longTokens, ...shortTokens];
1289
1753
  const isLoading = (() => {
@@ -1291,26 +1755,33 @@ const useTokenSelectionMetadataStore = create((set) => ({
1291
1755
  return true;
1292
1756
  if (allTokens.length === 0)
1293
1757
  return false;
1294
- const allMetadata = { ...longTokensMetadata, ...shortTokensMetadata };
1758
+ const allMetadata = {
1759
+ ...longTokensMetadata,
1760
+ ...shortTokensMetadata,
1761
+ };
1295
1762
  return allTokens.some((token) => !allMetadata[token.symbol]);
1296
1763
  })();
1297
1764
  // Open interest and volume (from market data for matching asset basket)
1765
+ // Use base symbols (without prefix) for matching against market data
1298
1766
  const { openInterest, volume } = (() => {
1299
1767
  const empty = { openInterest: "0", volume: "0" };
1300
1768
  if (!(marketData === null || marketData === void 0 ? void 0 : marketData.active) || (!longTokens.length && !shortTokens.length))
1301
1769
  return empty;
1302
- const selectedLong = longTokens.map((t) => t.symbol).sort();
1303
- const selectedShort = shortTokens.map((t) => t.symbol).sort();
1770
+ const selectedLong = longBaseSymbols.slice().sort();
1771
+ const selectedShort = shortBaseSymbols.slice().sort();
1304
1772
  const match = marketData.active.find((item) => {
1305
1773
  const longs = [...item.longAssets].sort();
1306
1774
  const shorts = [...item.shortAssets].sort();
1307
- if (longs.length !== selectedLong.length || shorts.length !== selectedShort.length)
1775
+ if (longs.length !== selectedLong.length ||
1776
+ shorts.length !== selectedShort.length)
1308
1777
  return false;
1309
1778
  const longsEqual = longs.every((s, i) => s.asset === selectedLong[i]);
1310
1779
  const shortsEqual = shorts.every((s, i) => s.asset === selectedShort[i]);
1311
1780
  return longsEqual && shortsEqual;
1312
1781
  });
1313
- return match ? { openInterest: match.openInterest, volume: match.volume } : empty;
1782
+ return match
1783
+ ? { openInterest: match.openInterest, volume: match.volume }
1784
+ : empty;
1314
1785
  })();
1315
1786
  // Price ratio (only when exactly one long and one short)
1316
1787
  const { priceRatio, priceRatio24h } = (() => {
@@ -1390,17 +1861,27 @@ const useTokenSelectionMetadataStore = create((set) => ({
1390
1861
  return totalFunding;
1391
1862
  })();
1392
1863
  // Max leverage (maximum across all tokens)
1864
+ // Use tokens with their market prefixes for proper lookup in perpMetaAssets
1393
1865
  const maxLeverage = (() => {
1394
1866
  if (!perpMetaAssets)
1395
1867
  return 0;
1396
- const allTokenSymbols = [...longTokens, ...shortTokens].map((t) => t.symbol);
1397
- if (allTokenSymbols.length === 0)
1868
+ const allTokensForLookup = [
1869
+ ...longTokensForLookup,
1870
+ ...shortTokensForLookup,
1871
+ ];
1872
+ if (allTokensForLookup.length === 0)
1398
1873
  return 0;
1399
1874
  let maxLev = 0;
1400
- allTokenSymbols.forEach((symbol) => {
1401
- const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol);
1402
- if (tokenUniverse === null || tokenUniverse === void 0 ? void 0 : tokenUniverse.maxLeverage)
1403
- maxLev = Math.max(maxLev, tokenUniverse.maxLeverage);
1875
+ allTokensForLookup.forEach(({ symbol, marketPrefix }) => {
1876
+ // Match by both name AND marketPrefix for HIP3 assets
1877
+ const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol &&
1878
+ (marketPrefix
1879
+ ? u.marketPrefix === marketPrefix
1880
+ : !u.marketPrefix));
1881
+ // Fallback to just matching by name if no exact match
1882
+ const fallbackUniverse = tokenUniverse || perpMetaAssets.find((u) => u.name === symbol);
1883
+ if (fallbackUniverse === null || fallbackUniverse === void 0 ? void 0 : fallbackUniverse.maxLeverage)
1884
+ maxLev = Math.max(maxLev, fallbackUniverse.maxLeverage);
1404
1885
  });
1405
1886
  return maxLev;
1406
1887
  })();
@@ -1412,7 +1893,10 @@ const useTokenSelectionMetadataStore = create((set) => ({
1412
1893
  // Whether all tokens have matching leverage
1413
1894
  const leverageMatched = (() => {
1414
1895
  const allTokensArr = [...longTokens, ...shortTokens];
1415
- const allMetadata = { ...longTokensMetadata, ...shortTokensMetadata };
1896
+ const allMetadata = {
1897
+ ...longTokensMetadata,
1898
+ ...shortTokensMetadata,
1899
+ };
1416
1900
  if (allTokensArr.length === 0)
1417
1901
  return true;
1418
1902
  const tokensWithLev = allTokensArr.filter((token) => { var _a; return (_a = allMetadata[token.symbol]) === null || _a === void 0 ? void 0 : _a.leverage; });
@@ -5597,8 +6081,8 @@ function addAuthInterceptors(params) {
5597
6081
  /**
5598
6082
  * Fetch historical candle data from HyperLiquid API
5599
6083
  */
5600
- const fetchHistoricalCandles = async (coin, startTime, endTime, interval, displayToFull) => {
5601
- const backendCoin = toBackendSymbol(coin, displayToFull);
6084
+ const fetchHistoricalCandles = async (coin, startTime, endTime, interval, hip3Assets) => {
6085
+ const backendCoin = toBackendSymbol(coin, hip3Assets);
5602
6086
  const request = {
5603
6087
  req: { coin: backendCoin, startTime, endTime, interval },
5604
6088
  type: 'candleSnapshot',
@@ -5757,10 +6241,10 @@ const useHistoricalPriceData = () => {
5757
6241
  setTokenLoading(token.symbol, true);
5758
6242
  });
5759
6243
  try {
5760
- const displayToFull = useHyperliquidData.getState().hip3DisplayToFull;
6244
+ const hip3Assets = useHyperliquidData.getState().hip3Assets;
5761
6245
  const fetchPromises = tokensToFetch.map(async (token) => {
5762
6246
  try {
5763
- const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, displayToFull);
6247
+ const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, hip3Assets);
5764
6248
  addHistoricalPriceData(token.symbol, interval, response.data, { start: startTime, end: endTime });
5765
6249
  return { symbol: token.symbol, candles: response.data, success: true };
5766
6250
  }
@@ -6302,165 +6786,27 @@ function useAgentWallet() {
6302
6786
  };
6303
6787
  }
6304
6788
 
6305
- /**
6306
- * Sync external fills into Pear Hyperliquid service (POST /sync/fills)
6307
- */
6308
- const syncFills = async (baseUrl, payload) => {
6309
- const url = joinUrl(baseUrl, '/sync/fills');
6310
- try {
6311
- const response = await apiClient.post(url, payload, {
6312
- headers: { 'Content-Type': 'application/json' },
6313
- timeout: 30000,
6314
- });
6315
- return { data: response.data, status: response.status, headers: response.headers };
6316
- }
6317
- catch (error) {
6318
- throw toApiError(error);
6319
- }
6320
- };
6321
- /**
6322
- * Convenience: fetch user fills from HyperLiquid, then sync them to Pear backend
6323
- */
6324
- const syncUserFillsFromHyperliquid = async (baseUrl, user, aggregateByTime = true, lastSyncAt = null, assetPositions) => {
6325
- const firstStartTime = lastSyncAt ? Number(lastSyncAt) + 1 : 0;
6326
- const allFills = [];
6327
- const seenTids = new Set();
6328
- let startTime = firstStartTime;
6329
- let batchSize = 0;
6330
- do {
6331
- const { data: batch } = await fetchUserFillsFromHyperliquid(user, startTime, aggregateByTime);
6332
- batchSize = batch.length;
6333
- for (const fill of batch) {
6334
- const tid = fill.tid;
6335
- if (tid === undefined)
6336
- continue;
6337
- if (!seenTids.has(tid)) {
6338
- seenTids.add(tid);
6339
- allFills.push(fill);
6340
- }
6341
- }
6342
- if (batchSize === 2000) {
6343
- const last = batch[batch.length - 1];
6344
- startTime = last.time;
6345
- }
6346
- } while (batchSize === 2000);
6347
- startTime = firstStartTime;
6348
- batchSize = 0;
6349
- do {
6350
- const { data: twapBatch } = await fetchUserTwapSliceFillsByTime(user, startTime, aggregateByTime);
6351
- batchSize = twapBatch.length;
6352
- for (const item of twapBatch) {
6353
- const fill = item.fill;
6354
- const tid = fill.tid;
6355
- if (tid === undefined)
6356
- continue;
6357
- if (!seenTids.has(tid)) {
6358
- seenTids.add(tid);
6359
- allFills.push(fill);
6360
- }
6361
- }
6362
- if (batchSize === 2000) {
6363
- const last = twapBatch[twapBatch.length - 1];
6364
- startTime = last.fill.time;
6365
- }
6366
- } while (batchSize === 2000);
6367
- const sortedFills = [...allFills].sort((a, b) => Number(a.time) - Number(b.time));
6368
- return syncFills(baseUrl, { user, fills: sortedFills, assetPositions });
6369
- };
6370
-
6371
- /**
6372
- * Listens to address changes and periodically syncs user fills
6373
- * Defaults: aggregate=true, interval=60s
6374
- */
6375
- function useAutoSyncFills(options) {
6376
- const { baseUrl, address, intervalMs = 60000, aggregateByTime = true, } = options;
6377
- const [lastRunAt, setLastRunAt] = useState(null);
6378
- const [lastResult, setLastResult] = useState(null);
6379
- const [error, setError] = useState(null);
6380
- const [isSyncing, setIsSyncing] = useState(false);
6381
- const mountedRef = useRef(true);
6382
- const runningRef = useRef(null);
6383
- const lastSyncedAt = useUserData((state) => { var _a; return (_a = state.accountSummary) === null || _a === void 0 ? void 0 : _a.lastSyncedAt; });
6384
- const enabled = useUserData((state) => state.isAuthenticated);
6385
- useEffect(() => {
6386
- mountedRef.current = true;
6387
- return () => { mountedRef.current = false; };
6388
- }, []);
6389
- const canRun = useMemo(() => {
6390
- return Boolean(enabled && address && baseUrl);
6391
- }, [enabled, address, baseUrl]);
6392
- const doSync = useCallback(async () => {
6393
- var _a;
6394
- if (!canRun)
6395
- return;
6396
- if (runningRef.current)
6397
- return;
6398
- if (!useUserData.getState().accountSummary)
6399
- return;
6400
- if (!((_a = useHyperliquidData.getState().aggregatedClearingHouseState) === null || _a === void 0 ? void 0 : _a.assetPositions))
6401
- return;
6402
- setIsSyncing(true);
6403
- setError(null);
6404
- const promise = (async () => {
6405
- var _a;
6406
- try {
6407
- const { data } = await syncUserFillsFromHyperliquid(baseUrl, address, aggregateByTime, lastSyncedAt, useHyperliquidData.getState().aggregatedClearingHouseState.assetPositions);
6408
- if (!mountedRef.current)
6409
- return;
6410
- setLastResult(data);
6411
- setLastRunAt(Date.now());
6412
- }
6413
- catch (e) {
6414
- if (!mountedRef.current)
6415
- return;
6416
- setError((_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : 'Failed to sync user fills');
6417
- }
6418
- finally {
6419
- if (mountedRef.current)
6420
- setIsSyncing(false);
6421
- runningRef.current = null;
6422
- }
6423
- })();
6424
- runningRef.current = promise;
6425
- await promise;
6426
- }, [canRun, baseUrl, address, aggregateByTime, lastSyncedAt]);
6427
- useEffect(() => {
6428
- if (!canRun)
6429
- return;
6430
- // Fire immediately on address change/enable
6431
- void doSync();
6432
- const id = setInterval(() => {
6433
- void doSync();
6434
- }, Math.max(1000, intervalMs));
6435
- return () => clearInterval(id);
6436
- }, [canRun, doSync, intervalMs]);
6437
- return {
6438
- lastRunAt,
6439
- lastResult,
6440
- error,
6441
- isSyncing,
6442
- triggerSync: doSync,
6443
- };
6444
- }
6445
-
6446
6789
  /**
6447
6790
  * Create a position (MARKET/LIMIT/TWAP) using Pear Hyperliquid service
6448
6791
  * Authorization is derived from headers (Axios defaults or browser localStorage fallback)
6449
6792
  * @throws MinimumPositionSizeError if any asset has less than $11 USD value
6450
6793
  * @throws MaxAssetsPerLegError if any leg exceeds the maximum allowed assets (15)
6451
6794
  */
6452
- async function createPosition(baseUrl, payload, displayToFull) {
6795
+ async function createPosition(baseUrl, payload, hip3Assets) {
6453
6796
  // Validate maximum assets per leg before creating position
6454
6797
  validateMaxAssetsPerLeg(payload.longAssets, payload.shortAssets);
6455
6798
  // Validate minimum asset size before creating position
6456
6799
  validateMinimumAssetSize(payload.usdValue, payload.longAssets, payload.shortAssets);
6457
6800
  const url = joinUrl(baseUrl, "/positions");
6458
6801
  // Translate display symbols to backend format
6459
- const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
6802
+ const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
6460
6803
  const translatedPayload = {
6461
6804
  ...payload,
6462
6805
  longAssets: mapAssets(payload.longAssets),
6463
6806
  shortAssets: mapAssets(payload.shortAssets),
6807
+ assetName: payload.assetName
6808
+ ? toBackendSymbol(payload.assetName, hip3Assets)
6809
+ : undefined,
6464
6810
  };
6465
6811
  try {
6466
6812
  const resp = await apiClient.post(url, translatedPayload, {
@@ -6555,9 +6901,9 @@ async function adjustPosition(baseUrl, positionId, payload) {
6555
6901
  throw toApiError(error);
6556
6902
  }
6557
6903
  }
6558
- async function adjustAdvancePosition(baseUrl, positionId, payload, displayToFull) {
6904
+ async function adjustAdvancePosition(baseUrl, positionId, payload, hip3Assets) {
6559
6905
  const url = joinUrl(baseUrl, `/positions/${positionId}/adjust-advance`);
6560
- const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
6906
+ const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
6561
6907
  const translatedPayload = (payload || []).map((item) => ({
6562
6908
  longAssets: mapAssets(item.longAssets),
6563
6909
  shortAssets: mapAssets(item.shortAssets),
@@ -6640,10 +6986,11 @@ const calculatePositionAsset = (asset, currentPrice, totalInitialPositionSize, l
6640
6986
  positionValue: currentNotional,
6641
6987
  unrealizedPnl: unrealizedPnl,
6642
6988
  entryPositionValue: entryNotional,
6643
- initialWeight: totalInitialPositionSize > 0
6644
- ? entryNotional / totalInitialPositionSize
6645
- : 0,
6989
+ initialWeight: totalInitialPositionSize > 0 ? entryNotional / totalInitialPositionSize : 0,
6646
6990
  fundingPaid: (_a = asset.fundingPaid) !== null && _a !== void 0 ? _a : 0,
6991
+ // Preserve market metadata from raw asset (if provided by backend)
6992
+ marketPrefix: asset.marketPrefix,
6993
+ collateralToken: asset.collateralToken,
6647
6994
  };
6648
6995
  };
6649
6996
  const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
@@ -6732,36 +7079,108 @@ const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
6732
7079
  });
6733
7080
  };
6734
7081
 
6735
- function usePosition() {
6736
- const context = useContext(PearHyperliquidContext);
6737
- if (!context) {
6738
- throw new Error('usePosition must be used within a PearHyperliquidProvider');
7082
+ function findAssetMeta$3(coinName, perpMetaAssets, knownPrefix, desiredCollateral) {
7083
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
7084
+ if (!perpMetaAssets) {
7085
+ return { collateralToken: 'USDC', marketPrefix: null };
6739
7086
  }
6740
- const { apiBaseUrl, isConnected } = context;
6741
- const displayToFull = useHyperliquidData((s) => s.hip3DisplayToFull);
6742
- // Create position API action
6743
- const createPosition$1 = async (payload) => {
6744
- return createPosition(apiBaseUrl, payload, displayToFull);
6745
- };
6746
- // Update TP/SL risk parameters for a position
6747
- const updateRiskParameters$1 = async (positionId, payload) => {
6748
- return updateRiskParameters(apiBaseUrl, positionId, payload);
6749
- };
6750
- // Close a position (MARKET or TWAP)
6751
- const closePosition$1 = async (positionId, payload) => {
7087
+ if (desiredCollateral) {
7088
+ const collateralMatch = perpMetaAssets.find((a) => a.name === coinName && a.collateralToken === desiredCollateral);
7089
+ if (collateralMatch) {
7090
+ return {
7091
+ collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
7092
+ marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
7093
+ };
7094
+ }
7095
+ }
7096
+ if (coinName.includes(':')) {
7097
+ const [prefix, symbol] = coinName.split(':');
7098
+ const exactMatch = perpMetaAssets.find((a) => {
7099
+ var _a;
7100
+ return a.name === symbol &&
7101
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
7102
+ });
7103
+ if (exactMatch) {
7104
+ return {
7105
+ collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
7106
+ marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
7107
+ };
7108
+ }
7109
+ }
7110
+ if (knownPrefix) {
7111
+ const exactMatch = perpMetaAssets.find((a) => {
7112
+ var _a;
7113
+ return a.name === coinName &&
7114
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
7115
+ });
7116
+ if (exactMatch) {
7117
+ return {
7118
+ collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
7119
+ marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
7120
+ };
7121
+ }
7122
+ }
7123
+ const regularAsset = perpMetaAssets.find((a) => a.name === coinName && !a.marketPrefix);
7124
+ if (regularAsset) {
7125
+ return {
7126
+ collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
7127
+ marketPrefix: null,
7128
+ };
7129
+ }
7130
+ const hip3Asset = perpMetaAssets.find((a) => a.name === coinName && a.marketPrefix);
7131
+ if (hip3Asset) {
7132
+ return {
7133
+ collateralToken: (_h = hip3Asset.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
7134
+ marketPrefix: (_j = hip3Asset.marketPrefix) !== null && _j !== void 0 ? _j : null,
7135
+ };
7136
+ }
7137
+ return { collateralToken: 'USDC', marketPrefix: null };
7138
+ }
7139
+ function enrichPositionAssets(assets, perpMetaAssets) {
7140
+ return assets.map((asset) => {
7141
+ var _a;
7142
+ if (asset.marketPrefix && asset.collateralToken) {
7143
+ return asset;
7144
+ }
7145
+ const meta = findAssetMeta$3(asset.coin, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
7146
+ return {
7147
+ ...asset,
7148
+ marketPrefix: asset.marketPrefix || meta.marketPrefix,
7149
+ collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
7150
+ };
7151
+ });
7152
+ }
7153
+ function enrichPositions(positions, perpMetaAssets) {
7154
+ return positions.map((position) => ({
7155
+ ...position,
7156
+ longAssets: enrichPositionAssets(position.longAssets, perpMetaAssets),
7157
+ shortAssets: enrichPositionAssets(position.shortAssets, perpMetaAssets),
7158
+ }));
7159
+ }
7160
+ function usePosition() {
7161
+ const context = useContext(PearHyperliquidContext);
7162
+ if (!context) {
7163
+ throw new Error('usePosition must be used within a PearHyperliquidProvider');
7164
+ }
7165
+ const { apiBaseUrl, isConnected } = context;
7166
+ const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
7167
+ const createPosition$1 = async (payload) => {
7168
+ return createPosition(apiBaseUrl, payload, hip3Assets);
7169
+ };
7170
+ const updateRiskParameters$1 = async (positionId, payload) => {
7171
+ return updateRiskParameters(apiBaseUrl, positionId, payload);
7172
+ };
7173
+ const closePosition$1 = async (positionId, payload) => {
6752
7174
  return closePosition(apiBaseUrl, positionId, payload);
6753
7175
  };
6754
- // Close all positions (MARKET or TWAP)
6755
7176
  const closeAllPositions$1 = async (payload) => {
6756
7177
  return closeAllPositions(apiBaseUrl, payload);
6757
7178
  };
6758
- // Adjust a position (REDUCE/INCREASE by %; MARKET or LIMIT)
6759
7179
  const adjustPosition$1 = async (positionId, payload) => {
6760
7180
  return adjustPosition(apiBaseUrl, positionId, payload);
6761
7181
  };
6762
- // Adjust to absolute target sizes per asset, optionally adding new assets
6763
7182
  const adjustAdvancePosition$1 = async (positionId, payload) => {
6764
- return adjustAdvancePosition(apiBaseUrl, positionId, payload, displayToFull);
7183
+ return adjustAdvancePosition(apiBaseUrl, positionId, payload, hip3Assets);
6765
7184
  };
6766
7185
  const updateLeverage$1 = async (positionId, leverage) => {
6767
7186
  return updateLeverage(apiBaseUrl, positionId, { leverage });
@@ -6770,22 +7189,46 @@ function usePosition() {
6770
7189
  const userOpenPositions = useUserData((state) => state.rawOpenPositions);
6771
7190
  const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
6772
7191
  const allMids = useHyperliquidData((state) => state.allMids);
7192
+ const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
6773
7193
  const isLoading = useMemo(() => {
6774
7194
  return userOpenPositions === null && isConnected;
6775
7195
  }, [userOpenPositions, isConnected]);
6776
7196
  const openPositions = useMemo(() => {
6777
7197
  if (!userOpenPositions || !aggregatedClearingHouseState || !allMids)
6778
7198
  return null;
6779
- return buildPositionValue(userOpenPositions, aggregatedClearingHouseState, allMids);
6780
- }, [userOpenPositions, aggregatedClearingHouseState, allMids]);
6781
- return { createPosition: createPosition$1, updateRiskParameters: updateRiskParameters$1, closePosition: closePosition$1, closeAllPositions: closeAllPositions$1, adjustPosition: adjustPosition$1, adjustAdvancePosition: adjustAdvancePosition$1, updateLeverage: updateLeverage$1, openPositions, isLoading };
7199
+ const positions = buildPositionValue(userOpenPositions, aggregatedClearingHouseState, allMids);
7200
+ return enrichPositions(positions, allPerpMetaAssets);
7201
+ }, [
7202
+ userOpenPositions,
7203
+ aggregatedClearingHouseState,
7204
+ allMids,
7205
+ allPerpMetaAssets,
7206
+ ]);
7207
+ return {
7208
+ createPosition: createPosition$1,
7209
+ updateRiskParameters: updateRiskParameters$1,
7210
+ closePosition: closePosition$1,
7211
+ closeAllPositions: closeAllPositions$1,
7212
+ adjustPosition: adjustPosition$1,
7213
+ adjustAdvancePosition: adjustAdvancePosition$1,
7214
+ updateLeverage: updateLeverage$1,
7215
+ openPositions,
7216
+ isLoading,
7217
+ };
6782
7218
  }
6783
7219
 
6784
7220
  async function adjustOrder(baseUrl, orderId, payload) {
6785
7221
  const url = joinUrl(baseUrl, `/orders/${orderId}/adjust`);
6786
7222
  try {
6787
- const resp = await apiClient.put(url, payload, { headers: { 'Content-Type': 'application/json' }, timeout: 60000 });
6788
- return { data: resp.data, status: resp.status, headers: resp.headers };
7223
+ const resp = await apiClient.put(url, payload, {
7224
+ headers: { 'Content-Type': 'application/json' },
7225
+ timeout: 60000,
7226
+ });
7227
+ return {
7228
+ data: resp.data,
7229
+ status: resp.status,
7230
+ headers: resp.headers,
7231
+ };
6789
7232
  }
6790
7233
  catch (error) {
6791
7234
  throw toApiError(error);
@@ -6794,8 +7237,14 @@ async function adjustOrder(baseUrl, orderId, payload) {
6794
7237
  async function cancelOrder(baseUrl, orderId) {
6795
7238
  const url = joinUrl(baseUrl, `/orders/${orderId}/cancel`);
6796
7239
  try {
6797
- const resp = await apiClient.delete(url, { timeout: 60000 });
6798
- return { data: resp.data, status: resp.status, headers: resp.headers };
7240
+ const resp = await apiClient.delete(url, {
7241
+ timeout: 60000,
7242
+ });
7243
+ return {
7244
+ data: resp.data,
7245
+ status: resp.status,
7246
+ headers: resp.headers,
7247
+ };
6799
7248
  }
6800
7249
  catch (error) {
6801
7250
  throw toApiError(error);
@@ -6805,19 +7254,129 @@ async function cancelTwapOrder(baseUrl, orderId) {
6805
7254
  const url = joinUrl(baseUrl, `/orders/${orderId}/twap/cancel`);
6806
7255
  try {
6807
7256
  const resp = await apiClient.post(url, {}, { headers: { 'Content-Type': 'application/json' }, timeout: 60000 });
6808
- return { data: resp.data, status: resp.status, headers: resp.headers };
7257
+ return {
7258
+ data: resp.data,
7259
+ status: resp.status,
7260
+ headers: resp.headers,
7261
+ };
7262
+ }
7263
+ catch (error) {
7264
+ throw toApiError(error);
7265
+ }
7266
+ }
7267
+ /**
7268
+ * Execute a spot order (swap) using Pear Hyperliquid service
7269
+ * POST /orders/spot
7270
+ */
7271
+ async function executeSpotOrder(baseUrl, payload) {
7272
+ const url = joinUrl(baseUrl, '/orders/spot');
7273
+ try {
7274
+ const resp = await apiClient.post(url, payload, {
7275
+ headers: { 'Content-Type': 'application/json' },
7276
+ timeout: 60000,
7277
+ });
7278
+ return {
7279
+ data: resp.data,
7280
+ status: resp.status,
7281
+ headers: resp.headers,
7282
+ };
6809
7283
  }
6810
7284
  catch (error) {
6811
7285
  throw toApiError(error);
6812
7286
  }
6813
7287
  }
6814
7288
 
7289
+ function findAssetMeta$2(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
7290
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
7291
+ if (!perpMetaAssets) {
7292
+ return { collateralToken: 'USDC', marketPrefix: null };
7293
+ }
7294
+ if (desiredCollateral) {
7295
+ const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
7296
+ if (collateralMatch) {
7297
+ return {
7298
+ collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
7299
+ marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
7300
+ };
7301
+ }
7302
+ }
7303
+ if (assetName.includes(':')) {
7304
+ const [prefix, symbol] = assetName.split(':');
7305
+ const exactMatch = perpMetaAssets.find((a) => {
7306
+ var _a;
7307
+ return a.name === symbol &&
7308
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
7309
+ });
7310
+ if (exactMatch) {
7311
+ return {
7312
+ collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
7313
+ marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
7314
+ };
7315
+ }
7316
+ }
7317
+ if (knownPrefix) {
7318
+ const exactMatch = perpMetaAssets.find((a) => {
7319
+ var _a;
7320
+ return a.name === assetName &&
7321
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
7322
+ });
7323
+ if (exactMatch) {
7324
+ return {
7325
+ collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
7326
+ marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
7327
+ };
7328
+ }
7329
+ }
7330
+ const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
7331
+ if (regularAsset) {
7332
+ return {
7333
+ collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
7334
+ marketPrefix: null,
7335
+ };
7336
+ }
7337
+ const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
7338
+ if (hip3Assets.length > 0) {
7339
+ if (desiredCollateral) {
7340
+ const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
7341
+ if (collateralMatch) {
7342
+ return {
7343
+ collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
7344
+ marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
7345
+ };
7346
+ }
7347
+ }
7348
+ const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
7349
+ const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
7350
+ return {
7351
+ collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
7352
+ marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
7353
+ };
7354
+ }
7355
+ return { collateralToken: 'USDC', marketPrefix: null };
7356
+ }
7357
+ function enrichOrderAssets$1(assets, perpMetaAssets) {
7358
+ if (!assets)
7359
+ return [];
7360
+ return assets.map((asset) => {
7361
+ var _a;
7362
+ if (asset.marketPrefix && asset.collateralToken) {
7363
+ return asset;
7364
+ }
7365
+ const meta = findAssetMeta$2(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
7366
+ return {
7367
+ ...asset,
7368
+ marketPrefix: asset.marketPrefix || meta.marketPrefix,
7369
+ collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
7370
+ };
7371
+ });
7372
+ }
6815
7373
  function useOrders() {
6816
7374
  const context = useContext(PearHyperliquidContext);
6817
7375
  if (!context)
6818
7376
  throw new Error('useOrders must be used within a PearHyperliquidProvider');
6819
7377
  const { apiBaseUrl } = context;
6820
7378
  const openOrders = useUserData((state) => state.openOrders);
7379
+ const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
6821
7380
  const isLoading = useMemo(() => openOrders === null && context.isConnected, [openOrders, context.isConnected]);
6822
7381
  const { openPositions } = usePosition();
6823
7382
  const positionsById = useMemo(() => {
@@ -6836,19 +7395,27 @@ function useOrders() {
6836
7395
  const isTpSl = ord.orderType === 'TP' || ord.orderType === 'SL';
6837
7396
  const hasAssets = ((_b = (_a = ord.longAssets) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 || ((_d = (_c = ord.shortAssets) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0;
6838
7397
  const pos = positionsById.get((_e = ord.positionId) !== null && _e !== void 0 ? _e : '');
6839
- if (!isTpSl || !pos)
6840
- return ord;
6841
- const mapAssets = (arr) => arr.map((a) => ({ asset: a.coin, weight: a.initialWeight }));
6842
- if (!hasAssets) {
6843
- return {
6844
- ...ord,
7398
+ let enrichedOrd = {
7399
+ ...ord,
7400
+ longAssets: enrichOrderAssets$1(ord.longAssets, allPerpMetaAssets),
7401
+ shortAssets: enrichOrderAssets$1(ord.shortAssets, allPerpMetaAssets),
7402
+ };
7403
+ if (isTpSl && !hasAssets && pos) {
7404
+ const mapAssets = (arr) => arr.map((a) => ({
7405
+ asset: a.coin,
7406
+ weight: a.initialWeight,
7407
+ marketPrefix: a.marketPrefix,
7408
+ collateralToken: a.collateralToken,
7409
+ }));
7410
+ enrichedOrd = {
7411
+ ...enrichedOrd,
6845
7412
  longAssets: mapAssets(pos.longAssets),
6846
7413
  shortAssets: mapAssets(pos.shortAssets),
6847
7414
  };
6848
7415
  }
6849
- return ord;
7416
+ return enrichedOrd;
6850
7417
  });
6851
- }, [openOrders, positionsById]);
7418
+ }, [openOrders, positionsById, allPerpMetaAssets]);
6852
7419
  const adjustOrder$1 = async (orderId, payload) => {
6853
7420
  return adjustOrder(apiBaseUrl, orderId, payload);
6854
7421
  };
@@ -6858,16 +7425,156 @@ function useOrders() {
6858
7425
  const cancelTwapOrder$1 = async (orderId) => {
6859
7426
  return cancelTwapOrder(apiBaseUrl, orderId);
6860
7427
  };
6861
- return { adjustOrder: adjustOrder$1, cancelOrder: cancelOrder$1, cancelTwapOrder: cancelTwapOrder$1, openOrders: enrichedOpenOrders, isLoading };
7428
+ return {
7429
+ adjustOrder: adjustOrder$1,
7430
+ cancelOrder: cancelOrder$1,
7431
+ cancelTwapOrder: cancelTwapOrder$1,
7432
+ openOrders: enrichedOpenOrders,
7433
+ isLoading,
7434
+ };
7435
+ }
7436
+
7437
+ /**
7438
+ * Hook for executing spot orders (swaps) on Hyperliquid
7439
+ * Use this to swap between USDC and USDH or other spot assets
7440
+ */
7441
+ function useSpotOrder() {
7442
+ const context = useContext(PearHyperliquidContext);
7443
+ if (!context) {
7444
+ throw new Error('useSpotOrder must be used within a PearHyperliquidProvider');
7445
+ }
7446
+ const { apiBaseUrl } = context;
7447
+ const [isLoading, setIsLoading] = useState(false);
7448
+ const [error, setError] = useState(null);
7449
+ const resetError = useCallback(() => {
7450
+ setError(null);
7451
+ }, []);
7452
+ const executeSpotOrder$1 = useCallback(async (payload) => {
7453
+ setIsLoading(true);
7454
+ setError(null);
7455
+ try {
7456
+ const response = await executeSpotOrder(apiBaseUrl, payload);
7457
+ return response;
7458
+ }
7459
+ catch (err) {
7460
+ const apiError = err;
7461
+ setError(apiError);
7462
+ throw apiError;
7463
+ }
7464
+ finally {
7465
+ setIsLoading(false);
7466
+ }
7467
+ }, [apiBaseUrl]);
7468
+ return {
7469
+ executeSpotOrder: executeSpotOrder$1,
7470
+ isLoading,
7471
+ error,
7472
+ resetError,
7473
+ };
6862
7474
  }
6863
7475
 
7476
+ function findAssetMeta$1(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
7477
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
7478
+ if (!perpMetaAssets) {
7479
+ return { collateralToken: 'USDC', marketPrefix: null };
7480
+ }
7481
+ if (desiredCollateral) {
7482
+ const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
7483
+ if (collateralMatch) {
7484
+ return {
7485
+ collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
7486
+ marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
7487
+ };
7488
+ }
7489
+ }
7490
+ if (assetName.includes(':')) {
7491
+ const [prefix, symbol] = assetName.split(':');
7492
+ const exactMatch = perpMetaAssets.find((a) => {
7493
+ var _a;
7494
+ return a.name === symbol &&
7495
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
7496
+ });
7497
+ if (exactMatch) {
7498
+ return {
7499
+ collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
7500
+ marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
7501
+ };
7502
+ }
7503
+ }
7504
+ if (knownPrefix) {
7505
+ const exactMatch = perpMetaAssets.find((a) => {
7506
+ var _a;
7507
+ return a.name === assetName &&
7508
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
7509
+ });
7510
+ if (exactMatch) {
7511
+ return {
7512
+ collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
7513
+ marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
7514
+ };
7515
+ }
7516
+ }
7517
+ const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
7518
+ if (regularAsset) {
7519
+ return {
7520
+ collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
7521
+ marketPrefix: null,
7522
+ };
7523
+ }
7524
+ const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
7525
+ if (hip3Assets.length > 0) {
7526
+ if (desiredCollateral) {
7527
+ const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
7528
+ if (collateralMatch) {
7529
+ return {
7530
+ collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
7531
+ marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
7532
+ };
7533
+ }
7534
+ }
7535
+ const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
7536
+ const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
7537
+ return {
7538
+ collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
7539
+ marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
7540
+ };
7541
+ }
7542
+ return { collateralToken: 'USDC', marketPrefix: null };
7543
+ }
7544
+ function enrichOrderAssets(assets, perpMetaAssets) {
7545
+ if (!assets)
7546
+ return [];
7547
+ return assets.map((asset) => {
7548
+ var _a;
7549
+ if (asset.marketPrefix && asset.collateralToken) {
7550
+ return asset;
7551
+ }
7552
+ const meta = findAssetMeta$1(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
7553
+ return {
7554
+ ...asset,
7555
+ marketPrefix: asset.marketPrefix || meta.marketPrefix,
7556
+ collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
7557
+ };
7558
+ });
7559
+ }
7560
+ function enrichTwapOrders(orders, perpMetaAssets) {
7561
+ return orders.map((order) => ({
7562
+ ...order,
7563
+ longAssets: enrichOrderAssets(order.longAssets, perpMetaAssets),
7564
+ shortAssets: enrichOrderAssets(order.shortAssets, perpMetaAssets),
7565
+ }));
7566
+ }
6864
7567
  function useTwap() {
6865
- const twapDetails = useUserData(state => state.twapDetails);
7568
+ const twapDetails = useUserData((state) => state.twapDetails);
7569
+ const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
6866
7570
  const context = useContext(PearHyperliquidContext);
6867
7571
  if (!context)
6868
7572
  throw new Error('useTwap must be used within a PearHyperliquidProvider');
6869
7573
  const { apiBaseUrl } = context;
6870
- const orders = useMemo(() => twapDetails !== null && twapDetails !== void 0 ? twapDetails : [], [twapDetails]);
7574
+ const orders = useMemo(() => {
7575
+ const rawOrders = twapDetails !== null && twapDetails !== void 0 ? twapDetails : [];
7576
+ return enrichTwapOrders(rawOrders, allPerpMetaAssets);
7577
+ }, [twapDetails, allPerpMetaAssets]);
6871
7578
  const cancelTwap$1 = async (orderId) => {
6872
7579
  return cancelTwap(apiBaseUrl, orderId);
6873
7580
  };
@@ -6950,59 +7657,170 @@ function useNotifications() {
6950
7657
  };
6951
7658
  }
6952
7659
 
6953
- // Base selector for the full market-data payload
7660
+ // Helper to find asset metadata from perpMetaAssets
7661
+ function findAssetMeta(assetName, perpMetaAssets) {
7662
+ var _a, _b, _c, _d;
7663
+ if (!perpMetaAssets) {
7664
+ return { collateralToken: 'USDC', marketPrefix: null };
7665
+ }
7666
+ // Try exact match first (for prefixed assets like "xyz:TSLA")
7667
+ const exactMatch = perpMetaAssets.find((a) => a.name === assetName);
7668
+ if (exactMatch) {
7669
+ return {
7670
+ collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
7671
+ marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
7672
+ };
7673
+ }
7674
+ // Try matching by base symbol (for non-prefixed names in data)
7675
+ const baseMatch = perpMetaAssets.find((a) => {
7676
+ const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
7677
+ return baseName === assetName;
7678
+ });
7679
+ if (baseMatch) {
7680
+ return {
7681
+ collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
7682
+ marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
7683
+ };
7684
+ }
7685
+ return { collateralToken: 'USDC', marketPrefix: null };
7686
+ }
7687
+ // Enrich a single asset with metadata
7688
+ function enrichAsset(asset, perpMetaAssets) {
7689
+ const meta = findAssetMeta(asset.asset, perpMetaAssets);
7690
+ return {
7691
+ ...asset,
7692
+ collateralToken: meta.collateralToken,
7693
+ marketPrefix: meta.marketPrefix,
7694
+ };
7695
+ }
7696
+ // Enrich a basket item with collateral info
7697
+ function enrichBasketItem(item, perpMetaAssets) {
7698
+ const enrichedLongs = item.longAssets.map((a) => enrichAsset(a, perpMetaAssets));
7699
+ const enrichedShorts = item.shortAssets.map((a) => enrichAsset(a, perpMetaAssets));
7700
+ // Determine collateral type
7701
+ const allAssets = [...enrichedLongs, ...enrichedShorts];
7702
+ const hasUsdc = allAssets.some((a) => a.collateralToken === 'USDC');
7703
+ const hasUsdh = allAssets.some((a) => a.collateralToken === 'USDH');
7704
+ let collateralType = 'USDC';
7705
+ if (hasUsdc && hasUsdh) {
7706
+ collateralType = 'MIXED';
7707
+ }
7708
+ else if (hasUsdh) {
7709
+ collateralType = 'USDH';
7710
+ }
7711
+ return {
7712
+ ...item,
7713
+ longAssets: enrichedLongs,
7714
+ shortAssets: enrichedShorts,
7715
+ collateralType,
7716
+ };
7717
+ }
7718
+ /**
7719
+ * Filter baskets by collateral type
7720
+ * - 'USDC': Only baskets where ALL assets use USDC (collateralType === 'USDC')
7721
+ * - 'USDH': Only baskets where ALL assets use USDH (collateralType === 'USDH')
7722
+ * - 'ALL' or undefined: No filtering, returns all baskets
7723
+ */
7724
+ function filterByCollateral(baskets, filter) {
7725
+ if (!filter || filter === 'ALL') {
7726
+ return baskets;
7727
+ }
7728
+ return baskets.filter((basket) => {
7729
+ if (filter === 'USDC') {
7730
+ // Include baskets that are purely USDC or have USDC assets
7731
+ return (basket.collateralType === 'USDC' || basket.collateralType === 'MIXED');
7732
+ }
7733
+ if (filter === 'USDH') {
7734
+ // Include baskets that are purely USDH or have USDH assets
7735
+ return (basket.collateralType === 'USDH' || basket.collateralType === 'MIXED');
7736
+ }
7737
+ return true;
7738
+ });
7739
+ }
7740
+ // Base selector for the full market-data payload (raw from WS)
6954
7741
  const useMarketDataPayload = () => {
6955
7742
  return useMarketData((s) => s.marketData);
6956
7743
  };
6957
- // Full payload for 'market-data-all' channel
7744
+ // Full payload for 'market-data-all' channel (raw from WS)
6958
7745
  const useMarketDataAllPayload = () => {
6959
7746
  return useMarketData((s) => s.marketDataAll);
6960
7747
  };
6961
- // Active baskets
6962
- const useActiveBaskets = () => {
6963
- var _a;
7748
+ // Access perpMetaAssets for enrichment
7749
+ const usePerpMetaAssets = () => {
7750
+ return useHyperliquidData((s) => s.perpMetaAssets);
7751
+ };
7752
+ // Active baskets (with collateral and market prefix info)
7753
+ const useActiveBaskets = (collateralFilter) => {
6964
7754
  const data = useMarketDataPayload();
6965
- return (_a = data === null || data === void 0 ? void 0 : data.active) !== null && _a !== void 0 ? _a : [];
7755
+ const perpMetaAssets = usePerpMetaAssets();
7756
+ return useMemo(() => {
7757
+ if (!(data === null || data === void 0 ? void 0 : data.active))
7758
+ return [];
7759
+ const enriched = data.active.map((item) => enrichBasketItem(item, perpMetaAssets));
7760
+ return filterByCollateral(enriched, collateralFilter);
7761
+ }, [data, perpMetaAssets, collateralFilter]);
6966
7762
  };
6967
- // Top gainers (optional limit override)
6968
- const useTopGainers = (limit) => {
7763
+ // Top gainers (with collateral and market prefix info)
7764
+ const useTopGainers = (limit, collateralFilter) => {
6969
7765
  const data = useMarketDataPayload();
7766
+ const perpMetaAssets = usePerpMetaAssets();
6970
7767
  return useMemo(() => {
6971
7768
  var _a;
6972
7769
  const list = (_a = data === null || data === void 0 ? void 0 : data.topGainers) !== null && _a !== void 0 ? _a : [];
6973
- return typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
6974
- }, [data, limit]);
7770
+ const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7771
+ const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
7772
+ return filterByCollateral(enriched, collateralFilter);
7773
+ }, [data, perpMetaAssets, limit, collateralFilter]);
6975
7774
  };
6976
- // Top losers (optional limit override)
6977
- const useTopLosers = (limit) => {
7775
+ // Top losers (with collateral and market prefix info)
7776
+ const useTopLosers = (limit, collateralFilter) => {
6978
7777
  const data = useMarketDataPayload();
7778
+ const perpMetaAssets = usePerpMetaAssets();
6979
7779
  return useMemo(() => {
6980
7780
  var _a;
6981
7781
  const list = (_a = data === null || data === void 0 ? void 0 : data.topLosers) !== null && _a !== void 0 ? _a : [];
6982
- return typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
6983
- }, [data, limit]);
7782
+ const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7783
+ const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
7784
+ return filterByCollateral(enriched, collateralFilter);
7785
+ }, [data, perpMetaAssets, limit, collateralFilter]);
6984
7786
  };
6985
- // Highlighted baskets
6986
- const useHighlightedBaskets = () => {
6987
- var _a;
7787
+ // Highlighted baskets (with collateral and market prefix info)
7788
+ const useHighlightedBaskets = (collateralFilter) => {
6988
7789
  const data = useMarketDataPayload();
6989
- return (_a = data === null || data === void 0 ? void 0 : data.highlighted) !== null && _a !== void 0 ? _a : [];
7790
+ const perpMetaAssets = usePerpMetaAssets();
7791
+ return useMemo(() => {
7792
+ if (!(data === null || data === void 0 ? void 0 : data.highlighted))
7793
+ return [];
7794
+ const enriched = data.highlighted.map((item) => enrichBasketItem(item, perpMetaAssets));
7795
+ return filterByCollateral(enriched, collateralFilter);
7796
+ }, [data, perpMetaAssets, collateralFilter]);
6990
7797
  };
6991
- // Watchlist baskets (from market-data payload when subscribed with address)
6992
- const useWatchlistBaskets = () => {
6993
- var _a;
7798
+ // Watchlist baskets (with collateral and market prefix info)
7799
+ const useWatchlistBaskets = (collateralFilter) => {
6994
7800
  const data = useMarketDataPayload();
6995
- return (_a = data === null || data === void 0 ? void 0 : data.watchlist) !== null && _a !== void 0 ? _a : [];
7801
+ const perpMetaAssets = usePerpMetaAssets();
7802
+ return useMemo(() => {
7803
+ if (!(data === null || data === void 0 ? void 0 : data.watchlist))
7804
+ return [];
7805
+ const enriched = data.watchlist.map((item) => enrichBasketItem(item, perpMetaAssets));
7806
+ return filterByCollateral(enriched, collateralFilter);
7807
+ }, [data, perpMetaAssets, collateralFilter]);
6996
7808
  };
6997
- // All baskets (from market-data-all)
6998
- const useAllBaskets = () => {
6999
- var _a;
7809
+ // All baskets (with collateral and market prefix info)
7810
+ const useAllBaskets = (collateralFilter) => {
7000
7811
  const dataAll = useMarketDataAllPayload();
7001
- return (_a = dataAll === null || dataAll === void 0 ? void 0 : dataAll.all) !== null && _a !== void 0 ? _a : [];
7812
+ const perpMetaAssets = usePerpMetaAssets();
7813
+ return useMemo(() => {
7814
+ if (!(dataAll === null || dataAll === void 0 ? void 0 : dataAll.all))
7815
+ return [];
7816
+ const enriched = dataAll.all.map((item) => enrichBasketItem(item, perpMetaAssets));
7817
+ return filterByCollateral(enriched, collateralFilter);
7818
+ }, [dataAll, perpMetaAssets, collateralFilter]);
7002
7819
  };
7003
7820
  // Find a basket by its exact asset composition (order-insensitive)
7004
7821
  const useFindBasket = (longs, shorts) => {
7005
7822
  const data = useMarketDataPayload();
7823
+ const perpMetaAssets = usePerpMetaAssets();
7006
7824
  return useMemo(() => {
7007
7825
  if (!data)
7008
7826
  return undefined;
@@ -7016,17 +7834,28 @@ const useFindBasket = (longs, shorts) => {
7016
7834
  : '';
7017
7835
  const lKey = normalize(longs);
7018
7836
  const sKey = normalize(shorts);
7019
- const match = (item) => normalize(item.longAssets) === lKey && normalize(item.shortAssets) === sKey;
7020
- return data.active.find(match) || data.highlighted.find(match);
7021
- }, [data, longs, shorts]);
7837
+ const match = (item) => normalize(item.longAssets) === lKey &&
7838
+ normalize(item.shortAssets) === sKey;
7839
+ const found = data.active.find(match) || data.highlighted.find(match);
7840
+ return found
7841
+ ? enrichBasketItem(found, perpMetaAssets)
7842
+ : undefined;
7843
+ }, [data, longs, shorts, perpMetaAssets]);
7022
7844
  };
7023
7845
 
7024
- async function toggleWatchlist(baseUrl, longAssets, shortAssets, displayToFull) {
7846
+ async function toggleWatchlist(baseUrl, longAssets, shortAssets, hip3Assets) {
7025
7847
  const url = joinUrl(baseUrl, '/watchlist');
7026
- const mapAssets = (arr) => arr.map(a => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
7848
+ const mapAssets = (arr) => arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
7027
7849
  try {
7028
- const response = await apiClient.post(url, { longAssets: mapAssets(longAssets), shortAssets: mapAssets(shortAssets) }, { headers: { 'Content-Type': 'application/json' } });
7029
- return { data: response.data, status: response.status, headers: response.headers };
7850
+ const response = await apiClient.post(url, {
7851
+ longAssets: mapAssets(longAssets),
7852
+ shortAssets: mapAssets(shortAssets),
7853
+ }, { headers: { 'Content-Type': 'application/json' } });
7854
+ return {
7855
+ data: response.data,
7856
+ status: response.status,
7857
+ headers: response.headers,
7858
+ };
7030
7859
  }
7031
7860
  catch (error) {
7032
7861
  throw toApiError(error);
@@ -7038,11 +7867,11 @@ function useWatchlist() {
7038
7867
  if (!context)
7039
7868
  throw new Error('useWatchlist must be used within a PearHyperliquidProvider');
7040
7869
  const { apiBaseUrl, isConnected } = context;
7041
- const displayToFull = useHyperliquidData((s) => s.hip3DisplayToFull);
7870
+ const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
7042
7871
  const marketData = useMarketDataPayload();
7043
7872
  const isLoading = useMemo(() => !marketData && isConnected, [marketData, isConnected]);
7044
7873
  const toggle = async (longAssets, shortAssets) => {
7045
- const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, displayToFull);
7874
+ const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, hip3Assets);
7046
7875
  // Server will push updated market-data over WS; nothing to set here
7047
7876
  return resp;
7048
7877
  };
@@ -7296,17 +8125,249 @@ function useAuth() {
7296
8125
  };
7297
8126
  }
7298
8127
 
8128
+ const useAllUserBalances = () => {
8129
+ const spotState = useUserData((state) => state.spotState);
8130
+ const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
8131
+ const rawClearinghouseStates = useHyperliquidData((state) => state.rawClearinghouseStates);
8132
+ const activeAssetData = useHyperliquidData((state) => state.activeAssetData);
8133
+ const { longTokensMetadata, shortTokensMetadata } = useTokenSelectionMetadata();
8134
+ return useMemo(() => {
8135
+ const isLoading = !spotState || !aggregatedClearingHouseState;
8136
+ // Helper function to truncate to 2 decimal places without rounding
8137
+ const truncateToTwoDecimals = (value) => {
8138
+ return Math.floor(value * 100) / 100;
8139
+ };
8140
+ // Get spot balances from spotState
8141
+ let spotUsdcBal = undefined;
8142
+ let spotUsdhBal = undefined;
8143
+ if (spotState) {
8144
+ const balances = spotState.balances || [];
8145
+ for (const balance of balances) {
8146
+ const total = parseFloat(balance.total || '0');
8147
+ if (balance.coin === 'USDC') {
8148
+ spotUsdcBal = truncateToTwoDecimals(total);
8149
+ }
8150
+ if (balance.coin === 'USDH') {
8151
+ spotUsdhBal = truncateToTwoDecimals(total);
8152
+ }
8153
+ }
8154
+ }
8155
+ let availableToTradeUsdhFromAsset = 0;
8156
+ // This activeAssetData only contains data for SELECTED tokens (user's long and short Tokens)
8157
+ // It does NOT contain data for all tokens, so we cannot reliably use it for available to trade as used on hl trade page
8158
+ // so intead, we rely on rawClearinghouseStates which provides market-specific data
8159
+ // if (activeAssetData) {
8160
+ // Object.values(activeAssetData).forEach((assetData) => {
8161
+ // if (!assetData.availableToTrade) return;
8162
+ // const coinSymbol = assetData.coin;
8163
+ // const availableValue = truncateToTwoDecimals(
8164
+ // parseFloat(assetData.availableToTrade[0] || '0'),
8165
+ // );
8166
+ // // Determine collateral type based on market prefix
8167
+ // // HIP3 markets have prefix: "xyz:SYMBOL", "flx:SYMBOL", "vntl:SYMBOL", etc.
8168
+ // if (coinSymbol.includes(':')) {
8169
+ // const prefix = coinSymbol.split(':')[0];
8170
+ // if (prefix === 'xyz') {
8171
+ // // xyz markets use USDC
8172
+ // availableToTradeUsdcFromAsset = availableValue;
8173
+ // } else {
8174
+ // // flx, vntl, hyna and other markets use USDH
8175
+ // availableToTradeUsdhFromAsset = availableValue;
8176
+ // }
8177
+ // } else {
8178
+ // // Regular markets without prefix are automatically USDC
8179
+ // availableToTradeUsdcFromAsset = availableValue;
8180
+ // }
8181
+ // });
8182
+ // }
8183
+ // Calculate USDC available to trade
8184
+ // Priority 1: Use value from activeAssetData if available (> 0)
8185
+ // Priority 2: Calculate from USDC-specific clearinghouseState (empty prefix)
8186
+ let availableToTradeUsdcValue = undefined;
8187
+ if (rawClearinghouseStates) {
8188
+ // Find USDC market (empty prefix)
8189
+ const usdcMarket = rawClearinghouseStates.find(([prefix]) => prefix === '');
8190
+ const usdcState = usdcMarket === null || usdcMarket === void 0 ? void 0 : usdcMarket[1];
8191
+ if (usdcState === null || usdcState === void 0 ? void 0 : usdcState.marginSummary) {
8192
+ const accountValue = parseFloat(usdcState.marginSummary.accountValue || '0');
8193
+ const totalMarginUsed = parseFloat(usdcState.marginSummary.totalMarginUsed || '0');
8194
+ const calculatedValue = Math.max(0, accountValue - totalMarginUsed);
8195
+ availableToTradeUsdcValue = truncateToTwoDecimals(calculatedValue);
8196
+ }
8197
+ }
8198
+ // Calculate USDH available to trade
8199
+ // Priority 1: Use value from activeAssetData if available (> 0)
8200
+ // Priority 2: Use spot USDH balance
8201
+ const availableToTradeUsdhValue = availableToTradeUsdhFromAsset > 0
8202
+ ? availableToTradeUsdhFromAsset
8203
+ : spotUsdhBal;
8204
+ return {
8205
+ spotUsdcBalance: spotUsdcBal,
8206
+ availableToTradeUsdc: availableToTradeUsdcValue,
8207
+ spotUsdhBalance: spotUsdhBal,
8208
+ availableToTradeUsdh: availableToTradeUsdhValue,
8209
+ isLoading,
8210
+ };
8211
+ }, [
8212
+ spotState,
8213
+ aggregatedClearingHouseState,
8214
+ rawClearinghouseStates,
8215
+ activeAssetData,
8216
+ longTokensMetadata,
8217
+ shortTokensMetadata,
8218
+ ]);
8219
+ };
8220
+
8221
+ /**
8222
+ * Sync external fills into Pear Hyperliquid service (POST /sync/fills)
8223
+ */
8224
+ const syncFills = async (baseUrl, payload) => {
8225
+ const url = joinUrl(baseUrl, '/sync/fills');
8226
+ try {
8227
+ const response = await apiClient.post(url, payload, {
8228
+ headers: { 'Content-Type': 'application/json' },
8229
+ timeout: 30000,
8230
+ });
8231
+ return { data: response.data, status: response.status, headers: response.headers };
8232
+ }
8233
+ catch (error) {
8234
+ throw toApiError(error);
8235
+ }
8236
+ };
8237
+ /**
8238
+ * Convenience: fetch user fills from HyperLiquid, then sync them to Pear backend
8239
+ */
8240
+ const syncUserFillsFromHyperliquid = async (baseUrl, user, aggregateByTime = true, lastSyncAt = null, assetPositions) => {
8241
+ const firstStartTime = lastSyncAt ? Number(lastSyncAt) + 1 : 0;
8242
+ const allFills = [];
8243
+ const seenTids = new Set();
8244
+ let startTime = firstStartTime;
8245
+ let batchSize = 0;
8246
+ do {
8247
+ const { data: batch } = await fetchUserFillsFromHyperliquid(user, startTime, aggregateByTime);
8248
+ batchSize = batch.length;
8249
+ for (const fill of batch) {
8250
+ const tid = fill.tid;
8251
+ if (tid === undefined)
8252
+ continue;
8253
+ if (!seenTids.has(tid)) {
8254
+ seenTids.add(tid);
8255
+ allFills.push(fill);
8256
+ }
8257
+ }
8258
+ if (batchSize === 2000) {
8259
+ const last = batch[batch.length - 1];
8260
+ startTime = last.time;
8261
+ }
8262
+ } while (batchSize === 2000);
8263
+ startTime = firstStartTime;
8264
+ batchSize = 0;
8265
+ do {
8266
+ const { data: twapBatch } = await fetchUserTwapSliceFillsByTime(user, startTime, aggregateByTime);
8267
+ batchSize = twapBatch.length;
8268
+ for (const item of twapBatch) {
8269
+ const fill = item.fill;
8270
+ const tid = fill.tid;
8271
+ if (tid === undefined)
8272
+ continue;
8273
+ if (!seenTids.has(tid)) {
8274
+ seenTids.add(tid);
8275
+ allFills.push(fill);
8276
+ }
8277
+ }
8278
+ if (batchSize === 2000) {
8279
+ const last = twapBatch[twapBatch.length - 1];
8280
+ startTime = last.fill.time;
8281
+ }
8282
+ } while (batchSize === 2000);
8283
+ const sortedFills = [...allFills].sort((a, b) => Number(a.time) - Number(b.time));
8284
+ return syncFills(baseUrl, { user, fills: sortedFills, assetPositions });
8285
+ };
8286
+
8287
+ /**
8288
+ * Provides a callback to sync user fills whenever the native WebSocket emits a userFills event.
8289
+ */
8290
+ function useHyperliquidUserFills(options) {
8291
+ const { baseUrl, address: addressOverride, aggregateByTime = true } = options;
8292
+ const [lastRunAt, setLastRunAt] = useState(null);
8293
+ const [lastResult, setLastResult] = useState(null);
8294
+ const [error, setError] = useState(null);
8295
+ const [isSyncing, setIsSyncing] = useState(false);
8296
+ const mountedRef = useRef(true);
8297
+ const runningRef = useRef(null);
8298
+ const enabled = useUserData((state) => state.isAuthenticated);
8299
+ useEffect(() => {
8300
+ mountedRef.current = true;
8301
+ return () => {
8302
+ mountedRef.current = false;
8303
+ };
8304
+ }, []);
8305
+ const handleUserFillsEvent = useCallback(async () => {
8306
+ var _a;
8307
+ const userState = useUserData.getState();
8308
+ const currentAddress = userState.address || addressOverride;
8309
+ const lastSyncedAt = (_a = userState.accountSummary) === null || _a === void 0 ? void 0 : _a.lastSyncedAt;
8310
+ if (!(enabled && currentAddress && baseUrl))
8311
+ return;
8312
+ if (runningRef.current)
8313
+ return;
8314
+ if (!userState.accountSummary)
8315
+ return;
8316
+ const clearinghouseState = useHyperliquidData.getState().aggregatedClearingHouseState;
8317
+ if (!(clearinghouseState === null || clearinghouseState === void 0 ? void 0 : clearinghouseState.assetPositions))
8318
+ return;
8319
+ setIsSyncing(true);
8320
+ setError(null);
8321
+ const promise = (async () => {
8322
+ var _a;
8323
+ try {
8324
+ const { data } = await syncUserFillsFromHyperliquid(baseUrl, currentAddress, aggregateByTime, lastSyncedAt, clearinghouseState.assetPositions);
8325
+ if (!mountedRef.current)
8326
+ return;
8327
+ setLastResult(data);
8328
+ setLastRunAt(Date.now());
8329
+ }
8330
+ catch (e) {
8331
+ if (!mountedRef.current)
8332
+ return;
8333
+ setError((_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : 'Failed to sync user fills');
8334
+ }
8335
+ finally {
8336
+ if (mountedRef.current)
8337
+ setIsSyncing(false);
8338
+ runningRef.current = null;
8339
+ }
8340
+ })();
8341
+ runningRef.current = promise;
8342
+ await promise;
8343
+ }, [baseUrl, addressOverride, aggregateByTime, enabled]);
8344
+ return {
8345
+ lastRunAt,
8346
+ lastResult,
8347
+ error,
8348
+ isSyncing,
8349
+ handleUserFillsEvent,
8350
+ };
8351
+ }
8352
+
7299
8353
  const PearHyperliquidContext = createContext(undefined);
7300
8354
  /**
7301
8355
  * React Provider for PearHyperliquidClient
7302
8356
  */
7303
- const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearprotocol.io", clientId = "PEARPROTOCOLUI", wsUrl = "wss://hl-ui.pearprotocol.io/ws", }) => {
8357
+ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearprotocol.io', clientId = 'PEARPROTOCOLUI', wsUrl = 'wss://hl-ui.pearprotocol.io/ws', }) => {
7304
8358
  const address = useUserData((s) => s.address);
7305
8359
  const setAddress = useUserData((s) => s.setAddress);
7306
8360
  const perpsMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
7307
8361
  const setPerpMetaAssets = useHyperliquidData((state) => state.setPerpMetaAssets);
7308
- const setHip3DisplayToFull = useHyperliquidData((state) => state.setHip3DisplayToFull);
8362
+ const setAllPerpMetaAssets = useHyperliquidData((state) => state.setAllPerpMetaAssets);
8363
+ const setHip3Assets = useHyperliquidData((state) => state.setHip3Assets);
8364
+ const setHip3MarketPrefixes = useHyperliquidData((state) => state.setHip3MarketPrefixes);
7309
8365
  const websocketsEnabled = useMemo(() => Array.isArray(perpsMetaAssets) && perpsMetaAssets.length > 0, [perpsMetaAssets]);
8366
+ const { handleUserFillsEvent } = useHyperliquidUserFills({
8367
+ baseUrl: apiBaseUrl,
8368
+ address,
8369
+ aggregateByTime: true,
8370
+ });
7310
8371
  const { isConnected, lastError } = useHyperliquidWebSocket({
7311
8372
  wsUrl,
7312
8373
  address,
@@ -7315,43 +8376,113 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
7315
8376
  const { isConnected: nativeIsConnected, lastError: nativeLastError } = useHyperliquidNativeWebSocket({
7316
8377
  address,
7317
8378
  enabled: websocketsEnabled,
8379
+ onUserFills: handleUserFillsEvent,
7318
8380
  });
7319
8381
  useEffect(() => {
7320
8382
  if (perpsMetaAssets === null) {
7321
8383
  fetchAllPerpMetas()
7322
8384
  .then((res) => {
7323
- // Only show HL and XYZ for now as other are using USDH collateral and need more work
7324
- const aggregatedPerpMetas = res.data
7325
- .slice(0, 2)
7326
- .flatMap((item) => item.universe);
7327
- const hip3Map = new Map();
7328
- const displayToFull = new Map();
7329
- const cleanedPerpMetas = aggregatedPerpMetas.map((asset) => {
7330
- var _a;
7331
- const [maybePrefix, maybeMarket] = asset.name.split(":");
7332
- if (maybeMarket) {
7333
- const prefix = maybePrefix.toLowerCase();
7334
- const market = maybeMarket;
7335
- const existing = (_a = hip3Map.get(prefix)) !== null && _a !== void 0 ? _a : [];
7336
- hip3Map.set(prefix, [...existing, market]);
7337
- displayToFull.set(market, `${prefix}:${market}`);
7338
- return { ...asset, name: market };
7339
- }
7340
- return asset;
8385
+ const assetToMarkets = new Map();
8386
+ const marketPrefixes = new Map();
8387
+ const FILTERED_PREFIXES = ['hyna'];
8388
+ // Group assets by market prefix to match WebSocket flattening order
8389
+ // WebSocket sends in order: "", "flx", "hyna", "vntl", "xyz" (we filter out hyna)
8390
+ const assetsByPrefix = new Map();
8391
+ const allAssetsByPrefix = new Map();
8392
+ res.data.forEach((item) => {
8393
+ const collateralToken = item.collateralToken === 360 ? 'USDH' : 'USDC';
8394
+ item.universe.forEach((asset) => {
8395
+ var _a;
8396
+ const [maybePrefix, maybeMarket] = asset.name.split(':');
8397
+ if (maybeMarket) {
8398
+ // HIP3 asset with market prefix
8399
+ const prefix = maybePrefix.toLowerCase();
8400
+ const displayName = maybeMarket;
8401
+ const fullName = `${prefix}:${displayName}`;
8402
+ marketPrefixes.set(fullName, prefix);
8403
+ if (!FILTERED_PREFIXES.includes(prefix)) {
8404
+ const existingMarkets = (_a = assetToMarkets.get(displayName)) !== null && _a !== void 0 ? _a : [];
8405
+ if (!existingMarkets.includes(fullName)) {
8406
+ assetToMarkets.set(displayName, [
8407
+ ...existingMarkets,
8408
+ fullName,
8409
+ ]);
8410
+ }
8411
+ }
8412
+ const assetWithMeta = {
8413
+ ...asset,
8414
+ name: displayName,
8415
+ marketPrefix: prefix,
8416
+ collateralToken,
8417
+ };
8418
+ // Group by market prefix
8419
+ const allList = allAssetsByPrefix.get(prefix) || [];
8420
+ allList.push(assetWithMeta);
8421
+ allAssetsByPrefix.set(prefix, allList);
8422
+ if (!FILTERED_PREFIXES.includes(prefix)) {
8423
+ const cleanedList = assetsByPrefix.get(prefix) || [];
8424
+ cleanedList.push(assetWithMeta);
8425
+ assetsByPrefix.set(prefix, cleanedList);
8426
+ }
8427
+ }
8428
+ else {
8429
+ // Default market asset (no prefix)
8430
+ const assetWithMeta = {
8431
+ ...asset,
8432
+ collateralToken,
8433
+ };
8434
+ // Add to default market group ("")
8435
+ const defaultList = assetsByPrefix.get('') || [];
8436
+ defaultList.push(assetWithMeta);
8437
+ assetsByPrefix.set('', defaultList);
8438
+ const allDefaultList = allAssetsByPrefix.get('') || [];
8439
+ allDefaultList.push(assetWithMeta);
8440
+ allAssetsByPrefix.set('', allDefaultList);
8441
+ }
8442
+ });
8443
+ });
8444
+ // Flatten in consistent order: default market first, then HIP3 markets alphabetically
8445
+ // This ensures both REST API and WebSocket data align properly
8446
+ const cleanedPrefixes = Array.from(assetsByPrefix.keys()).sort((a, b) => {
8447
+ // Empty prefix (default market) always comes first
8448
+ if (a === '' && b !== '')
8449
+ return -1;
8450
+ if (a !== '' && b === '')
8451
+ return 1;
8452
+ // HIP3 markets sorted alphabetically
8453
+ return a.localeCompare(b);
8454
+ });
8455
+ const allPrefixes = Array.from(allAssetsByPrefix.keys()).sort((a, b) => {
8456
+ if (a === '' && b !== '')
8457
+ return -1;
8458
+ if (a !== '' && b === '')
8459
+ return 1;
8460
+ return a.localeCompare(b);
8461
+ });
8462
+ const cleanedPerpMetas = [];
8463
+ const allPerpMetas = [];
8464
+ cleanedPrefixes.forEach((prefix) => {
8465
+ const assets = assetsByPrefix.get(prefix) || [];
8466
+ cleanedPerpMetas.push(...assets);
8467
+ });
8468
+ allPrefixes.forEach((prefix) => {
8469
+ const assets = allAssetsByPrefix.get(prefix) || [];
8470
+ allPerpMetas.push(...assets);
7341
8471
  });
7342
- setHip3DisplayToFull(displayToFull);
8472
+ setHip3Assets(assetToMarkets);
8473
+ setHip3MarketPrefixes(marketPrefixes);
7343
8474
  setPerpMetaAssets(cleanedPerpMetas);
8475
+ setAllPerpMetaAssets(allPerpMetas);
7344
8476
  })
7345
8477
  .catch(() => { });
7346
8478
  }
7347
- }, [perpsMetaAssets, setPerpMetaAssets, setHip3DisplayToFull]);
7348
- // Auth methods now sourced from useAuth hook
7349
- useAutoSyncFills({
7350
- baseUrl: apiBaseUrl,
7351
- address,
7352
- intervalMs: 60000,
7353
- aggregateByTime: true,
7354
- });
8479
+ }, [
8480
+ perpsMetaAssets,
8481
+ setPerpMetaAssets,
8482
+ setAllPerpMetaAssets,
8483
+ setHip3Assets,
8484
+ setHip3MarketPrefixes,
8485
+ ]);
7355
8486
  const contextValue = useMemo(() => ({
7356
8487
  // Config
7357
8488
  clientId,
@@ -7383,7 +8514,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
7383
8514
  function usePearHyperliquid() {
7384
8515
  const ctx = useContext(PearHyperliquidContext);
7385
8516
  if (!ctx)
7386
- throw new Error("usePearHyperliquid must be used within a PearHyperliquidProvider");
8517
+ throw new Error('usePearHyperliquid must be used within a PearHyperliquidProvider');
7387
8518
  return ctx;
7388
8519
  }
7389
8520
 
@@ -7474,4 +8605,129 @@ function mapCandleIntervalToTradingViewInterval(interval) {
7474
8605
  }
7475
8606
  }
7476
8607
 
7477
- export { AccountSummaryCalculator, ConflictDetector, MAX_ASSETS_PER_LEG, MINIMUM_ASSET_USD_VALUE, MaxAssetsPerLegError, MinimumPositionSizeError, PearHyperliquidProvider, TokenMetadataExtractor, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, getCompleteTimestamps, getPortfolio, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toggleWatchlist, updateLeverage, updateRiskParameters, useAccountSummary, useActiveBaskets, useAgentWallet, useAllBaskets, useAuth, useAutoSyncFills, useBasketCandles, useFindBasket, useHighlightedBaskets, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMarketData, useMarketDataAllPayload, useMarketDataPayload, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePortfolio, usePosition, useTokenSelectionMetadata, useTopGainers, useTopLosers, useTradeHistories, useTwap, useUserSelection, useWatchlist, useWatchlistBaskets, useWebData, validateMaxAssetsPerLeg, validateMinimumAssetSize, validatePositionSize };
8608
+ /**
8609
+ * Helper functions to safely extract values from order parameters
8610
+ * based on the order type and parameter structure.
8611
+ */
8612
+ /**
8613
+ * Get leverage from order parameters (available for Market, Trigger, Twap, Ladder orders)
8614
+ */
8615
+ function getOrderLeverage(order) {
8616
+ const p = order.parameters;
8617
+ if ('leverage' in p && p.leverage !== undefined) {
8618
+ return p.leverage;
8619
+ }
8620
+ return undefined;
8621
+ }
8622
+ /**
8623
+ * Get USD value from order parameters (available for Market, Trigger, Twap, Ladder orders)
8624
+ */
8625
+ function getOrderUsdValue(order) {
8626
+ const p = order.parameters;
8627
+ if ('usdValue' in p) {
8628
+ return p.usdValue;
8629
+ }
8630
+ return undefined;
8631
+ }
8632
+ /**
8633
+ * Get trigger value from order parameters (available for TP/SL and Trigger orders)
8634
+ */
8635
+ function getOrderTriggerValue(order) {
8636
+ const p = order.parameters;
8637
+ if ('triggerValue' in p) {
8638
+ return p.triggerValue;
8639
+ }
8640
+ return undefined;
8641
+ }
8642
+ /**
8643
+ * Get TP/SL trigger type from order parameters (only for TP/SL orders)
8644
+ */
8645
+ function getOrderTpSlTriggerType(order) {
8646
+ if (order.orderType === 'TP' || order.orderType === 'SL') {
8647
+ const p = order.parameters;
8648
+ return p.triggerType;
8649
+ }
8650
+ return undefined;
8651
+ }
8652
+ /**
8653
+ * Get trigger type from order parameters (for Trigger orders)
8654
+ */
8655
+ function getOrderTriggerType(order) {
8656
+ if (order.orderType === 'TRIGGER') {
8657
+ const p = order.parameters;
8658
+ return p.triggerType;
8659
+ }
8660
+ return undefined;
8661
+ }
8662
+ /**
8663
+ * Get order direction from order parameters (for Trigger orders)
8664
+ */
8665
+ function getOrderDirection(order) {
8666
+ if (order.orderType === 'TRIGGER') {
8667
+ const p = order.parameters;
8668
+ return p.direction;
8669
+ }
8670
+ return undefined;
8671
+ }
8672
+ /**
8673
+ * Get reduce only flag from order parameters (available for all order types)
8674
+ */
8675
+ function getOrderReduceOnly(order) {
8676
+ var _a;
8677
+ const p = order.parameters;
8678
+ if ('reduceOnly' in p) {
8679
+ return (_a = p.reduceOnly) !== null && _a !== void 0 ? _a : false;
8680
+ }
8681
+ return false;
8682
+ }
8683
+ /**
8684
+ * Get TWAP duration from order parameters (only for TWAP orders)
8685
+ */
8686
+ function getOrderTwapDuration(order) {
8687
+ if (order.orderType === 'TWAP') {
8688
+ const p = order.parameters;
8689
+ return p.duration;
8690
+ }
8691
+ return undefined;
8692
+ }
8693
+ /**
8694
+ * Get ladder config from order parameters (only for Ladder orders)
8695
+ */
8696
+ function getOrderLadderConfig(order) {
8697
+ if (order.orderType === 'LADDER') {
8698
+ const p = order.parameters;
8699
+ return {
8700
+ ratioStart: p.ratioStart,
8701
+ ratioEnd: p.ratioEnd,
8702
+ numberOfLevels: p.numberOfLevels,
8703
+ };
8704
+ }
8705
+ return undefined;
8706
+ }
8707
+ /**
8708
+ * Check if the order is a BTC Dominance trigger order
8709
+ */
8710
+ function isBtcDomOrder(order) {
8711
+ if (order.orderType === 'TRIGGER') {
8712
+ const p = order.parameters;
8713
+ return p.triggerType === 'BTC_DOM';
8714
+ }
8715
+ return false;
8716
+ }
8717
+ /**
8718
+ * Get trailing info from TP/SL order parameters
8719
+ */
8720
+ function getOrderTrailingInfo(order) {
8721
+ var _a;
8722
+ if (order.orderType === 'TP' || order.orderType === 'SL') {
8723
+ const p = order.parameters;
8724
+ return {
8725
+ isTrailing: (_a = p.isTrailing) !== null && _a !== void 0 ? _a : false,
8726
+ trailingDeltaValue: p.trailingDeltaValue,
8727
+ trailingActivationValue: p.trailingActivationValue,
8728
+ };
8729
+ }
8730
+ return undefined;
8731
+ }
8732
+
8733
+ 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, getOrderDirection, getOrderLadderConfig, getOrderLeverage, getOrderReduceOnly, getOrderTpSlTriggerType, getOrderTrailingInfo, getOrderTriggerType, getOrderTriggerValue, getOrderTwapDuration, getOrderUsdValue, getPortfolio, isBtcDomOrder, isHip3Market, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toBackendSymbol, toBackendSymbolWithMarket, toDisplaySymbol, toggleWatchlist, updateLeverage, updateRiskParameters, useAccountSummary, useActiveBaskets, useAgentWallet, useAllBaskets, useAllUserBalances, useAuth, useBasketCandles, useFindBasket, useHighlightedBaskets, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidUserFills, 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 };