@pear-protocol/hyperliquid-sdk 0.1.12 → 0.1.13-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clients/auth.d.ts +1 -1
- package/dist/hooks/useAuth.d.ts +3 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +408 -169
- package/dist/provider.d.ts +1 -1
- package/dist/store/hyperliquidDataStore.d.ts +2 -2
- package/dist/store/userDataStore.d.ts +2 -0
- package/dist/utils/http.d.ts +2 -2
- package/package.json +1 -1
package/dist/clients/auth.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApiResponse, GetEIP712MessageResponse, AuthenticateRequest, AuthenticateResponse, RefreshTokenResponse, LogoutResponse } from
|
|
1
|
+
import type { ApiResponse, GetEIP712MessageResponse, AuthenticateRequest, AuthenticateResponse, RefreshTokenResponse, LogoutResponse } from "../types";
|
|
2
2
|
export declare function getEIP712Message(baseUrl: string, address: string, clientId: string): Promise<ApiResponse<GetEIP712MessageResponse>>;
|
|
3
3
|
export declare function authenticate(baseUrl: string, body: AuthenticateRequest): Promise<ApiResponse<AuthenticateResponse>>;
|
|
4
4
|
/**
|
package/dist/hooks/useAuth.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GetEIP712MessageResponse } from
|
|
1
|
+
import type { GetEIP712MessageResponse } from '../types';
|
|
2
2
|
export declare function useAuth(): {
|
|
3
3
|
readonly isReady: boolean;
|
|
4
4
|
readonly isAuthenticated: boolean;
|
|
@@ -10,4 +10,6 @@ export declare function useAuth(): {
|
|
|
10
10
|
readonly loginWithPrivyToken: (address: string, appId: string, privyAccessToken: string) => Promise<void>;
|
|
11
11
|
readonly refreshTokens: () => Promise<import("../types").RefreshTokenResponse>;
|
|
12
12
|
readonly logout: () => Promise<void>;
|
|
13
|
+
readonly clearSession: () => void;
|
|
14
|
+
readonly setAddress: (address: string | null) => void;
|
|
13
15
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1429,6 +1429,8 @@ declare function useAuth(): {
|
|
|
1429
1429
|
readonly loginWithPrivyToken: (address: string, appId: string, privyAccessToken: string) => Promise<void>;
|
|
1430
1430
|
readonly refreshTokens: () => Promise<RefreshTokenResponse>;
|
|
1431
1431
|
readonly logout: () => Promise<void>;
|
|
1432
|
+
readonly clearSession: () => void;
|
|
1433
|
+
readonly setAddress: (address: string | null) => void;
|
|
1432
1434
|
};
|
|
1433
1435
|
|
|
1434
1436
|
interface MarginRequiredPerCollateral {
|
package/dist/index.js
CHANGED
|
@@ -65,21 +65,15 @@ const useUserData = create((set) => ({
|
|
|
65
65
|
userExtraAgents: null,
|
|
66
66
|
spotState: null,
|
|
67
67
|
userAbstractionMode: null,
|
|
68
|
+
isReady: false,
|
|
68
69
|
setUserAbstractionMode: (value) => set({ userAbstractionMode: value }),
|
|
69
70
|
setAccessToken: (token) => set({ accessToken: token }),
|
|
70
71
|
setRefreshToken: (token) => set({ refreshToken: token }),
|
|
71
72
|
setIsAuthenticated: (value) => set({ isAuthenticated: value }),
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
window.localStorage.removeItem('address');
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return { address };
|
|
82
|
-
}),
|
|
73
|
+
setIsReady: (value) => set({ isReady: value }),
|
|
74
|
+
setAddress: (address) => {
|
|
75
|
+
set({ address });
|
|
76
|
+
},
|
|
83
77
|
setTradeHistories: (value) => set({ tradeHistories: value }),
|
|
84
78
|
setRawOpenPositions: (value) => set({ rawOpenPositions: value }),
|
|
85
79
|
setOpenOrders: (value) => set({ openOrders: value }),
|
|
@@ -88,10 +82,11 @@ const useUserData = create((set) => ({
|
|
|
88
82
|
setNotifications: (value) => set({ notifications: value }),
|
|
89
83
|
setSpotState: (value) => set({ spotState: value }),
|
|
90
84
|
clean: () => set({
|
|
91
|
-
accessToken: null,
|
|
92
|
-
refreshToken: null,
|
|
93
|
-
isAuthenticated: false,
|
|
94
|
-
|
|
85
|
+
// accessToken: null,
|
|
86
|
+
// refreshToken: null,
|
|
87
|
+
// isAuthenticated: false,
|
|
88
|
+
// isReady: false,
|
|
89
|
+
// address: null,
|
|
95
90
|
tradeHistories: null,
|
|
96
91
|
rawOpenPositions: null,
|
|
97
92
|
openOrders: null,
|
|
@@ -384,19 +379,19 @@ class TokenMetadataExtractor {
|
|
|
384
379
|
if (!assetCtx) {
|
|
385
380
|
return null;
|
|
386
381
|
}
|
|
387
|
-
// Get current price - prefer
|
|
388
|
-
// fall back to
|
|
382
|
+
// Get current price - prefer allMids (real-time WebSocket data),
|
|
383
|
+
// fall back to assetCtx.midPx if not available
|
|
389
384
|
const actualSymbol = foundAsset.name;
|
|
390
385
|
let currentPrice = 0;
|
|
391
|
-
// Fallback: assetCtx.midPx (
|
|
392
|
-
if (!currentPrice || isNaN(currentPrice)) {
|
|
393
|
-
const currentPriceStr = allMids.mids[actualSymbol] || allMids.mids[symbol];
|
|
394
|
-
currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
|
|
395
|
-
}
|
|
396
|
-
// Primary source: allMids lookup
|
|
386
|
+
// Fallback: assetCtx.midPx (from REST API, less frequent)
|
|
397
387
|
if (assetCtx.midPx) {
|
|
398
388
|
currentPrice = parseFloat(assetCtx.midPx);
|
|
399
389
|
}
|
|
390
|
+
// Primary source: allMids (real-time WebSocket data)
|
|
391
|
+
const currentPriceStr = allMids.mids[actualSymbol] || allMids.mids[symbol];
|
|
392
|
+
if (currentPriceStr) {
|
|
393
|
+
currentPrice = parseFloat(currentPriceStr);
|
|
394
|
+
}
|
|
400
395
|
// Get previous day price
|
|
401
396
|
const prevDayPrice = parseFloat(assetCtx.prevDayPx);
|
|
402
397
|
// Calculate 24h price change
|
|
@@ -551,7 +546,7 @@ const useHyperliquidData = create((set) => ({
|
|
|
551
546
|
tokenMetadata: refreshTokenMetadata(state, { allMids: value }),
|
|
552
547
|
})),
|
|
553
548
|
setActiveAssetData: (value) => set((state) => {
|
|
554
|
-
const activeAssetData = typeof value ===
|
|
549
|
+
const activeAssetData = typeof value === "function" ? value(state.activeAssetData) : value;
|
|
555
550
|
return {
|
|
556
551
|
activeAssetData,
|
|
557
552
|
tokenMetadata: refreshTokenMetadata(state, { activeAssetData }),
|
|
@@ -591,7 +586,10 @@ const useHyperliquidData = create((set) => ({
|
|
|
591
586
|
setCandleData: (value) => set({ candleData: value }),
|
|
592
587
|
upsertActiveAssetData: (key, value) => set((state) => {
|
|
593
588
|
var _a;
|
|
594
|
-
const activeAssetData = {
|
|
589
|
+
const activeAssetData = {
|
|
590
|
+
...((_a = state.activeAssetData) !== null && _a !== void 0 ? _a : {}),
|
|
591
|
+
[key]: value,
|
|
592
|
+
};
|
|
595
593
|
return {
|
|
596
594
|
activeAssetData,
|
|
597
595
|
tokenMetadata: refreshTokenMetadata(state, { activeAssetData }, { symbols: [key] }),
|
|
@@ -906,6 +904,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
906
904
|
const reconnectAttemptsRef = useRef(0);
|
|
907
905
|
const manualCloseRef = useRef(false);
|
|
908
906
|
const onUserFillsRef = useRef(onUserFills);
|
|
907
|
+
const reconnectTimeoutRef = useRef(null);
|
|
909
908
|
const [readyState, setReadyState] = useState(ReadyState.CONNECTING);
|
|
910
909
|
// Keep the ref updated with the latest callback
|
|
911
910
|
useEffect(() => {
|
|
@@ -916,9 +915,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
916
915
|
try {
|
|
917
916
|
const message = JSON.parse(event.data);
|
|
918
917
|
// Handle subscription responses
|
|
919
|
-
if (
|
|
918
|
+
if ("success" in message || "error" in message) {
|
|
920
919
|
if (message.error) {
|
|
921
|
-
console.error(
|
|
920
|
+
console.error("[HyperLiquid WS] Subscription error:", message.error);
|
|
922
921
|
setLastError(message.error);
|
|
923
922
|
}
|
|
924
923
|
else {
|
|
@@ -927,44 +926,44 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
927
926
|
return;
|
|
928
927
|
}
|
|
929
928
|
// Handle channel data messages
|
|
930
|
-
if (
|
|
929
|
+
if ("channel" in message && "data" in message) {
|
|
931
930
|
const response = message;
|
|
932
931
|
switch (response.channel) {
|
|
933
|
-
case
|
|
932
|
+
case "userFills":
|
|
934
933
|
{
|
|
935
934
|
const maybePromise = (_a = onUserFillsRef.current) === null || _a === void 0 ? void 0 : _a.call(onUserFillsRef);
|
|
936
935
|
if (maybePromise instanceof Promise) {
|
|
937
|
-
maybePromise.catch((err) => console.error(
|
|
936
|
+
maybePromise.catch((err) => console.error("[HyperLiquid WS] userFills callback error", err));
|
|
938
937
|
}
|
|
939
938
|
}
|
|
940
939
|
break;
|
|
941
|
-
case
|
|
940
|
+
case "webData3":
|
|
942
941
|
const webData3 = response.data;
|
|
943
942
|
// finalAssetContexts now sourced from allDexsAssetCtxs channel
|
|
944
943
|
const finalAtOICaps = webData3.perpDexStates.flatMap((dex) => dex.perpsAtOpenInterestCap);
|
|
945
944
|
setFinalAtOICaps(finalAtOICaps);
|
|
946
945
|
setUserAbstractionMode(webData3.userState.abstraction || null);
|
|
947
946
|
break;
|
|
948
|
-
case
|
|
947
|
+
case "allDexsAssetCtxs":
|
|
949
948
|
{
|
|
950
949
|
const data = response.data;
|
|
951
950
|
// Store by DEX name, mapping '' to 'HYPERLIQUID'
|
|
952
951
|
const assetContextsByDex = new Map();
|
|
953
952
|
data.ctxs.forEach(([dexKey, ctxs]) => {
|
|
954
|
-
const dexName = dexKey ===
|
|
953
|
+
const dexName = dexKey === "" ? "HYPERLIQUID" : dexKey;
|
|
955
954
|
assetContextsByDex.set(dexName, ctxs || []);
|
|
956
955
|
});
|
|
957
956
|
setAssetContextsByDex(assetContextsByDex);
|
|
958
957
|
}
|
|
959
958
|
break;
|
|
960
|
-
case
|
|
959
|
+
case "allDexsClearinghouseState":
|
|
961
960
|
{
|
|
962
961
|
const data = response.data;
|
|
963
962
|
const states = (data.clearinghouseStates || [])
|
|
964
963
|
.map(([, s]) => s)
|
|
965
964
|
.filter(Boolean);
|
|
966
|
-
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v ||
|
|
967
|
-
const toStr = (n) => Number.isFinite(n) ? n.toString() :
|
|
965
|
+
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || "0") || 0), 0);
|
|
966
|
+
const toStr = (n) => Number.isFinite(n) ? n.toString() : "0";
|
|
968
967
|
const assetPositions = states.flatMap((s) => s.assetPositions || []);
|
|
969
968
|
const crossMaintenanceMarginUsed = toStr(sum(states.map((s) => s.crossMaintenanceMarginUsed)));
|
|
970
969
|
const crossMarginSummary = {
|
|
@@ -994,26 +993,26 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
994
993
|
setClearinghouseStateReceived(true);
|
|
995
994
|
}
|
|
996
995
|
break;
|
|
997
|
-
case
|
|
996
|
+
case "allMids":
|
|
998
997
|
{
|
|
999
998
|
const data = response.data;
|
|
1000
999
|
setAllMids(data);
|
|
1001
1000
|
}
|
|
1002
1001
|
break;
|
|
1003
|
-
case
|
|
1002
|
+
case "activeAssetData":
|
|
1004
1003
|
{
|
|
1005
1004
|
const assetData = response.data;
|
|
1006
1005
|
upsertActiveAssetData(assetData.coin, assetData);
|
|
1007
1006
|
}
|
|
1008
1007
|
break;
|
|
1009
|
-
case
|
|
1008
|
+
case "candle":
|
|
1010
1009
|
{
|
|
1011
1010
|
const candleDataItem = response.data;
|
|
1012
|
-
const symbol = candleDataItem.s ||
|
|
1011
|
+
const symbol = candleDataItem.s || "";
|
|
1013
1012
|
addCandleData(symbol, candleDataItem);
|
|
1014
1013
|
}
|
|
1015
1014
|
break;
|
|
1016
|
-
case
|
|
1015
|
+
case "spotState":
|
|
1017
1016
|
{
|
|
1018
1017
|
const spotStateData = response.data;
|
|
1019
1018
|
if (spotStateData === null || spotStateData === void 0 ? void 0 : spotStateData.spotState) {
|
|
@@ -1028,7 +1027,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1028
1027
|
}
|
|
1029
1028
|
catch (error) {
|
|
1030
1029
|
const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
|
|
1031
|
-
console.error(
|
|
1030
|
+
console.error("[HyperLiquid WS] Parse error:", errorMessage, "Raw message:", event.data);
|
|
1032
1031
|
setLastError(errorMessage);
|
|
1033
1032
|
}
|
|
1034
1033
|
}, [
|
|
@@ -1042,9 +1041,14 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1042
1041
|
setSpotState,
|
|
1043
1042
|
]);
|
|
1044
1043
|
const connect = useCallback(() => {
|
|
1045
|
-
console.log(
|
|
1044
|
+
console.log("[HyperLiquid WS] connect() called, enabled:", enabled);
|
|
1046
1045
|
if (!enabled)
|
|
1047
1046
|
return;
|
|
1047
|
+
// Clear any pending reconnect timeout
|
|
1048
|
+
if (reconnectTimeoutRef.current) {
|
|
1049
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
1050
|
+
reconnectTimeoutRef.current = null;
|
|
1051
|
+
}
|
|
1048
1052
|
try {
|
|
1049
1053
|
// Avoid opening multiple sockets if one is already active or connecting
|
|
1050
1054
|
if (wsRef.current &&
|
|
@@ -1053,10 +1057,10 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1053
1057
|
console.log('[HyperLiquid WS] connect() returning early - socket already exists, readyState:', wsRef.current.readyState);
|
|
1054
1058
|
return;
|
|
1055
1059
|
}
|
|
1056
|
-
console.log(
|
|
1060
|
+
console.log("[HyperLiquid WS] Creating new WebSocket connection");
|
|
1057
1061
|
manualCloseRef.current = false;
|
|
1058
1062
|
setReadyState(ReadyState.CONNECTING);
|
|
1059
|
-
const ws = new WebSocket(
|
|
1063
|
+
const ws = new WebSocket("wss://api.hyperliquid.xyz/ws");
|
|
1060
1064
|
wsRef.current = ws;
|
|
1061
1065
|
ws.onopen = () => {
|
|
1062
1066
|
reconnectAttemptsRef.current = 0;
|
|
@@ -1065,17 +1069,22 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1065
1069
|
};
|
|
1066
1070
|
ws.onmessage = handleMessage;
|
|
1067
1071
|
ws.onerror = (event) => {
|
|
1068
|
-
console.error(
|
|
1069
|
-
setLastError(
|
|
1072
|
+
console.error("[HyperLiquid WS] Connection error:", event);
|
|
1073
|
+
setLastError("WebSocket error");
|
|
1070
1074
|
};
|
|
1071
1075
|
ws.onclose = () => {
|
|
1072
1076
|
setReadyState(ReadyState.CLOSED);
|
|
1073
|
-
|
|
1077
|
+
// Reset subscription state so effects will resubscribe on reconnect
|
|
1078
|
+
setSubscribedAddress(null);
|
|
1079
|
+
setSubscribedTokens([]);
|
|
1080
|
+
setSubscribedCandleTokens([]);
|
|
1081
|
+
setClearinghouseStateReceived(false);
|
|
1082
|
+
if (!manualCloseRef.current) {
|
|
1074
1083
|
reconnectAttemptsRef.current += 1;
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}
|
|
1078
|
-
setTimeout(() => connect(),
|
|
1084
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s (max), then stay at 30s
|
|
1085
|
+
const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current - 1), 30000);
|
|
1086
|
+
console.log(`[HyperLiquid WS] Reconnecting in ${delay}ms (attempt ${reconnectAttemptsRef.current})`);
|
|
1087
|
+
reconnectTimeoutRef.current = setTimeout(() => connect(), delay);
|
|
1079
1088
|
}
|
|
1080
1089
|
};
|
|
1081
1090
|
}
|
|
@@ -1086,9 +1095,53 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1086
1095
|
useEffect(() => {
|
|
1087
1096
|
console.log('[HyperLiquid WS] Connection effect running - calling connect()');
|
|
1088
1097
|
connect();
|
|
1098
|
+
// Handle online/offline events to reconnect when internet is restored
|
|
1099
|
+
const handleOnline = () => {
|
|
1100
|
+
console.log('[HyperLiquid WS] Browser went online, attempting reconnect');
|
|
1101
|
+
// Reset reconnect attempts when internet comes back
|
|
1102
|
+
reconnectAttemptsRef.current = 0;
|
|
1103
|
+
// Clear any pending reconnect timeout
|
|
1104
|
+
if (reconnectTimeoutRef.current) {
|
|
1105
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
1106
|
+
reconnectTimeoutRef.current = null;
|
|
1107
|
+
}
|
|
1108
|
+
// Reset subscription state so effects will resubscribe on reconnect
|
|
1109
|
+
setSubscribedAddress(null);
|
|
1110
|
+
setSubscribedTokens([]);
|
|
1111
|
+
setSubscribedCandleTokens([]);
|
|
1112
|
+
setClearinghouseStateReceived(false);
|
|
1113
|
+
// Close existing socket if in a bad state
|
|
1114
|
+
if (wsRef.current &&
|
|
1115
|
+
wsRef.current.readyState !== WebSocket.OPEN &&
|
|
1116
|
+
wsRef.current.readyState !== WebSocket.CONNECTING) {
|
|
1117
|
+
try {
|
|
1118
|
+
wsRef.current.close();
|
|
1119
|
+
}
|
|
1120
|
+
catch (_a) { }
|
|
1121
|
+
wsRef.current = null;
|
|
1122
|
+
}
|
|
1123
|
+
// Attempt to reconnect
|
|
1124
|
+
connect();
|
|
1125
|
+
};
|
|
1126
|
+
const handleOffline = () => {
|
|
1127
|
+
console.log('[HyperLiquid WS] Browser went offline');
|
|
1128
|
+
// Clear pending reconnect timeout since we're offline
|
|
1129
|
+
if (reconnectTimeoutRef.current) {
|
|
1130
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
1131
|
+
reconnectTimeoutRef.current = null;
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
window.addEventListener('online', handleOnline);
|
|
1135
|
+
window.addEventListener('offline', handleOffline);
|
|
1089
1136
|
return () => {
|
|
1090
1137
|
console.log('[HyperLiquid WS] Connection effect cleanup - closing existing connection');
|
|
1138
|
+
window.removeEventListener('online', handleOnline);
|
|
1139
|
+
window.removeEventListener('offline', handleOffline);
|
|
1091
1140
|
manualCloseRef.current = true;
|
|
1141
|
+
if (reconnectTimeoutRef.current) {
|
|
1142
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
1143
|
+
reconnectTimeoutRef.current = null;
|
|
1144
|
+
}
|
|
1092
1145
|
if (wsRef.current) {
|
|
1093
1146
|
try {
|
|
1094
1147
|
wsRef.current.close();
|
|
@@ -1109,7 +1162,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1109
1162
|
if (isConnected) {
|
|
1110
1163
|
// Send ping every 30 seconds
|
|
1111
1164
|
pingIntervalRef.current = setInterval(() => {
|
|
1112
|
-
sendJsonMessage({ method:
|
|
1165
|
+
sendJsonMessage({ method: "ping" });
|
|
1113
1166
|
}, 30000);
|
|
1114
1167
|
}
|
|
1115
1168
|
else {
|
|
@@ -1127,27 +1180,27 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1127
1180
|
}, [isConnected, sendJsonMessage]);
|
|
1128
1181
|
// Handle address subscription changes
|
|
1129
1182
|
useEffect(() => {
|
|
1130
|
-
const DEFAULT_ADDRESS =
|
|
1183
|
+
const DEFAULT_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
1131
1184
|
const userAddress = (address || DEFAULT_ADDRESS).toLowerCase();
|
|
1132
1185
|
const normalizedSubscribedAddress = (subscribedAddress === null || subscribedAddress === void 0 ? void 0 : subscribedAddress.toLowerCase()) || null;
|
|
1133
|
-
console.log(
|
|
1134
|
-
console.log(
|
|
1135
|
-
console.log(
|
|
1186
|
+
console.log("[HyperLiquid WS] Address subscription effect running");
|
|
1187
|
+
console.log("[HyperLiquid WS] address:", address, "userAddress:", userAddress, "subscribedAddress:", subscribedAddress, "normalizedSubscribedAddress:", normalizedSubscribedAddress);
|
|
1188
|
+
console.log("[HyperLiquid WS] isConnected:", isConnected);
|
|
1136
1189
|
if (normalizedSubscribedAddress === userAddress) {
|
|
1137
|
-
console.log(
|
|
1190
|
+
console.log("[HyperLiquid WS] Address unchanged, skipping subscription update");
|
|
1138
1191
|
return;
|
|
1139
1192
|
}
|
|
1140
1193
|
if (!isConnected) {
|
|
1141
|
-
console.log(
|
|
1194
|
+
console.log("[HyperLiquid WS] Not connected, skipping subscription update");
|
|
1142
1195
|
return;
|
|
1143
1196
|
}
|
|
1144
1197
|
// Unsubscribe from previous address if exists
|
|
1145
1198
|
if (subscribedAddress) {
|
|
1146
|
-
console.log(
|
|
1199
|
+
console.log("[HyperLiquid WS] Unsubscribing from previous address:", subscribedAddress);
|
|
1147
1200
|
const unsubscribeMessage = {
|
|
1148
|
-
method:
|
|
1201
|
+
method: "unsubscribe",
|
|
1149
1202
|
subscription: {
|
|
1150
|
-
type:
|
|
1203
|
+
type: "webData3",
|
|
1151
1204
|
user: subscribedAddress,
|
|
1152
1205
|
},
|
|
1153
1206
|
};
|
|
@@ -1155,54 +1208,54 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1155
1208
|
// Unsubscribe from spotState for previous address
|
|
1156
1209
|
if (subscribedAddress !== DEFAULT_ADDRESS) {
|
|
1157
1210
|
const unsubscribeSpotState = {
|
|
1158
|
-
method:
|
|
1211
|
+
method: "unsubscribe",
|
|
1159
1212
|
subscription: {
|
|
1160
|
-
type:
|
|
1213
|
+
type: "spotState",
|
|
1161
1214
|
user: subscribedAddress,
|
|
1162
1215
|
},
|
|
1163
1216
|
};
|
|
1164
1217
|
sendJsonMessage(unsubscribeSpotState);
|
|
1165
1218
|
}
|
|
1166
1219
|
const unsubscribeAllDexsClearinghouseState = {
|
|
1167
|
-
method:
|
|
1220
|
+
method: "unsubscribe",
|
|
1168
1221
|
subscription: {
|
|
1169
|
-
type:
|
|
1222
|
+
type: "allDexsClearinghouseState",
|
|
1170
1223
|
user: subscribedAddress,
|
|
1171
1224
|
},
|
|
1172
1225
|
};
|
|
1173
1226
|
sendJsonMessage(unsubscribeAllDexsClearinghouseState);
|
|
1174
1227
|
const unsubscribeUserFills = {
|
|
1175
|
-
method:
|
|
1228
|
+
method: "unsubscribe",
|
|
1176
1229
|
subscription: {
|
|
1177
|
-
type:
|
|
1230
|
+
type: "userFills",
|
|
1178
1231
|
user: subscribedAddress,
|
|
1179
1232
|
},
|
|
1180
1233
|
};
|
|
1181
1234
|
sendJsonMessage(unsubscribeUserFills);
|
|
1182
1235
|
}
|
|
1183
1236
|
const subscribeWebData3 = {
|
|
1184
|
-
method:
|
|
1237
|
+
method: "subscribe",
|
|
1185
1238
|
subscription: {
|
|
1186
|
-
type:
|
|
1239
|
+
type: "webData3",
|
|
1187
1240
|
user: userAddress,
|
|
1188
1241
|
},
|
|
1189
1242
|
};
|
|
1190
1243
|
// Subscribe to allMids
|
|
1191
1244
|
const subscribeAllMids = {
|
|
1192
|
-
method:
|
|
1245
|
+
method: "subscribe",
|
|
1193
1246
|
subscription: {
|
|
1194
|
-
type:
|
|
1195
|
-
dex:
|
|
1247
|
+
type: "allMids",
|
|
1248
|
+
dex: "ALL_DEXS",
|
|
1196
1249
|
},
|
|
1197
1250
|
};
|
|
1198
1251
|
// Subscribe to allDexsAssetCtxs (no payload params, global feed)
|
|
1199
1252
|
const subscribeAllDexsAssetCtxs = {
|
|
1200
|
-
method:
|
|
1253
|
+
method: "subscribe",
|
|
1201
1254
|
subscription: {
|
|
1202
|
-
type:
|
|
1255
|
+
type: "allDexsAssetCtxs",
|
|
1203
1256
|
},
|
|
1204
1257
|
};
|
|
1205
|
-
console.log(
|
|
1258
|
+
console.log("[HyperLiquid WS] Subscribing to new address:", userAddress);
|
|
1206
1259
|
sendJsonMessage(subscribeWebData3);
|
|
1207
1260
|
sendJsonMessage(subscribeAllMids);
|
|
1208
1261
|
sendJsonMessage(subscribeAllDexsAssetCtxs);
|
|
@@ -1210,9 +1263,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1210
1263
|
// Only subscribe if we have a real user address (not the default)
|
|
1211
1264
|
if (userAddress !== DEFAULT_ADDRESS.toLowerCase()) {
|
|
1212
1265
|
const subscribeSpotState = {
|
|
1213
|
-
method:
|
|
1266
|
+
method: "subscribe",
|
|
1214
1267
|
subscription: {
|
|
1215
|
-
type:
|
|
1268
|
+
type: "spotState",
|
|
1216
1269
|
user: userAddress,
|
|
1217
1270
|
},
|
|
1218
1271
|
};
|
|
@@ -1222,9 +1275,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1222
1275
|
// Only subscribe if we have a real user address (not the default)
|
|
1223
1276
|
if (userAddress !== DEFAULT_ADDRESS.toLowerCase()) {
|
|
1224
1277
|
const subscribeAllDexsClearinghouseState = {
|
|
1225
|
-
method:
|
|
1278
|
+
method: "subscribe",
|
|
1226
1279
|
subscription: {
|
|
1227
|
-
type:
|
|
1280
|
+
type: "allDexsClearinghouseState",
|
|
1228
1281
|
user: userAddress,
|
|
1229
1282
|
},
|
|
1230
1283
|
};
|
|
@@ -1258,9 +1311,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1258
1311
|
!userSummary)
|
|
1259
1312
|
return;
|
|
1260
1313
|
const subscribeUserFills = {
|
|
1261
|
-
method:
|
|
1314
|
+
method: "subscribe",
|
|
1262
1315
|
subscription: {
|
|
1263
|
-
type:
|
|
1316
|
+
type: "userFills",
|
|
1264
1317
|
user: subscribedAddress,
|
|
1265
1318
|
},
|
|
1266
1319
|
};
|
|
@@ -1282,9 +1335,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1282
1335
|
// Unsubscribe from tokens no longer in the list
|
|
1283
1336
|
tokensToUnsubscribe.forEach((token) => {
|
|
1284
1337
|
const unsubscribeMessage = {
|
|
1285
|
-
method:
|
|
1338
|
+
method: "unsubscribe",
|
|
1286
1339
|
subscription: {
|
|
1287
|
-
type:
|
|
1340
|
+
type: "activeAssetData",
|
|
1288
1341
|
user: address,
|
|
1289
1342
|
coin: token,
|
|
1290
1343
|
},
|
|
@@ -1294,9 +1347,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1294
1347
|
// Subscribe to new tokens
|
|
1295
1348
|
tokensToSubscribe.forEach((token) => {
|
|
1296
1349
|
const subscribeMessage = {
|
|
1297
|
-
method:
|
|
1350
|
+
method: "subscribe",
|
|
1298
1351
|
subscription: {
|
|
1299
|
-
type:
|
|
1352
|
+
type: "activeAssetData",
|
|
1300
1353
|
user: address,
|
|
1301
1354
|
coin: token,
|
|
1302
1355
|
},
|
|
@@ -1325,9 +1378,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1325
1378
|
if (prevInterval && prevInterval !== candleInterval) {
|
|
1326
1379
|
subscribedCandleTokens.forEach((token) => {
|
|
1327
1380
|
const unsubscribeMessage = {
|
|
1328
|
-
method:
|
|
1381
|
+
method: "unsubscribe",
|
|
1329
1382
|
subscription: {
|
|
1330
|
-
type:
|
|
1383
|
+
type: "candle",
|
|
1331
1384
|
coin: token,
|
|
1332
1385
|
interval: prevInterval,
|
|
1333
1386
|
},
|
|
@@ -1342,9 +1395,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1342
1395
|
// Unsubscribe from tokens no longer in the list
|
|
1343
1396
|
tokensToUnsubscribe.forEach((token) => {
|
|
1344
1397
|
const unsubscribeMessage = {
|
|
1345
|
-
method:
|
|
1398
|
+
method: "unsubscribe",
|
|
1346
1399
|
subscription: {
|
|
1347
|
-
type:
|
|
1400
|
+
type: "candle",
|
|
1348
1401
|
coin: token,
|
|
1349
1402
|
interval: candleInterval,
|
|
1350
1403
|
},
|
|
@@ -1354,9 +1407,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1354
1407
|
// Subscribe to new tokens
|
|
1355
1408
|
tokensToSubscribe.forEach((token) => {
|
|
1356
1409
|
const subscribeMessage = {
|
|
1357
|
-
method:
|
|
1410
|
+
method: "subscribe",
|
|
1358
1411
|
subscription: {
|
|
1359
|
-
type:
|
|
1412
|
+
type: "candle",
|
|
1360
1413
|
coin: token,
|
|
1361
1414
|
interval: candleInterval,
|
|
1362
1415
|
},
|
|
@@ -5871,10 +5924,10 @@ function toApiError(error) {
|
|
|
5871
5924
|
var _a;
|
|
5872
5925
|
const axiosError = error;
|
|
5873
5926
|
const payload = (axiosError && axiosError.response ? axiosError.response.data : undefined);
|
|
5874
|
-
const message = typeof payload ===
|
|
5927
|
+
const message = typeof payload === "object" && payload && "message" in payload
|
|
5875
5928
|
? String(payload.message)
|
|
5876
|
-
: (axiosError === null || axiosError === void 0 ? void 0 : axiosError.message) ||
|
|
5877
|
-
const errField = typeof payload ===
|
|
5929
|
+
: (axiosError === null || axiosError === void 0 ? void 0 : axiosError.message) || "Request failed";
|
|
5930
|
+
const errField = typeof payload === "object" && payload && "error" in payload
|
|
5878
5931
|
? String(payload.error)
|
|
5879
5932
|
: undefined;
|
|
5880
5933
|
return {
|
|
@@ -5884,8 +5937,8 @@ function toApiError(error) {
|
|
|
5884
5937
|
};
|
|
5885
5938
|
}
|
|
5886
5939
|
function joinUrl(baseUrl, path) {
|
|
5887
|
-
const cleanBase = baseUrl.replace(/\/$/,
|
|
5888
|
-
const cleanPath = path.startsWith(
|
|
5940
|
+
const cleanBase = baseUrl.replace(/\/$/, "");
|
|
5941
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
5889
5942
|
return `${cleanBase}${cleanPath}`;
|
|
5890
5943
|
}
|
|
5891
5944
|
/**
|
|
@@ -5914,7 +5967,7 @@ function addAuthInterceptors(params) {
|
|
|
5914
5967
|
pendingRequests = [];
|
|
5915
5968
|
}
|
|
5916
5969
|
const isOurApiUrl = (url) => Boolean(url && url.startsWith(apiBaseUrl));
|
|
5917
|
-
const isRefreshUrl = (url) => Boolean(url && url.startsWith(joinUrl(apiBaseUrl,
|
|
5970
|
+
const isRefreshUrl = (url) => Boolean(url && url.startsWith(joinUrl(apiBaseUrl, "/auth/refresh")));
|
|
5918
5971
|
const reqId = apiClient.interceptors.request.use((config) => {
|
|
5919
5972
|
var _a;
|
|
5920
5973
|
try {
|
|
@@ -5922,11 +5975,12 @@ function addAuthInterceptors(params) {
|
|
|
5922
5975
|
const token = getAccessToken();
|
|
5923
5976
|
if (token) {
|
|
5924
5977
|
config.headers = (_a = config.headers) !== null && _a !== void 0 ? _a : {};
|
|
5925
|
-
|
|
5978
|
+
config.headers["Authorization"] = `Bearer ${token}`;
|
|
5926
5979
|
}
|
|
5927
5980
|
}
|
|
5928
5981
|
}
|
|
5929
|
-
catch (
|
|
5982
|
+
catch (err) {
|
|
5983
|
+
console.error("[Auth Interceptor] Request interceptor error:", err);
|
|
5930
5984
|
}
|
|
5931
5985
|
return config;
|
|
5932
5986
|
});
|
|
@@ -5937,22 +5991,36 @@ function addAuthInterceptors(params) {
|
|
|
5937
5991
|
const url = originalRequest === null || originalRequest === void 0 ? void 0 : originalRequest.url;
|
|
5938
5992
|
// If not our API or not 401, just reject
|
|
5939
5993
|
if (!status || status !== 401 || !isOurApiUrl(url)) {
|
|
5994
|
+
if (status === 401) {
|
|
5995
|
+
console.warn("[Auth Interceptor] 401 received but URL check failed:", {
|
|
5996
|
+
url,
|
|
5997
|
+
apiBaseUrl,
|
|
5998
|
+
isOurApiUrl: isOurApiUrl(url),
|
|
5999
|
+
});
|
|
6000
|
+
}
|
|
5940
6001
|
return Promise.reject(error);
|
|
5941
6002
|
}
|
|
6003
|
+
console.log("[Auth Interceptor] 401 detected, attempting token refresh for URL:", url);
|
|
5942
6004
|
// If the 401 is from refresh endpoint itself -> force logout
|
|
5943
6005
|
if (isRefreshUrl(url)) {
|
|
6006
|
+
console.warn("[Auth Interceptor] Refresh endpoint returned 401, logging out");
|
|
5944
6007
|
try {
|
|
5945
6008
|
await logout();
|
|
5946
6009
|
}
|
|
5947
|
-
catch (
|
|
6010
|
+
catch (err) {
|
|
6011
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6012
|
+
}
|
|
5948
6013
|
return Promise.reject(error);
|
|
5949
6014
|
}
|
|
5950
6015
|
// Prevent infinite loop
|
|
5951
6016
|
if (originalRequest && originalRequest._retry) {
|
|
6017
|
+
console.warn("[Auth Interceptor] Request already retried, logging out");
|
|
5952
6018
|
try {
|
|
5953
6019
|
await logout();
|
|
5954
6020
|
}
|
|
5955
|
-
catch (
|
|
6021
|
+
catch (err) {
|
|
6022
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6023
|
+
}
|
|
5956
6024
|
return Promise.reject(error);
|
|
5957
6025
|
}
|
|
5958
6026
|
// Mark so we don't retry twice
|
|
@@ -5966,31 +6034,45 @@ function addAuthInterceptors(params) {
|
|
|
5966
6034
|
if (!newToken || !originalRequest)
|
|
5967
6035
|
return reject(error);
|
|
5968
6036
|
originalRequest.headers = (_a = originalRequest.headers) !== null && _a !== void 0 ? _a : {};
|
|
5969
|
-
originalRequest.headers[
|
|
6037
|
+
originalRequest.headers["Authorization"] =
|
|
6038
|
+
`Bearer ${newToken}`;
|
|
5970
6039
|
resolve(apiClient.request(originalRequest));
|
|
5971
6040
|
});
|
|
5972
6041
|
});
|
|
5973
6042
|
}
|
|
5974
6043
|
isRefreshing = true;
|
|
5975
6044
|
try {
|
|
6045
|
+
console.log("[Auth Interceptor] Refreshing tokens...");
|
|
5976
6046
|
const refreshed = await refreshTokens();
|
|
5977
|
-
const newAccessToken = (_b = (refreshed &&
|
|
6047
|
+
const newAccessToken = (_b = (refreshed &&
|
|
6048
|
+
(refreshed.accessToken || ((_a = refreshed.data) === null || _a === void 0 ? void 0 : _a.accessToken)))) !== null && _b !== void 0 ? _b : null;
|
|
6049
|
+
if (!newAccessToken) {
|
|
6050
|
+
console.error("[Auth Interceptor] Token refresh succeeded but no access token in response:", refreshed);
|
|
6051
|
+
}
|
|
6052
|
+
else {
|
|
6053
|
+
console.log("[Auth Interceptor] Token refresh successful");
|
|
6054
|
+
}
|
|
5978
6055
|
resolvePendingRequests(newAccessToken);
|
|
5979
6056
|
if (originalRequest) {
|
|
5980
6057
|
originalRequest.headers = (_c = originalRequest.headers) !== null && _c !== void 0 ? _c : {};
|
|
5981
6058
|
if (newAccessToken)
|
|
5982
|
-
|
|
6059
|
+
originalRequest.headers["Authorization"] =
|
|
6060
|
+
`Bearer ${newAccessToken}`;
|
|
6061
|
+
console.log("[Auth Interceptor] Retrying original request with new token");
|
|
5983
6062
|
const resp = await apiClient.request(originalRequest);
|
|
5984
6063
|
return resp;
|
|
5985
6064
|
}
|
|
5986
6065
|
return Promise.reject(error);
|
|
5987
6066
|
}
|
|
5988
6067
|
catch (refreshErr) {
|
|
6068
|
+
console.error("[Auth Interceptor] Token refresh failed:", refreshErr);
|
|
5989
6069
|
resolvePendingRequests(null);
|
|
5990
6070
|
try {
|
|
5991
6071
|
await logout();
|
|
5992
6072
|
}
|
|
5993
|
-
catch (
|
|
6073
|
+
catch (err) {
|
|
6074
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6075
|
+
}
|
|
5994
6076
|
return Promise.reject(refreshErr);
|
|
5995
6077
|
}
|
|
5996
6078
|
finally {
|
|
@@ -6001,11 +6083,15 @@ function addAuthInterceptors(params) {
|
|
|
6001
6083
|
try {
|
|
6002
6084
|
apiClient.interceptors.request.eject(reqId);
|
|
6003
6085
|
}
|
|
6004
|
-
catch (
|
|
6086
|
+
catch (err) {
|
|
6087
|
+
console.error("[Auth Interceptor] Failed to eject request interceptor:", err);
|
|
6088
|
+
}
|
|
6005
6089
|
try {
|
|
6006
6090
|
apiClient.interceptors.response.eject(resId);
|
|
6007
6091
|
}
|
|
6008
|
-
catch (
|
|
6092
|
+
catch (err) {
|
|
6093
|
+
console.error("[Auth Interceptor] Failed to eject response interceptor:", err);
|
|
6094
|
+
}
|
|
6009
6095
|
};
|
|
6010
6096
|
}
|
|
6011
6097
|
|
|
@@ -7511,20 +7597,34 @@ function usePortfolio() {
|
|
|
7511
7597
|
}
|
|
7512
7598
|
|
|
7513
7599
|
async function getEIP712Message(baseUrl, address, clientId) {
|
|
7514
|
-
const url = joinUrl(baseUrl,
|
|
7600
|
+
const url = joinUrl(baseUrl, "/auth/eip712-message");
|
|
7515
7601
|
try {
|
|
7516
|
-
const resp = await
|
|
7517
|
-
|
|
7602
|
+
const resp = await apiClient.get(url, {
|
|
7603
|
+
params: { address, clientId },
|
|
7604
|
+
timeout: 30000,
|
|
7605
|
+
});
|
|
7606
|
+
return {
|
|
7607
|
+
data: resp.data,
|
|
7608
|
+
status: resp.status,
|
|
7609
|
+
headers: resp.headers,
|
|
7610
|
+
};
|
|
7518
7611
|
}
|
|
7519
7612
|
catch (error) {
|
|
7520
7613
|
throw toApiError(error);
|
|
7521
7614
|
}
|
|
7522
7615
|
}
|
|
7523
7616
|
async function authenticate(baseUrl, body) {
|
|
7524
|
-
const url = joinUrl(baseUrl,
|
|
7617
|
+
const url = joinUrl(baseUrl, "/auth/login");
|
|
7525
7618
|
try {
|
|
7526
|
-
const resp = await
|
|
7527
|
-
|
|
7619
|
+
const resp = await apiClient.post(url, body, {
|
|
7620
|
+
headers: { "Content-Type": "application/json" },
|
|
7621
|
+
timeout: 30000,
|
|
7622
|
+
});
|
|
7623
|
+
return {
|
|
7624
|
+
data: resp.data,
|
|
7625
|
+
status: resp.status,
|
|
7626
|
+
headers: resp.headers,
|
|
7627
|
+
};
|
|
7528
7628
|
}
|
|
7529
7629
|
catch (error) {
|
|
7530
7630
|
throw toApiError(error);
|
|
@@ -7535,7 +7635,7 @@ async function authenticate(baseUrl, body) {
|
|
|
7535
7635
|
*/
|
|
7536
7636
|
async function authenticateWithPrivy(baseUrl, params) {
|
|
7537
7637
|
const body = {
|
|
7538
|
-
method:
|
|
7638
|
+
method: "privy_access_token",
|
|
7539
7639
|
address: params.address,
|
|
7540
7640
|
clientId: params.clientId,
|
|
7541
7641
|
details: { appId: params.appId, accessToken: params.accessToken },
|
|
@@ -7543,62 +7643,124 @@ async function authenticateWithPrivy(baseUrl, params) {
|
|
|
7543
7643
|
return authenticate(baseUrl, body);
|
|
7544
7644
|
}
|
|
7545
7645
|
async function refreshToken(baseUrl, refreshTokenVal) {
|
|
7546
|
-
const url = joinUrl(baseUrl,
|
|
7646
|
+
const url = joinUrl(baseUrl, "/auth/refresh");
|
|
7547
7647
|
try {
|
|
7548
|
-
const resp = await
|
|
7549
|
-
return {
|
|
7648
|
+
const resp = await apiClient.post(url, { refreshToken: refreshTokenVal }, { headers: { "Content-Type": "application/json" }, timeout: 30000 });
|
|
7649
|
+
return {
|
|
7650
|
+
data: resp.data,
|
|
7651
|
+
status: resp.status,
|
|
7652
|
+
headers: resp.headers,
|
|
7653
|
+
};
|
|
7550
7654
|
}
|
|
7551
7655
|
catch (error) {
|
|
7552
7656
|
throw toApiError(error);
|
|
7553
7657
|
}
|
|
7554
7658
|
}
|
|
7555
7659
|
async function logout(baseUrl, refreshTokenVal) {
|
|
7556
|
-
const url = joinUrl(baseUrl,
|
|
7660
|
+
const url = joinUrl(baseUrl, "/auth/logout");
|
|
7557
7661
|
try {
|
|
7558
|
-
const resp = await
|
|
7559
|
-
return {
|
|
7662
|
+
const resp = await apiClient.post(url, { refreshToken: refreshTokenVal }, { headers: { "Content-Type": "application/json" }, timeout: 30000 });
|
|
7663
|
+
return {
|
|
7664
|
+
data: resp.data,
|
|
7665
|
+
status: resp.status,
|
|
7666
|
+
headers: resp.headers,
|
|
7667
|
+
};
|
|
7560
7668
|
}
|
|
7561
7669
|
catch (error) {
|
|
7562
7670
|
throw toApiError(error);
|
|
7563
7671
|
}
|
|
7564
7672
|
}
|
|
7565
7673
|
|
|
7674
|
+
// Token expiration constants
|
|
7675
|
+
const ACCESS_TOKEN_BUFFER_MS = 5 * 60 * 1000; // Refresh 5 min before expiry
|
|
7676
|
+
const REFRESH_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days fallback
|
|
7677
|
+
function nowMs() {
|
|
7678
|
+
return Date.now();
|
|
7679
|
+
}
|
|
7680
|
+
function calcExpiresAt(expiresInSeconds) {
|
|
7681
|
+
return nowMs() + expiresInSeconds * 1000;
|
|
7682
|
+
}
|
|
7566
7683
|
function useAuth() {
|
|
7567
7684
|
const context = useContext(PearHyperliquidContext);
|
|
7568
7685
|
if (!context) {
|
|
7569
|
-
throw new Error(
|
|
7686
|
+
throw new Error('useAuth must be used within a PearHyperliquidProvider');
|
|
7570
7687
|
}
|
|
7571
7688
|
const { apiBaseUrl, clientId } = context;
|
|
7572
|
-
const [isReady, setIsReady] = useState(false);
|
|
7573
7689
|
const accessToken = useUserData((s) => s.accessToken);
|
|
7574
7690
|
const refreshToken$1 = useUserData((s) => s.refreshToken);
|
|
7691
|
+
const isReady = useUserData((s) => s.isReady);
|
|
7692
|
+
const isAuthenticated = useUserData((s) => s.isAuthenticated);
|
|
7575
7693
|
const setAccessToken = useUserData((s) => s.setAccessToken);
|
|
7576
7694
|
const setRefreshToken = useUserData((s) => s.setRefreshToken);
|
|
7577
|
-
const
|
|
7695
|
+
const setIsReady = useUserData((s) => s.setIsReady);
|
|
7578
7696
|
const setIsAuthenticated = useUserData((s) => s.setIsAuthenticated);
|
|
7579
7697
|
const address = useUserData((s) => s.address);
|
|
7580
7698
|
const setAddress = useUserData((s) => s.setAddress);
|
|
7699
|
+
// Ref to prevent concurrent refresh attempts
|
|
7700
|
+
const isRefreshingRef = useRef(false);
|
|
7581
7701
|
useEffect(() => {
|
|
7582
|
-
if (typeof window ==
|
|
7702
|
+
if (typeof window == 'undefined') {
|
|
7583
7703
|
return;
|
|
7584
7704
|
}
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7705
|
+
if (address) {
|
|
7706
|
+
const accessTokenKey = `${address}_accessToken`;
|
|
7707
|
+
const refreshTokenKey = `${address}_refreshToken`;
|
|
7708
|
+
const accessTokenExpiresAtKey = `${address}_accessTokenExpiresAt`;
|
|
7709
|
+
const refreshTokenExpiresAtKey = `${address}_refreshTokenExpiresAt`;
|
|
7710
|
+
const storedAccessToken = localStorage.getItem(accessTokenKey);
|
|
7711
|
+
const storedRefreshToken = localStorage.getItem(refreshTokenKey);
|
|
7712
|
+
const accessExpRaw = localStorage.getItem(accessTokenExpiresAtKey);
|
|
7713
|
+
const refreshExpRaw = localStorage.getItem(refreshTokenExpiresAtKey);
|
|
7714
|
+
const accessExp = accessExpRaw ? Number(accessExpRaw) : 0;
|
|
7715
|
+
const refreshExp = refreshExpRaw ? Number(refreshExpRaw) : 0;
|
|
7716
|
+
const now = nowMs();
|
|
7717
|
+
const accessValid = !!storedAccessToken && accessExp > now;
|
|
7718
|
+
const refreshValid = !!storedRefreshToken && refreshExp > now;
|
|
7719
|
+
if (accessValid && refreshValid) {
|
|
7720
|
+
// Both tokens are valid
|
|
7721
|
+
setAccessToken(storedAccessToken);
|
|
7722
|
+
setRefreshToken(storedRefreshToken);
|
|
7723
|
+
setIsAuthenticated(true);
|
|
7724
|
+
setIsReady(true);
|
|
7725
|
+
}
|
|
7726
|
+
else if (refreshValid) {
|
|
7727
|
+
// Access token expired but refresh still valid → refresh immediately
|
|
7728
|
+
setAccessToken(storedAccessToken);
|
|
7729
|
+
setRefreshToken(storedRefreshToken);
|
|
7730
|
+
(async () => {
|
|
7731
|
+
try {
|
|
7732
|
+
await refreshTokens();
|
|
7733
|
+
}
|
|
7734
|
+
catch (_a) {
|
|
7735
|
+
// Refresh failed → clear tokens
|
|
7736
|
+
setAccessToken(null);
|
|
7737
|
+
setRefreshToken(null);
|
|
7738
|
+
setIsAuthenticated(false);
|
|
7739
|
+
}
|
|
7740
|
+
setIsReady(true);
|
|
7741
|
+
})();
|
|
7742
|
+
return; // setIsReady will be called in the async block
|
|
7743
|
+
}
|
|
7744
|
+
else {
|
|
7745
|
+
// Refresh expired or no tokens → clear
|
|
7746
|
+
setAccessToken(null);
|
|
7747
|
+
setRefreshToken(null);
|
|
7748
|
+
setIsAuthenticated(false);
|
|
7749
|
+
setIsReady(true);
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
else {
|
|
7753
|
+
setIsReady(true);
|
|
7754
|
+
}
|
|
7755
|
+
}, [address]);
|
|
7595
7756
|
useEffect(() => {
|
|
7596
7757
|
const cleanup = addAuthInterceptors({
|
|
7597
7758
|
apiBaseUrl,
|
|
7598
7759
|
getAccessToken: () => {
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
|
|
7760
|
+
if (typeof window === 'undefined')
|
|
7761
|
+
return null;
|
|
7762
|
+
// Read from Zustand state as single source of truth
|
|
7763
|
+
return useUserData.getState().accessToken;
|
|
7602
7764
|
},
|
|
7603
7765
|
refreshTokens: async () => {
|
|
7604
7766
|
const data = await refreshTokens();
|
|
@@ -7612,6 +7774,55 @@ function useAuth() {
|
|
|
7612
7774
|
cleanup();
|
|
7613
7775
|
};
|
|
7614
7776
|
}, [apiBaseUrl]);
|
|
7777
|
+
// Proactive refresh effect: refresh when app regains focus or timer fires
|
|
7778
|
+
useEffect(() => {
|
|
7779
|
+
if (typeof window === 'undefined' || !address || !refreshToken$1)
|
|
7780
|
+
return;
|
|
7781
|
+
const refreshIfNeeded = async () => {
|
|
7782
|
+
// Prevent concurrent refresh attempts
|
|
7783
|
+
if (isRefreshingRef.current)
|
|
7784
|
+
return;
|
|
7785
|
+
// Read fresh expiration values from localStorage (not stale closure)
|
|
7786
|
+
const accessExpRaw = localStorage.getItem(`${address}_accessTokenExpiresAt`);
|
|
7787
|
+
const refreshExpRaw = localStorage.getItem(`${address}_refreshTokenExpiresAt`);
|
|
7788
|
+
const accessExp = accessExpRaw ? Number(accessExpRaw) : 0;
|
|
7789
|
+
const refreshExp = refreshExpRaw ? Number(refreshExpRaw) : 0;
|
|
7790
|
+
const now = nowMs();
|
|
7791
|
+
// If refresh token is already expired, do nothing
|
|
7792
|
+
if (refreshExp <= now)
|
|
7793
|
+
return;
|
|
7794
|
+
// If access token is within buffer window, refresh
|
|
7795
|
+
if (accessExp - now <= ACCESS_TOKEN_BUFFER_MS) {
|
|
7796
|
+
isRefreshingRef.current = true;
|
|
7797
|
+
try {
|
|
7798
|
+
await refreshTokens();
|
|
7799
|
+
}
|
|
7800
|
+
catch (_a) {
|
|
7801
|
+
// Refresh failed, interceptor will handle logout on next API call
|
|
7802
|
+
}
|
|
7803
|
+
finally {
|
|
7804
|
+
isRefreshingRef.current = false;
|
|
7805
|
+
}
|
|
7806
|
+
}
|
|
7807
|
+
};
|
|
7808
|
+
const onVisibilityChange = () => {
|
|
7809
|
+
if (document.visibilityState === 'visible') {
|
|
7810
|
+
refreshIfNeeded();
|
|
7811
|
+
}
|
|
7812
|
+
};
|
|
7813
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
7814
|
+
// Schedule timer for (accessExp - buffer)
|
|
7815
|
+
const accessExpRaw = localStorage.getItem(`${address}_accessTokenExpiresAt`);
|
|
7816
|
+
const accessExp = accessExpRaw ? Number(accessExpRaw) : 0;
|
|
7817
|
+
const delay = Math.max(0, accessExp - nowMs() - ACCESS_TOKEN_BUFFER_MS);
|
|
7818
|
+
const timer = window.setTimeout(() => {
|
|
7819
|
+
refreshIfNeeded();
|
|
7820
|
+
}, delay);
|
|
7821
|
+
return () => {
|
|
7822
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
7823
|
+
clearTimeout(timer);
|
|
7824
|
+
};
|
|
7825
|
+
}, [address, refreshToken$1]);
|
|
7615
7826
|
async function getEip712(address) {
|
|
7616
7827
|
const { data } = await getEIP712Message(apiBaseUrl, address, clientId);
|
|
7617
7828
|
return data;
|
|
@@ -7619,17 +7830,21 @@ function useAuth() {
|
|
|
7619
7830
|
async function loginWithSignedMessage(address, signature, timestamp) {
|
|
7620
7831
|
try {
|
|
7621
7832
|
const { data } = await authenticate(apiBaseUrl, {
|
|
7622
|
-
method:
|
|
7833
|
+
method: 'eip712',
|
|
7623
7834
|
address,
|
|
7624
7835
|
clientId,
|
|
7625
7836
|
details: { signature, timestamp },
|
|
7626
7837
|
});
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7838
|
+
const accessTokenKey = `${address}_accessToken`;
|
|
7839
|
+
const refreshTokenKey = `${address}_refreshToken`;
|
|
7840
|
+
const accessTokenExpiresAtKey = `${address}_accessTokenExpiresAt`;
|
|
7841
|
+
const refreshTokenExpiresAtKey = `${address}_refreshTokenExpiresAt`;
|
|
7842
|
+
window.localStorage.setItem(accessTokenKey, data.accessToken);
|
|
7843
|
+
window.localStorage.setItem(refreshTokenKey, data.refreshToken);
|
|
7844
|
+
window.localStorage.setItem(accessTokenExpiresAtKey, String(calcExpiresAt(data.expiresIn)));
|
|
7845
|
+
window.localStorage.setItem(refreshTokenExpiresAtKey, String(nowMs() + REFRESH_TOKEN_TTL_MS));
|
|
7630
7846
|
setAccessToken(data.accessToken);
|
|
7631
7847
|
setRefreshToken(data.refreshToken);
|
|
7632
|
-
setAddress(address);
|
|
7633
7848
|
setIsAuthenticated(true);
|
|
7634
7849
|
}
|
|
7635
7850
|
catch (e) {
|
|
@@ -7644,12 +7859,16 @@ function useAuth() {
|
|
|
7644
7859
|
appId,
|
|
7645
7860
|
accessToken: privyAccessToken,
|
|
7646
7861
|
});
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7862
|
+
const accessTokenKey = `${address}_accessToken`;
|
|
7863
|
+
const refreshTokenKey = `${address}_refreshToken`;
|
|
7864
|
+
const accessTokenExpiresAtKey = `${address}_accessTokenExpiresAt`;
|
|
7865
|
+
const refreshTokenExpiresAtKey = `${address}_refreshTokenExpiresAt`;
|
|
7866
|
+
window.localStorage.setItem(accessTokenKey, data.accessToken);
|
|
7867
|
+
window.localStorage.setItem(refreshTokenKey, data.refreshToken);
|
|
7868
|
+
window.localStorage.setItem(accessTokenExpiresAtKey, String(calcExpiresAt(data.expiresIn)));
|
|
7869
|
+
window.localStorage.setItem(refreshTokenExpiresAtKey, String(nowMs() + REFRESH_TOKEN_TTL_MS));
|
|
7650
7870
|
setAccessToken(data.accessToken);
|
|
7651
7871
|
setRefreshToken(data.refreshToken);
|
|
7652
|
-
setAddress(address);
|
|
7653
7872
|
setIsAuthenticated(true);
|
|
7654
7873
|
}
|
|
7655
7874
|
catch (e) {
|
|
@@ -7657,35 +7876,56 @@ function useAuth() {
|
|
|
7657
7876
|
}
|
|
7658
7877
|
}
|
|
7659
7878
|
async function refreshTokens() {
|
|
7660
|
-
const
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7879
|
+
const currentAddress = address;
|
|
7880
|
+
const currentRefresh = refreshToken$1;
|
|
7881
|
+
if (!currentRefresh || !currentAddress)
|
|
7882
|
+
throw new Error('No refresh token');
|
|
7883
|
+
const { data } = await refreshToken(apiBaseUrl, currentRefresh);
|
|
7884
|
+
// Update tokens in localStorage
|
|
7885
|
+
const accessTokenKey = `${currentAddress}_accessToken`;
|
|
7886
|
+
const refreshTokenKey = `${currentAddress}_refreshToken`;
|
|
7887
|
+
const accessTokenExpiresAtKey = `${currentAddress}_accessTokenExpiresAt`;
|
|
7888
|
+
const refreshTokenExpiresAtKey = `${currentAddress}_refreshTokenExpiresAt`;
|
|
7889
|
+
window.localStorage.setItem(accessTokenKey, data.accessToken);
|
|
7890
|
+
window.localStorage.setItem(refreshTokenKey, data.refreshToken);
|
|
7891
|
+
window.localStorage.setItem(accessTokenExpiresAtKey, String(calcExpiresAt(data.expiresIn)));
|
|
7892
|
+
window.localStorage.setItem(refreshTokenExpiresAtKey, String(nowMs() + REFRESH_TOKEN_TTL_MS));
|
|
7666
7893
|
setAccessToken(data.accessToken);
|
|
7667
7894
|
setRefreshToken(data.refreshToken);
|
|
7668
7895
|
setIsAuthenticated(true);
|
|
7669
7896
|
return data;
|
|
7670
7897
|
}
|
|
7671
7898
|
async function logout$1() {
|
|
7672
|
-
const
|
|
7673
|
-
|
|
7899
|
+
const currentAddress = address;
|
|
7900
|
+
const currentRefresh = refreshToken$1;
|
|
7901
|
+
if (currentRefresh) {
|
|
7674
7902
|
try {
|
|
7675
|
-
await logout(apiBaseUrl,
|
|
7903
|
+
await logout(apiBaseUrl, currentRefresh);
|
|
7676
7904
|
}
|
|
7677
7905
|
catch (_a) {
|
|
7678
7906
|
/* ignore */
|
|
7679
7907
|
}
|
|
7680
7908
|
}
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7909
|
+
if (currentAddress) {
|
|
7910
|
+
const accessTokenKey = `${currentAddress}_accessToken`;
|
|
7911
|
+
const refreshTokenKey = `${currentAddress}_refreshToken`;
|
|
7912
|
+
const accessTokenExpiresAtKey = `${currentAddress}_accessTokenExpiresAt`;
|
|
7913
|
+
const refreshTokenExpiresAtKey = `${currentAddress}_refreshTokenExpiresAt`;
|
|
7914
|
+
window.localStorage.removeItem(accessTokenKey);
|
|
7915
|
+
window.localStorage.removeItem(refreshTokenKey);
|
|
7916
|
+
window.localStorage.removeItem(accessTokenExpiresAtKey);
|
|
7917
|
+
window.localStorage.removeItem(refreshTokenExpiresAtKey);
|
|
7918
|
+
}
|
|
7684
7919
|
setAccessToken(null);
|
|
7685
7920
|
setRefreshToken(null);
|
|
7686
7921
|
setAddress(null);
|
|
7687
7922
|
setIsAuthenticated(false);
|
|
7688
7923
|
}
|
|
7924
|
+
function clearSession() {
|
|
7925
|
+
setAccessToken(null);
|
|
7926
|
+
setRefreshToken(null);
|
|
7927
|
+
setIsAuthenticated(false);
|
|
7928
|
+
}
|
|
7689
7929
|
return {
|
|
7690
7930
|
isReady,
|
|
7691
7931
|
isAuthenticated,
|
|
@@ -7697,6 +7937,8 @@ function useAuth() {
|
|
|
7697
7937
|
loginWithPrivyToken,
|
|
7698
7938
|
refreshTokens,
|
|
7699
7939
|
logout: logout$1,
|
|
7940
|
+
clearSession,
|
|
7941
|
+
setAddress,
|
|
7700
7942
|
};
|
|
7701
7943
|
}
|
|
7702
7944
|
|
|
@@ -8009,9 +8251,8 @@ const PearHyperliquidContext = createContext(undefined);
|
|
|
8009
8251
|
/**
|
|
8010
8252
|
* React Provider for PearHyperliquidClient
|
|
8011
8253
|
*/
|
|
8012
|
-
const PearHyperliquidProvider = ({ children, apiBaseUrl =
|
|
8254
|
+
const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearprotocol.io", clientId = "PEARPROTOCOLUI", wsUrl = "wss://hl-ui.pearprotocol.io/ws", }) => {
|
|
8013
8255
|
const address = useUserData((s) => s.address);
|
|
8014
|
-
const setAddress = useUserData((s) => s.setAddress);
|
|
8015
8256
|
const perpMetasByDex = useHyperliquidData((state) => state.perpMetasByDex);
|
|
8016
8257
|
const setPerpDexs = useHyperliquidData((state) => state.setPerpDexs);
|
|
8017
8258
|
const setPerpMetasByDex = useHyperliquidData((state) => state.setPerpMetasByDex);
|
|
@@ -8042,20 +8283,20 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8042
8283
|
perpMetas.forEach((item, perpIndex) => {
|
|
8043
8284
|
var _a, _b;
|
|
8044
8285
|
const dexName = perpIndex === 0
|
|
8045
|
-
?
|
|
8286
|
+
? "HYPERLIQUID"
|
|
8046
8287
|
: ((_b = (_a = perpDexs[perpIndex]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : `DEX_${perpIndex}`);
|
|
8047
8288
|
var collateralToken;
|
|
8048
8289
|
if (item.collateralToken === 360) {
|
|
8049
|
-
collateralToken =
|
|
8290
|
+
collateralToken = "USDH";
|
|
8050
8291
|
}
|
|
8051
8292
|
if (item.collateralToken === 0) {
|
|
8052
|
-
collateralToken =
|
|
8293
|
+
collateralToken = "USDC";
|
|
8053
8294
|
}
|
|
8054
8295
|
if (item.collateralToken === 235) {
|
|
8055
|
-
collateralToken =
|
|
8296
|
+
collateralToken = "USDE";
|
|
8056
8297
|
}
|
|
8057
8298
|
if (item.collateralToken === 268) {
|
|
8058
|
-
collateralToken =
|
|
8299
|
+
collateralToken = "USDT0";
|
|
8059
8300
|
}
|
|
8060
8301
|
const universeAssets = item.universe.map((asset) => ({
|
|
8061
8302
|
...asset,
|
|
@@ -8083,8 +8324,6 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8083
8324
|
}), [
|
|
8084
8325
|
apiBaseUrl,
|
|
8085
8326
|
wsUrl,
|
|
8086
|
-
address,
|
|
8087
|
-
setAddress,
|
|
8088
8327
|
isConnected,
|
|
8089
8328
|
lastError,
|
|
8090
8329
|
nativeIsConnected,
|
|
@@ -8099,7 +8338,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8099
8338
|
function usePearHyperliquid() {
|
|
8100
8339
|
const ctx = useContext(PearHyperliquidContext);
|
|
8101
8340
|
if (!ctx)
|
|
8102
|
-
throw new Error(
|
|
8341
|
+
throw new Error("usePearHyperliquid must be used within a PearHyperliquidProvider");
|
|
8103
8342
|
return ctx;
|
|
8104
8343
|
}
|
|
8105
8344
|
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ActiveAssetData, CandleChartData, CandleData, ClearinghouseState, UniverseAsset, WebData3AssetCtx, WsAllMidsData, PerpDexsResponse } from
|
|
2
|
-
import { TokenMetadataBySymbol } from
|
|
1
|
+
import { ActiveAssetData, CandleChartData, CandleData, ClearinghouseState, UniverseAsset, WebData3AssetCtx, WsAllMidsData, PerpDexsResponse } from "../types";
|
|
2
|
+
import { TokenMetadataBySymbol } from "../utils/token-metadata-extractor";
|
|
3
3
|
interface HyperliquidDataState {
|
|
4
4
|
allMids: WsAllMidsData | null;
|
|
5
5
|
activeAssetData: Record<string, ActiveAssetData> | null;
|
|
@@ -3,6 +3,7 @@ interface UserDataState {
|
|
|
3
3
|
accessToken: string | null;
|
|
4
4
|
refreshToken: string | null;
|
|
5
5
|
isAuthenticated: boolean;
|
|
6
|
+
isReady: boolean;
|
|
6
7
|
address: string | null;
|
|
7
8
|
tradeHistories: TradeHistoryDataDto[] | null;
|
|
8
9
|
rawOpenPositions: RawPositionDto[] | null;
|
|
@@ -26,6 +27,7 @@ interface UserDataState {
|
|
|
26
27
|
setNotifications: (value: NotificationDto[] | null) => void;
|
|
27
28
|
setSpotState: (value: SpotState | null) => void;
|
|
28
29
|
setUserAbstractionMode: (value: UserAbstraction | null) => void;
|
|
30
|
+
setIsReady: (value: boolean) => void;
|
|
29
31
|
clean: () => void;
|
|
30
32
|
}
|
|
31
33
|
export declare const useUserData: import("zustand").UseBoundStore<import("zustand").StoreApi<UserDataState>>;
|
package/dist/utils/http.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AxiosInstance } from
|
|
2
|
-
import { ApiErrorResponse } from
|
|
1
|
+
import type { AxiosInstance } from "axios";
|
|
2
|
+
import { ApiErrorResponse } from "../types";
|
|
3
3
|
export declare function toApiError(error: unknown): ApiErrorResponse;
|
|
4
4
|
export declare function joinUrl(baseUrl: string, path: string): string;
|
|
5
5
|
/**
|