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

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
@@ -71,18 +71,71 @@ const useMarketData = create((set) => ({
71
71
  * Convert a full/prefixed symbol (e.g., "xyz:XYZ100") to a display symbol (e.g., "XYZ100").
72
72
  */
73
73
  function toDisplaySymbol(symbol) {
74
- const parts = symbol.split(":");
74
+ const parts = symbol.split(':');
75
75
  return parts.length > 1 ? parts.slice(-1)[0] : symbol;
76
76
  }
77
77
  /**
78
78
  * Convert a display symbol back to backend form using a provided map.
79
79
  * 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")
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"])
82
83
  */
83
- function toBackendSymbol(displaySymbol, displayToFull) {
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
96
+ */
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) {
84
120
  var _a;
85
- 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(':');
86
139
  }
87
140
 
88
141
  const useHyperliquidWebSocket = ({ wsUrl, address, enabled = true, }) => {
@@ -318,10 +371,11 @@ const useHyperliquidData = create((set, get) => ({
318
371
  finalAtOICaps: null,
319
372
  aggregatedClearingHouseState: null,
320
373
  perpMetaAssets: null,
321
- hip3DisplayToFull: new Map(),
374
+ hip3Assets: new Map(),
375
+ hip3MarketPrefixes: new Map(),
322
376
  setAllMids: (value) => set({ allMids: value }),
323
377
  setActiveAssetData: (value) => set((state) => ({
324
- activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value
378
+ activeAssetData: typeof value === 'function' ? value(state.activeAssetData) : value,
325
379
  })),
326
380
  deleteActiveAssetData: (key) => {
327
381
  set((state) => {
@@ -356,13 +410,14 @@ const useHyperliquidData = create((set, get) => ({
356
410
  activeAssetData: {
357
411
  ...state.activeAssetData,
358
412
  [key]: value,
359
- }
413
+ },
360
414
  })),
361
415
  setFinalAssetContexts: (value) => set({ finalAssetContexts: value }),
362
416
  setFinalAtOICaps: (value) => set({ finalAtOICaps: value }),
363
417
  setAggregatedClearingHouseState: (value) => set({ aggregatedClearingHouseState: value }),
364
418
  setPerpMetaAssets: (value) => set({ perpMetaAssets: value }),
365
- setHip3DisplayToFull: (value) => set({ hip3DisplayToFull: value })
419
+ setHip3Assets: (value) => set({ hip3Assets: value }),
420
+ setHip3MarketPrefixes: (value) => set({ hip3MarketPrefixes: value }),
366
421
  }));
367
422
 
368
423
  const DEFAULT_STATE = {
@@ -503,11 +558,11 @@ const useUserSelection$1 = create((set, get) => ({
503
558
  }));
504
559
 
505
560
  const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
506
- 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();
507
562
  const { candleInterval } = useUserSelection$1();
508
563
  const longTokens = useUserSelection$1((s) => s.longTokens);
509
564
  const shortTokens = useUserSelection$1((s) => s.shortTokens);
510
- const selectedTokenSymbols = useMemo(() => ([...longTokens, ...shortTokens].map((t) => t.symbol)), [longTokens, shortTokens]);
565
+ const selectedTokenSymbols = useMemo(() => [...longTokens, ...shortTokens].map((t) => t.symbol), [longTokens, shortTokens]);
511
566
  const [lastError, setLastError] = useState(null);
512
567
  const [subscribedAddress, setSubscribedAddress] = useState(null);
513
568
  const [subscribedTokens, setSubscribedTokens] = useState([]);
@@ -545,7 +600,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
545
600
  .map((dex) => dex.clearinghouseState)
546
601
  .filter(Boolean);
547
602
  const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || '0') || 0), 0);
548
- const toStr = (n) => (Number.isFinite(n) ? n.toString() : '0');
603
+ const toStr = (n) => Number.isFinite(n) ? n.toString() : '0';
549
604
  const assetPositions = states.flatMap((s) => s.assetPositions || []);
550
605
  const crossMaintenanceMarginUsed = toStr(sum(states.map((s) => s.crossMaintenanceMarginUsed)));
551
606
  const crossMarginSummary = {
@@ -578,17 +633,42 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
578
633
  case 'allMids':
579
634
  {
580
635
  const data = response.data;
581
- const remapped = {
582
- mids: Object.fromEntries(Object.entries(data.mids || {}).map(([k, v]) => [toDisplaySymbol(k), v]))
583
- };
584
- 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 });
585
662
  }
586
663
  break;
587
664
  case 'activeAssetData':
588
665
  {
589
666
  const assetData = response.data;
590
667
  const symbol = toDisplaySymbol(assetData.coin);
591
- const normalized = { ...assetData, coin: symbol };
668
+ const normalized = {
669
+ ...assetData,
670
+ coin: symbol,
671
+ };
592
672
  upsertActiveAssetData(symbol, normalized);
593
673
  }
594
674
  break;
@@ -610,7 +690,14 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
610
690
  console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', event.data);
611
691
  setLastError(errorMessage);
612
692
  }
613
- }, [setAllMids, upsertActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState]);
693
+ }, [
694
+ setAllMids,
695
+ upsertActiveAssetData,
696
+ addCandleData,
697
+ setFinalAssetContexts,
698
+ setFinalAtOICaps,
699
+ setAggregatedClearingHouseState,
700
+ ]);
614
701
  const connect = useCallback(() => {
615
702
  if (!enabled)
616
703
  return;
@@ -733,7 +820,13 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
733
820
  // clear aggregatedClearingHouseState
734
821
  setAggregatedClearingHouseState(null);
735
822
  }
736
- }, [isConnected, address, subscribedAddress, sendJsonMessage, setAggregatedClearingHouseState]);
823
+ }, [
824
+ isConnected,
825
+ address,
826
+ subscribedAddress,
827
+ sendJsonMessage,
828
+ setAggregatedClearingHouseState,
829
+ ]);
737
830
  // Handle token subscriptions for activeAssetData
