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

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,165 @@ 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
+ const KALSHI_API_BASE_URL = 'https://api.elections.kalshi.com/trade-api/v2';
7290
+ async function getKalshiMarkets(params) {
7291
+ const url = new URL(`${KALSHI_API_BASE_URL}/markets`);
7292
+ if (params) {
7293
+ if (params.limit !== undefined) {
7294
+ url.searchParams.set('limit', String(params.limit));
7295
+ }
7296
+ if (params.cursor) {
7297
+ url.searchParams.set('cursor', params.cursor);
7298
+ }
7299
+ if (params.tickers && params.tickers.length > 0) {
7300
+ url.searchParams.set('tickers', params.tickers.join(','));
7301
+ }
7302
+ if (params.event_ticker) {
7303
+ url.searchParams.set('event_ticker', params.event_ticker);
7304
+ }
7305
+ if (params.series_ticker) {
7306
+ url.searchParams.set('series_ticker', params.series_ticker);
7307
+ }
7308
+ if (params.status) {
7309
+ url.searchParams.set('status', params.status);
7310
+ }
7311
+ }
7312
+ try {
7313
+ const response = await axios$1.get(url.toString());
7314
+ return {
7315
+ data: response.data,
7316
+ status: response.status,
7317
+ headers: response.headers,
7318
+ };
7319
+ }
7320
+ catch (error) {
7321
+ throw toApiError(error);
7322
+ }
7323
+ }
7324
+
7325
+ function findAssetMeta$2(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
7326
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
7327
+ if (!perpMetaAssets) {
7328
+ return { collateralToken: 'USDC', marketPrefix: null };
7329
+ }
7330
+ if (desiredCollateral) {
7331
+ const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
7332
+ if (collateralMatch) {
7333
+ return {
7334
+ collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
7335
+ marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
7336
+ };
7337
+ }
7338
+ }
7339
+ if (assetName.includes(':')) {
7340
+ const [prefix, symbol] = assetName.split(':');
7341
+ const exactMatch = perpMetaAssets.find((a) => {
7342
+ var _a;
7343
+ return a.name === symbol &&
7344
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
7345
+ });
7346
+ if (exactMatch) {
7347
+ return {
7348
+ collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
7349
+ marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
7350
+ };
7351
+ }
7352
+ }
7353
+ if (knownPrefix) {
7354
+ const exactMatch = perpMetaAssets.find((a) => {
7355
+ var _a;
7356
+ return a.name === assetName &&
7357
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
7358
+ });
7359
+ if (exactMatch) {
7360
+ return {
7361
+ collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
7362
+ marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
7363
+ };
7364
+ }
7365
+ }
7366
+ const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
7367
+ if (regularAsset) {
7368
+ return {
7369
+ collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
7370
+ marketPrefix: null,
7371
+ };
7372
+ }
7373
+ const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
7374
+ if (hip3Assets.length > 0) {
7375
+ if (desiredCollateral) {
7376
+ const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
7377
+ if (collateralMatch) {
7378
+ return {
7379
+ collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
7380
+ marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
7381
+ };
7382
+ }
7383
+ }
7384
+ const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
7385
+ const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
7386
+ return {
7387
+ collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
7388
+ marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
7389
+ };
7390
+ }
7391
+ return { collateralToken: 'USDC', marketPrefix: null };
7392
+ }
7393
+ function enrichOrderAssets$1(assets, perpMetaAssets) {
7394
+ if (!assets)
7395
+ return [];
7396
+ return assets.map((asset) => {
7397
+ var _a;
7398
+ if (asset.marketPrefix && asset.collateralToken) {
7399
+ return asset;
7400
+ }
7401
+ const meta = findAssetMeta$2(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
7402
+ return {
7403
+ ...asset,
7404
+ marketPrefix: asset.marketPrefix || meta.marketPrefix,
7405
+ collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
7406
+ };
7407
+ });
7408
+ }
6815
7409
  function useOrders() {
6816
7410
  const context = useContext(PearHyperliquidContext);
6817
7411
  if (!context)
6818
7412
  throw new Error('useOrders must be used within a PearHyperliquidProvider');
6819
7413
  const { apiBaseUrl } = context;
6820
7414
  const openOrders = useUserData((state) => state.openOrders);
7415
+ const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
6821
7416
  const isLoading = useMemo(() => openOrders === null && context.isConnected, [openOrders, context.isConnected]);
6822
7417
  const { openPositions } = usePosition();
6823
7418
  const positionsById = useMemo(() => {
@@ -6828,27 +7423,63 @@ function useOrders() {
6828
7423
  }
6829
7424
  return map;
6830
7425
  }, [openPositions]);
7426
+ const [marketTitles, setMarketTitles] = useState({});
6831
7427
  const enrichedOpenOrders = useMemo(() => {
6832
7428
  if (!openOrders)
6833
7429
  return null;
6834
- return openOrders.map((ord) => {
7430
+ // Collect prediction market codes for fetching
7431
+ const predictionMarketCodes = [];
7432
+ const enrichedOrders = openOrders.map((ord) => {
6835
7433
  var _a, _b, _c, _d, _e;
6836
7434
  const isTpSl = ord.orderType === 'TP' || ord.orderType === 'SL';
6837
7435
  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
7436
  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,
7437
+ let enrichedOrd = {
7438
+ ...ord,
7439
+ longAssets: enrichOrderAssets$1(ord.longAssets, allPerpMetaAssets),
7440
+ shortAssets: enrichOrderAssets$1(ord.shortAssets, allPerpMetaAssets),
7441
+ };
7442
+ if (isTpSl && !hasAssets && pos) {
7443
+ const mapAssets = (arr) => arr.map((a) => ({
7444
+ asset: a.coin,
7445
+ weight: a.initialWeight,
7446
+ marketPrefix: a.marketPrefix,
7447
+ collateralToken: a.collateralToken,
7448
+ }));
7449
+ enrichedOrd = {
7450
+ ...enrichedOrd,
6845
7451
  longAssets: mapAssets(pos.longAssets),
6846
7452
  shortAssets: mapAssets(pos.shortAssets),
6847
7453
  };
6848
7454
  }
6849
- return ord;
7455
+ const params = ord.parameters;
7456
+ if ((params === null || params === void 0 ? void 0 : params.triggerType) === 'PREDICTION_MARKET_OUTCOME' && (params === null || params === void 0 ? void 0 : params.marketCode)) {
7457
+ const marketCode = params.marketCode;
7458
+ if (!predictionMarketCodes.includes(marketCode)) {
7459
+ predictionMarketCodes.push(marketCode);
7460
+ }
7461
+ enrichedOrd.marketTitle = marketTitles[marketCode];
7462
+ }
7463
+ return enrichedOrd;
6850
7464
  });
6851
- }, [openOrders, positionsById]);
7465
+ const missingCodes = predictionMarketCodes.filter((code) => !marketTitles[code]);
7466
+ if (missingCodes.length > 0) {
7467
+ getKalshiMarkets({ tickers: missingCodes })
7468
+ .then((response) => {
7469
+ const newTitles = {};
7470
+ for (const market of response.data.markets) {
7471
+ newTitles[market.ticker] = market.title;
7472
+ }
7473
+ if (Object.keys(newTitles).length > 0) {
7474
+ setMarketTitles((prev) => ({ ...prev, ...newTitles }));
7475
+ }
7476
+ })
7477
+ .catch((err) => {
7478
+ console.error('Failed to fetch Kalshi market titles:', err);
7479
+ });
7480
+ }
7481
+ return enrichedOrders;
7482
+ }, [openOrders, positionsById, allPerpMetaAssets, marketTitles]);
6852
7483
  const adjustOrder$1 = async (orderId, payload) => {
6853
7484
  return adjustOrder(apiBaseUrl, orderId, payload);
6854
7485
  };
@@ -6858,16 +7489,156 @@ function useOrders() {
6858
7489
  const cancelTwapOrder$1 = async (orderId) => {
6859
7490
  return cancelTwapOrder(apiBaseUrl, orderId);
6860
7491
  };
6861
- return { adjustOrder: adjustOrder$1, cancelOrder: cancelOrder$1, cancelTwapOrder: cancelTwapOrder$1, openOrders: enrichedOpenOrders, isLoading };
7492
+ return {
7493
+ adjustOrder: adjustOrder$1,
7494
+ cancelOrder: cancelOrder$1,
7495
+ cancelTwapOrder: cancelTwapOrder$1,
7496
+ openOrders: enrichedOpenOrders,
7497
+ isLoading,
7498
+ };
7499
+ }
7500
+
7501
+ /**
7502
+ * Hook for executing spot orders (swaps) on Hyperliquid
7503
+ * Use this to swap between USDC and USDH or other spot assets
7504
+ */
7505
+ function useSpotOrder() {
7506
+ const context = useContext(PearHyperliquidContext);
7507
+ if (!context) {
7508
+ throw new Error('useSpotOrder must be used within a PearHyperliquidProvider');
7509
+ }
7510
+ const { apiBaseUrl } = context;
7511
+ const [isLoading, setIsLoading] = useState(false);
7512
+ const [error, setError] = useState(null);
7513
+ const resetError = useCallback(() => {
7514
+ setError(null);
7515
+ }, []);
7516
+ const executeSpotOrder$1 = useCallback(async (payload) => {
7517
+ setIsLoading(true);
7518
+ setError(null);
7519
+ try {
7520
+ const response = await executeSpotOrder(apiBaseUrl, payload);
7521
+ return response;
7522
+ }
7523
+ catch (err) {
7524
+ const apiError = err;
7525
+ setError(apiError);
7526
+ throw apiError;
7527
+ }
7528
+ finally {
7529
+ setIsLoading(false);
7530
+ }
7531
+ }, [apiBaseUrl]);
7532
+ return {
7533
+ executeSpotOrder: executeSpotOrder$1,
7534
+ isLoading,
7535
+ error,
7536
+ resetError,
7537
+ };
6862
7538
  }
6863
7539
 
7540
+ function findAssetMeta$1(assetName, perpMetaAssets, knownPrefix, desiredCollateral) {
7541
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
7542
+ if (!perpMetaAssets) {
7543
+ return { collateralToken: 'USDC', marketPrefix: null };
7544
+ }
7545
+ if (desiredCollateral) {
7546
+ const collateralMatch = perpMetaAssets.find((a) => a.name === assetName && a.collateralToken === desiredCollateral);
7547
+ if (collateralMatch) {
7548
+ return {
7549
+ collateralToken: (_a = collateralMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
7550
+ marketPrefix: (_b = collateralMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
7551
+ };
7552
+ }
7553
+ }
7554
+ if (assetName.includes(':')) {
7555
+ const [prefix, symbol] = assetName.split(':');
7556
+ const exactMatch = perpMetaAssets.find((a) => {
7557
+ var _a;
7558
+ return a.name === symbol &&
7559
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === prefix.toLowerCase();
7560
+ });
7561
+ if (exactMatch) {
7562
+ return {
7563
+ collateralToken: (_c = exactMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
7564
+ marketPrefix: (_d = exactMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
7565
+ };
7566
+ }
7567
+ }
7568
+ if (knownPrefix) {
7569
+ const exactMatch = perpMetaAssets.find((a) => {
7570
+ var _a;
7571
+ return a.name === assetName &&
7572
+ ((_a = a.marketPrefix) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === knownPrefix.toLowerCase();
7573
+ });
7574
+ if (exactMatch) {
7575
+ return {
7576
+ collateralToken: (_e = exactMatch.collateralToken) !== null && _e !== void 0 ? _e : 'USDC',
7577
+ marketPrefix: (_f = exactMatch.marketPrefix) !== null && _f !== void 0 ? _f : null,
7578
+ };
7579
+ }
7580
+ }
7581
+ const regularAsset = perpMetaAssets.find((a) => a.name === assetName && !a.marketPrefix);
7582
+ if (regularAsset) {
7583
+ return {
7584
+ collateralToken: (_g = regularAsset.collateralToken) !== null && _g !== void 0 ? _g : 'USDC',
7585
+ marketPrefix: null,
7586
+ };
7587
+ }
7588
+ const hip3Assets = perpMetaAssets.filter((a) => a.name === assetName && a.marketPrefix);
7589
+ if (hip3Assets.length > 0) {
7590
+ if (desiredCollateral) {
7591
+ const collateralMatch = hip3Assets.find((a) => a.collateralToken === desiredCollateral);
7592
+ if (collateralMatch) {
7593
+ return {
7594
+ collateralToken: (_h = collateralMatch.collateralToken) !== null && _h !== void 0 ? _h : 'USDC',
7595
+ marketPrefix: (_j = collateralMatch.marketPrefix) !== null && _j !== void 0 ? _j : null,
7596
+ };
7597
+ }
7598
+ }
7599
+ const usdHMatch = hip3Assets.find((a) => a.collateralToken === 'USDH');
7600
+ const chosen = usdHMatch !== null && usdHMatch !== void 0 ? usdHMatch : hip3Assets[0];
7601
+ return {
7602
+ collateralToken: (_k = chosen.collateralToken) !== null && _k !== void 0 ? _k : 'USDC',
7603
+ marketPrefix: (_l = chosen.marketPrefix) !== null && _l !== void 0 ? _l : null,
7604
+ };
7605
+ }
7606
+ return { collateralToken: 'USDC', marketPrefix: null };
7607
+ }
7608
+ function enrichOrderAssets(assets, perpMetaAssets) {
7609
+ if (!assets)
7610
+ return [];
7611
+ return assets.map((asset) => {
7612
+ var _a;
7613
+ if (asset.marketPrefix && asset.collateralToken) {
7614
+ return asset;
7615
+ }
7616
+ const meta = findAssetMeta$1(asset.asset, perpMetaAssets, asset.marketPrefix, asset.collateralToken);
7617
+ return {
7618
+ ...asset,
7619
+ marketPrefix: asset.marketPrefix || meta.marketPrefix,
7620
+ collateralToken: (_a = asset.collateralToken) !== null && _a !== void 0 ? _a : meta.collateralToken,
7621
+ };
7622
+ });
7623
+ }
7624
+ function enrichTwapOrders(orders, perpMetaAssets) {
7625
+ return orders.map((order) => ({
7626
+ ...order,
7627
+ longAssets: enrichOrderAssets(order.longAssets, perpMetaAssets),
7628
+ shortAssets: enrichOrderAssets(order.shortAssets, perpMetaAssets),
7629
+ }));
7630
+ }
6864
7631
  function useTwap() {
6865
- const twapDetails = useUserData(state => state.twapDetails);
7632
+ const twapDetails = useUserData((state) => state.twapDetails);
7633
+ const allPerpMetaAssets = useHyperliquidData((state) => state.allPerpMetaAssets);
6866
7634
  const context = useContext(PearHyperliquidContext);
6867
7635
  if (!context)
6868
7636
  throw new Error('useTwap must be used within a PearHyperliquidProvider');
6869
7637
  const { apiBaseUrl } = context;
6870
- const orders = useMemo(() => twapDetails !== null && twapDetails !== void 0 ? twapDetails : [], [twapDetails]);
7638
+ const orders = useMemo(() => {
7639
+ const rawOrders = twapDetails !== null && twapDetails !== void 0 ? twapDetails : [];
7640
+ return enrichTwapOrders(rawOrders, allPerpMetaAssets);
7641
+ }, [twapDetails, allPerpMetaAssets]);
6871
7642
  const cancelTwap$1 = async (orderId) => {
6872
7643
  return cancelTwap(apiBaseUrl, orderId);
6873
7644
  };
@@ -6950,59 +7721,170 @@ function useNotifications() {
6950
7721
  };
6951
7722
  }
6952
7723
 
6953
- // Base selector for the full market-data payload
7724
+ // Helper to find asset metadata from perpMetaAssets
7725
+ function findAssetMeta(assetName, perpMetaAssets) {
7726
+ var _a, _b, _c, _d;
7727
+ if (!perpMetaAssets) {
7728
+ return { collateralToken: 'USDC', marketPrefix: null };
7729
+ }
7730
+ // Try exact match first (for prefixed assets like "xyz:TSLA")
7731
+ const exactMatch = perpMetaAssets.find((a) => a.name === assetName);
7732
+ if (exactMatch) {
7733
+ return {
7734
+ collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
7735
+ marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
7736
+ };
7737
+ }
7738
+ // Try matching by base symbol (for non-prefixed names in data)
7739
+ const baseMatch = perpMetaAssets.find((a) => {
7740
+ const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
7741
+ return baseName === assetName;
7742
+ });
7743
+ if (baseMatch) {
7744
+ return {
7745
+ collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
7746
+ marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
7747
+ };
7748
+ }
7749
+ return { collateralToken: 'USDC', marketPrefix: null };
7750
+ }
7751
+ // Enrich a single asset with metadata
7752
+ function enrichAsset(asset, perpMetaAssets) {
7753
+ const meta = findAssetMeta(asset.asset, perpMetaAssets);
7754
+ return {
7755
+ ...asset,
7756
+ collateralToken: meta.collateralToken,
7757
+ marketPrefix: meta.marketPrefix,
7758
+ };
7759
+ }
7760
+ // Enrich a basket item with collateral info
7761
+ function enrichBasketItem(item, perpMetaAssets) {
7762
+ const enrichedLongs = item.longAssets.map((a) => enrichAsset(a, perpMetaAssets));
7763
+ const enrichedShorts = item.shortAssets.map((a) => enrichAsset(a, perpMetaAssets));
7764
+ // Determine collateral type
7765
+ const allAssets = [...enrichedLongs, ...enrichedShorts];
7766
+ const hasUsdc = allAssets.some((a) => a.collateralToken === 'USDC');
7767
+ const hasUsdh = allAssets.some((a) => a.collateralToken === 'USDH');
7768
+ let collateralType = 'USDC';
7769
+ if (hasUsdc && hasUsdh) {
7770
+ collateralType = 'MIXED';
7771
+ }
7772
+ else if (hasUsdh) {
7773
+ collateralType = 'USDH';
7774
+ }
7775
+ return {
7776
+ ...item,
7777
+ longAssets: enrichedLongs,
7778
+ shortAssets: enrichedShorts,
7779
+ collateralType,
7780
+ };
7781
+ }
7782
+ /**
7783
+ * Filter baskets by collateral type
7784
+ * - 'USDC': Only baskets where ALL assets use USDC (collateralType === 'USDC')
7785
+ * - 'USDH': Only baskets where ALL assets use USDH (collateralType === 'USDH')
7786
+ * - 'ALL' or undefined: No filtering, returns all baskets
7787
+ */
7788
+ function filterByCollateral(baskets, filter) {
7789
+ if (!filter || filter === 'ALL') {
7790
+ return baskets;
7791
+ }
7792
+ return baskets.filter((basket) => {
7793
+ if (filter === 'USDC') {
7794
+ // Include baskets that are purely USDC or have USDC assets
7795
+ return (basket.collateralType === 'USDC' || basket.collateralType === 'MIXED');
7796
+ }
7797
+ if (filter === 'USDH') {
7798
+ // Include baskets that are purely USDH or have USDH assets
7799
+ return (basket.collateralType === 'USDH' || basket.collateralType === 'MIXED');
7800
+ }
7801
+ return true;
7802
+ });
7803
+ }
7804
+ // Base selector for the full market-data payload (raw from WS)
6954
7805
  const useMarketDataPayload = () => {
6955
7806
  return useMarketData((s) => s.marketData);
6956
7807
  };
6957
- // Full payload for 'market-data-all' channel
7808
+ // Full payload for 'market-data-all' channel (raw from WS)
6958
7809
  const useMarketDataAllPayload = () => {
6959
7810
  return useMarketData((s) => s.marketDataAll);
6960
7811
  };
6961
- // Active baskets
6962
- const useActiveBaskets = () => {
6963
- var _a;
7812
+ // Access perpMetaAssets for enrichment
7813
+ const usePerpMetaAssets = () => {
7814
+ return useHyperliquidData((s) => s.perpMetaAssets);
7815
+ };
7816
+ // Active baskets (with collateral and market prefix info)
7817
+ const useActiveBaskets = (collateralFilter) => {
6964
7818
  const data = useMarketDataPayload();
6965
- return (_a = data === null || data === void 0 ? void 0 : data.active) !== null && _a !== void 0 ? _a : [];
7819
+ const perpMetaAssets = usePerpMetaAssets();
7820
+ return useMemo(() => {
7821
+ if (!(data === null || data === void 0 ? void 0 : data.active))
7822
+ return [];
7823
+ const enriched = data.active.map((item) => enrichBasketItem(item, perpMetaAssets));
7824
+ return filterByCollateral(enriched, collateralFilter);
7825
+ }, [data, perpMetaAssets, collateralFilter]);
6966
7826
  };
6967
- // Top gainers (optional limit override)
6968
- const useTopGainers = (limit) => {
7827
+ // Top gainers (with collateral and market prefix info)
7828
+ const useTopGainers = (limit, collateralFilter) => {
6969
7829
  const data = useMarketDataPayload();
7830
+ const perpMetaAssets = usePerpMetaAssets();
6970
7831
  return useMemo(() => {
6971
7832
  var _a;
6972
7833
  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]);
7834
+ const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7835
+ const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
7836
+ return filterByCollateral(enriched, collateralFilter);
7837
+ }, [data, perpMetaAssets, limit, collateralFilter]);
6975
7838
  };
6976
- // Top losers (optional limit override)
6977
- const useTopLosers = (limit) => {
7839
+ // Top losers (with collateral and market prefix info)
7840
+ const useTopLosers = (limit, collateralFilter) => {
6978
7841
  const data = useMarketDataPayload();
7842
+ const perpMetaAssets = usePerpMetaAssets();
6979
7843
  return useMemo(() => {
6980
7844
  var _a;
6981
7845
  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]);
7846
+ const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7847
+ const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
7848
+ return filterByCollateral(enriched, collateralFilter);
7849
+ }, [data, perpMetaAssets, limit, collateralFilter]);
6984
7850
  };
6985
- // Highlighted baskets
6986
- const useHighlightedBaskets = () => {
6987
- var _a;
7851
+ // Highlighted baskets (with collateral and market prefix info)
7852
+ const useHighlightedBaskets = (collateralFilter) => {
6988
7853
  const data = useMarketDataPayload();
6989
- return (_a = data === null || data === void 0 ? void 0 : data.highlighted) !== null && _a !== void 0 ? _a : [];
7854
+ const perpMetaAssets = usePerpMetaAssets();
7855
+ return useMemo(() => {
7856
+ if (!(data === null || data === void 0 ? void 0 : data.highlighted))
7857
+ return [];
7858
+ const enriched = data.highlighted.map((item) => enrichBasketItem(item, perpMetaAssets));
7859
+ return filterByCollateral(enriched, collateralFilter);
7860
+ }, [data, perpMetaAssets, collateralFilter]);
6990
7861
  };
6991
- // Watchlist baskets (from market-data payload when subscribed with address)
6992
- const useWatchlistBaskets = () => {
6993
- var _a;
7862
+ // Watchlist baskets (with collateral and market prefix info)
7863
+ const useWatchlistBaskets = (collateralFilter) => {
6994
7864
  const data = useMarketDataPayload();
6995
- return (_a = data === null || data === void 0 ? void 0 : data.watchlist) !== null && _a !== void 0 ? _a : [];
7865
+ const perpMetaAssets = usePerpMetaAssets();
7866
+ return useMemo(() => {
7867
+ if (!(data === null || data === void 0 ? void 0 : data.watchlist))
7868
+ return [];
7869
+ const enriched = data.watchlist.map((item) => enrichBasketItem(item, perpMetaAssets));
7870
+ return filterByCollateral(enriched, collateralFilter);
7871
+ }, [data, perpMetaAssets, collateralFilter]);
6996
7872
  };
6997
- // All baskets (from market-data-all)
6998
- const useAllBaskets = () => {
6999
- var _a;
7873
+ // All baskets (with collateral and market prefix info)
7874
+ const useAllBaskets = (collateralFilter) => {
7000
7875
  const dataAll = useMarketDataAllPayload();
7001
- return (_a = dataAll === null || dataAll === void 0 ? void 0 : dataAll.all) !== null && _a !== void 0 ? _a : [];
7876
+ const perpMetaAssets = usePerpMetaAssets();
7877
+ return useMemo(() => {
7878
+ if (!(dataAll === null || dataAll === void 0 ? void 0 : dataAll.all))
7879
+ return [];
7880
+ const enriched = dataAll.all.map((item) => enrichBasketItem(item, perpMetaAssets));
7881
+ return filterByCollateral(enriched, collateralFilter);
7882
+ }, [dataAll, perpMetaAssets, collateralFilter]);
7002
7883
  };
7003
7884
  // Find a basket by its exact asset composition (order-insensitive)
7004
7885
  const useFindBasket = (longs, shorts) => {
7005
7886
  const data = useMarketDataPayload();
7887
+ const perpMetaAssets = usePerpMetaAssets();
7006
7888
  return useMemo(() => {
7007
7889
  if (!data)
7008
7890
  return undefined;
@@ -7016,17 +7898,28 @@ const useFindBasket = (longs, shorts) => {
7016
7898
  : '';
7017
7899
  const lKey = normalize(longs);
7018
7900
  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]);
7901
+ const match = (item) => normalize(item.longAssets) === lKey &&
7902
+ normalize(item.shortAssets) === sKey;
7903
+ const found = data.active.find(match) || data.highlighted.find(match);
7904
+ return found
7905
+ ? enrichBasketItem(found, perpMetaAssets)
7906
+ : undefined;
7907
+ }, [data, longs, shorts, perpMetaAssets]);
7022
7908
  };
7023
7909
 
7024
- async function toggleWatchlist(baseUrl, longAssets, shortAssets, displayToFull) {
7910
+ async function toggleWatchlist(baseUrl, longAssets, shortAssets, hip3Assets) {
7025
7911
  const url = joinUrl(baseUrl, '/watchlist');
7026
- const mapAssets = (arr) => arr.map(a => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
7912
+ const mapAssets = (arr) => arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
7027
7913
  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 };
7914
+ const response = await apiClient.post(url, {
7915
+ longAssets: mapAssets(longAssets),
7916
+ shortAssets: mapAssets(shortAssets),
7917
+ }, { headers: { 'Content-Type': 'application/json' } });
7918
+ return {
7919
+ data: response.data,
7920
+ status: response.status,
7921
+ headers: response.headers,
7922
+ };
7030
7923
  }
7031
7924
  catch (error) {
7032
7925
  throw toApiError(error);
@@ -7038,11 +7931,11 @@ function useWatchlist() {
7038
7931
  if (!context)
7039
7932
  throw new Error('useWatchlist must be used within a PearHyperliquidProvider');
7040
7933
  const { apiBaseUrl, isConnected } = context;
7041
- const displayToFull = useHyperliquidData((s) => s.hip3DisplayToFull);
7934
+ const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
7042
7935
  const marketData = useMarketDataPayload();
7043
7936
  const isLoading = useMemo(() => !marketData && isConnected, [marketData, isConnected]);
7044
7937
  const toggle = async (longAssets, shortAssets) => {
7045
- const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, displayToFull);
7938
+ const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, hip3Assets);
7046
7939
  // Server will push updated market-data over WS; nothing to set here
7047
7940
  return resp;
7048
7941
  };
@@ -7296,17 +8189,249 @@ function useAuth() {
7296
8189
  };
7297
8190
  }
7298
8191
 
8192
+ const useAllUserBalances = () => {
8193
+ const spotState = useUserData((state) => state.spotState);
8194
+ const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
8195
+ const rawClearinghouseStates = useHyperliquidData((state) => state.rawClearinghouseStates);
8196
+ const activeAssetData = useHyperliquidData((state) => state.activeAssetData);
8197
+ const { longTokensMetadata, shortTokensMetadata } = useTokenSelectionMetadata();
8198
+ return useMemo(() => {
8199
+ const isLoading = !spotState || !aggregatedClearingHouseState;
8200
+ // Helper function to truncate to 2 decimal places without rounding
8201
+ const truncateToTwoDecimals = (value) => {
8202
+ return Math.floor(value * 100) / 100;
8203
+ };
8204
+ // Get spot balances from spotState
8205
+ let spotUsdcBal = undefined;
8206
+ let spotUsdhBal = undefined;
8207
+ if (spotState) {
8208
+ const balances = spotState.balances || [];
8209
+ for (const balance of balances) {
8210
+ const total = parseFloat(balance.total || '0');
8211
+ if (balance.coin === 'USDC') {
8212
+ spotUsdcBal = truncateToTwoDecimals(total);
8213
+ }
8214
+ if (balance.coin === 'USDH') {
8215
+ spotUsdhBal = truncateToTwoDecimals(total);
8216
+ }
8217
+ }
8218
+ }
8219
+ let availableToTradeUsdhFromAsset = 0;
8220
+ // This activeAssetData only contains data for SELECTED tokens (user's long and short Tokens)
8221
+ // It does NOT contain data for all tokens, so we cannot reliably use it for available to trade as used on hl trade page
8222
+ // so intead, we rely on rawClearinghouseStates which provides market-specific data
8223
+ // if (activeAssetData) {
8224
+ // Object.values(activeAssetData).forEach((assetData) => {
8225
+ // if (!assetData.availableToTrade) return;
8226
+ // const coinSymbol = assetData.coin;
8227
+ // const availableValue = truncateToTwoDecimals(
8228
+ // parseFloat(assetData.availableToTrade[0] || '0'),
8229
+ // );
8230
+ // // Determine collateral type based on market prefix
8231
+ // // HIP3 markets have prefix: "xyz:SYMBOL", "flx:SYMBOL", "vntl:SYMBOL", etc.
8232
+ // if (coinSymbol.includes(':')) {
8233
+ // const prefix = coinSymbol.split(':')[0];
8234
+ // if (prefix === 'xyz') {
8235
+ // // xyz markets use USDC
8236
+ // availableToTradeUsdcFromAsset = availableValue;
8237
+ // } else {
8238
+ // // flx, vntl, hyna and other markets use USDH
8239
+ // availableToTradeUsdhFromAsset = availableValue;
8240
+ // }
8241
+ // } else {
8242
+ // // Regular markets without prefix are automatically USDC
8243
+ // availableToTradeUsdcFromAsset = availableValue;
8244
+ // }
8245
+ // });
8246
+ // }
8247
+ // Calculate USDC available to trade
8248
+ // Priority 1: Use value from activeAssetData if available (> 0)
8249
+ // Priority 2: Calculate from USDC-specific clearinghouseState (empty prefix)
8250
+ let availableToTradeUsdcValue = undefined;
8251
+ if (rawClearinghouseStates) {
8252
+ // Find USDC market (empty prefix)
8253
+ const usdcMarket = rawClearinghouseStates.find(([prefix]) => prefix === '');
8254
+ const usdcState = usdcMarket === null || usdcMarket === void 0 ? void 0 : usdcMarket[1];
8255
+ if (usdcState === null || usdcState === void 0 ? void 0 : usdcState.marginSummary) {
8256
+ const accountValue = parseFloat(usdcState.marginSummary.accountValue || '0');
8257
+ const totalMarginUsed = parseFloat(usdcState.marginSummary.totalMarginUsed || '0');
8258
+ const calculatedValue = Math.max(0, accountValue - totalMarginUsed);
8259
+ availableToTradeUsdcValue = truncateToTwoDecimals(calculatedValue);
8260
+ }
8261
+ }
8262
+ // Calculate USDH available to trade
8263
+ // Priority 1: Use value from activeAssetData if available (> 0)
8264
+ // Priority 2: Use spot USDH balance
8265
+ const availableToTradeUsdhValue = availableToTradeUsdhFromAsset > 0
8266
+ ? availableToTradeUsdhFromAsset
8267
+ : spotUsdhBal;
8268
+ return {
8269
+ spotUsdcBalance: spotUsdcBal,
8270
+ availableToTradeUsdc: availableToTradeUsdcValue,
8271
+ spotUsdhBalance: spotUsdhBal,
8272
+ availableToTradeUsdh: availableToTradeUsdhValue,
8273
+ isLoading,
8274
+ };
8275
+ }, [
8276
+ spotState,
8277
+ aggregatedClearingHouseState,
8278
+ rawClearinghouseStates,
8279
+ activeAssetData,
8280
+ longTokensMetadata,
8281
+ shortTokensMetadata,
8282
+ ]);
8283
+ };
8284
+
8285
+ /**
8286
+ * Sync external fills into Pear Hyperliquid service (POST /sync/fills)
8287
+ */
8288
+ const syncFills = async (baseUrl, payload) => {
8289
+ const url = joinUrl(baseUrl, '/sync/fills');
8290
+ try {
8291
+ const response = await apiClient.post(url, payload, {
8292
+ headers: { 'Content-Type': 'application/json' },
8293
+ timeout: 30000,
8294
+ });
8295
+ return { data: response.data, status: response.status, headers: response.headers };
8296
+ }
8297
+ catch (error) {
8298
+ throw toApiError(error);
8299
+ }
8300
+ };
8301
+ /**
8302
+ * Convenience: fetch user fills from HyperLiquid, then sync them to Pear backend
8303
+ */
8304
+ const syncUserFillsFromHyperliquid = async (baseUrl, user, aggregateByTime = true, lastSyncAt = null, assetPositions) => {
8305
+ const firstStartTime = lastSyncAt ? Number(lastSyncAt) + 1 : 0;
8306
+ const allFills = [];
8307
+ const seenTids = new Set();
8308
+ let startTime = firstStartTime;
8309
+ let batchSize = 0;
8310
+ do {
8311
+ const { data: batch } = await fetchUserFillsFromHyperliquid(user, startTime, aggregateByTime);
8312
+ batchSize = batch.length;
8313
+ for (const fill of batch) {
8314
+ const tid = fill.tid;
8315
+ if (tid === undefined)
8316
+ continue;
8317
+ if (!seenTids.has(tid)) {
8318
+ seenTids.add(tid);
8319
+ allFills.push(fill);
8320
+ }
8321
+ }
8322
+ if (batchSize === 2000) {
8323
+ const last = batch[batch.length - 1];
8324
+ startTime = last.time;
8325
+ }
8326
+ } while (batchSize === 2000);
8327
+ startTime = firstStartTime;
8328
+ batchSize = 0;
8329
+ do {
8330
+ const { data: twapBatch } = await fetchUserTwapSliceFillsByTime(user, startTime, aggregateByTime);
8331
+ batchSize = twapBatch.length;
8332
+ for (const item of twapBatch) {
8333
+ const fill = item.fill;
8334
+ const tid = fill.tid;
8335
+ if (tid === undefined)
8336
+ continue;
8337
+ if (!seenTids.has(tid)) {
8338
+ seenTids.add(tid);
8339
+ allFills.push(fill);
8340
+ }
8341
+ }
8342
+ if (batchSize === 2000) {
8343
+ const last = twapBatch[twapBatch.length - 1];
8344
+ startTime = last.fill.time;
8345
+ }
8346
+ } while (batchSize === 2000);
8347
+ const sortedFills = [...allFills].sort((a, b) => Number(a.time) - Number(b.time));
8348
+ return syncFills(baseUrl, { user, fills: sortedFills, assetPositions });
8349
+ };
8350
+
8351
+ /**
8352
+ * Provides a callback to sync user fills whenever the native WebSocket emits a userFills event.
8353
+ */
8354
+ function useHyperliquidUserFills(options) {
8355
+ const { baseUrl, address: addressOverride, aggregateByTime = true } = options;
8356
+ const [lastRunAt, setLastRunAt] = useState(null);
8357
+ const [lastResult, setLastResult] = useState(null);
8358
+ const [error, setError] = useState(null);
8359
+ const [isSyncing, setIsSyncing] = useState(false);
8360
+ const mountedRef = useRef(true);
8361
+ const runningRef = useRef(null);
8362
+ const enabled = useUserData((state) => state.isAuthenticated);
8363
+ useEffect(() => {
8364
+ mountedRef.current = true;
8365
+ return () => {
8366
+ mountedRef.current = false;
8367
+ };
8368
+ }, []);
8369
+ const handleUserFillsEvent = useCallback(async () => {
8370
+ var _a;
8371
+ const userState = useUserData.getState();
8372
+ const currentAddress = userState.address || addressOverride;
8373
+ const lastSyncedAt = (_a = userState.accountSummary) === null || _a === void 0 ? void 0 : _a.lastSyncedAt;
8374
+ if (!(enabled && currentAddress && baseUrl))
8375
+ return;
8376
+ if (runningRef.current)
8377
+ return;
8378
+ if (!userState.accountSummary)
8379
+ return;
8380
+ const clearinghouseState = useHyperliquidData.getState().aggregatedClearingHouseState;
8381
+ if (!(clearinghouseState === null || clearinghouseState === void 0 ? void 0 : clearinghouseState.assetPositions))
8382
+ return;
8383
+ setIsSyncing(true);
8384
+ setError(null);
8385
+ const promise = (async () => {
8386
+ var _a;
8387
+ try {
8388
+ const { data } = await syncUserFillsFromHyperliquid(baseUrl, currentAddress, aggregateByTime, lastSyncedAt, clearinghouseState.assetPositions);
8389
+ if (!mountedRef.current)
8390
+ return;
8391
+ setLastResult(data);
8392
+ setLastRunAt(Date.now());
8393
+ }
8394
+ catch (e) {
8395
+ if (!mountedRef.current)
8396
+ return;
8397
+ setError((_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : 'Failed to sync user fills');
8398
+ }
8399
+ finally {
8400
+ if (mountedRef.current)
8401
+ setIsSyncing(false);
8402
+ runningRef.current = null;
8403
+ }
8404
+ })();
8405
+ runningRef.current = promise;
8406
+ await promise;
8407
+ }, [baseUrl, addressOverride, aggregateByTime, enabled]);
8408
+ return {
8409
+ lastRunAt,
8410
+ lastResult,
8411
+ error,
8412
+ isSyncing,
8413
+ handleUserFillsEvent,
8414
+ };
8415
+ }
8416
+
7299
8417
  const PearHyperliquidContext = createContext(undefined);
7300
8418
  /**
7301
8419
  * React Provider for PearHyperliquidClient
7302
8420
  */
7303
- const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearprotocol.io", clientId = "PEARPROTOCOLUI", wsUrl = "wss://hl-ui.pearprotocol.io/ws", }) => {
8421
+ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearprotocol.io', clientId = 'PEARPROTOCOLUI', wsUrl = 'wss://hl-ui.pearprotocol.io/ws', }) => {
7304
8422
  const address = useUserData((s) => s.address);
7305
8423
  const setAddress = useUserData((s) => s.setAddress);
7306
8424
  const perpsMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
7307
8425
  const setPerpMetaAssets = useHyperliquidData((state) => state.setPerpMetaAssets);
7308
- const setHip3DisplayToFull = useHyperliquidData((state) => state.setHip3DisplayToFull);
8426
+ const setAllPerpMetaAssets = useHyperliquidData((state) => state.setAllPerpMetaAssets);
8427
+ const setHip3Assets = useHyperliquidData((state) => state.setHip3Assets);
8428
+ const setHip3MarketPrefixes = useHyperliquidData((state) => state.setHip3MarketPrefixes);
7309
8429
  const websocketsEnabled = useMemo(() => Array.isArray(perpsMetaAssets) && perpsMetaAssets.length > 0, [perpsMetaAssets]);
8430
+ const { handleUserFillsEvent } = useHyperliquidUserFills({
8431
+ baseUrl: apiBaseUrl,
8432
+ address,
8433
+ aggregateByTime: true,
8434
+ });
7310
8435
  const { isConnected, lastError } = useHyperliquidWebSocket({
7311
8436
  wsUrl,
7312
8437
  address,
@@ -7315,43 +8440,113 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
7315
8440
  const { isConnected: nativeIsConnected, lastError: nativeLastError } = useHyperliquidNativeWebSocket({
7316
8441
  address,
7317
8442
  enabled: websocketsEnabled,
8443
+ onUserFills: handleUserFillsEvent,
7318
8444
  });
7319
8445
  useEffect(() => {
7320
8446
  if (perpsMetaAssets === null) {
7321
8447
  fetchAllPerpMetas()
7322
8448
  .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;
8449
+ const assetToMarkets = new Map();
8450
+ const marketPrefixes = new Map();
8451
+ const FILTERED_PREFIXES = ['hyna'];
8452
+ // Group assets by market prefix to match WebSocket flattening order
8453
+ // WebSocket sends in order: "", "flx", "hyna", "vntl", "xyz" (we filter out hyna)
8454
+ const assetsByPrefix = new Map();
8455
+ const allAssetsByPrefix = new Map();
8456
+ res.data.forEach((item) => {
8457
+ const collateralToken = item.collateralToken === 360 ? 'USDH' : 'USDC';
8458
+ item.universe.forEach((asset) => {
8459
+ var _a;
8460
+ const [maybePrefix, maybeMarket] = asset.name.split(':');
8461
+ if (maybeMarket) {
8462
+ // HIP3 asset with market prefix
8463
+ const prefix = maybePrefix.toLowerCase();
8464
+ const displayName = maybeMarket;
8465
+ const fullName = `${prefix}:${displayName}`;
8466
+ marketPrefixes.set(fullName, prefix);
8467
+ if (!FILTERED_PREFIXES.includes(prefix)) {
8468
+ const existingMarkets = (_a = assetToMarkets.get(displayName)) !== null && _a !== void 0 ? _a : [];
8469
+ if (!existingMarkets.includes(fullName)) {
8470
+ assetToMarkets.set(displayName, [
8471
+ ...existingMarkets,
8472
+ fullName,
8473
+ ]);
8474
+ }
8475
+ }
8476
+ const assetWithMeta = {
8477
+ ...asset,
8478
+ name: displayName,
8479
+ marketPrefix: prefix,
8480
+ collateralToken,
8481
+ };
8482
+ // Group by market prefix
8483
+ const allList = allAssetsByPrefix.get(prefix) || [];
8484
+ allList.push(assetWithMeta);
8485
+ allAssetsByPrefix.set(prefix, allList);
8486
+ if (!FILTERED_PREFIXES.includes(prefix)) {
8487
+ const cleanedList = assetsByPrefix.get(prefix) || [];
8488
+ cleanedList.push(assetWithMeta);
8489
+ assetsByPrefix.set(prefix, cleanedList);
8490
+ }
8491
+ }
8492
+ else {
8493
+ // Default market asset (no prefix)
8494
+ const assetWithMeta = {
8495
+ ...asset,
8496
+ collateralToken,
8497
+ };
8498
+ // Add to default market group ("")
8499
+ const defaultList = assetsByPrefix.get('') || [];
8500
+ defaultList.push(assetWithMeta);
8501
+ assetsByPrefix.set('', defaultList);
8502
+ const allDefaultList = allAssetsByPrefix.get('') || [];
8503
+ allDefaultList.push(assetWithMeta);
8504
+ allAssetsByPrefix.set('', allDefaultList);
8505
+ }
8506
+ });
7341
8507
  });
7342
- setHip3DisplayToFull(displayToFull);
8508
+ // Flatten in consistent order: default market first, then HIP3 markets alphabetically
8509
+ // This ensures both REST API and WebSocket data align properly
8510
+ const cleanedPrefixes = Array.from(assetsByPrefix.keys()).sort((a, b) => {
8511
+ // Empty prefix (default market) always comes first
8512
+ if (a === '' && b !== '')
8513
+ return -1;
8514
+ if (a !== '' && b === '')
8515
+ return 1;
8516
+ // HIP3 markets sorted alphabetically
8517
+ return a.localeCompare(b);
8518
+ });
8519
+ const allPrefixes = Array.from(allAssetsByPrefix.keys()).sort((a, b) => {
8520
+ if (a === '' && b !== '')
8521
+ return -1;
8522
+ if (a !== '' && b === '')
8523
+ return 1;
8524
+ return a.localeCompare(b);
8525
+ });
8526
+ const cleanedPerpMetas = [];
8527
+ const allPerpMetas = [];
8528
+ cleanedPrefixes.forEach((prefix) => {
8529
+ const assets = assetsByPrefix.get(prefix) || [];
8530
+ cleanedPerpMetas.push(...assets);
8531
+ });
8532
+ allPrefixes.forEach((prefix) => {
8533
+ const assets = allAssetsByPrefix.get(prefix) || [];
8534
+ allPerpMetas.push(...assets);
8535
+ });
8536
+ setHip3Assets(assetToMarkets);
8537
+ setHip3MarketPrefixes(marketPrefixes);
7343
8538
  setPerpMetaAssets(cleanedPerpMetas);
8539
+ setAllPerpMetaAssets(allPerpMetas);
7344
8540
  })
7345
8541
  .catch(() => { });
7346
8542
  }
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
- });
8543
+ }, [
8544
+ perpsMetaAssets,
8545
+ setPerpMetaAssets,
8546
+ setAllPerpMetaAssets,
8547
+ setHip3Assets,
8548
+ setHip3MarketPrefixes,
8549
+ ]);
7355
8550
  const contextValue = useMemo(() => ({
7356
8551
  // Config
7357
8552
  clientId,
@@ -7383,7 +8578,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearpro
7383
8578
  function usePearHyperliquid() {
7384
8579
  const ctx = useContext(PearHyperliquidContext);
7385
8580
  if (!ctx)
7386
- throw new Error("usePearHyperliquid must be used within a PearHyperliquidProvider");
8581
+ throw new Error('usePearHyperliquid must be used within a PearHyperliquidProvider');
7387
8582
  return ctx;
7388
8583
  }
7389
8584
 
@@ -7474,4 +8669,129 @@ function mapCandleIntervalToTradingViewInterval(interval) {
7474
8669
  }
7475
8670
  }
7476
8671
 
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 };
8672
+ /**
8673
+ * Helper functions to safely extract values from order parameters
8674
+ * based on the order type and parameter structure.
8675
+ */
8676
+ /**
8677
+ * Get leverage from order parameters (available for Market, Trigger, Twap, Ladder orders)
8678
+ */
8679
+ function getOrderLeverage(order) {
8680
+ const p = order.parameters;
8681
+ if ('leverage' in p && p.leverage !== undefined) {
8682
+ return p.leverage;
8683
+ }
8684
+ return undefined;
8685
+ }
8686
+ /**
8687
+ * Get USD value from order parameters (available for Market, Trigger, Twap, Ladder orders)
8688
+ */
8689
+ function getOrderUsdValue(order) {
8690
+ const p = order.parameters;
8691
+ if ('usdValue' in p) {
8692
+ return p.usdValue;
8693
+ }
8694
+ return undefined;
8695
+ }
8696
+ /**
8697
+ * Get trigger value from order parameters (available for TP/SL and Trigger orders)
8698
+ */
8699
+ function getOrderTriggerValue(order) {
8700
+ const p = order.parameters;
8701
+ if ('triggerValue' in p) {
8702
+ return p.triggerValue;
8703
+ }
8704
+ return undefined;
8705
+ }
8706
+ /**
8707
+ * Get TP/SL trigger type from order parameters (only for TP/SL orders)
8708
+ */
8709
+ function getOrderTpSlTriggerType(order) {
8710
+ if (order.orderType === 'TP' || order.orderType === 'SL') {
8711
+ const p = order.parameters;
8712
+ return p.triggerType;
8713
+ }
8714
+ return undefined;
8715
+ }
8716
+ /**
8717
+ * Get trigger type from order parameters (for Trigger orders)
8718
+ */
8719
+ function getOrderTriggerType(order) {
8720
+ if (order.orderType === 'TRIGGER') {
8721
+ const p = order.parameters;
8722
+ return p.triggerType;
8723
+ }
8724
+ return undefined;
8725
+ }
8726
+ /**
8727
+ * Get order direction from order parameters (for Trigger orders)
8728
+ */
8729
+ function getOrderDirection(order) {
8730
+ if (order.orderType === 'TRIGGER') {
8731
+ const p = order.parameters;
8732
+ return p.direction;
8733
+ }
8734
+ return undefined;
8735
+ }
8736
+ /**
8737
+ * Get reduce only flag from order parameters (available for all order types)
8738
+ */
8739
+ function getOrderReduceOnly(order) {
8740
+ var _a;
8741
+ const p = order.parameters;
8742
+ if ('reduceOnly' in p) {
8743
+ return (_a = p.reduceOnly) !== null && _a !== void 0 ? _a : false;
8744
+ }
8745
+ return false;
8746
+ }
8747
+ /**
8748
+ * Get TWAP duration from order parameters (only for TWAP orders)
8749
+ */
8750
+ function getOrderTwapDuration(order) {
8751
+ if (order.orderType === 'TWAP') {
8752
+ const p = order.parameters;
8753
+ return p.duration;
8754
+ }
8755
+ return undefined;
8756
+ }
8757
+ /**
8758
+ * Get ladder config from order parameters (only for Ladder orders)
8759
+ */
8760
+ function getOrderLadderConfig(order) {
8761
+ if (order.orderType === 'LADDER') {
8762
+ const p = order.parameters;
8763
+ return {
8764
+ ratioStart: p.ratioStart,
8765
+ ratioEnd: p.ratioEnd,
8766
+ numberOfLevels: p.numberOfLevels,
8767
+ };
8768
+ }
8769
+ return undefined;
8770
+ }
8771
+ /**
8772
+ * Check if the order is a BTC Dominance trigger order
8773
+ */
8774
+ function isBtcDomOrder(order) {
8775
+ if (order.orderType === 'TRIGGER') {
8776
+ const p = order.parameters;
8777
+ return p.triggerType === 'BTC_DOM';
8778
+ }
8779
+ return false;
8780
+ }
8781
+ /**
8782
+ * Get trailing info from TP/SL order parameters
8783
+ */
8784
+ function getOrderTrailingInfo(order) {
8785
+ var _a;
8786
+ if (order.orderType === 'TP' || order.orderType === 'SL') {
8787
+ const p = order.parameters;
8788
+ return {
8789
+ isTrailing: (_a = p.isTrailing) !== null && _a !== void 0 ? _a : false,
8790
+ trailingDeltaValue: p.trailingDeltaValue,
8791
+ trailingActivationValue: p.trailingActivationValue,
8792
+ };
8793
+ }
8794
+ return undefined;
8795
+ }
8796
+
8797
+ 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, getKalshiMarkets, 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 };