@pear-protocol/hyperliquid-sdk 0.0.60-beta → 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/clients/hyperliquid.d.ts +1 -1
- package/dist/clients/positions.d.ts +2 -2
- package/dist/clients/watchlist.d.ts +1 -1
- package/dist/hooks/useAuth.d.ts +4 -4
- package/dist/hooks/useMarketData.d.ts +9 -7
- package/dist/hooks/useWebData.d.ts +21 -3
- package/dist/index.d.ts +124 -45
- package/dist/index.js +655 -259
- package/dist/provider.d.ts +1 -1
- package/dist/store/historicalPriceDataStore.d.ts +1 -12
- package/dist/store/hyperliquidDataStore.d.ts +1 -25
- package/dist/store/marketDataStore.d.ts +1 -10
- package/dist/store/tokenSelectionMetadataStore.d.ts +2 -2
- package/dist/store/userDataStore.d.ts +1 -28
- package/dist/store/userSelection.d.ts +1 -1
- package/dist/types.d.ts +36 -3
- package/dist/utils/symbol-translator.d.ts +32 -3
- package/dist/utils/token-metadata-extractor.d.ts +9 -5
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
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 !==
|
|
30
|
+
if (typeof window !== 'undefined') {
|
|
71
31
|
if (address) {
|
|
72
|
-
window.localStorage.setItem(
|
|
32
|
+
window.localStorage.setItem('address', address);
|
|
73
33
|
}
|
|
74
34
|
else {
|
|
75
|
-
window.localStorage.removeItem(
|
|
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
|
-
*
|
|
121
|
-
* @param
|
|
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
|
|
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"]
|
|
122
118
|
*/
|
|
123
|
-
function
|
|
119
|
+
function getAvailableMarkets(displaySymbol, hip3Assets) {
|
|
124
120
|
var _a;
|
|
125
|
-
return (_a =
|
|
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
|
-
|
|
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
|
-
|
|
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(() =>
|
|
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) =>
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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 = {
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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 ||
|
|
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
|
-
}, [
|
|
929
|
+
}, [
|
|
930
|
+
isConnected,
|
|
931
|
+
selectedTokenSymbols,
|
|
932
|
+
candleInterval,
|
|
933
|
+
subscribedCandleTokens,
|
|
934
|
+
sendJsonMessage,
|
|
935
|
+
setCandleData,
|
|
936
|
+
]);
|
|
868
937
|
return {
|
|
869
938
|
isConnected,
|
|
870
939
|
lastError,
|
|
@@ -986,21 +1055,51 @@ const useWebData = () => {
|
|
|
986
1055
|
const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
|
|
987
1056
|
const aggregatedClearinghouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
988
1057
|
const finalAtOICaps = useHyperliquidData((state) => state.finalAtOICaps);
|
|
989
|
-
const hip3Assets = useHyperliquidData((state) => state.
|
|
1058
|
+
const hip3Assets = useHyperliquidData((state) => state.hip3Assets);
|
|
1059
|
+
const hip3MarketPrefixes = useHyperliquidData((state) => state.hip3MarketPrefixes);
|
|
990
1060
|
let marketDataBySymbol = {};
|
|
991
1061
|
if (finalAssetContexts && perpMetaAssets) {
|
|
992
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
|
|
993
1068
|
for (let index = 0; index < perpMetaAssets.length; index++) {
|
|
994
1069
|
const name = perpMetaAssets[index].name;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
+
}
|
|
999
1097
|
}
|
|
1000
1098
|
marketDataBySymbol = result;
|
|
1001
1099
|
}
|
|
1002
1100
|
return {
|
|
1003
1101
|
hip3Assets,
|
|
1102
|
+
hip3MarketPrefixes,
|
|
1004
1103
|
clearinghouseState: aggregatedClearinghouseState,
|
|
1005
1104
|
perpsAtOpenInterestCap: finalAtOICaps,
|
|
1006
1105
|
marketDataBySymbol,
|
|
@@ -1015,30 +1114,60 @@ const useWebData = () => {
|
|
|
1015
1114
|
class TokenMetadataExtractor {
|
|
1016
1115
|
/**
|
|
1017
1116
|
* Extracts comprehensive token metadata
|
|
1018
|
-
* @param symbol - Token symbol
|
|
1117
|
+
* @param symbol - Token symbol (base symbol without prefix, e.g., "TSLA")
|
|
1019
1118
|
* @param perpMetaAssets - Aggregated universe assets (flattened across dexes)
|
|
1020
1119
|
* @param finalAssetContexts - Aggregated asset contexts (flattened across dexes)
|
|
1021
1120
|
* @param allMids - AllMids data containing current prices
|
|
1022
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
|
|
1023
1123
|
* @returns TokenMetadata or null if token not found
|
|
1024
1124
|
*/
|
|
1025
|
-
static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
|
|
1125
|
+
static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketPrefix) {
|
|
1026
1126
|
if (!perpMetaAssets || !finalAssetContexts || !allMids) {
|
|
1027
1127
|
return null;
|
|
1028
1128
|
}
|
|
1029
1129
|
// Find token index in aggregated universe
|
|
1030
|
-
|
|
1031
|
-
|
|
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) {
|
|
1032
1150
|
return null;
|
|
1033
1151
|
}
|
|
1034
|
-
const universeAsset = perpMetaAssets[
|
|
1035
|
-
const assetCtx = finalAssetContexts[
|
|
1152
|
+
const universeAsset = perpMetaAssets[resolvedIndex];
|
|
1153
|
+
const assetCtx = finalAssetContexts[resolvedIndex];
|
|
1036
1154
|
if (!assetCtx) {
|
|
1037
1155
|
return null;
|
|
1038
1156
|
}
|
|
1039
|
-
// Get current price
|
|
1040
|
-
|
|
1041
|
-
const
|
|
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
|
+
}
|
|
1042
1171
|
// Get previous day price
|
|
1043
1172
|
const prevDayPrice = parseFloat(assetCtx.prevDayPx);
|
|
1044
1173
|
// Calculate 24h price change
|
|
@@ -1049,7 +1178,11 @@ class TokenMetadataExtractor {
|
|
|
1049
1178
|
const markPrice = parseFloat(assetCtx.markPx);
|
|
1050
1179
|
const oraclePrice = parseFloat(assetCtx.oraclePx);
|
|
1051
1180
|
// Extract leverage info from activeAssetData if available
|
|
1052
|
-
|
|
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];
|
|
1053
1186
|
const leverage = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.leverage;
|
|
1054
1187
|
const maxTradeSzs = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.maxTradeSzs;
|
|
1055
1188
|
const availableToTrade = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.availableToTrade;
|
|
@@ -1067,21 +1200,27 @@ class TokenMetadataExtractor {
|
|
|
1067
1200
|
leverage,
|
|
1068
1201
|
maxTradeSzs,
|
|
1069
1202
|
availableToTrade,
|
|
1203
|
+
collateralToken: universeAsset.collateralToken,
|
|
1070
1204
|
};
|
|
1071
1205
|
}
|
|
1072
1206
|
/**
|
|
1073
1207
|
* Extracts metadata for multiple tokens
|
|
1074
|
-
* @param
|
|
1208
|
+
* @param tokens - Array of token objects with symbol and optional marketPrefix
|
|
1075
1209
|
* @param perpMetaAssets - Aggregated universe assets
|
|
1076
1210
|
* @param finalAssetContexts - Aggregated asset contexts
|
|
1077
1211
|
* @param allMids - AllMids data
|
|
1078
1212
|
* @param activeAssetData - Optional active asset data containing leverage information
|
|
1079
|
-
* @returns Record of
|
|
1213
|
+
* @returns Record of unique key to TokenMetadata. Key is "{prefix}:{symbol}" for HIP3 assets, or just "{symbol}" otherwise
|
|
1080
1214
|
*/
|
|
1081
|
-
static extractMultipleTokensMetadata(
|
|
1215
|
+
static extractMultipleTokensMetadata(tokens, perpMetaAssets, finalAssetContexts, allMids, activeAssetData) {
|
|
1082
1216
|
const result = {};
|
|
1083
|
-
for (const
|
|
1084
|
-
|
|
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);
|
|
1085
1224
|
}
|
|
1086
1225
|
return result;
|
|
1087
1226
|
}
|
|
@@ -1094,10 +1233,30 @@ class TokenMetadataExtractor {
|
|
|
1094
1233
|
static isTokenAvailable(symbol, perpMetaAssets) {
|
|
1095
1234
|
if (!perpMetaAssets)
|
|
1096
1235
|
return false;
|
|
1097
|
-
return perpMetaAssets.some(asset => asset.name === symbol);
|
|
1236
|
+
return perpMetaAssets.some((asset) => asset.name === symbol);
|
|
1098
1237
|
}
|
|
1099
1238
|
}
|
|
1100
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
|
+
}
|
|
1101
1260
|
const useTokenSelectionMetadataStore = create((set) => ({
|
|
1102
1261
|
isPriceDataReady: false,
|
|
1103
1262
|
isLoading: true,
|
|
@@ -1107,23 +1266,65 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1107
1266
|
weightedRatio24h: 1,
|
|
1108
1267
|
priceRatio: 1,
|
|
1109
1268
|
priceRatio24h: 1,
|
|
1110
|
-
openInterest:
|
|
1111
|
-
volume:
|
|
1269
|
+
openInterest: '0',
|
|
1270
|
+
volume: '0',
|
|
1112
1271
|
sumNetFunding: 0,
|
|
1113
1272
|
maxLeverage: 0,
|
|
1114
1273
|
minMargin: 0,
|
|
1115
1274
|
leverageMatched: true,
|
|
1116
|
-
recompute: ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, marketData, longTokens, shortTokens }) => {
|
|
1117
|
-
const isPriceDataReady = !!(perpMetaAssets &&
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
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)
|
|
1123
1305
|
: {};
|
|
1124
|
-
const
|
|
1125
|
-
? TokenMetadataExtractor.extractMultipleTokensMetadata(
|
|
1306
|
+
const shortBaseMetadata = isPriceDataReady
|
|
1307
|
+
? TokenMetadataExtractor.extractMultipleTokensMetadata(shortTokensForLookup, perpMetaAssets, finalAssetContexts, allMids, activeAssetData)
|
|
1126
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
|
+
});
|
|
1127
1328
|
// Determine loading state
|
|
1128
1329
|
const allTokens = [...longTokens, ...shortTokens];
|
|
1129
1330
|
const isLoading = (() => {
|
|
@@ -1131,26 +1332,33 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1131
1332
|
return true;
|
|
1132
1333
|
if (allTokens.length === 0)
|
|
1133
1334
|
return false;
|
|
1134
|
-
const allMetadata = {
|
|
1335
|
+
const allMetadata = {
|
|
1336
|
+
...longTokensMetadata,
|
|
1337
|
+
...shortTokensMetadata,
|
|
1338
|
+
};
|
|
1135
1339
|
return allTokens.some((token) => !allMetadata[token.symbol]);
|
|
1136
1340
|
})();
|
|
1137
1341
|
// Open interest and volume (from market data for matching asset basket)
|
|
1342
|
+
// Use base symbols (without prefix) for matching against market data
|
|
1138
1343
|
const { openInterest, volume } = (() => {
|
|
1139
|
-
const empty = { openInterest:
|
|
1344
|
+
const empty = { openInterest: '0', volume: '0' };
|
|
1140
1345
|
if (!(marketData === null || marketData === void 0 ? void 0 : marketData.active) || (!longTokens.length && !shortTokens.length))
|
|
1141
1346
|
return empty;
|
|
1142
|
-
const selectedLong =
|
|
1143
|
-
const selectedShort =
|
|
1347
|
+
const selectedLong = longBaseSymbols.slice().sort();
|
|
1348
|
+
const selectedShort = shortBaseSymbols.slice().sort();
|
|
1144
1349
|
const match = marketData.active.find((item) => {
|
|
1145
1350
|
const longs = [...item.longAssets].sort();
|
|
1146
1351
|
const shorts = [...item.shortAssets].sort();
|
|
1147
|
-
if (longs.length !== selectedLong.length ||
|
|
1352
|
+
if (longs.length !== selectedLong.length ||
|
|
1353
|
+
shorts.length !== selectedShort.length)
|
|
1148
1354
|
return false;
|
|
1149
1355
|
const longsEqual = longs.every((s, i) => s.asset === selectedLong[i]);
|
|
1150
1356
|
const shortsEqual = shorts.every((s, i) => s.asset === selectedShort[i]);
|
|
1151
1357
|
return longsEqual && shortsEqual;
|
|
1152
1358
|
});
|
|
1153
|
-
return match
|
|
1359
|
+
return match
|
|
1360
|
+
? { openInterest: match.openInterest, volume: match.volume }
|
|
1361
|
+
: empty;
|
|
1154
1362
|
})();
|
|
1155
1363
|
// Price ratio (only when exactly one long and one short)
|
|
1156
1364
|
const { priceRatio, priceRatio24h } = (() => {
|
|
@@ -1230,17 +1438,27 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1230
1438
|
return totalFunding;
|
|
1231
1439
|
})();
|
|
1232
1440
|
// Max leverage (minimum across all tokens)
|
|
1441
|
+
// Use tokens with their market prefixes for proper lookup in perpMetaAssets
|
|
1233
1442
|
const maxLeverage = (() => {
|
|
1234
1443
|
if (!perpMetaAssets)
|
|
1235
1444
|
return 0;
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1445
|
+
const allTokensForLookup = [
|
|
1446
|
+
...longTokensForLookup,
|
|
1447
|
+
...shortTokensForLookup,
|
|
1448
|
+
];
|
|
1449
|
+
if (allTokensForLookup.length === 0)
|
|
1238
1450
|
return 0;
|
|
1239
1451
|
let minLev = Infinity;
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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);
|
|
1244
1462
|
});
|
|
1245
1463
|
return minLev === Infinity ? 0 : minLev;
|
|
1246
1464
|
})();
|
|
@@ -1252,7 +1470,10 @@ const useTokenSelectionMetadataStore = create((set) => ({
|
|
|
1252
1470
|
// Whether all tokens have matching leverage
|
|
1253
1471
|
const leverageMatched = (() => {
|
|
1254
1472
|
const allTokensArr = [...longTokens, ...shortTokens];
|
|
1255
|
-
const allMetadata = {
|
|
1473
|
+
const allMetadata = {
|
|
1474
|
+
...longTokensMetadata,
|
|
1475
|
+
...shortTokensMetadata,
|
|
1476
|
+
};
|
|
1256
1477
|
if (allTokensArr.length === 0)
|
|
1257
1478
|
return true;
|
|
1258
1479
|
const tokensWithLev = allTokensArr.filter((token) => { var _a; return (_a = allMetadata[token.symbol]) === null || _a === void 0 ? void 0 : _a.leverage; });
|
|
@@ -5437,8 +5658,8 @@ function addAuthInterceptors(params) {
|
|
|
5437
5658
|
/**
|
|
5438
5659
|
* Fetch historical candle data from HyperLiquid API
|
|
5439
5660
|
*/
|
|
5440
|
-
const fetchHistoricalCandles = async (coin, startTime, endTime, interval,
|
|
5441
|
-
const backendCoin = toBackendSymbol(coin,
|
|
5661
|
+
const fetchHistoricalCandles = async (coin, startTime, endTime, interval, hip3Assets) => {
|
|
5662
|
+
const backendCoin = toBackendSymbol(coin, hip3Assets);
|
|
5442
5663
|
const request = {
|
|
5443
5664
|
req: { coin: backendCoin, startTime, endTime, interval },
|
|
5444
5665
|
type: 'candleSnapshot',
|
|
@@ -5597,10 +5818,10 @@ const useHistoricalPriceData = () => {
|
|
|
5597
5818
|
setTokenLoading(token.symbol, true);
|
|
5598
5819
|
});
|
|
5599
5820
|
try {
|
|
5600
|
-
const
|
|
5821
|
+
const hip3Assets = useHyperliquidData.getState().hip3Assets;
|
|
5601
5822
|
const fetchPromises = tokensToFetch.map(async (token) => {
|
|
5602
5823
|
try {
|
|
5603
|
-
const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval,
|
|
5824
|
+
const response = await fetchHistoricalCandles(token.symbol, startTime, endTime, interval, hip3Assets);
|
|
5604
5825
|
addHistoricalPriceData(token.symbol, interval, response.data, { start: startTime, end: endTime });
|
|
5605
5826
|
return { symbol: token.symbol, candles: response.data, success: true };
|
|
5606
5827
|
}
|
|
@@ -6369,12 +6590,12 @@ function validatePositionSize(usdValue, longAssets, shortAssets) {
|
|
|
6369
6590
|
* Authorization is derived from headers (Axios defaults or browser localStorage fallback)
|
|
6370
6591
|
* @throws MinimumPositionSizeError if any asset has less than $11 USD value
|
|
6371
6592
|
*/
|
|
6372
|
-
async function createPosition(baseUrl, payload,
|
|
6593
|
+
async function createPosition(baseUrl, payload, hip3Assets) {
|
|
6373
6594
|
// Validate minimum asset size before creating position
|
|
6374
6595
|
validateMinimumAssetSize(payload.usdValue, payload.longAssets, payload.shortAssets);
|
|
6375
6596
|
const url = joinUrl(baseUrl, "/positions");
|
|
6376
6597
|
// 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,
|
|
6598
|
+
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6378
6599
|
const translatedPayload = {
|
|
6379
6600
|
...payload,
|
|
6380
6601
|
longAssets: mapAssets(payload.longAssets),
|
|
@@ -6473,9 +6694,9 @@ async function adjustPosition(baseUrl, positionId, payload) {
|
|
|
6473
6694
|
throw toApiError(error);
|
|
6474
6695
|
}
|
|
6475
6696
|
}
|
|
6476
|
-
async function adjustAdvancePosition(baseUrl, positionId, payload,
|
|
6697
|
+
async function adjustAdvancePosition(baseUrl, positionId, payload, hip3Assets) {
|
|
6477
6698
|
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,
|
|
6699
|
+
const mapAssets = (arr) => arr === null || arr === void 0 ? void 0 : arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6479
6700
|
const translatedPayload = (payload || []).map((item) => ({
|
|
6480
6701
|
longAssets: mapAssets(item.longAssets),
|
|
6481
6702
|
shortAssets: mapAssets(item.shortAssets),
|
|
@@ -6600,16 +6821,62 @@ const buildPositionValue = (rawPositions, clearinghouseState, allMids) => {
|
|
|
6600
6821
|
});
|
|
6601
6822
|
};
|
|
6602
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
|
+
}
|
|
6603
6870
|
function usePosition() {
|
|
6604
6871
|
const context = useContext(PearHyperliquidContext);
|
|
6605
6872
|
if (!context) {
|
|
6606
6873
|
throw new Error('usePosition must be used within a PearHyperliquidProvider');
|
|
6607
6874
|
}
|
|
6608
6875
|
const { apiBaseUrl, isConnected } = context;
|
|
6609
|
-
const
|
|
6876
|
+
const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
|
|
6610
6877
|
// Create position API action
|
|
6611
6878
|
const createPosition$1 = async (payload) => {
|
|
6612
|
-
return createPosition(apiBaseUrl, payload,
|
|
6879
|
+
return createPosition(apiBaseUrl, payload, hip3Assets);
|
|
6613
6880
|
};
|
|
6614
6881
|
// Update TP/SL risk parameters for a position
|
|
6615
6882
|
const updateRiskParameters$1 = async (positionId, payload) => {
|
|
@@ -6629,21 +6896,33 @@ function usePosition() {
|
|
|
6629
6896
|
};
|
|
6630
6897
|
// Adjust to absolute target sizes per asset, optionally adding new assets
|
|
6631
6898
|
const adjustAdvancePosition$1 = async (positionId, payload) => {
|
|
6632
|
-
return adjustAdvancePosition(apiBaseUrl, positionId, payload,
|
|
6899
|
+
return adjustAdvancePosition(apiBaseUrl, positionId, payload, hip3Assets);
|
|
6633
6900
|
};
|
|
6634
6901
|
// Open positions using WS data, with derived values
|
|
6635
6902
|
const userOpenPositions = useUserData((state) => state.rawOpenPositions);
|
|
6636
6903
|
const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
|
|
6637
6904
|
const allMids = useHyperliquidData((state) => state.allMids);
|
|
6905
|
+
const perpMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
|
|
6638
6906
|
const isLoading = useMemo(() => {
|
|
6639
6907
|
return userOpenPositions === null && isConnected;
|
|
6640
6908
|
}, [userOpenPositions, isConnected]);
|
|
6641
6909
|
const openPositions = useMemo(() => {
|
|
6642
6910
|
if (!userOpenPositions || !aggregatedClearingHouseState || !allMids)
|
|
6643
6911
|
return null;
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
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
|
+
};
|
|
6647
6926
|
}
|
|
6648
6927
|
|
|
6649
6928
|
async function adjustOrder(baseUrl, orderId, payload) {
|
|
@@ -6815,59 +7094,170 @@ function useNotifications() {
|
|
|
6815
7094
|
};
|
|
6816
7095
|
}
|
|
6817
7096
|
|
|
6818
|
-
//
|
|
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)
|
|
6819
7178
|
const useMarketDataPayload = () => {
|
|
6820
7179
|
return useMarketData((s) => s.marketData);
|
|
6821
7180
|
};
|
|
6822
|
-
// Full payload for 'market-data-all' channel
|
|
7181
|
+
// Full payload for 'market-data-all' channel (raw from WS)
|
|
6823
7182
|
const useMarketDataAllPayload = () => {
|
|
6824
7183
|
return useMarketData((s) => s.marketDataAll);
|
|
6825
7184
|
};
|
|
6826
|
-
//
|
|
6827
|
-
const
|
|
6828
|
-
|
|
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) => {
|
|
6829
7191
|
const data = useMarketDataPayload();
|
|
6830
|
-
|
|
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]);
|
|
6831
7199
|
};
|
|
6832
|
-
// Top gainers (
|
|
6833
|
-
const useTopGainers = (limit) => {
|
|
7200
|
+
// Top gainers (with collateral and market prefix info)
|
|
7201
|
+
const useTopGainers = (limit, collateralFilter) => {
|
|
6834
7202
|
const data = useMarketDataPayload();
|
|
7203
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
6835
7204
|
return useMemo(() => {
|
|
6836
7205
|
var _a;
|
|
6837
7206
|
const list = (_a = data === null || data === void 0 ? void 0 : data.topGainers) !== null && _a !== void 0 ? _a : [];
|
|
6838
|
-
|
|
6839
|
-
|
|
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]);
|
|
6840
7211
|
};
|
|
6841
|
-
// Top losers (
|
|
6842
|
-
const useTopLosers = (limit) => {
|
|
7212
|
+
// Top losers (with collateral and market prefix info)
|
|
7213
|
+
const useTopLosers = (limit, collateralFilter) => {
|
|
6843
7214
|
const data = useMarketDataPayload();
|
|
7215
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
6844
7216
|
return useMemo(() => {
|
|
6845
7217
|
var _a;
|
|
6846
7218
|
const list = (_a = data === null || data === void 0 ? void 0 : data.topLosers) !== null && _a !== void 0 ? _a : [];
|
|
6847
|
-
|
|
6848
|
-
|
|
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]);
|
|
6849
7223
|
};
|
|
6850
|
-
// Highlighted baskets
|
|
6851
|
-
const useHighlightedBaskets = () => {
|
|
6852
|
-
var _a;
|
|
7224
|
+
// Highlighted baskets (with collateral and market prefix info)
|
|
7225
|
+
const useHighlightedBaskets = (collateralFilter) => {
|
|
6853
7226
|
const data = useMarketDataPayload();
|
|
6854
|
-
|
|
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]);
|
|
6855
7234
|
};
|
|
6856
|
-
// Watchlist baskets (
|
|
6857
|
-
const useWatchlistBaskets = () => {
|
|
6858
|
-
var _a;
|
|
7235
|
+
// Watchlist baskets (with collateral and market prefix info)
|
|
7236
|
+
const useWatchlistBaskets = (collateralFilter) => {
|
|
6859
7237
|
const data = useMarketDataPayload();
|
|
6860
|
-
|
|
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]);
|
|
6861
7245
|
};
|
|
6862
|
-
// All baskets (
|
|
6863
|
-
const useAllBaskets = () => {
|
|
6864
|
-
var _a;
|
|
7246
|
+
// All baskets (with collateral and market prefix info)
|
|
7247
|
+
const useAllBaskets = (collateralFilter) => {
|
|
6865
7248
|
const dataAll = useMarketDataAllPayload();
|
|
6866
|
-
|
|
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]);
|
|
6867
7256
|
};
|
|
6868
7257
|
// Find a basket by its exact asset composition (order-insensitive)
|
|
6869
7258
|
const useFindBasket = (longs, shorts) => {
|
|
6870
7259
|
const data = useMarketDataPayload();
|
|
7260
|
+
const perpMetaAssets = usePerpMetaAssets();
|
|
6871
7261
|
return useMemo(() => {
|
|
6872
7262
|
if (!data)
|
|
6873
7263
|
return undefined;
|
|
@@ -6881,17 +7271,28 @@ const useFindBasket = (longs, shorts) => {
|
|
|
6881
7271
|
: '';
|
|
6882
7272
|
const lKey = normalize(longs);
|
|
6883
7273
|
const sKey = normalize(shorts);
|
|
6884
|
-
const match = (item) => normalize(item.longAssets) === lKey &&
|
|
6885
|
-
|
|
6886
|
-
|
|
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]);
|
|
6887
7281
|
};
|
|
6888
7282
|
|
|
6889
|
-
async function toggleWatchlist(baseUrl, longAssets, shortAssets,
|
|
7283
|
+
async function toggleWatchlist(baseUrl, longAssets, shortAssets, hip3Assets) {
|
|
6890
7284
|
const url = joinUrl(baseUrl, '/watchlist');
|
|
6891
|
-
const mapAssets = (arr) => arr.map(a => ({ ...a, asset: toBackendSymbol(a.asset,
|
|
7285
|
+
const mapAssets = (arr) => arr.map((a) => ({ ...a, asset: toBackendSymbol(a.asset, hip3Assets) }));
|
|
6892
7286
|
try {
|
|
6893
|
-
const response = await apiClient.post(url, {
|
|
6894
|
-
|
|
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
|
+
};
|
|
6895
7296
|
}
|
|
6896
7297
|
catch (error) {
|
|
6897
7298
|
throw toApiError(error);
|
|
@@ -6903,11 +7304,11 @@ function useWatchlist() {
|
|
|
6903
7304
|
if (!context)
|
|
6904
7305
|
throw new Error('useWatchlist must be used within a PearHyperliquidProvider');
|
|
6905
7306
|
const { apiBaseUrl, isConnected } = context;
|
|
6906
|
-
const
|
|
7307
|
+
const hip3Assets = useHyperliquidData((s) => s.hip3Assets);
|
|
6907
7308
|
const marketData = useMarketDataPayload();
|
|
6908
7309
|
const isLoading = useMemo(() => !marketData && isConnected, [marketData, isConnected]);
|
|
6909
7310
|
const toggle = async (longAssets, shortAssets) => {
|
|
6910
|
-
const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets,
|
|
7311
|
+
const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets, hip3Assets);
|
|
6911
7312
|
// Server will push updated market-data over WS; nothing to set here
|
|
6912
7313
|
return resp;
|
|
6913
7314
|
};
|
|
@@ -7029,7 +7430,7 @@ async function logout(baseUrl, refreshTokenVal) {
|
|
|
7029
7430
|
function useAuth() {
|
|
7030
7431
|
const context = useContext(PearHyperliquidContext);
|
|
7031
7432
|
if (!context) {
|
|
7032
|
-
throw new Error(
|
|
7433
|
+
throw new Error('usePortfolio must be used within a PearHyperliquidProvider');
|
|
7033
7434
|
}
|
|
7034
7435
|
const { apiBaseUrl, clientId } = context;
|
|
7035
7436
|
const [isReady, setIsReady] = useState(false);
|
|
@@ -7039,46 +7440,26 @@ function useAuth() {
|
|
|
7039
7440
|
const setRefreshToken = useUserData((s) => s.setRefreshToken);
|
|
7040
7441
|
const isAuthenticated = useUserData((s) => s.isAuthenticated);
|
|
7041
7442
|
const setIsAuthenticated = useUserData((s) => s.setIsAuthenticated);
|
|
7042
|
-
const address = useUserData((s) => s.address);
|
|
7043
7443
|
const setAddress = useUserData((s) => s.setAddress);
|
|
7044
7444
|
useEffect(() => {
|
|
7045
|
-
if (typeof window ==
|
|
7445
|
+
if (typeof window == 'undefined') {
|
|
7046
7446
|
return;
|
|
7047
7447
|
}
|
|
7048
|
-
|
|
7049
|
-
const
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
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
|
-
]);
|
|
7448
|
+
const access = localStorage.getItem('accessToken');
|
|
7449
|
+
const refresh = localStorage.getItem('refreshToken');
|
|
7450
|
+
const addr = localStorage.getItem('address');
|
|
7451
|
+
setAccessToken(access);
|
|
7452
|
+
setRefreshToken(refresh);
|
|
7453
|
+
setAddress(addr);
|
|
7454
|
+
const authed = Boolean(access && addr);
|
|
7455
|
+
setIsAuthenticated(authed);
|
|
7456
|
+
setIsReady(true);
|
|
7457
|
+
}, [setAccessToken, setRefreshToken, setIsAuthenticated, setAddress]);
|
|
7071
7458
|
useEffect(() => {
|
|
7072
7459
|
const cleanup = addAuthInterceptors({
|
|
7073
7460
|
apiBaseUrl,
|
|
7074
7461
|
getAccessToken: () => {
|
|
7075
|
-
|
|
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);
|
|
7462
|
+
return typeof window !== 'undefined' ? window.localStorage.getItem('accessToken') : null;
|
|
7082
7463
|
},
|
|
7083
7464
|
refreshTokens: async () => {
|
|
7084
7465
|
const data = await refreshTokens();
|
|
@@ -7099,15 +7480,14 @@ function useAuth() {
|
|
|
7099
7480
|
async function loginWithSignedMessage(address, signature, timestamp) {
|
|
7100
7481
|
try {
|
|
7101
7482
|
const { data } = await authenticate(apiBaseUrl, {
|
|
7102
|
-
method:
|
|
7483
|
+
method: 'eip712',
|
|
7103
7484
|
address,
|
|
7104
7485
|
clientId,
|
|
7105
7486
|
details: { signature, timestamp },
|
|
7106
7487
|
});
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
window.localStorage.setItem(
|
|
7110
|
-
window.localStorage.setItem(refreshTokenKey, data.refreshToken);
|
|
7488
|
+
window.localStorage.setItem('accessToken', data.accessToken);
|
|
7489
|
+
window.localStorage.setItem('refreshToken', data.refreshToken);
|
|
7490
|
+
window.localStorage.setItem('address', address);
|
|
7111
7491
|
setAccessToken(data.accessToken);
|
|
7112
7492
|
setRefreshToken(data.refreshToken);
|
|
7113
7493
|
setAddress(address);
|
|
@@ -7119,16 +7499,10 @@ function useAuth() {
|
|
|
7119
7499
|
}
|
|
7120
7500
|
async function loginWithPrivyToken(address, appId, privyAccessToken) {
|
|
7121
7501
|
try {
|
|
7122
|
-
const { data } = await authenticateWithPrivy(apiBaseUrl, {
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
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);
|
|
7502
|
+
const { data } = await authenticateWithPrivy(apiBaseUrl, { address, clientId, appId, accessToken: privyAccessToken });
|
|
7503
|
+
window.localStorage.setItem('accessToken', data.accessToken);
|
|
7504
|
+
window.localStorage.setItem('refreshToken', data.refreshToken);
|
|
7505
|
+
window.localStorage.setItem('address', address);
|
|
7132
7506
|
setAccessToken(data.accessToken);
|
|
7133
7507
|
setRefreshToken(data.refreshToken);
|
|
7134
7508
|
setAddress(address);
|
|
@@ -7139,38 +7513,28 @@ function useAuth() {
|
|
|
7139
7513
|
}
|
|
7140
7514
|
}
|
|
7141
7515
|
async function refreshTokens() {
|
|
7142
|
-
const
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
const accessTokenKey = `${currentAddress}_accessToken`;
|
|
7149
|
-
const refreshTokenKey = `${currentAddress}_refreshToken`;
|
|
7150
|
-
window.localStorage.setItem(accessTokenKey, data.accessToken);
|
|
7151
|
-
window.localStorage.setItem(refreshTokenKey, data.refreshToken);
|
|
7516
|
+
const refresh = window.localStorage.getItem('refreshToken');
|
|
7517
|
+
if (!refresh)
|
|
7518
|
+
throw new Error('No refresh token');
|
|
7519
|
+
const { data } = await refreshToken(apiBaseUrl, refresh);
|
|
7520
|
+
window.localStorage.setItem('accessToken', data.accessToken);
|
|
7521
|
+
window.localStorage.setItem('refreshToken', data.refreshToken);
|
|
7152
7522
|
setAccessToken(data.accessToken);
|
|
7153
7523
|
setRefreshToken(data.refreshToken);
|
|
7154
7524
|
setIsAuthenticated(true);
|
|
7155
7525
|
return data;
|
|
7156
7526
|
}
|
|
7157
7527
|
async function logout$1() {
|
|
7158
|
-
const
|
|
7159
|
-
|
|
7160
|
-
if (currentRefresh) {
|
|
7528
|
+
const refresh = window.localStorage.getItem('refreshToken');
|
|
7529
|
+
if (refresh) {
|
|
7161
7530
|
try {
|
|
7162
|
-
await logout(apiBaseUrl,
|
|
7163
|
-
}
|
|
7164
|
-
catch (_a) {
|
|
7165
|
-
/* ignore */
|
|
7531
|
+
await logout(apiBaseUrl, refresh);
|
|
7166
7532
|
}
|
|
7533
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
7167
7534
|
}
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
window.localStorage.removeItem(accessTokenKey);
|
|
7172
|
-
window.localStorage.removeItem(refreshTokenKey);
|
|
7173
|
-
}
|
|
7535
|
+
window.localStorage.removeItem('accessToken');
|
|
7536
|
+
window.localStorage.removeItem('refreshToken');
|
|
7537
|
+
window.localStorage.removeItem('address');
|
|
7174
7538
|
setAccessToken(null);
|
|
7175
7539
|
setRefreshToken(null);
|
|
7176
7540
|
setAddress(null);
|
|
@@ -7193,12 +7557,13 @@ const PearHyperliquidContext = createContext(undefined);
|
|
|
7193
7557
|
/**
|
|
7194
7558
|
* React Provider for PearHyperliquidClient
|
|
7195
7559
|
*/
|
|
7196
|
-
const PearHyperliquidProvider = ({ children, apiBaseUrl =
|
|
7560
|
+
const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-v2.pearprotocol.io', clientId = 'PEARPROTOCOLUI', wsUrl = 'wss://hl-v2.pearprotocol.io/ws', }) => {
|
|
7197
7561
|
const address = useUserData((s) => s.address);
|
|
7198
7562
|
const setAddress = useUserData((s) => s.setAddress);
|
|
7199
7563
|
const perpsMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
|
|
7200
7564
|
const setPerpMetaAssets = useHyperliquidData((state) => state.setPerpMetaAssets);
|
|
7201
|
-
const
|
|
7565
|
+
const setHip3Assets = useHyperliquidData((state) => state.setHip3Assets);
|
|
7566
|
+
const setHip3MarketPrefixes = useHyperliquidData((state) => state.setHip3MarketPrefixes);
|
|
7202
7567
|
const websocketsEnabled = useMemo(() => Array.isArray(perpsMetaAssets) && perpsMetaAssets.length > 0, [perpsMetaAssets]);
|
|
7203
7568
|
const { isConnected, lastError } = useHyperliquidWebSocket({
|
|
7204
7569
|
wsUrl,
|
|
@@ -7213,28 +7578,62 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-v2.pearpro
|
|
|
7213
7578
|
if (perpsMetaAssets === null) {
|
|
7214
7579
|
fetchAllPerpMetas()
|
|
7215
7580
|
.then((res) => {
|
|
7216
|
-
|
|
7217
|
-
const
|
|
7218
|
-
const
|
|
7219
|
-
const cleanedPerpMetas =
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
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
|
+
});
|
|
7231
7624
|
});
|
|
7232
|
-
|
|
7625
|
+
setHip3Assets(assetToMarkets);
|
|
7626
|
+
setHip3MarketPrefixes(marketPrefixes);
|
|
7233
7627
|
setPerpMetaAssets(cleanedPerpMetas);
|
|
7234
7628
|
})
|
|
7235
7629
|
.catch(() => { });
|
|
7236
7630
|
}
|
|
7237
|
-
}, [
|
|
7631
|
+
}, [
|
|
7632
|
+
perpsMetaAssets,
|
|
7633
|
+
setPerpMetaAssets,
|
|
7634
|
+
setHip3Assets,
|
|
7635
|
+
setHip3MarketPrefixes,
|
|
7636
|
+
]);
|
|
7238
7637
|
// Auth methods now sourced from useAuth hook
|
|
7239
7638
|
useAutoSyncFills({
|
|
7240
7639
|
baseUrl: apiBaseUrl,
|
|
@@ -7254,9 +7653,6 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-v2.pearpro
|
|
|
7254
7653
|
// HyperLiquid native WebSocket state
|
|
7255
7654
|
nativeIsConnected,
|
|
7256
7655
|
nativeLastError,
|
|
7257
|
-
// Address utilities
|
|
7258
|
-
address,
|
|
7259
|
-
setAddress,
|
|
7260
7656
|
}), [
|
|
7261
7657
|
apiBaseUrl,
|
|
7262
7658
|
wsUrl,
|
|
@@ -7276,7 +7672,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-v2.pearpro
|
|
|
7276
7672
|
function usePearHyperliquid() {
|
|
7277
7673
|
const ctx = useContext(PearHyperliquidContext);
|
|
7278
7674
|
if (!ctx)
|
|
7279
|
-
throw new Error(
|
|
7675
|
+
throw new Error('usePearHyperliquid must be used within a PearHyperliquidProvider');
|
|
7280
7676
|
return ctx;
|
|
7281
7677
|
}
|
|
7282
7678
|
|
|
@@ -7367,4 +7763,4 @@ function mapCandleIntervalToTradingViewInterval(interval) {
|
|
|
7367
7763
|
}
|
|
7368
7764
|
}
|
|
7369
7765
|
|
|
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 };
|
|
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 };
|