738
831
  useEffect(() => {
739
832
  if (!isConnected || !address)
@@ -742,7 +835,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
742
835
  const tokensToSubscribe = effectiveTokens.filter((token) => token && !subscribedTokens.includes(token));
743
836
  const tokensToUnsubscribe = subscribedTokens.filter((token) => !effectiveTokens.includes(token));
744
837
  // Unsubscribe from tokens no longer in the list
745
- tokensToUnsubscribe.forEach(token => {
838
+ tokensToUnsubscribe.forEach((token) => {
746
839
  const unsubscribeMessage = {
747
840
  method: 'unsubscribe',
748
841
  subscription: {
@@ -754,7 +847,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
754
847
  sendJsonMessage(unsubscribeMessage);
755
848
  });
756
849
  // Subscribe to new tokens
757
- tokensToSubscribe.forEach(token => {
850
+ tokensToSubscribe.forEach((token) => {
758
851
  const subscribeMessage = {
759
852
  method: 'subscribe',
760
853
  subscription: {
@@ -769,7 +862,14 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
769
862
  setSubscribedTokens(effectiveTokens.filter((token) => token));
770
863
  tokensToSubscribe.forEach((token) => deleteActiveAssetData(token));
771
864
  }
772
- }, [isConnected, address, selectedTokenSymbols, subscribedTokens, sendJsonMessage, setActiveAssetData]);
865
+ }, [
866
+ isConnected,
867
+ address,
868
+ selectedTokenSymbols,
869
+ subscribedTokens,
870
+ sendJsonMessage,
871
+ setActiveAssetData,
872
+ ]);
773
873
  // Handle candle subscriptions for tokens and interval changes
774
874
  useEffect(() => {
775
875
  if (!isConnected)
@@ -778,7 +878,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
778
878
  // Unsubscribe from previous candle subscriptions if interval changed
779
879
  const prevInterval = prevCandleIntervalRef.current;
780
880
  if (prevInterval && prevInterval !== candleInterval) {
781
- subscribedCandleTokens.forEach(token => {
881
+ subscribedCandleTokens.forEach((token) => {
782
882
  const unsubscribeMessage = {
783
883
  method: 'unsubscribe',
784
884
  subscription: {
@@ -819,12 +919,21 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, }) => {
819
919
  sendJsonMessage(subscribeMessage);
820
920
  });
821
921
  // Update subscribed state
822
- if (tokensToSubscribe.length > 0 || tokensToUnsubscribe.length > 0 || prevInterval !== candleInterval) {
922
+ if (tokensToSubscribe.length > 0 ||
923
+ tokensToUnsubscribe.length > 0 ||
924
+ prevInterval !== candleInterval) {
823
925
  setSubscribedCandleTokens(effectiveTokens.filter((token) => token));
824
926
  prevCandleIntervalRef.current = candleInterval;
825
927
  tokensToUnsubscribe.forEach((token) => deleteCandleSymbol(token));
826
928
  }
827
- }, [isConnected, selectedTokenSymbols, candleInterval, subscribedCandleTokens, sendJsonMessage, setCandleData]);
929
+ }, [
930
+ isConnected,
931
+ selectedTokenSymbols,
932
+ candleInterval,
933
+ subscribedCandleTokens,
934
+ sendJsonMessage,
935
+ setCandleData,
936
+ ]);
828
937
  return {
829
938
  isConnected,
830
939
  lastError,
@@ -946,21 +1055,51 @@ const useWebData = () => {
946
1055
  const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
947
1056
  const aggregatedClearinghouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
948
1057
  const finalAtOICaps = useHyperliquidData((state) => state.finalAtOICaps);
949
- const hip3Assets = useHyperliquidData((state) => state.hip3DisplayToFull);
1058
+ const hip3Assets = useHyperliquidData((state) => state.hip3Assets);
1059
+ const hip3MarketPrefixes = useHyperliquidData((state) => state.hip3MarketPrefixes);
950
1060
  let marketDataBySymbol = {};
951
1061
  if (finalAssetContexts && perpMetaAssets) {
952
1062
  const result = {};
1063
+ // Build a map of display name -> asset context index (for unique display names)
1064
+ const displayNameToContextIndex = new Map();
1065
+ const seenNames = new Set();
1066
+ let contextIndex = 0;
1067
+ // First pass: map unique display names to their context index
953
1068
  for (let index = 0; index < perpMetaAssets.length; index++) {
954
1069
  const name = perpMetaAssets[index].name;
955
- result[name] = {
956
- asset: finalAssetContexts[index],
957
- universe: perpMetaAssets[index],
958
- };
1070
+ if (!seenNames.has(name)) {
1071
+ seenNames.add(name);
1072
+ if (contextIndex < finalAssetContexts.length) {
1073
+ displayNameToContextIndex.set(name, contextIndex);
1074
+ contextIndex++;
1075
+ }
1076
+ }
1077
+ }
1078
+ // Second pass: create nested entries for all market variants
1079
+ for (let index = 0; index < perpMetaAssets.length; index++) {
1080
+ const universeAsset = perpMetaAssets[index];
1081
+ const displayName = universeAsset.name;
1082
+ const marketPrefix = universeAsset.marketPrefix;
1083
+ const ctxIndex = displayNameToContextIndex.get(displayName);
1084
+ if (ctxIndex !== undefined) {
1085
+ const assetContext = finalAssetContexts[ctxIndex];
1086
+ // Initialize the symbol entry if it doesn't exist
1087
+ if (!result[displayName]) {
1088
+ result[displayName] = {};
1089
+ }
1090
+ // Use marketPrefix as key for HIP-3 assets, "default" for regular assets
1091
+ const variantKey = marketPrefix || 'default';
1092
+ result[displayName][variantKey] = {
1093
+ asset: assetContext,
1094
+ universe: universeAsset,
1095
+ };
1096
+ }
959
1097
  }
960
1098
  marketDataBySymbol = result;
961
1099
  }
962
1100
  return {
963
1101
  hip3Assets,
1102
+ hip3MarketPrefixes,
964
1103
  clearinghouseState: aggregatedClearinghouseState,
965
1104
  perpsAtOpenInterestCap: finalAtOICaps,
966
1105
  marketDataBySymbol,
@@ -975,30 +1114,60 @@ const useWebData = () => {
975
1114
  class TokenMetadataExtractor {
976
1115
  /**
977
1116
  * Extracts comprehensive token metadata
978
- * @param symbol - Token symbol
1117
+ * @param symbol - Token symbol (base symbol without prefix, e.g., "TSLA")
979
1118
  * @param perpMetaAssets - Aggregated universe assets (flattened across dexes)
980
1119
  * @param finalAssetContexts - Aggregated asset contexts (flattened across dexes)
981
1120
  * @param allMids - AllMids data containing current prices
982
1121
  * @param activeAssetData - Optional active asset data containing leverage information
1122
+ * @param marketPrefix - Optional market prefix (e.g., "xyz", "flx") for HIP3 multi-market assets
983
1123
  * @returns TokenMetadata or null if token not found
984
1124
  */
985
- static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
1125
+ static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketPrefix) {
986
1126
  if (!perpMetaAssets || !finalAssetContexts || !allMids) {
987
1127
  return null;
988
1128
  }
989
1129
  // Find token index in aggregated universe
990
- const universeIndex = perpMetaAssets.findIndex(asset => asset.name === symbol);
991
- if (universeIndex === -1) {
1130
+ // For HIP3 assets, match both name AND marketPrefix
1131
+ const universeIndex = perpMetaAssets.findIndex((asset) => {
1132
+ if (asset.name !== symbol)
1133
+ return false;
1134
+ // If marketPrefix is specified, match it; otherwise match assets without prefix
1135
+ if (marketPrefix) {
1136
+ return asset.marketPrefix === marketPrefix;
1137
+ }
1138
+ // No prefix specified - match non-HIP3 asset (no marketPrefix) or first matching asset
1139
+ return !asset.marketPrefix;
1140
+ });
1141
+ // If no exact match found and prefix was specified, try finding the specific market variant
1142
+ const finalIndex = universeIndex === -1 && marketPrefix
1143
+ ? perpMetaAssets.findIndex((asset) => asset.name === symbol && asset.marketPrefix === marketPrefix)
1144
+ : universeIndex;
1145
+ // Fallback: if still not found and no prefix, find first matching by name (for backward compatibility)
1146
+ const resolvedIndex = finalIndex === -1
1147
+ ? perpMetaAssets.findIndex((asset) => asset.name === symbol)
1148
+ : finalIndex;
1149
+ if (resolvedIndex === -1) {
992
1150
  return null;
993
1151
  }
994
- const universeAsset = perpMetaAssets[universeIndex];
995
- const assetCtx = finalAssetContexts[universeIndex];
1152
+ const universeAsset = perpMetaAssets[resolvedIndex];
1153
+ const assetCtx = finalAssetContexts[resolvedIndex];
996
1154
  if (!assetCtx) {
997
1155
  return null;
998
1156
  }
999
- // Get current price from allMids
1000
- const currentPriceStr = allMids.mids[symbol];
1001
- const currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
1157
+ // Get current price - prefer assetCtx.midPx as it's already index-matched,
1158
+ // fall back to allMids lookup if midPx is null
1159
+ const prefixedKeyColon = marketPrefix ? `${marketPrefix}:${symbol}` : null;
1160
+ let currentPrice = 0;
1161
+ // Primary source: assetCtx.midPx (already properly indexed)
1162
+ if (assetCtx.midPx) {
1163
+ currentPrice = parseFloat(assetCtx.midPx);
1164
+ }
1165
+ // Fallback: allMids lookup with multiple key formats for HIP3 markets
1166
+ if (!currentPrice || isNaN(currentPrice)) {
1167
+ const currentPriceStr = (prefixedKeyColon && allMids.mids[prefixedKeyColon]) ||
1168
+ allMids.mids[symbol];
1169
+ currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
1170
+ }
1002
1171
  // Get previous day price
1003
1172
  const prevDayPrice = parseFloat(assetCtx.prevDayPx);
1004
1173
  // Calculate 24h price change
@@ -1009,7 +1178,11 @@ class TokenMetadataExtractor {
1009
1178
  const markPrice = parseFloat(assetCtx.markPx);
1010
1179
  const oraclePrice = parseFloat(assetCtx.oraclePx);
1011
1180
  // Extract leverage info from activeAssetData if available
1012
- const tokenActiveData = activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[symbol];
1181
+ // Try prefixed key first (e.g., "xyz:TSLA"), then fall back to plain symbol
1182
+ const activeDataKey = prefixedKeyColon && (activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[prefixedKeyColon])
1183
+ ? prefixedKeyColon
1184
+ : symbol;
1185
+ const tokenActiveData = activeAssetData === null || activeAssetData === void 0 ? void 0 : activeAssetData[activeDataKey];
1013
1186
  const leverage = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.leverage;
1014
1187
  const maxTradeSzs = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.maxTradeSzs;
1015
1188
  const availableToTrade = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.availableToTrade;
@@ -1027,21 +1200,27 @@ class TokenMetadataExtractor {
1027
1200
  leverage,
1028
1201
  maxTradeSzs,
1029
1202
  availableToTrade,
1203
+ collateralToken: universeAsset.collateralToken,
1030
1204
  };
1031
1205
  }
1032
1206
  /**
1033
1207
  * Extracts metadata for multiple tokens
1034
- * @param symbols - Array of token symbols
1208
+ * @param tokens - Array of token objects with symbol and optional marketPrefix
1035
1209
  * @param perpMetaAssets - Aggregated universe assets
1036
1210
  * @param finalAssetContexts - Aggregated asset contexts
1037
1211
  * @param allMids - AllMids data
1038
1212
  * @param activeAssetData - Optional active asset data containing leverage information
1039
- * @returns Record of symbol to TokenMetadata
1213
+ * @returns Record of unique key to TokenMetadata. Key is "{prefix}:{symbol}" for HIP3 assets, or just "{symbol}" otherwise
1040
1214
  */
1041
- static extractMultipleTokensMetadata(symbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
1215
+ static extractMultipleTokensMetadata(tokens, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
1042
1216
  const result = {};
1043
- for (const symbol of symbols) {
1044
- result[symbol] = this.extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData);
1217
+ for (const token of tokens) {
1218
+ // Use a unique key that includes the prefix for HIP3 assets
1219
+ // This ensures xyz:TSLA and flx:TSLA get separate entries
1220
+ const resultKey = token.marketPrefix
1221
+ ? `${token.marketPrefix}:${token.symbol}`
1222
+ : token.symbol;
1223
+ result[resultKey] = this.extractTokenMetadata(token.symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, token.marketPrefix);
1045
1224
  }
1046
1225
  return result;
1047
1226
  }
@@ -1054,10 +1233,30 @@ class TokenMetadataExtractor {
1054
1233
  static isTokenAvailable(symbol, perpMetaAssets) {
1055
1234
  if (!perpMetaAssets)
1056
1235
  return false;
1057
- return perpMetaAssets.some(asset => asset.name === symbol);
1236
+ return perpMetaAssets.some((asset) => asset.name === symbol);
1058
1237
  }
1059
1238
  }
1060
1239
 
1240
+ /**
1241
+ * Parse a token string that may have a market prefix (e.g., "xyz:GOOGL" -> { prefix: "xyz", symbol: "GOOGL" })
1242
+ * This allows us to keep the full name (xyz:GOOGL) for URLs/tags while extracting just the symbol for SDK lookups.
1243
+ */
1244
+ function parseTokenWithPrefix(token) {
1245
+ if (token.includes(':')) {
1246
+ const [prefix, ...rest] = token.split(':');
1247
+ const symbol = rest.join(':').toUpperCase();
1248
+ return {
1249
+ prefix: prefix.toLowerCase(),
1250
+ symbol,
1251
+ fullName: `${prefix.toLowerCase()}:${symbol}`,
1252
+ };
1253
+ }
1254
+ return {
1255
+ prefix: null,
1256
+ symbol: token.toUpperCase(),
1257
+ fullName: token.toUpperCase(),
1258
+ };
1259
+ }
1061
1260
  const useTokenSelectionMetadataStore = create((set) => ({
1062
1261
  isPriceDataReady: false,
1063
1262
  isLoading: true,
@@ -1067,23 +1266,65 @@ const useTokenSelectionMetadataStore = create((set) => ({
1067
1266
  weightedRatio24h: 1,
1068
1267
  priceRatio: 1,
1069
1268
  priceRatio24h: 1,
1070
- openInterest: "0",
1071
- volume: "0",
1269
+ openInterest: '0',
1270
+ volume: '0',
1072
1271
  sumNetFunding: 0,
1073
1272
  maxLeverage: 0,
1074
1273
  minMargin: 0,
1075
1274
  leverageMatched: true,
1076
- recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens }) => {
1077
- const isPriceDataReady = !!(perpMetaAssets && finalAssetContexts && allMids);
1078
- // Compute metadata when ready
1079
- const longSymbols = longTokens.map((t) => t.symbol);
1080
- const shortSymbols = shortTokens.map((t) => t.symbol);
1081
- const longTokensMetadata = isPriceDataReady
1082
- ? TokenMetadataExtractor.extractMultipleTokensMetadata(longSymbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1275
+ recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens, }) => {
1276
+ const isPriceDataReady = !!(perpMetaAssets &&
1277
+ finalAssetContexts &&
1278
+ allMids);
1279
+ // Parse tokens - handle prefixed tokens like "xyz:GOOGL" by extracting the symbol and market prefix
1280
+ // The full name (xyz:GOOGL) is kept as the metadata key for UI consistency
1281
+ const parsedLongTokens = longTokens.map((t) => ({
1282
+ ...t,
1283
+ parsed: parseTokenWithPrefix(t.symbol),
1284
+ }));
1285
+ const parsedShortTokens = shortTokens.map((t) => ({
1286
+ ...t,
1287
+ parsed: parseTokenWithPrefix(t.symbol),
1288
+ }));
1289
+ // Extract base symbols with their market prefixes for SDK lookups
1290
+ // This ensures xyz:TSLA and flx:TSLA get different market data
1291
+ const longTokensForLookup = parsedLongTokens.map((t) => ({
1292
+ symbol: t.parsed.symbol,
1293
+ marketPrefix: t.parsed.prefix,
1294
+ }));
1295
+ const shortTokensForLookup = parsedShortTokens.map((t) => ({
1296
+ symbol: t.parsed.symbol,
1297
+ marketPrefix: t.parsed.prefix,
1298
+ }));
1299
+ // Also extract just the base symbols (without prefix) for lookups that don't support prefixes
1300
+ const longBaseSymbols = longTokensForLookup.map((t) => t.symbol);
1301
+ const shortBaseSymbols = shortTokensForLookup.map((t) => t.symbol);
1302
+ // Get metadata using base symbols with market prefix for proper market differentiation
1303
+ const longBaseMetadata = isPriceDataReady
1304
+ ? TokenMetadataExtractor.extractMultipleTokensMetadata(longTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1083
1305
  : {};
1084
- const shortTokensMetadata = isPriceDataReady
1085
- ? TokenMetadataExtractor.extractMultipleTokensMetadata(shortSymbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1306
+ const shortBaseMetadata = isPriceDataReady
1307
+ ? TokenMetadataExtractor.extractMultipleTokensMetadata(shortTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
1086
1308
  : {};
1309
+ // Re-map metadata using original full names (with prefix) as keys for UI consistency
1310
+ // The extractor now keys by "{prefix}:{symbol}" for prefixed tokens, which matches our parsed.fullName
1311
+ const longTokensMetadata = {};
1312
+ parsedLongTokens.forEach((t) => {
1313
+ var _a;
1314
+ // Use the full name (e.g., "xyz:TSLA") as the lookup key since extractor uses the same format
1315
+ const lookupKey = t.parsed.prefix
1316
+ ? `${t.parsed.prefix}:${t.parsed.symbol}`
1317
+ : t.parsed.symbol;
1318
+ longTokensMetadata[t.symbol] = (_a = longBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
1319
+ });
1320
+ const shortTokensMetadata = {};
1321
+ parsedShortTokens.forEach((t) => {
1322
+ var _a;
1323
+ const lookupKey = t.parsed.prefix
1324
+ ? `${t.parsed.prefix}:${t.parsed.symbol}`
1325
+ : t.parsed.symbol;
1326
+ shortTokensMetadata[t.symbol] = (_a = shortBaseMetadata[lookupKey]) !== null && _a !== void 0 ? _a : null;
1327
+ });
1087
1328
  // Determine loading state
1088
1329
  const allTokens = [...longTokens, ...shortTokens];
1089
1330
  const isLoading = (() => {
@@ -1091,26 +1332,33 @@ const useTokenSelectionMetadataStore = create((set) => ({
1091
1332
  return true;
1092
1333
  if (allTokens.length === 0)
1093
1334
  return false;
1094
- const allMetadata = { ...longTokensMetadata, ...shortTokensMetadata };
1335
+ const allMetadata = {
1336
+ ...longTokensMetadata,
1337
+ ...shortTokensMetadata,
1338
+ };
1095
1339
  return allTokens.some((token) => !allMetadata[token.symbol]);
1096
1340
  })();
1097
1341
  // Open interest and volume (from market data for matching asset basket)
1342
+ // Use base symbols (without prefix) for matching against market data
1098
1343
  const { openInterest, volume } = (() => {
1099
- const empty = { openInterest: "0", volume: "0" };
1344
+ const empty = { openInterest: '0', volume: '0' };
1100
1345
  if (!(marketData === null || marketData === void 0 ? void 0 : marketData.active) || (!longTokens.length && !shortTokens.length))
1101
1346
  return empty;
1102
- const selectedLong = longTokens.map((t) => t.symbol).sort();
1103
- const selectedShort = shortTokens.map((t) => t.symbol).sort();
1347
+ const selectedLong = longBaseSymbols.slice().sort();
1348
+ const selectedShort = shortBaseSymbols.slice().sort();
1104
1349
  const match = marketData.active.find((item) => {
1105
1350
  const longs = [...item.longAssets].sort();
1106
1351
  const shorts = [...item.shortAssets].sort();
1107
- if (longs.length !== selectedLong.length || shorts.length !== selectedShort.length)
1352
+ if (longs.length !== selectedLong.length ||
1353
+ shorts.length !== selectedShort.length)
1108
1354
  return false;
1109
1355
  const longsEqual = longs.every((s, i) => s.asset === selectedLong[i]);
1110
1356
  const shortsEqual = shorts.every((s, i) => s.asset === selectedShort[i]);
1111
1357
  return longsEqual && shortsEqual;
1112
1358
  });
1113
- return match ? { openInterest: match.openInterest, volume: match.volume } : empty;
1359
+ return match
1360
+ ? { openInterest: match.openInterest, volume: match.volume }
1361
+ : empty;
1114
1362
  })();
1115
1363
  // Price ratio (only when exactly one long and one short)
1116
1364
  const { priceRatio, priceRatio24h } = (() => {
@@ -1190,17 +1438,27 @@ const useTokenSelectionMetadataStore = create((set) => ({
1190
1438
  return totalFunding;
1191
1439
  })();
1192
1440
  // Max leverage (minimum across all tokens)
1441
+ // Use tokens with their market prefixes for proper lookup in perpMetaAssets
1193
1442
  const maxLeverage = (() => {
1194
1443
  if (!perpMetaAssets)
1195
1444
  return 0;
1196
- const allTokenSymbols = [...longTokens, ...shortTokens].map((t) => t.symbol);
1197
- if (allTokenSymbols.length === 0)
1445
+ const allTokensForLookup = [
1446
+ ...longTokensForLookup,
1447
+ ...shortTokensForLookup,
1448
+ ];
1449
+ if (allTokensForLookup.length === 0)
1198
1450
  return 0;
1199
1451
  let minLev = Infinity;
1200
- allTokenSymbols.forEach((symbol) => {
1201
- const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol);
1202
- if (tokenUniverse === null || tokenUniverse === void 0 ? void 0 : tokenUniverse.maxLeverage)
1203
- minLev = Math.min(minLev, tokenUniverse.maxLeverage);
1452
+ allTokensForLookup.forEach(({ symbol, marketPrefix }) => {
1453
+ // Match by both name AND marketPrefix for HIP3 assets
1454
+ const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol &&
1455
+ (marketPrefix
1456
+ ? u.marketPrefix === marketPrefix
1457
+ : !u.marketPrefix));
1458
+ // Fallback to just matching by name if no exact match
1459
+ const fallbackUniverse = tokenUniverse || perpMetaAssets.find((u) => u.name === symbol);
1460
+ if (fallbackUniverse === null || fallbackUniverse === void 0 ? void 0 : fallbackUniverse.maxLeverage)
1461
+ minLev = Math.min(minLev, fallbackUniverse.maxLeverage);
1204
1462
  });
1205
1463
  return minLev === Infinity ? 0 : minLev;
1206
1464
  })();
@@ -1212,7 +1470,10 @@ const useTokenSelectionMetadataStore = create((set) => ({
1212
1470
  // Whether all tokens have matching leverage
1213
1471
  const leverageMatched = (() => {
1214
1472
  const allTokensArr = [...longTokens, ...shortTokens];
1215
- const allMetadata = { ...longTokensMetadata, ...shortTokensMetadata };
1473
+ const allMetadata = {
1474
+ ...longTokensMetadata,
1475
+ ...shortTokensMetadata,
1476
+ };
1216
1477
  if (allTokensArr.length === 0)
1217
1478
  return true;
1218
1479
  const tokensWithLev = allTokensArr.filter((token) => { var _a; return (_a = allMetadata[token.symbol]) === null || _a === void 0 ? void 0 : _a.leverage; });
@@ -5397,8 +5658,8 @@ function addAuthInterceptors(params) {
5397
5658
  /**
5398
5659
  * Fetch historical candle data from HyperLiquid API
5399
5660
  */
5400
- const fetchHistoricalCandles = async (coin, startTime, endTime, interval, displayToFull) => {
5401
- const backendCoin = toBackendSymbol(coin, displayToFull);
5661
+ const fetchHistoricalCandles = async (coin, startTime, endTime, interval, hip3Assets) => {
5662
+ const backendCoin = toBackendSymbol(coin, hip3Assets);
5402
5663
  const request = {
5403
5664
  req: { coin: backendCoin, startTime, endTime, interval },
5404
5665
  type: 'candleSnapshot',
@@ -5557,10 +5818,10 @@ const useHistoricalPriceData = () => {
5557
5818
  setTokenLoading(token.symbol, true);
5558
5819
  });
5559
5820
  try {
5560
- const displayToFull = useHyperliquidData.getState().hip3DisplayToFull;
5821
+ const hip3Assets = useHyperliquidData.getState().hip3Assets;
5561
5822
  const fetchPromises = tokensToFetch.map(async (token) => {
5562
5823
  try {
5563
- const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, displayToFull);
5824
+ const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, hip3Assets);
5564
5825
  addHistoricalPriceData(token.symbol, interval, response.data, { start: startTime, end: endTime });
5565
5826
  return { symbol: token.symbol, candles: response.data, success: true };
5566
5827
  }
@@ -6329,12 +6590,12 @@ function validatePositionSize(usdValue, longAssets, shortAssets) {
6329
6590
  * Authorization is derived from headers (Axios defaults or browser localStorage fallback)
6330
6591
  * @throws MinimumPositionSizeError if any asset has less than $11 USD value
6331
6592
  */
6332
- async function createPosition(baseUrl, payload, displayToFull) {
6593
+ async function createPosition(baseUrl, payload, hip3Assets) {
6333
6594
  // Validate minimum asset size before creating position
6334
6595
  validateMinimumAssetSize(payload.usdValue, payload.longAssets, payload.shortAssets);
6335
6596
  const url = joinUrl(baseUrl, "/positions");
6336
6597
  // Translate display symbols to backend format
6337
- const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
6598
+ const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
6338
6599
  const translatedPayload = {
6339
6600
  ...payload,
6340
6601
  longAssets: mapAssets(payload.longAssets),
@@ -6433,9 +6694,9 @@ async function adjustPosition(baseUrl, positionId, payload) {
6433
6694
  throw toApiError(error);
6434
6695
  }
6435
6696
  }
6436
- async function adjustAdvancePosition(baseUrl, positionId, payload, displayToFull) {
6697
+ async function adjustAdvancePosition(baseUrl, positionId, payload, hip3Assets) {
6437
6698
  const url = joinUrl(baseUrl, `/positions/${positionId}/adjust-advance`);
6438
- const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
6699
+ const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
6439
6700
  const translatedPayload = (payload || []).map((item) => ({
6440
6701
  longAssets: mapAssets(item.longAssets),
6441
6702
  shortAssets: mapAssets(item.shortAssets),
@@ -6560,16 +6821,62 @@ const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
6560
6821
  });
6561
6822
  };
6562
6823
 
6824
+ // Helper to find asset metadata from perpMetaAssets
6825
+ function findAssetMeta$1(coinName, perpMetaAssets) {
6826
+ var _a, _b, _c, _d;
6827
+ if (!perpMetaAssets) {
6828
+ return { collateralToken: 'USDC', marketPrefix: null };
6829
+ }
6830
+ // Try exact match first (for prefixed assets like "xyz:TSLA")
6831
+ const exactMatch = perpMetaAssets.find((a) => a.name === coinName);
6832
+ if (exactMatch) {
6833
+ return {
6834
+ collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
6835
+ marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
6836
+ };
6837
+ }
6838
+ // Try matching by base symbol (for non-prefixed names in data)
6839
+ const baseMatch = perpMetaAssets.find((a) => {
6840
+ const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
6841
+ return baseName === coinName;
6842
+ });
6843
+ if (baseMatch) {
6844
+ return {
6845
+ collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
6846
+ marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
6847
+ };
6848
+ }
6849
+ return { collateralToken: 'USDC', marketPrefix: null };
6850
+ }
6851
+ // Enrich position assets with market prefix and collateral token
6852
+ function enrichPositionAssets(assets, perpMetaAssets) {
6853
+ return assets.map((asset) => {
6854
+ const meta = findAssetMeta$1(asset.coin, perpMetaAssets);
6855
+ return {
6856
+ ...asset,
6857
+ marketPrefix: meta.marketPrefix,
6858
+ collateralToken: meta.collateralToken,
6859
+ };
6860
+ });
6861
+ }
6862
+ // Enrich all positions with market metadata
6863
+ function enrichPositions(positions, perpMetaAssets) {
6864
+ return positions.map((position) => ({
6865
+ ...position,
6866
+ longAssets: enrichPositionAssets(position.longAssets, perpMetaAssets),
6867
+ shortAssets: enrichPositionAssets(position.shortAssets, perpMetaAssets),
6868
+ }));
6869
+ }
6563
6870
  function usePosition() {
6564
6871
  const context = useContext(PearHyperliquidContext);
6565
6872
  if (!context) {
6566
6873
  throw new Error('usePosition must be used within a PearHyperliquidProvider');
6567
6874
  }
6568
6875
  const { apiBaseUrl, isConnected } = context;
6569
- const displayToFull = useHyperliquidData((s) => s.hip3DisplayToFull);
6876
+ const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
6570
6877
  // Create position API action
6571
6878
  const createPosition$1 = async (payload) => {
6572
- return createPosition(apiBaseUrl, payload, displayToFull);
6879
+ return createPosition(apiBaseUrl, payload, hip3Assets);
6573
6880
  };
6574
6881
  // Update TP/SL risk parameters for a position
6575
6882
  const updateRiskParameters$1 = async (positionId, payload) => {
@@ -6589,21 +6896,33 @@ function usePosition() {
6589
6896
  };
6590
6897
  // Adjust to absolute target sizes per asset, optionally adding new assets
6591
6898
  const adjustAdvancePosition$1 = async (positionId, payload) => {
6592
- return adjustAdvancePosition(apiBaseUrl, positionId, payload, displayToFull);
6899
+ return adjustAdvancePosition(apiBaseUrl, positionId, payload, hip3Assets);
6593
6900
  };
6594
6901
  // Open positions using WS data, with derived values
6595
6902
  const userOpenPositions = useUserData((state) => state.rawOpenPositions);
6596
6903
  const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
6597
6904
  const allMids = useHyperliquidData((state) => state.allMids);
6905
+ const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
6598
6906
  const isLoading = useMemo(() => {
6599
6907
  return userOpenPositions === null && isConnected;
6600
6908
  }, [userOpenPositions, isConnected]);
6601
6909
  const openPositions = useMemo(() => {
6602
6910
  if (!userOpenPositions || !aggregatedClearingHouseState || !allMids)
6603
6911
  return null;
6604
- return buildPositionValue(userOpenPositions, aggregatedClearingHouseState, allMids);
6605
- }, [userOpenPositions, aggregatedClearingHouseState, allMids]);
6606
- return { createPosition: createPosition$1, updateRiskParameters: updateRiskParameters$1, closePosition: closePosition$1, closeAllPositions: closeAllPositions$1, adjustPosition: adjustPosition$1, adjustAdvancePosition: adjustAdvancePosition$1, openPositions, isLoading };
6912
+ const positions = buildPositionValue(userOpenPositions, aggregatedClearingHouseState, allMids);
6913
+ // Enrich with market prefix and collateral token
6914
+ return enrichPositions(positions, perpMetaAssets);
6915
+ }, [userOpenPositions, aggregatedClearingHouseState, allMids, perpMetaAssets]);
6916
+ return {
6917
+ createPosition: createPosition$1,
6918
+ updateRiskParameters: updateRiskParameters$1,
6919
+ closePosition: closePosition$1,
6920
+ closeAllPositions: closeAllPositions$1,
6921
+ adjustPosition: adjustPosition$1,
6922
+ adjustAdvancePosition: adjustAdvancePosition$1,
6923
+ openPositions,
6924
+ isLoading,
6925
+ };
6607
6926
  }
6608
6927
 
6609
6928
  async function adjustOrder(baseUrl, orderId, payload) {
@@ -6775,59 +7094,170 @@ function useNotifications() {
6775
7094
  };
6776
7095
  }
6777
7096
 
6778
- // Base selector for the full market-data payload
7097
+ // Helper to find asset metadata from perpMetaAssets
7098
+ function findAssetMeta(assetName, perpMetaAssets) {
7099
+ var _a, _b, _c, _d;
7100
+ if (!perpMetaAssets) {
7101
+ return { collateralToken: 'USDC', marketPrefix: null };
7102
+ }
7103
+ // Try exact match first (for prefixed assets like "xyz:TSLA")
7104
+ const exactMatch = perpMetaAssets.find((a) => a.name === assetName);
7105
+ if (exactMatch) {
7106
+ return {
7107
+ collateralToken: (_a = exactMatch.collateralToken) !== null && _a !== void 0 ? _a : 'USDC',
7108
+ marketPrefix: (_b = exactMatch.marketPrefix) !== null && _b !== void 0 ? _b : null,
7109
+ };
7110
+ }
7111
+ // Try matching by base symbol (for non-prefixed names in data)
7112
+ const baseMatch = perpMetaAssets.find((a) => {
7113
+ const baseName = a.name.includes(':') ? a.name.split(':')[1] : a.name;
7114
+ return baseName === assetName;
7115
+ });
7116
+ if (baseMatch) {
7117
+ return {
7118
+ collateralToken: (_c = baseMatch.collateralToken) !== null && _c !== void 0 ? _c : 'USDC',
7119
+ marketPrefix: (_d = baseMatch.marketPrefix) !== null && _d !== void 0 ? _d : null,
7120
+ };
7121
+ }
7122
+ return { collateralToken: 'USDC', marketPrefix: null };
7123
+ }
7124
+ // Enrich a single asset with metadata
7125
+ function enrichAsset(asset, perpMetaAssets) {
7126
+ const meta = findAssetMeta(asset.asset, perpMetaAssets);
7127
+ return {
7128
+ ...asset,
7129
+ collateralToken: meta.collateralToken,
7130
+ marketPrefix: meta.marketPrefix,
7131
+ };
7132
+ }
7133
+ // Enrich a basket item with collateral info
7134
+ function enrichBasketItem(item, perpMetaAssets) {
7135
+ const enrichedLongs = item.longAssets.map((a) => enrichAsset(a, perpMetaAssets));
7136
+ const enrichedShorts = item.shortAssets.map((a) => enrichAsset(a, perpMetaAssets));
7137
+ // Determine collateral type
7138
+ const allAssets = [...enrichedLongs, ...enrichedShorts];
7139
+ const hasUsdc = allAssets.some((a) => a.collateralToken === 'USDC');
7140
+ const hasUsdh = allAssets.some((a) => a.collateralToken === 'USDH');
7141
+ let collateralType = 'USDC';
7142
+ if (hasUsdc && hasUsdh) {
7143
+ collateralType = 'MIXED';
7144
+ }
7145
+ else if (hasUsdh) {
7146
+ collateralType = 'USDH';
7147
+ }
7148
+ return {
7149
+ ...item,
7150
+ longAssets: enrichedLongs,
7151
+ shortAssets: enrichedShorts,
7152
+ collateralType,
7153
+ };
7154
+ }
7155
+ /**
7156
+ * Filter baskets by collateral type
7157
+ * - 'USDC': Only baskets where ALL assets use USDC (collateralType === 'USDC')
7158
+ * - 'USDH': Only baskets where ALL assets use USDH (collateralType === 'USDH')
7159
+ * - 'ALL' or undefined: No filtering, returns all baskets
7160
+ */
7161
+ function filterByCollateral(baskets, filter) {
7162
+ if (!filter || filter === 'ALL') {
7163
+ return baskets;
7164
+ }
7165
+ return baskets.filter((basket) => {
7166
+ if (filter === 'USDC') {
7167
+ // Include baskets that are purely USDC or have USDC assets
7168
+ return (basket.collateralType === 'USDC' || basket.collateralType === 'MIXED');
7169
+ }
7170
+ if (filter === 'USDH') {
7171
+ // Include baskets that are purely USDH or have USDH assets
7172
+ return (basket.collateralType === 'USDH' || basket.collateralType === 'MIXED');
7173
+ }
7174
+ return true;
7175
+ });
7176
+ }
7177
+ // Base selector for the full market-data payload (raw from WS)
6779
7178
  const useMarketDataPayload = () => {
6780
7179
  return useMarketData((s) => s.marketData);
6781
7180
  };
6782
- // Full payload for 'market-data-all' channel
7181
+ // Full payload for 'market-data-all' channel (raw from WS)
6783
7182
  const useMarketDataAllPayload = () => {
6784
7183
  return useMarketData((s) => s.marketDataAll);
6785
7184
  };
6786
- // Active baskets
6787
- const useActiveBaskets = () => {
6788
- var _a;
7185
+ // Access perpMetaAssets for enrichment
7186
+ const usePerpMetaAssets = () => {
7187
+ return useHyperliquidData((s) => s.perpMetaAssets);
7188
+ };
7189
+ // Active baskets (with collateral and market prefix info)
7190
+ const useActiveBaskets = (collateralFilter) => {
6789
7191
  const data = useMarketDataPayload();
6790
- return (_a = data === null || data === void 0 ? void 0 : data.active) !== null && _a !== void 0 ? _a : [];
7192
+ const perpMetaAssets = usePerpMetaAssets();
7193
+ return useMemo(() => {
7194
+ if (!(data === null || data === void 0 ? void 0 : data.active))
7195
+ return [];
7196
+ const enriched = data.active.map((item) => enrichBasketItem(item, perpMetaAssets));
7197
+ return filterByCollateral(enriched, collateralFilter);
7198
+ }, [data, perpMetaAssets, collateralFilter]);
6791
7199
  };
6792
- // Top gainers (optional limit override)
6793
- const useTopGainers = (limit) => {
7200
+ // Top gainers (with collateral and market prefix info)
7201
+ const useTopGainers = (limit, collateralFilter) => {
6794
7202
  const data = useMarketDataPayload();
7203
+ const perpMetaAssets = usePerpMetaAssets();
6795
7204
  return useMemo(() => {
6796
7205
  var _a;
6797
7206
  const list = (_a = data === null || data === void 0 ? void 0 : data.topGainers) !== null && _a !== void 0 ? _a : [];
6798
- return typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
6799
- }, [data, limit]);
7207
+ const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7208
+ const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
7209
+ return filterByCollateral(enriched, collateralFilter);
7210
+ }, [data, perpMetaAssets, limit, collateralFilter]);
6800
7211
  };
6801
- // Top losers (optional limit override)
6802
- const useTopLosers = (limit) => {
7212
+ // Top losers (with collateral and market prefix info)
7213
+ const useTopLosers = (limit, collateralFilter) => {
6803
7214
  const data = useMarketDataPayload();
7215
+ const perpMetaAssets = usePerpMetaAssets();
6804
7216
  return useMemo(() => {
6805
7217
  var _a;
6806
7218
  const list = (_a = data === null || data === void 0 ? void 0 : data.topLosers) !== null && _a !== void 0 ? _a : [];
6807
- return typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
6808
- }, [data, limit]);
7219
+ const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7220
+ const enriched = limited.map((item) => enrichBasketItem(item, perpMetaAssets));
7221
+ return filterByCollateral(enriched, collateralFilter);
7222
+ }, [data, perpMetaAssets, limit, collateralFilter]);
6809
7223
  };
6810
- // Highlighted baskets
6811
- const useHighlightedBaskets = () => {
6812
- var _a;
7224
+ // Highlighted baskets (with collateral and market prefix info)
7225
+ const useHighlightedBaskets = (collateralFilter) => {
6813
7226
  const data = useMarketDataPayload();
6814
- return (_a = data === null || data === void 0 ? void 0 : data.highlighted) !== null && _a !== void 0 ? _a : [];
7227
+ const perpMetaAssets = usePerpMetaAssets();
7228
+ return useMemo(() => {
7229
+ if (!(data === null || data === void 0 ? void 0 : data.highlighted))
7230
+ return [];
7231
+ const enriched = data.highlighted.map((item) => enrichBasketItem(item, perpMetaAssets));
7232
+ return filterByCollateral(enriched, collateralFilter);
7233
+ }, [data, perpMetaAssets, collateralFilter]);
6815
7234
  };
6816
- // Watchlist baskets (from market-data payload when subscribed with address)
6817
- const useWatchlistBaskets = () => {
6818
- var _a;
7235
+ // Watchlist baskets (with collateral and market prefix info)
7236
+ const useWatchlistBaskets = (collateralFilter) => {
6819
7237
  const data = useMarketDataPayload();
6820
- return (_a = data === null || data === void 0 ? void 0 : data.watchlist) !== null && _a !== void 0 ? _a : [];
7238
+ const perpMetaAssets = usePerpMetaAssets();
7239
+ return useMemo(() => {
7240
+ if (!(data === null || data === void 0 ? void 0 : data.watchlist))
7241
+ return [];
7242
+ const enriched = data.watchlist.map((item) => enrichBasketItem(item, perpMetaAssets));
7243
+ return filterByCollateral(enriched, collateralFilter);
7244
+ }, [data, perpMetaAssets, collateralFilter]);
6821
7245
  };
6822
- // All baskets (from market-data-all)
6823
- const useAllBaskets = () => {
6824
- var _a;
7246
+ // All baskets (with collateral and market prefix info)
7247
+ const useAllBaskets = (collateralFilter) => {
6825
7248
  const dataAll = useMarketDataAllPayload();
6826
- return (_a = dataAll === null || dataAll === void 0 ? void 0 : dataAll.all) !== null && _a !== void 0 ? _a : [];
7249
+ const perpMetaAssets = usePerpMetaAssets();
7250
+ return useMemo(() => {
7251
+ if (!(dataAll === null || dataAll === void 0 ? void 0 : dataAll.all))
7252
+ return [];
7253
+ const enriched = dataAll.all.map((item) => enrichBasketItem(item, perpMetaAssets));
7254
+ return filterByCollateral(enriched, collateralFilter);
7255
+ }, [dataAll, perpMetaAssets, collateralFilter]);
6827
7256
  };
6828
7257
  // Find a basket by its exact asset composition (order-insensitive)
6829
7258
  const useFindBasket = (longs, shorts) => {
6830
7259
  const data = useMarketDataPayload();
7260
+ const perpMetaAssets = usePerpMetaAssets();
6831
7261
  return useMemo(() => {
6832
7262
  if (!data)
6833
7263
  return undefined;
@@ -6841,17 +7271,28 @@ const useFindBasket = (longs, shorts) => {
6841
7271
  : '';
6842
7272
  const lKey = normalize(longs);
6843
7273
  const sKey = normalize(shorts);
6844
- const match = (item) => normalize(item.longAssets) === lKey && normalize(item.shortAssets) === sKey;
6845
- return data.active.find(match) || data.highlighted.find(match);
6846
- }, [data, longs, shorts]);
7274
+ const match = (item) => normalize(item.longAssets) === lKey &&
7275
+ normalize(item.shortAssets) === sKey;
7276
+ const found = data.active.find(match) || data.highlighted.find(match);
7277
+ return found
7278
+ ? enrichBasketItem(found, perpMetaAssets)
7279
+ : undefined;
7280
+ }, [data, longs, shorts, perpMetaAssets]);
6847
7281
  };
6848
7282
 
6849
- async function toggleWatchlist(baseUrl, longAssets, shortAssets, displayToFull) {
7283
+ async function toggleWatchlist(baseUrl, longAssets, shortAssets, hip3Assets) {
6850
7284
  const url = joinUrl(baseUrl, '/watchlist');
6851
- const mapAssets = (arr) => arr.map(a => ({ ...a, asset: toBackendSymbol(a.asset, displayToFull) }));
7285
+ const mapAssets = (arr) => arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
6852
7286
  try {
6853
- const response = await apiClient.post(url, { longAssets: mapAssets(longAssets), shortAssets: mapAssets(shortAssets) }, { headers: { 'Content-Type': 'application/json' } });
6854
- return { data: response.data, status: response.status, headers: response.headers };
7287
+ const response = await apiClient.post(url, {
7288
+ longAssets: mapAssets(longAssets),
7289
+ shortAssets: mapAssets(shortAssets),
7290
+ }, { headers: { 'Content-Type': 'application/json' } });
7291
+ return {
7292
+ data: response.data,
7293
+ status: response.status,
7294
+ headers: response.headers,
7295
+ };
6855
7296
  }
6856
7297
  catch (error) {
6857
7298
  throw toApiError(error);
@@ -6863,11 +7304,11 @@ function useWatchlist() {
6863
7304
  if (!context)
6864
7305
  throw new Error('useWatchlist must be used within a PearHyperliquidProvider');
6865
7306
  const { apiBaseUrl, isConnected } = context;
6866
- const displayToFull = useHyperliquidData((s) => s.hip3DisplayToFull);
7307
+ const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
6867
7308
  const marketData = useMarketDataPayload();
6868
7309
  const isLoading = useMemo(() => !marketData && isConnected, [marketData, isConnected]);
6869
7310
  const toggle = async (longAssets, shortAssets) => {
6870
- const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, displayToFull);
7311
+ const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, hip3Assets);
6871
7312
  // Server will push updated market-data over WS; nothing to set here
6872
7313
  return resp;
6873
7314
  };
@@ -7121,7 +7562,8 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-v2.pearpro
7121
7562
  const setAddress = useUserData((s) => s.setAddress);
7122
7563
  const perpsMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
7123
7564
  const setPerpMetaAssets = useHyperliquidData((state) => state.setPerpMetaAssets);
7124
- const setHip3DisplayToFull = useHyperliquidData((state) => state.setHip3DisplayToFull);
7565
+ const setHip3Assets = useHyperliquidData((state) => state.setHip3Assets);
7566
+ const setHip3MarketPrefixes = useHyperliquidData((state) => state.setHip3MarketPrefixes);
7125
7567
  const websocketsEnabled = useMemo(() => Array.isArray(perpsMetaAssets) && perpsMetaAssets.length > 0, [perpsMetaAssets]);
7126
7568
  const { isConnected, lastError } = useHyperliquidWebSocket({
7127
7569
  wsUrl,
@@ -7134,29 +7576,64 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-v2.pearpro
7134
7576
  });
7135
7577
  useEffect(() => {
7136
7578
  if (perpsMetaAssets === null) {
7137
- fetchAllPerpMetas().then(res => {
7138
- // Only show HL and XYZ for now as other are using USDH collateral and need more work
7139
- const aggregatedPerpMetas = res.data.slice(0, 2).flatMap(item => item.universe);
7140
- const hip3Map = new Map();
7141
- const displayToFull = new Map();
7142
- const cleanedPerpMetas = aggregatedPerpMetas.map((asset) => {
7143
- var _a;
7144
- const [maybePrefix, maybeMarket] = asset.name.split(":");
7145
- if (maybeMarket) {
7146
- const prefix = maybePrefix.toLowerCase();
7147
- const market = maybeMarket;
7148
- const existing = (_a = hip3Map.get(prefix)) !== null && _a !== void 0 ? _a : [];
7149
- hip3Map.set(prefix, [...existing, market]);
7150
- displayToFull.set(market, `${prefix}:${market}`);
7151
- return { ...asset, name: market };
7152
- }
7153
- return asset;
7579
+ fetchAllPerpMetas()
7580
+ .then((res) => {
7581
+ // Maps for multi-market assets
7582
+ const assetToMarkets = new Map(); // TSLA -> ["xyz:TSLA", "flx:TSLA"]
7583
+ const marketPrefixes = new Map(); // "xyz:TSLA" -> "xyz"
7584
+ const cleanedPerpMetas = [];
7585
+ // Process each market group (different collateral tokens)
7586
+ res.data.forEach((item) => {
7587
+ // Convert numeric collateral token to human-readable name
7588
+ const collateralToken = item.collateralToken === 360 ? 'USDH' : 'USDC';
7589
+ item.universe.forEach((asset) => {
7590
+ var _a;
7591
+ const [maybePrefix, maybeMarket] = asset.name.split(':');
7592
+ if (maybeMarket) {
7593
+ // HIP-3 market with prefix (e.g., "xyz:TSLA")
7594
+ const prefix = maybePrefix.toLowerCase();
7595
+ const displayName = maybeMarket;
7596
+ const fullName = `${prefix}:${displayName}`;
7597
+ // Store full market name with prefix
7598
+ marketPrefixes.set(fullName, prefix);
7599
+ // Track all markets for this asset
7600
+ const existingMarkets = (_a = assetToMarkets.get(displayName)) !== null && _a !== void 0 ? _a : [];
7601
+ if (!existingMarkets.includes(fullName)) {
7602
+ assetToMarkets.set(displayName, [
7603
+ ...existingMarkets,
7604
+ fullName,
7605
+ ]);
7606
+ }
7607
+ // Add asset with all metadata INCLUDING collateral token for THIS specific market
7608
+ // Important: We keep ALL variants so each market+collateral combo is tracked
7609
+ cleanedPerpMetas.push({
7610
+ ...asset,
7611
+ name: displayName, // Use display name for UI
7612
+ marketPrefix: prefix, // Which market (xyz, flx, etc)
7613
+ collateralToken, // "USDC" or "USDH"
7614
+ });
7615
+ }
7616
+ else {
7617
+ // Regular market without prefix
7618
+ cleanedPerpMetas.push({
7619
+ ...asset,
7620
+ collateralToken, // "USDC" or "USDH"
7621
+ });
7622
+ }
7623
+ });
7154
7624
  });
7155
- setHip3DisplayToFull(displayToFull);
7625
+ setHip3Assets(assetToMarkets);
7626
+ setHip3MarketPrefixes(marketPrefixes);
7156
7627
  setPerpMetaAssets(cleanedPerpMetas);
7157
- }).catch(() => { });
7628
+ })
7629
+ .catch(() => { });
7158
7630
  }
7159
- }, [perpsMetaAssets, setPerpMetaAssets, setHip3DisplayToFull]);
7631
+ }, [
7632
+ perpsMetaAssets,
7633
+ setPerpMetaAssets,
7634
+ setHip3Assets,
7635
+ setHip3MarketPrefixes,
7636
+ ]);
7160
7637
  // Auth methods now sourced from useAuth hook
7161
7638
  useAutoSyncFills({
7162
7639
  baseUrl: apiBaseUrl,
@@ -7177,10 +7654,14 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-v2.pearpro
7177
7654
  nativeIsConnected,
7178
7655
  nativeLastError,
7179
7656
  }), [
7180
- apiBaseUrl, wsUrl,
7181
- address, setAddress,
7182
- isConnected, lastError,
7183
- nativeIsConnected, nativeLastError,
7657
+ apiBaseUrl,
7658
+ wsUrl,
7659
+ address,
7660
+ setAddress,
7661
+ isConnected,
7662
+ lastError,
7663
+ nativeIsConnected,
7664
+ nativeLastError,
7184
7665
  ]);
7185
7666
  return (jsx(PearHyperliquidContext.Provider, { value: contextValue, children: children }));
7186
7667
  };
@@ -7282,4 +7763,4 @@ function mapCandleIntervalToTradingViewInterval(interval) {
7282
7763
  }
7283
7764
  }
7284
7765
 
7285
- 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 };
7766
+ 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 };