@pear-protocol/hyperliquid-sdk 0.0.60-beta → 0.0.60-beta-usdh-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import React, { useState, useRef, useCallback, useEffect, useMemo, useContext, createContext } from 'react';
2
+ import { useState, useRef, useCallback, useEffect, useMemo, useContext, createContext } from 'react';
3
+ import { create } from 'zustand';
3
4
 
4
5
  // Browser-compatible WebSocket ready state enum (mirrors native values)
5
6
  var ReadyState;
@@ -10,47 +11,6 @@ var ReadyState;
10
11
  ReadyState[ReadyState["CLOSED"] = 3] = "CLOSED";
11
12
  })(ReadyState || (ReadyState = {}));
12
13
 
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
-
54
14
  const useUserData = create((set) => ({
55
15
  accessToken: null,
56
16
  refreshToken: null,
@@ -67,12 +27,12 @@ const useUserData = create((set) => ({
67
27
  setRefreshToken: (token) => set({ refreshToken: token }),
68
28
  setIsAuthenticated: (value) => set({ isAuthenticated: value }),
69
29
  setAddress: (address) => set(() => {
70
- if (typeof window !== "undefined") {
30
+ if (typeof window !== 'undefined') {
71
31
  if (address) {
72
- window.localStorage.setItem("address", address);
32
+ window.localStorage.setItem('address', address);
73
33
  }
74
34
  else {
75
- window.localStorage.removeItem("address");
35
+ window.localStorage.removeItem('address');
76
36
  }
77
37
  }
78
38
  return { address };
@@ -111,18 +71,71 @@ const useMarketData = create((set) => ({
111
71
  * Convert a full/prefixed symbol (e.g., "xyz:XYZ100") to a display symbol (e.g., "XYZ100").
112
72
  */
113
73
  function toDisplaySymbol(symbol) {
114
- const parts = symbol.split(":");
74
+ const parts = symbol.split(':');
115
75
  return parts.length > 1 ? parts.slice(-1)[0] : symbol;
116
76
  }
117
77
  /**
118
78
  * Convert a display symbol back to backend form using a provided map.
119
79
  * If mapping is missing, returns the original symbol.
120
- * @param displaySymbol e.g., "XYZ100"
121
- * @param displayToFull map of display -> full (e.g., "XYZ100" -> "xyz:XYZ100")
80
+ * For multi-market assets, returns the first available market.
81
+ * @param displaySymbol e.g., "TSLA"
82
+ * @param hip3Assets map of display -> all full market names (e.g., "TSLA" -> ["xyz:TSLA", "flx:TSLA"])
83
+ */
84
+ function toBackendSymbol(displaySymbol, hip3Assets) {
85
+ const markets = hip3Assets.get(displaySymbol);
86
+ // Return first market if available, otherwise return original symbol
87
+ return markets && markets.length > 0 ? markets[0] : displaySymbol;
88
+ }
89
+ /**
90
+ * Convert a display symbol to backend form for a specific market prefix.
91
+ * This is useful when an asset is available on multiple markets (e.g., xyz:TSLA and flx:TSLA).
92
+ * @param displaySymbol e.g., "TSLA"
93
+ * @param marketPrefix e.g., "xyz" or "flx"
94
+ * @param hip3Assets map of display -> all full market names
95
+ * @returns Full market name if found, null if prefix not specified for multi-market asset, otherwise displaySymbol with prefix
122
96
  */
123
- function toBackendSymbol(displaySymbol, displayToFull) {
97
+ function toBackendSymbolWithMarket(displaySymbol, marketPrefix, hip3Assets) {
98
+ const availableMarkets = hip3Assets.get(displaySymbol);
99
+ if (!availableMarkets || availableMarkets.length === 0) {
100
+ // Not a HIP-3 asset, return as-is or with prefix if provided
101
+ return marketPrefix ? `${marketPrefix}:${displaySymbol}` : displaySymbol;
102
+ }
103
+ if (marketPrefix) {
104
+ // Find the market with the specified prefix
105
+ const targetMarket = availableMarkets.find((market) => market.toLowerCase().startsWith(`${marketPrefix.toLowerCase()}:`));
106
+ if (targetMarket) {
107
+ return targetMarket;
108
+ }
109
+ }
110
+ // No prefix specified or not found, return null to force explicit market selection
111
+ return null;
112
+ }
113
+ /**
114
+ * Get all available markets for a display symbol.
115
+ * @param displaySymbol e.g., "TSLA"
116
+ * @param hip3Assets map of display -> all full market names
117
+ * @returns Array of full market names, e.g., ["xyz:TSLA", "flx:TSLA"]
118
+ */
119
+ function getAvailableMarkets(displaySymbol, hip3Assets) {
124
120
  var _a;
125
- return (_a = displayToFull.get(displaySymbol)) !== null && _a !== void 0 ? _a : displaySymbol;
121
+ return (_a = hip3Assets.get(displaySymbol)) !== null && _a !== void 0 ? _a : [];
122
+ }
123
+ /**
124
+ * Extract the market prefix from a full market name.
125
+ * @param fullSymbol e.g., "xyz:TSLA"
126
+ * @returns The prefix (e.g., "xyz") or undefined if no prefix
127
+ */
128
+ function getMarketPrefix(fullSymbol) {
129
+ const parts = fullSymbol.split(':');
130
+ return parts.length > 1 ? parts[0] : undefined;
131
+ }
132
+ /**
133
+ * Check if a symbol is a HIP-3 market (has a prefix).
134
+ * @param symbol e.g., "xyz:TSLA" or "TSLA"
135
+ * @returns true if the symbol has a market prefix
136
+ */
137
+ function isHip3Market(symbol) {
138
+ return symbol.includes(':');
126
139
  }
127
140
 
128
141
  const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
@@ -358,10 +371,11 @@ const useHyperliquidData = create((set, get) => ({
358
371
  finalAtOICaps: null,
359
372
  aggregatedClearingHouseState: null,
360
373
  perpMetaAssets: null,
361
- hip3DisplayToFull: new Map(),
374
+ hip3Assets: new Map(),
375
+ hip3MarketPrefixes: new Map(),
362
376
  setAllMids: (value) => set({ allMids: value }),
363
377
  setActiveAssetData: (value) => set((state) => ({
364
- activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value
378
+ activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value,
365
379
  })),
366
380
  deleteActiveAssetData: (key) => {
367
381
  set((state) => {
@@ -396,13 +410,14 @@ const useHyperliquidData = create((set, get) => ({
396
410
  activeAssetData: {
397
411
  ...state.activeAssetData,
398
412
  [key]: value,
399
- }
413
+ },
400
414
  })),
401
415
  setFinalAssetContexts: (value) => set({ finalAssetContexts: value }),
402
416
  setFinalAtOICaps: (value) => set({ finalAtOICaps: value }),
403
417
  setAggregatedClearingHouseState: (value) => set({ aggregatedClearingHouseState: value }),
404
418
  setPerpMetaAssets: (value) => set({ perpMetaAssets: value }),
405
- setHip3DisplayToFull: (value) => set({ hip3DisplayToFull: value })
419
+ setHip3Assets: (value) => set({ hip3Assets: value }),
420
+ setHip3MarketPrefixes: (value) => set({ hip3MarketPrefixes: value }),
406
421
  }));
407
422
 
408
423
  const DEFAULT_STATE = {
@@ -543,11 +558,11 @@ const useUserSelection$1 = create((set, get) => ({
543
558
  }));
544
559
 
545
560
  const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
546
- const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState } = useHyperliquidData();
561
+ const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState, } = useHyperliquidData();
547
562
  const { candleInterval } = useUserSelection$1();
548
563
  const longTokens = useUserSelection$1((s) => s.longTokens);
549
564
  const shortTokens = useUserSelection$1((s) => s.shortTokens);
550
- const selectedTokenSymbols = useMemo(() => ([...longTokens, ...shortTokens].map((t) => t.symbol)), [longTokens, shortTokens]);
565
+ const selectedTokenSymbols = useMemo(() => [...longTokens, ...shortTokens].map((t) => t.symbol), [longTokens, shortTokens]);
551
566
  const [lastError, setLastError] = useState(null);
552
567
  const [subscribedAddress, setSubscribedAddress] = useState(null);
553
568
  const [subscribedTokens, setSubscribedTokens] = useState([]);
@@ -585,7 +600,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
585
600
  .map((dex) => dex.clearinghouseState)
586
601
  .filter(Boolean);
587
602
  const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || '0') || 0), 0);
588
- const toStr = (n) => (Number.isFinite(n) ? n.toString() : '0');
603
+ const toStr = (n) => Number.isFinite(n) ? n.toString() : '0';
589
604
  const assetPositions = states.flatMap((s) => s.assetPositions || []);
590
605
  const crossMaintenanceMarginUsed = toStr(sum(states.map((s) => s.crossMaintenanceMarginUsed)));
591
606
  const crossMarginSummary = {
@@ -618,17 +633,42 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
618
633
  case 'allMids':
619
634
  {
620
635
  const data = response.data;
621
- const remapped = {
622
- mids: Object.fromEntries(Object.entries(data.mids || {}).map(([k, v]) => [toDisplaySymbol(k), v]))
623
- };
624
- setAllMids(remapped);
636
+ // Keep BOTH normalized prefixed keys AND display symbol keys
637
+ // This ensures xyz:TSLA and flx:TSLA are stored separately,
638
+ // while also maintaining backward compatibility with non-prefixed lookups
639
+ const mids = {};
640
+ Object.entries(data.mids || {}).forEach(([k, v]) => {
641
+ // Normalize prefixed keys to lowercase prefix (e.g., "XYZ:TSLA" -> "xyz:TSLA")
642
+ // This matches how we look up tokens in the SDK
643
+ let normalizedKey = k;
644
+ if (k.includes(':')) {
645
+ const [prefix, ...rest] = k.split(':');
646
+ normalizedKey = `${prefix.toLowerCase()}:${rest.join(':')}`;
647
+ }
648
+ // Store with normalized key
649
+ mids[normalizedKey] = v;
650
+ // Also store with original key for backward compatibility
651
+ if (k !== normalizedKey) {
652
+ mids[k] = v;
653
+ }
654
+ // Also store with display symbol for backward compatibility
655
+ const displayKey = toDisplaySymbol(k);
656
+ // Only set display key if it doesn't already exist (avoid overwriting market-specific prices)
657
+ if (!(displayKey in mids)) {
658
+ mids[displayKey] = v;
659
+ }
660
+ });
661
+ setAllMids({ mids });
625
662
  }
626
663
  break;
627
664
  case 'activeAssetData':
628
665
  {
629
666
  const assetData = response.data;
630
667
  const symbol = toDisplaySymbol(assetData.coin);
631
- const normalized = { ...assetData, coin: symbol };
668
+ const normalized = {
669
+ ...assetData,
670
+ coin: symbol,
671
+ };
632
672
  upsertActiveAssetData(symbol, normalized);
633
673
  }
634
674
  break;
@@ -650,7 +690,14 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
650
690
  console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', event.data);
651
691
  setLastError(errorMessage);
652
692
  }
653
- }, [setAllMids, upsertActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState]);
693
+ }, [
694
+ setAllMids,
695
+ upsertActiveAssetData,
696
+ addCandleData,
697
+ setFinalAssetContexts,
698
+ setFinalAtOICaps,
699
+ setAggregatedClearingHouseState,
700
+ ]);
654
701
  const connect = useCallback(() => {
655
702
  if (!enabled)
656
703
  return;
@@ -773,7 +820,13 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
773
820
  // clear aggregatedClearingHouseState
774
821
  setAggregatedClearingHouseState(null);
775
822
  }
776
- }, [isConnected, address, subscribedAddress, sendJsonMessage, setAggregatedClearingHouseState]);
823
+ }, [
824
+ isConnected,
825
+ address,
826
+ subscribedAddress,
827
+ sendJsonMessage,
828
+ setAggregatedClearingHouseState,
829
+ ]);
777
830
  // Handle token subscriptions for activeAssetData
778
831
  useEffect(() => {
779
832
  if (!isConnected || !address)
@@ -782,7 +835,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
782
835
  const tokensToSubscribe = effectiveTokens.filter((token) => token && !subscribedTokens.includes(token));
783
836
  const tokensToUnsubscribe = subscribedTokens.filter((token) => !effectiveTokens.includes(token));
784
837
  // Unsubscribe from tokens no longer in the list
785
- tokensToUnsubscribe.forEach(token => {
838
+ tokensToUnsubscribe.forEach((token) => {
786
839
  const unsubscribeMessage = {
787
840
  method: 'unsubscribe',
788
841
  subscription: {
@@ -794,7 +847,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
794
847
  sendJsonMessage(unsubscribeMessage);
795
848
  });
796
849
  // Subscribe to new tokens
797
- tokensToSubscribe.forEach(token => {
850
+ tokensToSubscribe.forEach((token) => {
798
851
  const subscribeMessage = {
799
852
  method: 'subscribe',
800
853
  subscription: {
@@ -809,7 +862,14 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
809
862
  setSubscribedTokens(effectiveTokens.filter((token) => token));
810
863
  tokensToSubscribe.forEach((token) => deleteActiveAssetData(token));
811
864
  }
812
- }, [isConnected, address, selectedTokenSymbols, subscribedTokens, sendJsonMessage, setActiveAssetData]);
865
+ }, [
866
+ isConnected,
867
+ address,
868
+ selectedTokenSymbols,
869
+ subscribedTokens,
870
+ sendJsonMessage,
871
+ setActiveAssetData,
872
+ ]);
813
873
  // Handle candle subscriptions for tokens and interval changes
814
874
  useEffect(() => {
815
875
  if (!isConnected)
@@ -818,7 +878,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
818
878
  // Unsubscribe from previous candle subscriptions if interval changed
819
879
  const prevInterval = prevCandleIntervalRef.current;
820
880
  if (prevInterval && prevInterval !== candleInterval) {
821
- subscribedCandleTokens.forEach(token => {
881
+ subscribedCandleTokens.forEach((token) => {
822
882
  const unsubscribeMessage = {
823
883
  method: 'unsubscribe',
824
884
  subscription: {
@@ -859,12 +919,21 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
859
919
  sendJsonMessage(subscribeMessage);
860
920
  });
861
921
  // Update subscribed state
862
- if (tokensToSubscribe.length > 0 || tokensToUnsubscribe.length > 0 || prevInterval !== candleInterval) {
922
+ if (tokensToSubscribe.length > 0 ||
923
+ tokensToUnsubscribe.length > 0 ||
924
+ prevInterval !== candleInterval) {
863
925
  setSubscribedCandleTokens(effectiveTokens.filter((token) => token));
864
926
  prevCandleIntervalRef.current = candleInterval;
865
927
  tokensToUnsubscribe.forEach((token) => deleteCandleSymbol(token));
866
928
  }
867
- }, [isConnected, selectedTokenSymbols, candleInterval, subscribedCandleTokens, sendJsonMessage, setCandleData]);
929
+ }, [
930
+ isConnected,
931
+ selectedTokenSymbols,
932
+ candleInterval,
933
+ subscribedCandleTokens,
934
+ sendJsonMessage,
935
+ setCandleData,
936
+ ]);
868
937
  return {
869
938
  isConnected,
870
939
  lastError,
@@ -944,16 +1013,69 @@ const useAccountSummary = () => {
944
1013
  return { data: calculated, isLoading };
945
1014
  };
946
1015
 
1016
+ // Helper to find asset metadata from perpMetaAssets
1017
+ function findAssetMeta$2(coinName, perpMetaAssets) {
1018
+ var _a, _b, _c, _d;
1019
+ if (!perpMetaAssets) {
1020
+ return { collateralToken: 'USDC', marketPrefix: null };
1021
+ }
1022
+ // Try exact match first (for prefixed assets like "xyz:TSLA")
1023
+ const exactMatch = perpMetaAssets.find((a) => a.name === coinName);
1024
+ if (exactMatch) {
1025
+ return {
1026
+ collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
1027
+ marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
1028
+ };
1029
+ }
1030
+ // Try matching by base symbol (for non-prefixed names in data)
1031
+ const baseMatch = perpMetaAssets.find((a) => {
1032
+ const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
1033
+ return baseName === coinName;
1034
+ });
1035
+ if (baseMatch) {
1036
+ return {
1037
+ collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
1038
+ marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
1039
+ };
1040
+ }
1041
+ return { collateralToken: 'USDC', marketPrefix: null };
1042
+ }
1043
+ // Enrich trade history assets with market prefix and collateral token
1044
+ function enrichTradeHistoryAssets(assets, perpMetaAssets) {
1045
+ return assets.map((asset) => {
1046
+ const meta = findAssetMeta$2(asset.coin, perpMetaAssets);
1047
+ return {
1048
+ ...asset,
1049
+ marketPrefix: meta.marketPrefix,
1050
+ collateralToken: meta.collateralToken,
1051
+ };
1052
+ });
1053
+ }
1054
+ // Enrich all trade histories with market metadata
1055
+ function enrichTradeHistories(histories, perpMetaAssets) {
1056
+ return histories.map((history) => ({
1057
+ ...history,
1058
+ closedLongAssets: enrichTradeHistoryAssets(history.closedLongAssets, perpMetaAssets),
1059
+ closedShortAssets: enrichTradeHistoryAssets(history.closedShortAssets, perpMetaAssets),
1060
+ }));
1061
+ }
947
1062
  const useTradeHistories = () => {
948
1063
  const context = useContext(PearHyperliquidContext);
949
1064
  if (!context) {
950
1065
  throw new Error('useTradeHistories must be used within a PearHyperliquidProvider');
951
1066
  }
952
1067
  const tradeHistories = useUserData((state) => state.tradeHistories);
1068
+ const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
953
1069
  const isLoading = useMemo(() => {
954
1070
  return tradeHistories === null && context.isConnected;
955
1071
  }, [tradeHistories, context.isConnected]);
956
- return { data: tradeHistories, isLoading };
1072
+ // Enrich trade histories with market prefix and collateral token
1073
+ const enrichedTradeHistories = useMemo(() => {
1074
+ if (!tradeHistories)
1075
+ return null;
1076
+ return enrichTradeHistories(tradeHistories, perpMetaAssets);
1077
+ }, [tradeHistories, perpMetaAssets]);
1078
+ return { data: enrichedTradeHistories, isLoading };
957
1079
  };
958
1080
  /**
959
1081
  * Hook to access open orders with loading state
@@ -986,21 +1108,51 @@ const useWebData = () => {
986
1108
  const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
987
1109
  const aggregatedClearinghouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
988
1110
  const finalAtOICaps = useHyperliquidData((state) => state.finalAtOICaps);
989
- const hip3Assets = useHyperliquidData((state) => state.hip3DisplayToFull);
1111
+ const hip3Assets = useHyperliquidData((state) => state.hip3Assets);
1112
+ const hip3MarketPrefixes = useHyperliquidData((state) => state.hip3MarketPrefixes);
990
1113
  let marketDataBySymbol = {};
991
1114
  if (finalAssetContexts && perpMetaAssets) {
992
1115
  const result = {};
1116
+ // Build a map of display name -> asset context index (for unique display names)
1117
+ const displayNameToContextIndex = new Map();
1118
+ const seenNames = new Set();
1119
+ let contextIndex = 0;
1120
+ // First pass: map unique display names to their context index
993
1121
  for (let index = 0; index < perpMetaAssets.length; index++) {
994
1122
  const name = perpMetaAssets[index].name;
995
- result[name] = {
996
- asset: finalAssetContexts[index],
997
- universe: perpMetaAssets[index],
998
- };
1123
+ if (!seenNames.has(name)) {
1124
+ seenNames.add(name);
1125
+ if (contextIndex < finalAssetContexts.length) {
1126
+ displayNameToContextIndex.set(name, contextIndex);
1127
+ contextIndex++;
1128
+ }
1129
+ }
1130
+ }
1131
+ // Second pass: create nested entries for all market variants
1132
+ for (let index = 0; index < perpMetaAssets.length; index++) {
1133
+ const universeAsset = perpMetaAssets[index];
1134
+ const displayName = universeAsset.name;
1135
+ const marketPrefix = universeAsset.marketPrefix;
1136
+ const ctxIndex = displayNameToContextIndex.get(displayName);
1137
+ if (ctxIndex !== undefined) {
1138
+ const assetContext = finalAssetContexts[ctxIndex];
1139
+ // Initialize the symbol entry if it doesn't exist
1140
+ if (!result[displayName]) {
1141
+ result[displayName] = {};
1142
+ }
1143
+ // Use marketPrefix as key for HIP-3 assets, "default" for regular assets
1144
+ const variantKey = marketPrefix || 'default';
1145
+ result[displayName][variantKey] = {
1146
+ asset: assetContext,
1147
+ universe: universeAsset,
1148
+ };
1149
+ }
999
1150
  }
1000
1151
  marketDataBySymbol = result;
1001
1152
  }
1002
1153
  return {
1003
1154
  hip3Assets,
1155
+ hip3MarketPrefixes,
1004
1156
  clearinghouseState: aggregatedClearinghouseState,
1005
1157
  perpsAtOpenInterestCap: finalAtOICaps,
1006
1158
  marketDataBySymbol,
@@ -1015,30 +1167,60 @@ const useWebData = () => {
1015
1167
  class TokenMetadataExtractor {
1016
1168
  /**
1017
1169
  * Extracts comprehensive token metadata
1018
- * @param symbol - Token symbol
1170
+ * @param symbol - Token symbol (base symbol without prefix, e.g., "TSLA")
1019
1171
  * @param perpMetaAssets - Aggregated universe assets (flattened across dexes)
1020
1172
  * @param finalAssetContexts - Aggregated asset contexts (flattened across dexes)
1021
1173
  * @param allMids - AllMids data containing current prices
1022
1174
  * @param activeAssetData - Optional active asset data containing leverage information
1175
+ * @param marketPrefix - Optional market prefix (e.g., "xyz", "flx") for HIP3 multi-market assets
1023
1176
  * @returns TokenMetadata or null if token not found
1024
1177
  */
1025
- static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
1178
+ static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketPrefix) {
1026
1179
  if (!perpMetaAssets || !finalAssetContexts || !allMids) {
1027
1180
  return null;
1028
1181
  }
1029
1182
  // Find token index in aggregated universe
1030
- const universeIndex = perpMetaAssets.findIndex(asset => asset.name === symbol);
1031
- if (universeIndex === -1) {
1183
+ // For HIP3 assets, match both name AND marketPrefix
1184
+ const universeIndex = perpMetaAssets.findIndex((asset) => {
1185
+ if (asset.name !== symbol)
1186
+ return false;
1187
+ // If marketPrefix is specified, match it; otherwise match assets without prefix
1188
+ if (marketPrefix) {
1189
+ return asset.marketPrefix === marketPrefix;
1190
+ }
1191
+ // No prefix specified - match non-HIP3 asset (no marketPrefix) or first matching asset
1192
+ return !asset.marketPrefix;
1193
+ });
1194
+ // If no exact match found and prefix was specified, try finding the specific market variant
1195
+ const finalIndex = universeIndex === -1 && marketPrefix
1196
+ ? perpMetaAssets.findIndex((asset) => asset.name === symbol && asset.marketPrefix === marketPrefix)
1197
+ : universeIndex;
1198
+ // Fallback: if still not found and no prefix, find first matching by name (for backward compatibility)
1199
+ const resolvedIndex = finalIndex === -1
1200
+ ? perpMetaAssets.findIndex((asset) => asset.name === symbol)
1201
+ : finalIndex;
1202
+ if (resolvedIndex === -1) {
1032
1203
  return null;
1033
1204
  }
1034
- const universeAsset = perpMetaAssets[universeIndex];
1035
- const assetCtx = finalAssetContexts[universeIndex];
1205
+ const universeAsset = perpMetaAssets[resolvedIndex];
1206
+ const assetCtx = finalAssetContexts[resolvedIndex];
1036
1207
  if (!assetCtx) {
1037
1208
  return null;
1038
1209
  }
1039
- // Get current price from allMids
1040
- const currentPriceStr = allMids.mids[symbol];
1041
- const currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
1210
+ // Get current price - prefer assetCtx.midPx as it's already index-matched,
1211
+ // fall back to allMids lookup if midPx is null
1212
+ const prefixedKeyColon = marketPrefix ? `${marketPrefix}:${symbol}` : null;
1213
+ let currentPrice = 0;
1214
+ // Primary source: assetCtx.midPx (already properly indexed)
1215
+ if (assetCtx.midPx) {
1216
+ currentPrice = parseFloat(assetCtx.midPx);
1217
+ }
1218
+ // Fallback: allMids lookup with multiple key formats for HIP3 markets
1219
+ if (!currentPrice || isNaN(currentPrice)) {
1220
+ const currentPriceStr = (prefixedKeyColon && allMids.mids[prefixedKeyColon]) ||
1221
+ allMids.mids[symbol];
1222
+ currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
1223
+ }
1042
1224
  // Get previous day price
1043
1225
  const prevDayPrice = parseFloat(assetCtx.prevDayPx);
1044
1226
  // Calculate 24h price change
@@ -1049,7 +1231,11 @@ class TokenMetadataExtractor {
1049
1231
  const markPrice = parseFloat(assetCtx.markPx);
1050
1232
  const oraclePrice = parseFloat(assetCtx.oraclePx);
1051
1233
  // Extract leverage info from activeAssetData if available
1052
- const tokenActiveData = activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[symbol];
1234
+ // Try prefixed key first (e.g., "xyz:TSLA"), then fall back to plain symbol
1235
+ const activeDataKey = prefixedKeyColon && (activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[prefixedKeyColon])
1236
+ ? prefixedKeyColon
1237
+ : symbol;
1238
+ const tokenActiveData = activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[activeDataKey];
1053
1239
  const leverage = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.leverage;
1054
1240
  const maxTradeSzs = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.maxTradeSzs;
1055
1241
  const availableToTrade = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.availableToTrade;
@@ -1067,21 +1253,27 @@ class TokenMetadataExtractor {
1067
1253
  leverage,
1068
1254
  maxTradeSzs,
1069
1255
  availableToTrade,
1256
+ collateralToken: universeAsset.collateralToken,
1070
1257
  };
1071
1258
  }
1072
1259
  /**
1073
1260
  * Extracts metadata for multiple tokens
1074
- * @param symbols - Array of token symbols
1261
+ * @param tokens - Array of token objects with symbol and optional marketPrefix
1075
1262
  * @param perpMetaAssets - Aggregated universe assets
1076
1263
  * @param finalAssetContexts - Aggregated asset contexts
1077
1264
  * @param allMids - AllMids data
1078
1265
  * @param activeAssetData - Optional active asset data containing leverage information
1079
- * @returns Record of symbol to TokenMetadata
1266
+ * @returns Record of unique key to TokenMetadata. Key is "{prefix}:{symbol}" for HIP3 assets, or just "{symbol}" otherwise
1080
1267
  */
1081
- static extractMultipleTokensMetadata(symbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
1268
+ static extractMultipleTokensMetadata(tokens, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
1082
1269
  const result = {};
1083
- for (const symbol of symbols) {
1084
- result[symbol] = this.extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData);
1270
+ for (const token of tokens) {
1271
+ // Use a unique key that includes the prefix for HIP3 assets
1272
+ // This ensures xyz:TSLA and flx:TSLA get separate entries
1273
+ const resultKey = token.marketPrefix
1274
+ ? `${token.marketPrefix}:${token.symbol}`
1275
+ : token.symbol;
1276
+ result[resultKey] = this.extractTokenMetadata(token.symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, token.marketPrefix);
1085
1277
  }
1086
1278
  return result;
1087
1279
  }
@@ -1094,10 +1286,30 @@ class TokenMetadataExtractor {
1094
1286
  static isTokenAvailable(symbol, perpMetaAssets) {
1095
1287
  if (!perpMetaAssets)
1096
1288
  return false;
1097
- return perpMetaAssets.some(asset => asset.name === symbol);
1289
+ return perpMetaAssets.some((asset) => asset.name === symbol);
1098
1290
  }
1099
1291
  }
1100
1292
 
1293
+ /**
1294
+ * Parse a token string that may have a market prefix (e.g., "xyz:GOOGL" -> { prefix: "xyz", symbol: "GOOGL" })
1295
+ * This allows us to keep the full name (xyz:GOOGL) for URLs/tags while extracting just the symbol for SDK lookups.
1296
+ */
1297
+ function parseTokenWithPrefix(token) {
1298
+ if (token.includes(':')) {
1299
+ const [prefix, ...rest] = token.split(':');
1300
+ const symbol = rest.join(':').toUpperCase();
1301
+ return {
1302
+ prefix: prefix.toLowerCase(),
1303
+ symbol,
1304
+ fullName: `${prefix.toLowerCase()}:${symbol}`,
1305
+ };
1306
+ }
1307
+ return {
1308
+ prefix: null,
1309
+ symbol: token.toUpperCase(),
1310
+ fullName: token.toUpperCase(),
1311
+ };
1312
+ }
1101
1313
  const useTokenSelectionMetadataStore = create((set) => ({
1102
1314
  isPriceDataReady: false,
1103
1315
  isLoading: true,
@@ -1107,23 +1319,65 @@ const useTokenSelectionMetadataStore = create((set) => ({
1107
1319
  weightedRatio24h: 1,
1108
1320
  priceRatio: 1,
1109
1321
  priceRatio24h: 1,
1110
- openInterest: "0",
1111
- volume: "0",
1322
+ openInterest: '0',
1323
+ volume: '0',
1112
1324
  sumNetFunding: 0,
1113
1325
  maxLeverage: 0,
1114
1326
  minMargin: 0,
1115
1327
  leverageMatched: true,
1116
- recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens }) => {
1117
- const isPriceDataReady = !!(perpMetaAssets && finalAssetContexts && allMids);
1118
- // Compute metadata when ready
1119
- const longSymbols = longTokens.map((t) => t.symbol);
1120
- const shortSymbols = shortTokens.map((t) => t.symbol);
1121
- const longTokensMetadata = isPriceDataReady
1122
- ? TokenMetadataExtractor.extractMultipleTokensMetadata(longSymbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1328
+ recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens, }) => {
1329
+ const isPriceDataReady = !!(perpMetaAssets &&
1330
+ finalAssetContexts &&
1331
+ allMids);
1332
+ // Parse tokens - handle prefixed tokens like "xyz:GOOGL" by extracting the symbol and market prefix
1333
+ // The full name (xyz:GOOGL) is kept as the metadata key for UI consistency
1334
+ const parsedLongTokens = longTokens.map((t) => ({
1335
+ ...t,
1336
+ parsed: parseTokenWithPrefix(t.symbol),
1337
+ }));
1338
+ const parsedShortTokens = shortTokens.map((t) => ({
1339
+ ...t,
1340
+ parsed: parseTokenWithPrefix(t.symbol),
1341
+ }));
1342
+ // Extract base symbols with their market prefixes for SDK lookups
1343
+ // This ensures xyz:TSLA and flx:TSLA get different market data
1344
+ const longTokensForLookup = parsedLongTokens.map((t) => ({
1345
+ symbol: t.parsed.symbol,
1346
+ marketPrefix: t.parsed.prefix,
1347
+ }));
1348
+ const shortTokensForLookup = parsedShortTokens.map((t) => ({
1349
+ symbol: t.parsed.symbol,
1350
+ marketPrefix: t.parsed.prefix,
1351
+ }));
1352
+ // Also extract just the base symbols (without prefix) for lookups that don't support prefixes
1353
+ const longBaseSymbols = longTokensForLookup.map((t) => t.symbol);
1354
+ const shortBaseSymbols = shortTokensForLookup.map((t) => t.symbol);
1355
+ // Get metadata using base symbols with market prefix for proper market differentiation
1356
+ const longBaseMetadata = isPriceDataReady
1357
+ ? TokenMetadataExtractor.extractMultipleTokensMetadata(longTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1123
1358
  : {};
1124
- const shortTokensMetadata = isPriceDataReady
1125
- ? TokenMetadataExtractor.extractMultipleTokensMetadata(shortSymbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1359
+ const shortBaseMetadata = isPriceDataReady
1360
+ ? TokenMetadataExtractor.extractMultipleTokensMetadata(shortTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1126
1361
  : {};
1362
+ // Re-map metadata using original full names (with prefix) as keys for UI consistency
1363
+ // The extractor now keys by "{prefix}:{symbol}" for prefixed tokens, which matches our parsed.fullName
1364
+ const longTokensMetadata = {};
1365
+ parsedLongTokens.forEach((t) => {
1366
+ var _a;
1367
+ // Use the full name (e.g., "xyz:TSLA") as the lookup key since extractor uses the same format
1368
+ const lookupKey = t.parsed.prefix
1369
+ ? `${t.parsed.prefix}:${t.parsed.symbol}`
1370
+ : t.parsed.symbol;
1371
+ longTokensMetadata[t.symbol] = (_a = longBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
1372
+ });
1373
+ const shortTokensMetadata = {};
1374
+ parsedShortTokens.forEach((t) => {
1375
+ var _a;
1376
+ const lookupKey = t.parsed.prefix
1377
+ ? `${t.parsed.prefix}:${t.parsed.symbol}`
1378
+ : t.parsed.symbol;
1379
+ shortTokensMetadata[t.symbol] = (_a = shortBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
1380
+ });
1127
1381
  // Determine loading state
1128
1382
  const allTokens = [...longTokens, ...shortTokens];
1129
1383
  const isLoading = (() => {
@@ -1131,26 +1385,33 @@ const useTokenSelectionMetadataStore = create((set) => ({
1131
1385
  return true;
1132
1386
  if (allTokens.length === 0)
1133
1387
  return false;
1134
- const allMetadata = { ...longTokensMetadata, ...shortTokensMetadata };
1388
+ const allMetadata = {
1389
+ ...longTokensMetadata,
1390
+ ...shortTokensMetadata,
1391
+ };
1135
1392
  return allTokens.some((token) => !allMetadata[token.symbol]);
1136
1393
  })();
1137
1394
  // Open interest and volume (from market data for matching asset basket)
1395
+ // Use base symbols (without prefix) for matching against market data
1138
1396
  const { openInterest, volume } = (() => {
1139
- const empty = { openInterest: "0", volume: "0" };
1397
+ const empty = { openInterest: '0', volume: '0' };
1140
1398
  if (!(marketData === null || marketData === void 0 ? void 0 : marketData.active) || (!longTokens.length && !shortTokens.length))
1141
1399
  return empty;
1142
- const selectedLong = longTokens.map((t) => t.symbol).sort();
1143
- const selectedShort = shortTokens.map((t) => t.symbol).sort();
1400
+ const selectedLong = longBaseSymbols.slice().sort();
1401
+ const selectedShort = shortBaseSymbols.slice().sort();
1144
1402
  const match = marketData.active.find((item) => {
1145
1403
  const longs = [...item.longAssets].sort();
1146
1404
  const shorts = [...item.shortAssets].sort();
1147
- if (longs.length !== selectedLong.length || shorts.length !== selectedShort.length)
1405
+ if (longs.length !== selectedLong.length ||
1406
+ shorts.length !== selectedShort.length)
1148
1407
  return false;
1149
1408
  const longsEqual = longs.every((s, i) => s.asset === selectedLong[i]);
1150
1409
  const shortsEqual = shorts.every((s, i) => s.asset === selectedShort[i]);
1151
1410
  return longsEqual && shortsEqual;
1152
1411
  });
1153
- return match ? { openInterest: match.openInterest, volume: match.volume } : empty;
1412
+ return match
1413
+ ? { openInterest: match.openInterest, volume: match.volume }
1414
+ : empty;
1154
1415
  })();
1155
1416
  // Price ratio (only when exactly one long and one short)
1156
1417
  const { priceRatio, priceRatio24h } = (() => {
@@ -1230,17 +1491,27 @@ const useTokenSelectionMetadataStore = create((set) => ({
1230
1491
  return totalFunding;
1231
1492
  })();
1232
1493
  // Max leverage (minimum across all tokens)
1494
+ // Use tokens with their market prefixes for proper lookup in perpMetaAssets
1233
1495
  const maxLeverage = (() => {
1234
1496
  if (!perpMetaAssets)
1235
1497
  return 0;
1236
- const allTokenSymbols = [...longTokens, ...shortTokens].map((t) => t.symbol);
1237
- if (allTokenSymbols.length === 0)
1498
+ const allTokensForLookup = [
1499
+ ...longTokensForLookup,
1500
+ ...shortTokensForLookup,
1501
+ ];
1502
+ if (allTokensForLookup.length === 0)
1238
1503
  return 0;
1239
1504
  let minLev = Infinity;
1240
- allTokenSymbols.forEach((symbol) => {
1241
- const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol);
1242
- if (tokenUniverse === null || tokenUniverse === void 0 ? void 0 : tokenUniverse.maxLeverage)
1243
- minLev = Math.min(minLev, tokenUniverse.maxLeverage);
1505
+ allTokensForLookup.forEach(({ symbol, marketPrefix }) => {
1506
+ // Match by both name AND marketPrefix for HIP3 assets
1507
+ const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol &&
1508
+ (marketPrefix
1509
+ ? u.marketPrefix === marketPrefix
1510
+ : !u.marketPrefix));
1511
+ // Fallback to just matching by name if no exact match
1512
+ const fallbackUniverse = tokenUniverse || perpMetaAssets.find((u) => u.name === symbol);
1513
+ if (fallbackUniverse === null || fallbackUniverse === void 0 ? void 0 : fallbackUniverse.maxLeverage)
1514
+ minLev = Math.min(minLev, fallbackUniverse.maxLeverage);
1244
1515
  });
1245
1516
  return minLev === Infinity ? 0 : minLev;
1246
1517
  })();
@@ -1252,7 +1523,10 @@ const useTokenSelectionMetadataStore = create((set) => ({
1252
1523
  // Whether all tokens have matching leverage
1253
1524
  const leverageMatched = (() => {
1254
1525
  const allTokensArr = [...longTokens, ...shortTokens];
1255
- const allMetadata = { ...longTokensMetadata, ...shortTokensMetadata };
1526
+ const allMetadata = {
1527
+ ...longTokensMetadata,
1528
+ ...shortTokensMetadata,
1529
+ };
1256
1530
  if (allTokensArr.length === 0)
1257
1531
  return true;
1258
1532
  const tokensWithLev = allTokensArr.filter((token) => { var _a; return (_a = allMetadata[token.symbol]) === null || _a === void 0 ? void 0 : _a.leverage; });
@@ -5437,8 +5711,8 @@ function addAuthInterceptors(params) {
5437
5711
  /**
5438
5712
  * Fetch historical candle data from HyperLiquid API
5439
5713
  */
5440
- const fetchHistoricalCandles = async (coin, startTime, endTime, interval, displayToFull) => {
5441
- const backendCoin = toBackendSymbol(coin, displayToFull);
5714
+ const fetchHistoricalCandles = async (coin, startTime, endTime, interval, hip3Assets) => {
5715
+ const backendCoin = toBackendSymbol(coin, hip3Assets);
5442
5716
  const request = {
5443
5717
  req: { coin: backendCoin, startTime, endTime, interval },
5444
5718
  type: 'candleSnapshot',
@@ -5597,10 +5871,10 @@ const useHistoricalPriceData = () => {
5597
5871
  setTokenLoading(token.symbol, true);
5598
5872
  });
5599
5873
  try {
5600
- const displayToFull = useHyperliquidData.getState().hip3DisplayToFull;
5874
+ const hip3Assets = useHyperliquidData.getState().hip3Assets;
5601
5875
  const fetchPromises = tokensToFetch.map(async (token) => {
5602
5876
  try {
5603
- const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, displayToFull);
5877
+ const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, hip3Assets);
5604
5878
  addHistoricalPriceData(token.symbol, interval, response.data, { start: startTime, end: endTime });
5605
5879
  return { symbol: token.symbol, candles: response.data, success: true };
5606
5880
  }
@@ -6369,12 +6643,12 @@ function validatePositionSize(usdValue, longAssets, shortAssets) {
6369
6643
  * Authorization is derived from headers (Axios defaults or browser localStorage fallback)
6370
6644
  * @throws MinimumPositionSizeError if any asset has less than $11 USD value
6371
6645
  */
6372
- async function createPosition(baseUrl, payload, displayToFull) {
6646
+ async function createPosition(baseUrl, payload, hip3Assets) {
6373
6647
  // Validate minimum asset size before creating position
6374
6648
  validateMinimumAssetSize(payload.usdValue, payload.longAssets, payload.shortAssets);
6375
6649
  const url = joinUrl(baseUrl, "/positions");
6376
6650
  // Translate display symbols to backend format
6377
- const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
6651
+ const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
6378
6652
  const translatedPayload = {
6379
6653
  ...payload,
6380
6654
  longAssets: mapAssets(payload.longAssets),
@@ -6473,9 +6747,9 @@ async function adjustPosition(baseUrl, positionId, payload) {
6473
6747
  throw toApiError(error);
6474
6748
  }
6475
6749
  }
6476
- async function adjustAdvancePosition(baseUrl, positionId, payload, displayToFull) {
6750
+ async function adjustAdvancePosition(baseUrl, positionId, payload, hip3Assets) {
6477
6751
  const url = joinUrl(baseUrl, `/positions/${positionId}/adjust-advance`);
6478
- const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
6752
+ const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
6479
6753
  const translatedPayload = (payload || []).map((item) => ({
6480
6754
  longAssets: mapAssets(item.longAssets),
6481
6755
  shortAssets: mapAssets(item.shortAssets),
@@ -6600,16 +6874,62 @@ const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
6600
6874
  });
6601
6875
  };
6602
6876
 
6877
+ // Helper to find asset metadata from perpMetaAssets
6878
+ function findAssetMeta$1(coinName, perpMetaAssets) {
6879
+ var _a, _b, _c, _d;
6880
+ if (!perpMetaAssets) {
6881
+ return { collateralToken: 'USDC', marketPrefix: null };
6882
+ }
6883
+ // Try exact match first (for prefixed assets like "xyz:TSLA")
6884
+ const exactMatch = perpMetaAssets.find((a) => a.name === coinName);
6885
+ if (exactMatch) {
6886
+ return {
6887
+ collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
6888
+ marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
6889
+ };
6890
+ }
6891
+ // Try matching by base symbol (for non-prefixed names in data)
6892
+ const baseMatch = perpMetaAssets.find((a) => {
6893
+ const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
6894
+ return baseName === coinName;
6895
+ });
6896
+ if (baseMatch) {
6897
+ return {
6898
+ collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
6899
+ marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
6900
+ };
6901
+ }
6902
+ return { collateralToken: 'USDC', marketPrefix: null };
6903
+ }
6904
+ // Enrich position assets with market prefix and collateral token
6905
+ function enrichPositionAssets(assets, perpMetaAssets) {
6906
+ return assets.map((asset) => {
6907
+ const meta = findAssetMeta$1(asset.coin, perpMetaAssets);
6908
+ return {
6909
+ ...asset,
6910
+ marketPrefix: meta.marketPrefix,
6911
+ collateralToken: meta.collateralToken,
6912
+ };
6913
+ });
6914
+ }
6915
+ // Enrich all positions with market metadata
6916
+ function enrichPositions(positions, perpMetaAssets) {
6917
+ return positions.map((position) => ({
6918
+ ...position,
6919
+ longAssets: enrichPositionAssets(position.longAssets, perpMetaAssets),
6920
+ shortAssets: enrichPositionAssets(position.shortAssets, perpMetaAssets),
6921
+ }));
6922
+ }
6603
6923
  function usePosition() {
6604
6924
  const context = useContext(PearHyperliquidContext);
6605
6925
  if (!context) {
6606
6926
  throw new Error('usePosition must be used within a PearHyperliquidProvider');
6607
6927
  }
6608
6928
  const { apiBaseUrl, isConnected } = context;
6609
- const displayToFull = useHyperliquidData((s) => s.hip3DisplayToFull);
6929
+ const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
6610
6930
  // Create position API action
6611
6931
  const createPosition$1 = async (payload) => {
6612
- return createPosition(apiBaseUrl, payload, displayToFull);
6932
+ return createPosition(apiBaseUrl, payload, hip3Assets);
6613
6933
  };
6614
6934
  // Update TP/SL risk parameters for a position
6615
6935
  const updateRiskParameters$1 = async (positionId, payload) => {
@@ -6629,21 +6949,33 @@ function usePosition() {
6629
6949
  };
6630
6950
  // Adjust to absolute target sizes per asset, optionally adding new assets
6631
6951
  const adjustAdvancePosition$1 = async (positionId, payload) => {
6632
- return adjustAdvancePosition(apiBaseUrl, positionId, payload, displayToFull);
6952
+ return adjustAdvancePosition(apiBaseUrl, positionId, payload, hip3Assets);
6633
6953
  };
6634
6954
  // Open positions using WS data, with derived values
6635
6955
  const userOpenPositions = useUserData((state) => state.rawOpenPositions);
6636
6956
  const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
6637
6957
  const allMids = useHyperliquidData((state) => state.allMids);
6958
+ const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
6638
6959
  const isLoading = useMemo(() => {
6639
6960
  return userOpenPositions === null && isConnected;
6640
6961
  }, [userOpenPositions, isConnected]);
6641
6962
  const openPositions = useMemo(() => {
6642
6963
  if (!userOpenPositions || !aggregatedClearingHouseState || !allMids)
6643
6964
  return null;
6644
- return buildPositionValue(userOpenPositions, aggregatedClearingHouseState, allMids);
6645
- }, [userOpenPositions, aggregatedClearingHouseState, allMids]);
6646
- return { createPosition: createPosition$1, updateRiskParameters: updateRiskParameters$1, closePosition: closePosition$1, closeAllPositions: closeAllPositions$1, adjustPosition: adjustPosition$1, adjustAdvancePosition: adjustAdvancePosition$1, openPositions, isLoading };
6965
+ const positions = buildPositionValue(userOpenPositions, aggregatedClearingHouseState, allMids);
6966
+ // Enrich with market prefix and collateral token
6967
+ return enrichPositions(positions, perpMetaAssets);
6968
+ }, [userOpenPositions, aggregatedClearingHouseState, allMids, perpMetaAssets]);
6969
+ return {
6970
+ createPosition: createPosition$1,
6971
+ updateRiskParameters: updateRiskParameters$1,
6972
+ closePosition: closePosition$1,
6973
+ closeAllPositions: closeAllPositions$1,
6974
+ adjustPosition: adjustPosition$1,
6975
+ adjustAdvancePosition: adjustAdvancePosition$1,
6976
+ openPositions,
6977
+ isLoading,
6978
+ };
6647
6979
  }
6648
6980
 
6649
6981
  async function adjustOrder(baseUrl, orderId, payload) {
@@ -6815,59 +7147,170 @@ function useNotifications() {
6815
7147
  };
6816
7148
  }
6817
7149
 
6818
- // Base selector for the full market-data payload
7150
+ // Helper to find asset metadata from perpMetaAssets
7151
+ function findAssetMeta(assetName, perpMetaAssets) {
7152
+ var _a, _b, _c, _d;
7153
+ if (!perpMetaAssets) {
7154
+ return { collateralToken: 'USDC', marketPrefix: null };
7155
+ }
7156
+ // Try exact match first (for prefixed assets like "xyz:TSLA")
7157
+ const exactMatch = perpMetaAssets.find((a) => a.name === assetName);
7158
+ if (exactMatch) {
7159
+ return {
7160
+ collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
7161
+ marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
7162
+ };
7163
+ }
7164
+ // Try matching by base symbol (for non-prefixed names in data)
7165
+ const baseMatch = perpMetaAssets.find((a) => {
7166
+ const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
7167
+ return baseName === assetName;
7168
+ });
7169
+ if (baseMatch) {
7170
+ return {
7171
+ collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
7172
+ marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
7173
+ };
7174
+ }
7175
+ return { collateralToken: 'USDC', marketPrefix: null };
7176
+ }
7177
+ // Enrich a single asset with metadata
7178
+ function enrichAsset(asset, perpMetaAssets) {
7179
+ const meta = findAssetMeta(asset.asset, perpMetaAssets);
7180
+ return {
7181
+ ...asset,
7182
+ collateralToken: meta.collateralToken,
7183
+ marketPrefix: meta.marketPrefix,
7184
+ };
7185
+ }
7186
+ // Enrich a basket item with collateral info
7187
+ function enrichBasketItem(item, perpMetaAssets) {
7188
+ const enrichedLongs = item.longAssets.map((a) => enrichAsset(a, perpMetaAssets));
7189
+ const enrichedShorts = item.shortAssets.map((a) => enrichAsset(a, perpMetaAssets));
7190
+ // Determine collateral type
7191
+ const allAssets = [...enrichedLongs, ...enrichedShorts];
7192
+ const hasUsdc = allAssets.some((a) => a.collateralToken === 'USDC');
7193
+ const hasUsdh = allAssets.some((a) => a.collateralToken === 'USDH');
7194
+ let collateralType = 'USDC';
7195
+ if (hasUsdc && hasUsdh) {
7196
+ collateralType = 'MIXED';
7197
+ }
7198
+ else if (hasUsdh) {
7199
+ collateralType = 'USDH';
7200
+ }
7201
+ return {
7202
+ ...item,
7203
+ longAssets: enrichedLongs,
7204
+ shortAssets: enrichedShorts,
7205
+ collateralType,
7206
+ };
7207
+ }
7208
+ /**
7209
+ * Filter baskets by collateral type
7210
+ * - 'USDC': Only baskets where ALL assets use USDC (collateralType === 'USDC')
7211
+ * - 'USDH': Only baskets where ALL assets use USDH (collateralType === 'USDH')
7212
+ * - 'ALL' or undefined: No filtering, returns all baskets
7213
+ */
7214
+ function filterByCollateral(baskets, filter) {
7215
+ if (!filter || filter === 'ALL') {
7216
+ return baskets;
7217
+ }
7218
+ return baskets.filter((basket) => {
7219
+ if (filter === 'USDC') {
7220
+ // Include baskets that are purely USDC or have USDC assets
7221
+ return (basket.collateralType === 'USDC' || basket.collateralType === 'MIXED');
7222
+ }
7223
+ if (filter === 'USDH') {
7224
+ // Include baskets that are purely USDH or have USDH assets
7225
+ return (basket.collateralType === 'USDH' || basket.collateralType === 'MIXED');
7226
+ }
7227
+ return true;
7228
+ });
7229
+ }
7230
+ // Base selector for the full market-data payload (raw from WS)
6819
7231
  const useMarketDataPayload = () => {
6820
7232
  return useMarketData((s) => s.marketData);
6821
7233
  };
6822
- // Full payload for 'market-data-all' channel
7234
+ // Full payload for 'market-data-all' channel (raw from WS)
6823
7235
  const useMarketDataAllPayload = () => {
6824
7236
  return useMarketData((s) => s.marketDataAll);
6825
7237
  };
6826
- // Active baskets
6827
- const useActiveBaskets = () => {
6828
- var _a;
7238
+ // Access perpMetaAssets for enrichment
7239
+ const usePerpMetaAssets = () => {
7240
+ return useHyperliquidData((s) => s.perpMetaAssets);
7241
+ };
7242
+ // Active baskets (with collateral and market prefix info)
7243
+ const useActiveBaskets = (collateralFilter) => {
6829
7244
  const data = useMarketDataPayload();
6830
- return (_a = data === null || data === void 0 ? void 0 : data.active) !== null && _a !== void 0 ? _a : [];
7245
+ const perpMetaAssets = usePerpMetaAssets();
7246
+ return useMemo(() => {
7247
+ if (!(data === null || data === void 0 ? void 0 : data.active))
7248
+ return [];
7249
+ const enriched = data.active.map((item) => enrichBasketItem(item, perpMetaAssets));
7250
+ return filterByCollateral(enriched, collateralFilter);
7251
+ }, [data, perpMetaAssets, collateralFilter]);
6831
7252
  };
6832
- // Top gainers (optional limit override)
6833
- const useTopGainers = (limit) => {
7253
+ // Top gainers (with collateral and market prefix info)
7254
+ const useTopGainers = (limit, collateralFilter) => {
6834
7255
  const data = useMarketDataPayload();
7256
+ const perpMetaAssets = usePerpMetaAssets();
6835
7257
  return useMemo(() => {
6836
7258
  var _a;
6837
7259
  const list = (_a = data === null || data === void 0 ? void 0 : data.topGainers) !== null && _a !== void 0 ? _a : [];
6838
- return typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
6839
- }, [data, limit]);
7260
+ const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7261
+ const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
7262
+ return filterByCollateral(enriched, collateralFilter);
7263
+ }, [data, perpMetaAssets, limit, collateralFilter]);
6840
7264
  };
6841
- // Top losers (optional limit override)
6842
- const useTopLosers = (limit) => {
7265
+ // Top losers (with collateral and market prefix info)
7266
+ const useTopLosers = (limit, collateralFilter) => {
6843
7267
  const data = useMarketDataPayload();
7268
+ const perpMetaAssets = usePerpMetaAssets();
6844
7269
  return useMemo(() => {
6845
7270
  var _a;
6846
7271
  const list = (_a = data === null || data === void 0 ? void 0 : data.topLosers) !== null && _a !== void 0 ? _a : [];
6847
- return typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
6848
- }, [data, limit]);
7272
+ const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7273
+ const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
7274
+ return filterByCollateral(enriched, collateralFilter);
7275
+ }, [data, perpMetaAssets, limit, collateralFilter]);
6849
7276
  };
6850
- // Highlighted baskets
6851
- const useHighlightedBaskets = () => {
6852
- var _a;
7277
+ // Highlighted baskets (with collateral and market prefix info)
7278
+ const useHighlightedBaskets = (collateralFilter) => {
6853
7279
  const data = useMarketDataPayload();
6854
- return (_a = data === null || data === void 0 ? void 0 : data.highlighted) !== null && _a !== void 0 ? _a : [];
7280
+ const perpMetaAssets = usePerpMetaAssets();
7281
+ return useMemo(() => {
7282
+ if (!(data === null || data === void 0 ? void 0 : data.highlighted))
7283
+ return [];
7284
+ const enriched = data.highlighted.map((item) => enrichBasketItem(item, perpMetaAssets));
7285
+ return filterByCollateral(enriched, collateralFilter);
7286
+ }, [data, perpMetaAssets, collateralFilter]);
6855
7287
  };
6856
- // Watchlist baskets (from market-data payload when subscribed with address)
6857
- const useWatchlistBaskets = () => {
6858
- var _a;
7288
+ // Watchlist baskets (with collateral and market prefix info)
7289
+ const useWatchlistBaskets = (collateralFilter) => {
6859
7290
  const data = useMarketDataPayload();
6860
- return (_a = data === null || data === void 0 ? void 0 : data.watchlist) !== null && _a !== void 0 ? _a : [];
7291
+ const perpMetaAssets = usePerpMetaAssets();
7292
+ return useMemo(() => {
7293
+ if (!(data === null || data === void 0 ? void 0 : data.watchlist))
7294
+ return [];
7295
+ const enriched = data.watchlist.map((item) => enrichBasketItem(item, perpMetaAssets));
7296
+ return filterByCollateral(enriched, collateralFilter);
7297
+ }, [data, perpMetaAssets, collateralFilter]);
6861
7298
  };
6862
- // All baskets (from market-data-all)
6863
- const useAllBaskets = () => {
6864
- var _a;
7299
+ // All baskets (with collateral and market prefix info)
7300
+ const useAllBaskets = (collateralFilter) => {
6865
7301
  const dataAll = useMarketDataAllPayload();
6866
- return (_a = dataAll === null || dataAll === void 0 ? void 0 : dataAll.all) !== null && _a !== void 0 ? _a : [];
7302
+ const perpMetaAssets = usePerpMetaAssets();
7303
+ return useMemo(() => {
7304
+ if (!(dataAll === null || dataAll === void 0 ? void 0 : dataAll.all))
7305
+ return [];
7306
+ const enriched = dataAll.all.map((item) => enrichBasketItem(item, perpMetaAssets));
7307
+ return filterByCollateral(enriched, collateralFilter);
7308
+ }, [dataAll, perpMetaAssets, collateralFilter]);
6867
7309
  };
6868
7310
  // Find a basket by its exact asset composition (order-insensitive)
6869
7311
  const useFindBasket = (longs, shorts) => {
6870
7312
  const data = useMarketDataPayload();
7313
+ const perpMetaAssets = usePerpMetaAssets();
6871
7314
  return useMemo(() => {
6872
7315
  if (!data)
6873
7316
  return undefined;
@@ -6881,17 +7324,28 @@ const useFindBasket = (longs, shorts) => {
6881
7324
  : '';
6882
7325
  const lKey = normalize(longs);
6883
7326
  const sKey = normalize(shorts);
6884
- const match = (item) => normalize(item.longAssets) === lKey && normalize(item.shortAssets) === sKey;
6885
- return data.active.find(match) || data.highlighted.find(match);
6886
- }, [data, longs, shorts]);
7327
+ const match = (item) => normalize(item.longAssets) === lKey &&
7328
+ normalize(item.shortAssets) === sKey;
7329
+ const found = data.active.find(match) || data.highlighted.find(match);
7330
+ return found
7331
+ ? enrichBasketItem(found, perpMetaAssets)
7332
+ : undefined;
7333
+ }, [data, longs, shorts, perpMetaAssets]);
6887
7334
  };
6888
7335
 
6889
- async function toggleWatchlist(baseUrl, longAssets, shortAssets, displayToFull) {
7336
+ async function toggleWatchlist(baseUrl, longAssets, shortAssets, hip3Assets) {
6890
7337
  const url = joinUrl(baseUrl, '/watchlist');
6891
- const mapAssets = (arr) => arr.map(a => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
7338
+ const mapAssets = (arr) => arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
6892
7339
  try {
6893
- const response = await apiClient.post(url, { longAssets: mapAssets(longAssets), shortAssets: mapAssets(shortAssets) }, { headers: { 'Content-Type': 'application/json' } });
6894
- return { data: response.data, status: response.status, headers: response.headers };
7340
+ const response = await apiClient.post(url, {
7341
+ longAssets: mapAssets(longAssets),
7342
+ shortAssets: mapAssets(shortAssets),
7343
+ }, { headers: { 'Content-Type': 'application/json' } });
7344
+ return {
7345
+ data: response.data,
7346
+ status: response.status,
7347
+ headers: response.headers,
7348
+ };
6895
7349
  }
6896
7350
  catch (error) {
6897
7351
  throw toApiError(error);
@@ -6903,11 +7357,11 @@ function useWatchlist() {
6903
7357
  if (!context)
6904
7358
  throw new Error('useWatchlist must be used within a PearHyperliquidProvider');
6905
7359
  const { apiBaseUrl, isConnected } = context;
6906
- const displayToFull = useHyperliquidData((s) => s.hip3DisplayToFull);
7360
+ const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
6907
7361
  const marketData = useMarketDataPayload();
6908
7362
  const isLoading = useMemo(() => !marketData && isConnected, [marketData, isConnected]);
6909
7363
  const toggle = async (longAssets, shortAssets) => {
6910
- const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, displayToFull);
7364
+ const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, hip3Assets);
6911
7365
  // Server will push updated market-data over WS; nothing to set here
6912
7366
  return resp;
6913
7367
  };
@@ -7029,7 +7483,7 @@ async function logout(baseUrl, refreshTokenVal) {
7029
7483
  function useAuth() {
7030
7484
  const context = useContext(PearHyperliquidContext);
7031
7485
  if (!context) {
7032
- throw new Error("usePortfolio must be used within a PearHyperliquidProvider");
7486
+ throw new Error('usePortfolio must be used within a PearHyperliquidProvider');
7033
7487
  }
7034
7488
  const { apiBaseUrl, clientId } = context;
7035
7489
  const [isReady, setIsReady] = useState(false);
@@ -7039,46 +7493,26 @@ function useAuth() {
7039
7493
  const setRefreshToken = useUserData((s) => s.setRefreshToken);
7040
7494
  const isAuthenticated = useUserData((s) => s.isAuthenticated);
7041
7495
  const setIsAuthenticated = useUserData((s) => s.setIsAuthenticated);
7042
- const address = useUserData((s) => s.address);
7043
7496
  const setAddress = useUserData((s) => s.setAddress);
7044
7497
  useEffect(() => {
7045
- if (typeof window == "undefined") {
7498
+ if (typeof window == 'undefined') {
7046
7499
  return;
7047
7500
  }
7048
- // Get the current address from state if it exists
7049
- const currentAddress = address;
7050
- if (currentAddress) {
7051
- // If we already have an address in state, use it to load the session
7052
- const accessTokenKey = `${currentAddress}_accessToken`;
7053
- const refreshTokenKey = `${currentAddress}_refreshToken`;
7054
- const storedAccessToken = localStorage.getItem(accessTokenKey);
7055
- const storedRefreshToken = localStorage.getItem(refreshTokenKey);
7056
- if (storedAccessToken && storedRefreshToken) {
7057
- setAccessToken(storedAccessToken);
7058
- setRefreshToken(storedRefreshToken);
7059
- setIsAuthenticated(true);
7060
- setIsReady(true);
7061
- return;
7062
- }
7063
- }
7064
- }, [
7065
- setAccessToken,
7066
- setRefreshToken,
7067
- setIsAuthenticated,
7068
- setAddress,
7069
- address,
7070
- ]);
7501
+ const access = localStorage.getItem('accessToken');
7502
+ const refresh = localStorage.getItem('refreshToken');
7503
+ const addr = localStorage.getItem('address');
7504
+ setAccessToken(access);
7505
+ setRefreshToken(refresh);
7506
+ setAddress(addr);
7507
+ const authed = Boolean(access && addr);
7508
+ setIsAuthenticated(authed);
7509
+ setIsReady(true);
7510
+ }, [setAccessToken, setRefreshToken, setIsAuthenticated, setAddress]);
7071
7511
  useEffect(() => {
7072
7512
  const cleanup = addAuthInterceptors({
7073
7513
  apiBaseUrl,
7074
7514
  getAccessToken: () => {
7075
- if (typeof window === "undefined")
7076
- return null;
7077
- const currentAddress = useUserData.getState().address;
7078
- if (!currentAddress)
7079
- return null;
7080
- const accessTokenKey = `${currentAddress}_accessToken`;
7081
- return localStorage.getItem(accessTokenKey);
7515
+ return typeof window !== 'undefined' ? window.localStorage.getItem('accessToken') : null;
7082
7516
  },
7083
7517
  refreshTokens: async () => {
7084
7518
  const data = await refreshTokens();
@@ -7099,15 +7533,14 @@ function useAuth() {
7099
7533
  async function loginWithSignedMessage(address, signature, timestamp) {
7100
7534
  try {
7101
7535
  const { data } = await authenticate(apiBaseUrl, {
7102
- method: "eip712",
7536
+ method: 'eip712',
7103
7537
  address,
7104
7538
  clientId,
7105
7539
  details: { signature, timestamp },
7106
7540
  });
7107
- const accessTokenKey = `${address}_accessToken`;
7108
- const refreshTokenKey = `${address}_refreshToken`;
7109
- window.localStorage.setItem(accessTokenKey, data.accessToken);
7110
- window.localStorage.setItem(refreshTokenKey, data.refreshToken);
7541
+ window.localStorage.setItem('accessToken', data.accessToken);
7542
+ window.localStorage.setItem('refreshToken', data.refreshToken);
7543
+ window.localStorage.setItem('address', address);
7111
7544
  setAccessToken(data.accessToken);
7112
7545
  setRefreshToken(data.refreshToken);
7113
7546
  setAddress(address);
@@ -7119,16 +7552,10 @@ function useAuth() {
7119
7552
  }
7120
7553
  async function loginWithPrivyToken(address, appId, privyAccessToken) {
7121
7554
  try {
7122
- const { data } = await authenticateWithPrivy(apiBaseUrl, {
7123
- address,
7124
- clientId,
7125
- appId,
7126
- accessToken: privyAccessToken,
7127
- });
7128
- const accessTokenKey = `${address}_accessToken`;
7129
- const refreshTokenKey = `${address}_refreshToken`;
7130
- window.localStorage.setItem(accessTokenKey, data.accessToken);
7131
- window.localStorage.setItem(refreshTokenKey, data.refreshToken);
7555
+ const { data } = await authenticateWithPrivy(apiBaseUrl, { address, clientId, appId, accessToken: privyAccessToken });
7556
+ window.localStorage.setItem('accessToken', data.accessToken);
7557
+ window.localStorage.setItem('refreshToken', data.refreshToken);
7558
+ window.localStorage.setItem('address', address);
7132
7559
  setAccessToken(data.accessToken);
7133
7560
  setRefreshToken(data.refreshToken);
7134
7561
  setAddress(address);
@@ -7139,38 +7566,28 @@ function useAuth() {
7139
7566
  }
7140
7567
  }
7141
7568
  async function refreshTokens() {
7142
- const currentAddress = address;
7143
- const currentRefresh = refreshToken$1;
7144
- if (!currentRefresh || !currentAddress)
7145
- throw new Error("No refresh token");
7146
- const { data } = await refreshToken(apiBaseUrl, currentRefresh);
7147
- // Update tokens in localStorage
7148
- const accessTokenKey = `${currentAddress}_accessToken`;
7149
- const refreshTokenKey = `${currentAddress}_refreshToken`;
7150
- window.localStorage.setItem(accessTokenKey, data.accessToken);
7151
- window.localStorage.setItem(refreshTokenKey, data.refreshToken);
7569
+ const refresh = window.localStorage.getItem('refreshToken');
7570
+ if (!refresh)
7571
+ throw new Error('No refresh token');
7572
+ const { data } = await refreshToken(apiBaseUrl, refresh);
7573
+ window.localStorage.setItem('accessToken', data.accessToken);
7574
+ window.localStorage.setItem('refreshToken', data.refreshToken);
7152
7575
  setAccessToken(data.accessToken);
7153
7576
  setRefreshToken(data.refreshToken);
7154
7577
  setIsAuthenticated(true);
7155
7578
  return data;
7156
7579
  }
7157
7580
  async function logout$1() {
7158
- const currentAddress = address;
7159
- const currentRefresh = refreshToken$1;
7160
- if (currentRefresh) {
7581
+ const refresh = window.localStorage.getItem('refreshToken');
7582
+ if (refresh) {
7161
7583
  try {
7162
- await logout(apiBaseUrl, currentRefresh);
7584
+ await logout(apiBaseUrl, refresh);
7163
7585
  }
7164
- catch (_a) {
7165
- /* ignore */
7166
- }
7167
- }
7168
- if (currentAddress) {
7169
- const accessTokenKey = `${currentAddress}_accessToken`;
7170
- const refreshTokenKey = `${currentAddress}_refreshToken`;
7171
- window.localStorage.removeItem(accessTokenKey);
7172
- window.localStorage.removeItem(refreshTokenKey);
7586
+ catch ( /* ignore */_a) { /* ignore */ }
7173
7587
  }
7588
+ window.localStorage.removeItem('accessToken');
7589
+ window.localStorage.removeItem('refreshToken');
7590
+ window.localStorage.removeItem('address');
7174
7591
  setAccessToken(null);
7175
7592
  setRefreshToken(null);
7176
7593
  setAddress(null);
@@ -7193,12 +7610,13 @@ const PearHyperliquidContext = createContext(undefined);
7193
7610
  /**
7194
7611
  * React Provider for PearHyperliquidClient
7195
7612
  */
7196
- const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-v2.pearprotocol.io", clientId = "PEARPROTOCOLUI", wsUrl = "wss://hl-v2.pearprotocol.io/ws", }) => {
7613
+ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-v2.pearprotocol.io', clientId = 'PEARPROTOCOLUI', wsUrl = 'wss://hl-v2.pearprotocol.io/ws', }) => {
7197
7614
  const address = useUserData((s) => s.address);
7198
7615
  const setAddress = useUserData((s) => s.setAddress);
7199
7616
  const perpsMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
7200
7617
  const setPerpMetaAssets = useHyperliquidData((state) => state.setPerpMetaAssets);
7201
- const setHip3DisplayToFull = useHyperliquidData((state) => state.setHip3DisplayToFull);
7618
+ const setHip3Assets = useHyperliquidData((state) => state.setHip3Assets);
7619
+ const setHip3MarketPrefixes = useHyperliquidData((state) => state.setHip3MarketPrefixes);
7202
7620
  const websocketsEnabled = useMemo(() => Array.isArray(perpsMetaAssets) && perpsMetaAssets.length > 0, [perpsMetaAssets]);
7203
7621
  const { isConnected, lastError } = useHyperliquidWebSocket({
7204
7622
  wsUrl,
@@ -7213,28 +7631,62 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-v2.pearpro
7213
7631
  if (perpsMetaAssets === null) {
7214
7632
  fetchAllPerpMetas()
7215
7633
  .then((res) => {
7216
- const aggregatedPerpMetas = res.data.flatMap((item) => item.universe);
7217
- const hip3Map = new Map();
7218
- const displayToFull = new Map();
7219
- const cleanedPerpMetas = aggregatedPerpMetas.map((asset) => {
7220
- var _a;
7221
- const [maybePrefix, maybeMarket] = asset.name.split(":");
7222
- if (maybeMarket) {
7223
- const prefix = maybePrefix.toLowerCase();
7224
- const market = maybeMarket;
7225
- const existing = (_a = hip3Map.get(prefix)) !== null && _a !== void 0 ? _a : [];
7226
- hip3Map.set(prefix, [...existing, market]);
7227
- displayToFull.set(market, `${prefix}:${market}`);
7228
- return { ...asset, name: market };
7229
- }
7230
- return asset;
7634
+ // Maps for multi-market assets
7635
+ const assetToMarkets = new Map(); // TSLA -> ["xyz:TSLA", "flx:TSLA"]
7636
+ const marketPrefixes = new Map(); // "xyz:TSLA" -> "xyz"
7637
+ const cleanedPerpMetas = [];
7638
+ // Process each market group (different collateral tokens)
7639
+ res.data.forEach((item) => {
7640
+ // Convert numeric collateral token to human-readable name
7641
+ const collateralToken = item.collateralToken === 360 ? 'USDH' : 'USDC';
7642
+ item.universe.forEach((asset) => {
7643
+ var _a;
7644
+ const [maybePrefix, maybeMarket] = asset.name.split(':');
7645
+ if (maybeMarket) {
7646
+ // HIP-3 market with prefix (e.g., "xyz:TSLA")
7647
+ const prefix = maybePrefix.toLowerCase();
7648
+ const displayName = maybeMarket;
7649
+ const fullName = `${prefix}:${displayName}`;
7650
+ // Store full market name with prefix
7651
+ marketPrefixes.set(fullName, prefix);
7652
+ // Track all markets for this asset
7653
+ const existingMarkets = (_a = assetToMarkets.get(displayName)) !== null && _a !== void 0 ? _a : [];
7654
+ if (!existingMarkets.includes(fullName)) {
7655
+ assetToMarkets.set(displayName, [
7656
+ ...existingMarkets,
7657
+ fullName,
7658
+ ]);
7659
+ }
7660
+ // Add asset with all metadata INCLUDING collateral token for THIS specific market
7661
+ // Important: We keep ALL variants so each market+collateral combo is tracked
7662
+ cleanedPerpMetas.push({
7663
+ ...asset,
7664
+ name: displayName, // Use display name for UI
7665
+ marketPrefix: prefix, // Which market (xyz, flx, etc)
7666
+ collateralToken, // "USDC" or "USDH"
7667
+ });
7668
+ }
7669
+ else {
7670
+ // Regular market without prefix
7671
+ cleanedPerpMetas.push({
7672
+ ...asset,
7673
+ collateralToken, // "USDC" or "USDH"
7674
+ });
7675
+ }
7676
+ });
7231
7677
  });
7232
- setHip3DisplayToFull(displayToFull);
7678
+ setHip3Assets(assetToMarkets);
7679
+ setHip3MarketPrefixes(marketPrefixes);
7233
7680
  setPerpMetaAssets(cleanedPerpMetas);
7234
7681
  })
7235
7682
  .catch(() => { });
7236
7683
  }
7237
- }, [perpsMetaAssets, setPerpMetaAssets, setHip3DisplayToFull]);
7684
+ }, [
7685
+ perpsMetaAssets,
7686
+ setPerpMetaAssets,
7687
+ setHip3Assets,
7688
+ setHip3MarketPrefixes,
7689
+ ]);
7238
7690
  // Auth methods now sourced from useAuth hook
7239
7691
  useAutoSyncFills({
7240
7692
  baseUrl: apiBaseUrl,
@@ -7254,9 +7706,6 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-v2.pearpro
7254
7706
  // HyperLiquid native WebSocket state
7255
7707
  nativeIsConnected,
7256
7708
  nativeLastError,
7257
- // Address utilities
7258
- address,
7259
- setAddress,
7260
7709
  }), [
7261
7710
  apiBaseUrl,
7262
7711
  wsUrl,
@@ -7276,7 +7725,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-v2.pearpro
7276
7725
  function usePearHyperliquid() {
7277
7726
  const ctx = useContext(PearHyperliquidContext);
7278
7727
  if (!ctx)
7279
- throw new Error("usePearHyperliquid must be used within a PearHyperliquidProvider");
7728
+ throw new Error('usePearHyperliquid must be used within a PearHyperliquidProvider');
7280
7729
  return ctx;
7281
7730
  }
7282
7731
 
@@ -7367,4 +7816,4 @@ function mapCandleIntervalToTradingViewInterval(interval) {
7367
7816
  }
7368
7817
  }
7369
7818
 
7370
- export { AccountSummaryCalculator, ConflictDetector, MINIMUM_ASSET_USD_VALUE, MinimumPositionSizeError, PearHyperliquidProvider, TokenMetadataExtractor, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, getCompleteTimestamps, getPortfolio, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toggleWatchlist, 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, validateMinimumAssetSize, validatePositionSize };
7819
+ export { AccountSummaryCalculator, ConflictDetector, MINIMUM_ASSET_USD_VALUE, MinimumPositionSizeError, PearHyperliquidProvider, TokenMetadataExtractor, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, getAvailableMarkets, getCompleteTimestamps, getMarketPrefix, getPortfolio, isHip3Market, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toBackendSymbol, toBackendSymbolWithMarket, toDisplaySymbol, toggleWatchlist, updateRiskParameters, useAccountSummary, useActiveBaskets, useAgentWallet, useAllBaskets, useAuth, useAutoSyncFills, useBasketCandles, useFindBasket, useHighlightedBaskets, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMarketData, useMarketDataAllPayload, useMarketDataPayload, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePerpMetaAssets, usePortfolio, usePosition, useTokenSelectionMetadata, useTopGainers, useTopLosers, useTradeHistories, useTwap, useUserSelection, useWatchlist, useWatchlistBaskets, useWebData, validateMinimumAssetSize, validatePositionSize };