@pear-protocol/hyperliquid-sdk 0.1.15 → 0.1.16
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 +364 -153
- package/dist/provider.d.ts +1 -1
- package/dist/store/hyperliquidDataStore.d.ts +3 -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
|
@@ -1431,6 +1431,8 @@ declare function useAuth(): {
|
|
|
1431
1431
|
readonly loginWithPrivyToken: (address: string, appId: string, privyAccessToken: string) => Promise<void>;
|
|
1432
1432
|
readonly refreshTokens: () => Promise<RefreshTokenResponse>;
|
|
1433
1433
|
readonly logout: () => Promise<void>;
|
|
1434
|
+
readonly clearSession: () => void;
|
|
1435
|
+
readonly setAddress: (address: string | null) => void;
|
|
1434
1436
|
};
|
|
1435
1437
|
|
|
1436
1438
|
interface MarginRequiredPerCollateral {
|
package/dist/index.js
CHANGED
|
@@ -65,18 +65,27 @@ 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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
setIsReady: (value) => set({ isReady: value }),
|
|
74
|
+
setAddress: (address) => set((state) => {
|
|
75
|
+
const addressChanged = state.address !== null && state.address !== address;
|
|
76
|
+
if (addressChanged) {
|
|
77
|
+
return {
|
|
78
|
+
address,
|
|
79
|
+
spotState: null,
|
|
80
|
+
tradeHistories: null,
|
|
81
|
+
rawOpenPositions: null,
|
|
82
|
+
openOrders: null,
|
|
83
|
+
accountSummary: null,
|
|
84
|
+
twapDetails: null,
|
|
85
|
+
notifications: null,
|
|
86
|
+
userExtraAgents: null,
|
|
87
|
+
userAbstractionMode: null,
|
|
88
|
+
};
|
|
80
89
|
}
|
|
81
90
|
return { address };
|
|
82
91
|
}),
|
|
@@ -88,10 +97,11 @@ const useUserData = create((set) => ({
|
|
|
88
97
|
setNotifications: (value) => set({ notifications: value }),
|
|
89
98
|
setSpotState: (value) => set({ spotState: value }),
|
|
90
99
|
clean: () => set({
|
|
91
|
-
accessToken: null,
|
|
92
|
-
refreshToken: null,
|
|
93
|
-
isAuthenticated: false,
|
|
94
|
-
|
|
100
|
+
// accessToken: null,
|
|
101
|
+
// refreshToken: null,
|
|
102
|
+
// isAuthenticated: false,
|
|
103
|
+
// isReady: false,
|
|
104
|
+
// address: null,
|
|
95
105
|
tradeHistories: null,
|
|
96
106
|
rawOpenPositions: null,
|
|
97
107
|
openOrders: null,
|
|
@@ -551,7 +561,7 @@ const useHyperliquidData = create((set) => ({
|
|
|
551
561
|
tokenMetadata: refreshTokenMetadata(state, { allMids: value }),
|
|
552
562
|
})),
|
|
553
563
|
setActiveAssetData: (value) => set((state) => {
|
|
554
|
-
const activeAssetData = typeof value ===
|
|
564
|
+
const activeAssetData = typeof value === "function" ? value(state.activeAssetData) : value;
|
|
555
565
|
return {
|
|
556
566
|
activeAssetData,
|
|
557
567
|
tokenMetadata: refreshTokenMetadata(state, { activeAssetData }),
|
|
@@ -591,7 +601,10 @@ const useHyperliquidData = create((set) => ({
|
|
|
591
601
|
setCandleData: (value) => set({ candleData: value }),
|
|
592
602
|
upsertActiveAssetData: (key, value) => set((state) => {
|
|
593
603
|
var _a;
|
|
594
|
-
const activeAssetData = {
|
|
604
|
+
const activeAssetData = {
|
|
605
|
+
...((_a = state.activeAssetData) !== null && _a !== void 0 ? _a : {}),
|
|
606
|
+
[key]: value,
|
|
607
|
+
};
|
|
595
608
|
return {
|
|
596
609
|
activeAssetData,
|
|
597
610
|
tokenMetadata: refreshTokenMetadata(state, { activeAssetData }, { symbols: [key] }),
|
|
@@ -626,6 +639,10 @@ const useHyperliquidData = create((set) => ({
|
|
|
626
639
|
perpMetasByDex: state.perpMetasByDex,
|
|
627
640
|
}),
|
|
628
641
|
})),
|
|
642
|
+
clearUserData: () => set({
|
|
643
|
+
aggregatedClearingHouseState: null,
|
|
644
|
+
rawClearinghouseStates: null,
|
|
645
|
+
}),
|
|
629
646
|
}));
|
|
630
647
|
|
|
631
648
|
/**
|
|
@@ -917,9 +934,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
917
934
|
try {
|
|
918
935
|
const message = JSON.parse(event.data);
|
|
919
936
|
// Handle subscription responses
|
|
920
|
-
if (
|
|
937
|
+
if ("success" in message || "error" in message) {
|
|
921
938
|
if (message.error) {
|
|
922
|
-
console.error(
|
|
939
|
+
console.error("[HyperLiquid WS] Subscription error:", message.error);
|
|
923
940
|
setLastError(message.error);
|
|
924
941
|
}
|
|
925
942
|
else {
|
|
@@ -928,44 +945,44 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
928
945
|
return;
|
|
929
946
|
}
|
|
930
947
|
// Handle channel data messages
|
|
931
|
-
if (
|
|
948
|
+
if ("channel" in message && "data" in message) {
|
|
932
949
|
const response = message;
|
|
933
950
|
switch (response.channel) {
|
|
934
|
-
case
|
|
951
|
+
case "userFills":
|
|
935
952
|
{
|
|
936
953
|
const maybePromise = (_a = onUserFillsRef.current) === null || _a === void 0 ? void 0 : _a.call(onUserFillsRef);
|
|
937
954
|
if (maybePromise instanceof Promise) {
|
|
938
|
-
maybePromise.catch((err) => console.error(
|
|
955
|
+
maybePromise.catch((err) => console.error("[HyperLiquid WS] userFills callback error", err));
|
|
939
956
|
}
|
|
940
957
|
}
|
|
941
958
|
break;
|
|
942
|
-
case
|
|
959
|
+
case "webData3":
|
|
943
960
|
const webData3 = response.data;
|
|
944
961
|
// finalAssetContexts now sourced from allDexsAssetCtxs channel
|
|
945
962
|
const finalAtOICaps = webData3.perpDexStates.flatMap((dex) => dex.perpsAtOpenInterestCap);
|
|
946
963
|
setFinalAtOICaps(finalAtOICaps);
|
|
947
964
|
setUserAbstractionMode(webData3.userState.abstraction || null);
|
|
948
965
|
break;
|
|
949
|
-
case
|
|
966
|
+
case "allDexsAssetCtxs":
|
|
950
967
|
{
|
|
951
968
|
const data = response.data;
|
|
952
969
|
// Store by DEX name, mapping '' to 'HYPERLIQUID'
|
|
953
970
|
const assetContextsByDex = new Map();
|
|
954
971
|
data.ctxs.forEach(([dexKey, ctxs]) => {
|
|
955
|
-
const dexName = dexKey ===
|
|
972
|
+
const dexName = dexKey === "" ? "HYPERLIQUID" : dexKey;
|
|
956
973
|
assetContextsByDex.set(dexName, ctxs || []);
|
|
957
974
|
});
|
|
958
975
|
setAssetContextsByDex(assetContextsByDex);
|
|
959
976
|
}
|
|
960
977
|
break;
|
|
961
|
-
case
|
|
978
|
+
case "allDexsClearinghouseState":
|
|
962
979
|
{
|
|
963
980
|
const data = response.data;
|
|
964
981
|
const states = (data.clearinghouseStates || [])
|
|
965
982
|
.map(([, s]) => s)
|
|
966
983
|
.filter(Boolean);
|
|
967
|
-
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v ||
|
|
968
|
-
const toStr = (n) => Number.isFinite(n) ? n.toString() :
|
|
984
|
+
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || "0") || 0), 0);
|
|
985
|
+
const toStr = (n) => Number.isFinite(n) ? n.toString() : "0";
|
|
969
986
|
const assetPositions = states.flatMap((s) => s.assetPositions || []);
|
|
970
987
|
const crossMaintenanceMarginUsed = toStr(sum(states.map((s) => s.crossMaintenanceMarginUsed)));
|
|
971
988
|
const crossMarginSummary = {
|
|
@@ -995,26 +1012,26 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
995
1012
|
setClearinghouseStateReceived(true);
|
|
996
1013
|
}
|
|
997
1014
|
break;
|
|
998
|
-
case
|
|
1015
|
+
case "allMids":
|
|
999
1016
|
{
|
|
1000
1017
|
const data = response.data;
|
|
1001
1018
|
setAllMids(data);
|
|
1002
1019
|
}
|
|
1003
1020
|
break;
|
|
1004
|
-
case
|
|
1021
|
+
case "activeAssetData":
|
|
1005
1022
|
{
|
|
1006
1023
|
const assetData = response.data;
|
|
1007
1024
|
upsertActiveAssetData(assetData.coin, assetData);
|
|
1008
1025
|
}
|
|
1009
1026
|
break;
|
|
1010
|
-
case
|
|
1027
|
+
case "candle":
|
|
1011
1028
|
{
|
|
1012
1029
|
const candleDataItem = response.data;
|
|
1013
|
-
const symbol = candleDataItem.s ||
|
|
1030
|
+
const symbol = candleDataItem.s || "";
|
|
1014
1031
|
addCandleData(symbol, candleDataItem);
|
|
1015
1032
|
}
|
|
1016
1033
|
break;
|
|
1017
|
-
case
|
|
1034
|
+
case "spotState":
|
|
1018
1035
|
{
|
|
1019
1036
|
const spotStateData = response.data;
|
|
1020
1037
|
if (spotStateData === null || spotStateData === void 0 ? void 0 : spotStateData.spotState) {
|
|
@@ -1029,7 +1046,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1029
1046
|
}
|
|
1030
1047
|
catch (error) {
|
|
1031
1048
|
const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
|
|
1032
|
-
console.error(
|
|
1049
|
+
console.error("[HyperLiquid WS] Parse error:", errorMessage, "Raw message:", event.data);
|
|
1033
1050
|
setLastError(errorMessage);
|
|
1034
1051
|
}
|
|
1035
1052
|
}, [
|
|
@@ -1043,7 +1060,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1043
1060
|
setSpotState,
|
|
1044
1061
|
]);
|
|
1045
1062
|
const connect = useCallback(() => {
|
|
1046
|
-
console.log(
|
|
1063
|
+
console.log("[HyperLiquid WS] connect() called, enabled:", enabled);
|
|
1047
1064
|
if (!enabled)
|
|
1048
1065
|
return;
|
|
1049
1066
|
// Clear any pending reconnect timeout
|
|
@@ -1059,10 +1076,10 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1059
1076
|
console.log('[HyperLiquid WS] connect() returning early - socket already exists, readyState:', wsRef.current.readyState);
|
|
1060
1077
|
return;
|
|
1061
1078
|
}
|
|
1062
|
-
console.log(
|
|
1079
|
+
console.log("[HyperLiquid WS] Creating new WebSocket connection");
|
|
1063
1080
|
manualCloseRef.current = false;
|
|
1064
1081
|
setReadyState(ReadyState.CONNECTING);
|
|
1065
|
-
const ws = new WebSocket(
|
|
1082
|
+
const ws = new WebSocket("wss://api.hyperliquid.xyz/ws");
|
|
1066
1083
|
wsRef.current = ws;
|
|
1067
1084
|
ws.onopen = () => {
|
|
1068
1085
|
reconnectAttemptsRef.current = 0;
|
|
@@ -1071,8 +1088,8 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1071
1088
|
};
|
|
1072
1089
|
ws.onmessage = handleMessage;
|
|
1073
1090
|
ws.onerror = (event) => {
|
|
1074
|
-
console.error(
|
|
1075
|
-
setLastError(
|
|
1091
|
+
console.error("[HyperLiquid WS] Connection error:", event);
|
|
1092
|
+
setLastError("WebSocket error");
|
|
1076
1093
|
};
|
|
1077
1094
|
ws.onclose = () => {
|
|
1078
1095
|
setReadyState(ReadyState.CLOSED);
|
|
@@ -1164,7 +1181,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1164
1181
|
if (isConnected) {
|
|
1165
1182
|
// Send ping every 30 seconds
|
|
1166
1183
|
pingIntervalRef.current = setInterval(() => {
|
|
1167
|
-
sendJsonMessage({ method:
|
|
1184
|
+
sendJsonMessage({ method: "ping" });
|
|
1168
1185
|
}, 30000);
|
|
1169
1186
|
}
|
|
1170
1187
|
else {
|
|
@@ -1182,27 +1199,27 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1182
1199
|
}, [isConnected, sendJsonMessage]);
|
|
1183
1200
|
// Handle address subscription changes
|
|
1184
1201
|
useEffect(() => {
|
|
1185
|
-
const DEFAULT_ADDRESS =
|
|
1202
|
+
const DEFAULT_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
1186
1203
|
const userAddress = (address || DEFAULT_ADDRESS).toLowerCase();
|
|
1187
1204
|
const normalizedSubscribedAddress = (subscribedAddress === null || subscribedAddress === void 0 ? void 0 : subscribedAddress.toLowerCase()) || null;
|
|
1188
|
-
console.log(
|
|
1189
|
-
console.log(
|
|
1190
|
-
console.log(
|
|
1205
|
+
console.log("[HyperLiquid WS] Address subscription effect running");
|
|
1206
|
+
console.log("[HyperLiquid WS] address:", address, "userAddress:", userAddress, "subscribedAddress:", subscribedAddress, "normalizedSubscribedAddress:", normalizedSubscribedAddress);
|
|
1207
|
+
console.log("[HyperLiquid WS] isConnected:", isConnected);
|
|
1191
1208
|
if (normalizedSubscribedAddress === userAddress) {
|
|
1192
|
-
console.log(
|
|
1209
|
+
console.log("[HyperLiquid WS] Address unchanged, skipping subscription update");
|
|
1193
1210
|
return;
|
|
1194
1211
|
}
|
|
1195
1212
|
if (!isConnected) {
|
|
1196
|
-
console.log(
|
|
1213
|
+
console.log("[HyperLiquid WS] Not connected, skipping subscription update");
|
|
1197
1214
|
return;
|
|
1198
1215
|
}
|
|
1199
1216
|
// Unsubscribe from previous address if exists
|
|
1200
1217
|
if (subscribedAddress) {
|
|
1201
|
-
console.log(
|
|
1218
|
+
console.log("[HyperLiquid WS] Unsubscribing from previous address:", subscribedAddress);
|
|
1202
1219
|
const unsubscribeMessage = {
|
|
1203
|
-
method:
|
|
1220
|
+
method: "unsubscribe",
|
|
1204
1221
|
subscription: {
|
|
1205
|
-
type:
|
|
1222
|
+
type: "webData3",
|
|
1206
1223
|
user: subscribedAddress,
|
|
1207
1224
|
},
|
|
1208
1225
|
};
|
|
@@ -1210,54 +1227,54 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1210
1227
|
// Unsubscribe from spotState for previous address
|
|
1211
1228
|
if (subscribedAddress !== DEFAULT_ADDRESS) {
|
|
1212
1229
|
const unsubscribeSpotState = {
|
|
1213
|
-
method:
|
|
1230
|
+
method: "unsubscribe",
|
|
1214
1231
|
subscription: {
|
|
1215
|
-
type:
|
|
1232
|
+
type: "spotState",
|
|
1216
1233
|
user: subscribedAddress,
|
|
1217
1234
|
},
|
|
1218
1235
|
};
|
|
1219
1236
|
sendJsonMessage(unsubscribeSpotState);
|
|
1220
1237
|
}
|
|
1221
1238
|
const unsubscribeAllDexsClearinghouseState = {
|
|
1222
|
-
method:
|
|
1239
|
+
method: "unsubscribe",
|
|
1223
1240
|
subscription: {
|
|
1224
|
-
type:
|
|
1241
|
+
type: "allDexsClearinghouseState",
|
|
1225
1242
|
user: subscribedAddress,
|
|
1226
1243
|
},
|
|
1227
1244
|
};
|
|
1228
1245
|
sendJsonMessage(unsubscribeAllDexsClearinghouseState);
|
|
1229
1246
|
const unsubscribeUserFills = {
|
|
1230
|
-
method:
|
|
1247
|
+
method: "unsubscribe",
|
|
1231
1248
|
subscription: {
|
|
1232
|
-
type:
|
|
1249
|
+
type: "userFills",
|
|
1233
1250
|
user: subscribedAddress,
|
|
1234
1251
|
},
|
|
1235
1252
|
};
|
|
1236
1253
|
sendJsonMessage(unsubscribeUserFills);
|
|
1237
1254
|
}
|
|
1238
1255
|
const subscribeWebData3 = {
|
|
1239
|
-
method:
|
|
1256
|
+
method: "subscribe",
|
|
1240
1257
|
subscription: {
|
|
1241
|
-
type:
|
|
1258
|
+
type: "webData3",
|
|
1242
1259
|
user: userAddress,
|
|
1243
1260
|
},
|
|
1244
1261
|
};
|
|
1245
1262
|
// Subscribe to allMids
|
|
1246
1263
|
const subscribeAllMids = {
|
|
1247
|
-
method:
|
|
1264
|
+
method: "subscribe",
|
|
1248
1265
|
subscription: {
|
|
1249
|
-
type:
|
|
1250
|
-
dex:
|
|
1266
|
+
type: "allMids",
|
|
1267
|
+
dex: "ALL_DEXS",
|
|
1251
1268
|
},
|
|
1252
1269
|
};
|
|
1253
1270
|
// Subscribe to allDexsAssetCtxs (no payload params, global feed)
|
|
1254
1271
|
const subscribeAllDexsAssetCtxs = {
|
|
1255
|
-
method:
|
|
1272
|
+
method: "subscribe",
|
|
1256
1273
|
subscription: {
|
|
1257
|
-
type:
|
|
1274
|
+
type: "allDexsAssetCtxs",
|
|
1258
1275
|
},
|
|
1259
1276
|
};
|
|
1260
|
-
console.log(
|
|
1277
|
+
console.log("[HyperLiquid WS] Subscribing to new address:", userAddress);
|
|
1261
1278
|
sendJsonMessage(subscribeWebData3);
|
|
1262
1279
|
sendJsonMessage(subscribeAllMids);
|
|
1263
1280
|
sendJsonMessage(subscribeAllDexsAssetCtxs);
|
|
@@ -1265,9 +1282,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1265
1282
|
// Only subscribe if we have a real user address (not the default)
|
|
1266
1283
|
if (userAddress !== DEFAULT_ADDRESS.toLowerCase()) {
|
|
1267
1284
|
const subscribeSpotState = {
|
|
1268
|
-
method:
|
|
1285
|
+
method: "subscribe",
|
|
1269
1286
|
subscription: {
|
|
1270
|
-
type:
|
|
1287
|
+
type: "spotState",
|
|
1271
1288
|
user: userAddress,
|
|
1272
1289
|
},
|
|
1273
1290
|
};
|
|
@@ -1277,9 +1294,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1277
1294
|
// Only subscribe if we have a real user address (not the default)
|
|
1278
1295
|
if (userAddress !== DEFAULT_ADDRESS.toLowerCase()) {
|
|
1279
1296
|
const subscribeAllDexsClearinghouseState = {
|
|
1280
|
-
method:
|
|
1297
|
+
method: "subscribe",
|
|
1281
1298
|
subscription: {
|
|
1282
|
-
type:
|
|
1299
|
+
type: "allDexsClearinghouseState",
|
|
1283
1300
|
user: userAddress,
|
|
1284
1301
|
},
|
|
1285
1302
|
};
|
|
@@ -1313,9 +1330,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1313
1330
|
!userSummary)
|
|
1314
1331
|
return;
|
|
1315
1332
|
const subscribeUserFills = {
|
|
1316
|
-
method:
|
|
1333
|
+
method: "subscribe",
|
|
1317
1334
|
subscription: {
|
|
1318
|
-
type:
|
|
1335
|
+
type: "userFills",
|
|
1319
1336
|
user: subscribedAddress,
|
|
1320
1337
|
},
|
|
1321
1338
|
};
|
|
@@ -1337,9 +1354,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1337
1354
|
// Unsubscribe from tokens no longer in the list
|
|
1338
1355
|
tokensToUnsubscribe.forEach((token) => {
|
|
1339
1356
|
const unsubscribeMessage = {
|
|
1340
|
-
method:
|
|
1357
|
+
method: "unsubscribe",
|
|
1341
1358
|
subscription: {
|
|
1342
|
-
type:
|
|
1359
|
+
type: "activeAssetData",
|
|
1343
1360
|
user: address,
|
|
1344
1361
|
coin: token,
|
|
1345
1362
|
},
|
|
@@ -1349,9 +1366,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1349
1366
|
// Subscribe to new tokens
|
|
1350
1367
|
tokensToSubscribe.forEach((token) => {
|
|
1351
1368
|
const subscribeMessage = {
|
|
1352
|
-
method:
|
|
1369
|
+
method: "subscribe",
|
|
1353
1370
|
subscription: {
|
|
1354
|
-
type:
|
|
1371
|
+
type: "activeAssetData",
|
|
1355
1372
|
user: address,
|
|
1356
1373
|
coin: token,
|
|
1357
1374
|
},
|
|
@@ -1380,9 +1397,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1380
1397
|
if (prevInterval && prevInterval !== candleInterval) {
|
|
1381
1398
|
subscribedCandleTokens.forEach((token) => {
|
|
1382
1399
|
const unsubscribeMessage = {
|
|
1383
|
-
method:
|
|
1400
|
+
method: "unsubscribe",
|
|
1384
1401
|
subscription: {
|
|
1385
|
-
type:
|
|
1402
|
+
type: "candle",
|
|
1386
1403
|
coin: token,
|
|
1387
1404
|
interval: prevInterval,
|
|
1388
1405
|
},
|
|
@@ -1397,9 +1414,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1397
1414
|
// Unsubscribe from tokens no longer in the list
|
|
1398
1415
|
tokensToUnsubscribe.forEach((token) => {
|
|
1399
1416
|
const unsubscribeMessage = {
|
|
1400
|
-
method:
|
|
1417
|
+
method: "unsubscribe",
|
|
1401
1418
|
subscription: {
|
|
1402
|
-
type:
|
|
1419
|
+
type: "candle",
|
|
1403
1420
|
coin: token,
|
|
1404
1421
|
interval: candleInterval,
|
|
1405
1422
|
},
|
|
@@ -1409,9 +1426,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1409
1426
|
// Subscribe to new tokens
|
|
1410
1427
|
tokensToSubscribe.forEach((token) => {
|
|
1411
1428
|
const subscribeMessage = {
|
|
1412
|
-
method:
|
|
1429
|
+
method: "subscribe",
|
|
1413
1430
|
subscription: {
|
|
1414
|
-
type:
|
|
1431
|
+
type: "candle",
|
|
1415
1432
|
coin: token,
|
|
1416
1433
|
interval: candleInterval,
|
|
1417
1434
|
},
|
|
@@ -5942,10 +5959,10 @@ function toApiError(error) {
|
|
|
5942
5959
|
var _a;
|
|
5943
5960
|
const axiosError = error;
|
|
5944
5961
|
const payload = (axiosError && axiosError.response ? axiosError.response.data : undefined);
|
|
5945
|
-
const message = typeof payload ===
|
|
5962
|
+
const message = typeof payload === "object" && payload && "message" in payload
|
|
5946
5963
|
? String(payload.message)
|
|
5947
|
-
: (axiosError === null || axiosError === void 0 ? void 0 : axiosError.message) ||
|
|
5948
|
-
const errField = typeof payload ===
|
|
5964
|
+
: (axiosError === null || axiosError === void 0 ? void 0 : axiosError.message) || "Request failed";
|
|
5965
|
+
const errField = typeof payload === "object" && payload && "error" in payload
|
|
5949
5966
|
? String(payload.error)
|
|
5950
5967
|
: undefined;
|
|
5951
5968
|
return {
|
|
@@ -5955,8 +5972,8 @@ function toApiError(error) {
|
|
|
5955
5972
|
};
|
|
5956
5973
|
}
|
|
5957
5974
|
function joinUrl(baseUrl, path) {
|
|
5958
|
-
const cleanBase = baseUrl.replace(/\/$/,
|
|
5959
|
-
const cleanPath = path.startsWith(
|
|
5975
|
+
const cleanBase = baseUrl.replace(/\/$/, "");
|
|
5976
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
5960
5977
|
return `${cleanBase}${cleanPath}`;
|
|
5961
5978
|
}
|
|
5962
5979
|
/**
|
|
@@ -5985,7 +6002,7 @@ function addAuthInterceptors(params) {
|
|
|
5985
6002
|
pendingRequests = [];
|
|
5986
6003
|
}
|
|
5987
6004
|
const isOurApiUrl = (url) => Boolean(url && url.startsWith(apiBaseUrl));
|
|
5988
|
-
const isRefreshUrl = (url) => Boolean(url && url.startsWith(joinUrl(apiBaseUrl,
|
|
6005
|
+
const isRefreshUrl = (url) => Boolean(url && url.startsWith(joinUrl(apiBaseUrl, "/auth/refresh")));
|
|
5989
6006
|
const reqId = apiClient.interceptors.request.use((config) => {
|
|
5990
6007
|
var _a;
|
|
5991
6008
|
try {
|
|
@@ -5993,11 +6010,12 @@ function addAuthInterceptors(params) {
|
|
|
5993
6010
|
const token = getAccessToken();
|
|
5994
6011
|
if (token) {
|
|
5995
6012
|
config.headers = (_a = config.headers) !== null && _a !== void 0 ? _a : {};
|
|
5996
|
-
|
|
6013
|
+
config.headers["Authorization"] = `Bearer ${token}`;
|
|
5997
6014
|
}
|
|
5998
6015
|
}
|
|
5999
6016
|
}
|
|
6000
|
-
catch (
|
|
6017
|
+
catch (err) {
|
|
6018
|
+
console.error("[Auth Interceptor] Request interceptor error:", err);
|
|
6001
6019
|
}
|
|
6002
6020
|
return config;
|
|
6003
6021
|
});
|
|
@@ -6008,22 +6026,36 @@ function addAuthInterceptors(params) {
|
|
|
6008
6026
|
const url = originalRequest === null || originalRequest === void 0 ? void 0 : originalRequest.url;
|
|
6009
6027
|
// If not our API or not 401, just reject
|
|
6010
6028
|
if (!status || status !== 401 || !isOurApiUrl(url)) {
|
|
6029
|
+
if (status === 401) {
|
|
6030
|
+
console.warn("[Auth Interceptor] 401 received but URL check failed:", {
|
|
6031
|
+
url,
|
|
6032
|
+
apiBaseUrl,
|
|
6033
|
+
isOurApiUrl: isOurApiUrl(url),
|
|
6034
|
+
});
|
|
6035
|
+
}
|
|
6011
6036
|
return Promise.reject(error);
|
|
6012
6037
|
}
|
|
6038
|
+
console.log("[Auth Interceptor] 401 detected, attempting token refresh for URL:", url);
|
|
6013
6039
|
// If the 401 is from refresh endpoint itself -> force logout
|
|
6014
6040
|
if (isRefreshUrl(url)) {
|
|
6041
|
+
console.warn("[Auth Interceptor] Refresh endpoint returned 401, logging out");
|
|
6015
6042
|
try {
|
|
6016
6043
|
await logout();
|
|
6017
6044
|
}
|
|
6018
|
-
catch (
|
|
6045
|
+
catch (err) {
|
|
6046
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6047
|
+
}
|
|
6019
6048
|
return Promise.reject(error);
|
|
6020
6049
|
}
|
|
6021
6050
|
// Prevent infinite loop
|
|
6022
6051
|
if (originalRequest && originalRequest._retry) {
|
|
6052
|
+
console.warn("[Auth Interceptor] Request already retried, logging out");
|
|
6023
6053
|
try {
|
|
6024
6054
|
await logout();
|
|
6025
6055
|
}
|
|
6026
|
-
catch (
|
|
6056
|
+
catch (err) {
|
|
6057
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6058
|
+
}
|
|
6027
6059
|
return Promise.reject(error);
|
|
6028
6060
|
}
|
|
6029
6061
|
// Mark so we don't retry twice
|
|
@@ -6037,31 +6069,45 @@ function addAuthInterceptors(params) {
|
|
|
6037
6069
|
if (!newToken || !originalRequest)
|
|
6038
6070
|
return reject(error);
|
|
6039
6071
|
originalRequest.headers = (_a = originalRequest.headers) !== null && _a !== void 0 ? _a : {};
|
|
6040
|
-
originalRequest.headers[
|
|
6072
|
+
originalRequest.headers["Authorization"] =
|
|
6073
|
+
`Bearer ${newToken}`;
|
|
6041
6074
|
resolve(apiClient.request(originalRequest));
|
|
6042
6075
|
});
|
|
6043
6076
|
});
|
|
6044
6077
|
}
|
|
6045
6078
|
isRefreshing = true;
|
|
6046
6079
|
try {
|
|
6080
|
+
console.log("[Auth Interceptor] Refreshing tokens...");
|
|
6047
6081
|
const refreshed = await refreshTokens();
|
|
6048
|
-
const newAccessToken = (_b = (refreshed &&
|
|
6082
|
+
const newAccessToken = (_b = (refreshed &&
|
|
6083
|
+
(refreshed.accessToken || ((_a = refreshed.data) === null || _a === void 0 ? void 0 : _a.accessToken)))) !== null && _b !== void 0 ? _b : null;
|
|
6084
|
+
if (!newAccessToken) {
|
|
6085
|
+
console.error("[Auth Interceptor] Token refresh succeeded but no access token in response:", refreshed);
|
|
6086
|
+
}
|
|
6087
|
+
else {
|
|
6088
|
+
console.log("[Auth Interceptor] Token refresh successful");
|
|
6089
|
+
}
|
|
6049
6090
|
resolvePendingRequests(newAccessToken);
|
|
6050
6091
|
if (originalRequest) {
|
|
6051
6092
|
originalRequest.headers = (_c = originalRequest.headers) !== null && _c !== void 0 ? _c : {};
|
|
6052
6093
|
if (newAccessToken)
|
|
6053
|
-
|
|
6094
|
+
originalRequest.headers["Authorization"] =
|
|
6095
|
+
`Bearer ${newAccessToken}`;
|
|
6096
|
+
console.log("[Auth Interceptor] Retrying original request with new token");
|
|
6054
6097
|
const resp = await apiClient.request(originalRequest);
|
|
6055
6098
|
return resp;
|
|
6056
6099
|
}
|
|
6057
6100
|
return Promise.reject(error);
|
|
6058
6101
|
}
|
|
6059
6102
|
catch (refreshErr) {
|
|
6103
|
+
console.error("[Auth Interceptor] Token refresh failed:", refreshErr);
|
|
6060
6104
|
resolvePendingRequests(null);
|
|
6061
6105
|
try {
|
|
6062
6106
|
await logout();
|
|
6063
6107
|
}
|
|
6064
|
-
catch (
|
|
6108
|
+
catch (err) {
|
|
6109
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6110
|
+
}
|
|
6065
6111
|
return Promise.reject(refreshErr);
|
|
6066
6112
|
}
|
|
6067
6113
|
finally {
|
|
@@ -6072,11 +6118,15 @@ function addAuthInterceptors(params) {
|
|
|
6072
6118
|
try {
|
|
6073
6119
|
apiClient.interceptors.request.eject(reqId);
|
|
6074
6120
|
}
|
|
6075
|
-
catch (
|
|
6121
|
+
catch (err) {
|
|
6122
|
+
console.error("[Auth Interceptor] Failed to eject request interceptor:", err);
|
|
6123
|
+
}
|
|
6076
6124
|
try {
|
|
6077
6125
|
apiClient.interceptors.response.eject(resId);
|
|
6078
6126
|
}
|
|
6079
|
-
catch (
|
|
6127
|
+
catch (err) {
|
|
6128
|
+
console.error("[Auth Interceptor] Failed to eject response interceptor:", err);
|
|
6129
|
+
}
|
|
6080
6130
|
};
|
|
6081
6131
|
}
|
|
6082
6132
|
|
|
@@ -7593,20 +7643,34 @@ function usePortfolio() {
|
|
|
7593
7643
|
}
|
|
7594
7644
|
|
|
7595
7645
|
async function getEIP712Message(baseUrl, address, clientId) {
|
|
7596
|
-
const url = joinUrl(baseUrl,
|
|
7646
|
+
const url = joinUrl(baseUrl, "/auth/eip712-message");
|
|
7597
7647
|
try {
|
|
7598
|
-
const resp = await
|
|
7599
|
-
|
|
7648
|
+
const resp = await apiClient.get(url, {
|
|
7649
|
+
params: { address, clientId },
|
|
7650
|
+
timeout: 30000,
|
|
7651
|
+
});
|
|
7652
|
+
return {
|
|
7653
|
+
data: resp.data,
|
|
7654
|
+
status: resp.status,
|
|
7655
|
+
headers: resp.headers,
|
|
7656
|
+
};
|
|
7600
7657
|
}
|
|
7601
7658
|
catch (error) {
|
|
7602
7659
|
throw toApiError(error);
|
|
7603
7660
|
}
|
|
7604
7661
|
}
|
|
7605
7662
|
async function authenticate(baseUrl, body) {
|
|
7606
|
-
const url = joinUrl(baseUrl,
|
|
7663
|
+
const url = joinUrl(baseUrl, "/auth/login");
|
|
7607
7664
|
try {
|
|
7608
|
-
const resp = await
|
|
7609
|
-
|
|
7665
|
+
const resp = await apiClient.post(url, body, {
|
|
7666
|
+
headers: { "Content-Type": "application/json" },
|
|
7667
|
+
timeout: 30000,
|
|
7668
|
+
});
|
|
7669
|
+
return {
|
|
7670
|
+
data: resp.data,
|
|
7671
|
+
status: resp.status,
|
|
7672
|
+
headers: resp.headers,
|
|
7673
|
+
};
|
|
7610
7674
|
}
|
|
7611
7675
|
catch (error) {
|
|
7612
7676
|
throw toApiError(error);
|
|
@@ -7617,7 +7681,7 @@ async function authenticate(baseUrl, body) {
|
|
|
7617
7681
|
*/
|
|
7618
7682
|
async function authenticateWithPrivy(baseUrl, params) {
|
|
7619
7683
|
const body = {
|
|
7620
|
-
method:
|
|
7684
|
+
method: "privy_access_token",
|
|
7621
7685
|
address: params.address,
|
|
7622
7686
|
clientId: params.clientId,
|
|
7623
7687
|
details: { appId: params.appId, accessToken: params.accessToken },
|
|
@@ -7625,62 +7689,124 @@ async function authenticateWithPrivy(baseUrl, params) {
|
|
|
7625
7689
|
return authenticate(baseUrl, body);
|
|
7626
7690
|
}
|
|
7627
7691
|
async function refreshToken(baseUrl, refreshTokenVal) {
|
|
7628
|
-
const url = joinUrl(baseUrl,
|
|
7692
|
+
const url = joinUrl(baseUrl, "/auth/refresh");
|
|
7629
7693
|
try {
|
|
7630
|
-
const resp = await
|
|
7631
|
-
return {
|
|
7694
|
+
const resp = await apiClient.post(url, { refreshToken: refreshTokenVal }, { headers: { "Content-Type": "application/json" }, timeout: 30000 });
|
|
7695
|
+
return {
|
|
7696
|
+
data: resp.data,
|
|
7697
|
+
status: resp.status,
|
|
7698
|
+
headers: resp.headers,
|
|
7699
|
+
};
|
|
7632
7700
|
}
|
|
7633
7701
|
catch (error) {
|
|
7634
7702
|
throw toApiError(error);
|
|
7635
7703
|
}
|
|
7636
7704
|
}
|
|
7637
7705
|
async function logout(baseUrl, refreshTokenVal) {
|
|
7638
|
-
const url = joinUrl(baseUrl,
|
|
7706
|
+
const url = joinUrl(baseUrl, "/auth/logout");
|
|
7639
7707
|
try {
|
|
7640
|
-
const resp = await
|
|
7641
|
-
return {
|
|
7708
|
+
const resp = await apiClient.post(url, { refreshToken: refreshTokenVal }, { headers: { "Content-Type": "application/json" }, timeout: 30000 });
|
|
7709
|
+
return {
|
|
7710
|
+
data: resp.data,
|
|
7711
|
+
status: resp.status,
|
|
7712
|
+
headers: resp.headers,
|
|
7713
|
+
};
|
|
7642
7714
|
}
|
|
7643
7715
|
catch (error) {
|
|
7644
7716
|
throw toApiError(error);
|
|
7645
7717
|
}
|
|
7646
7718
|
}
|
|
7647
7719
|
|
|
7720
|
+
// Token expiration constants
|
|
7721
|
+
const ACCESS_TOKEN_BUFFER_MS = 5 * 60 * 1000; // Refresh 5 min before expiry
|
|
7722
|
+
const REFRESH_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days fallback
|
|
7723
|
+
function nowMs() {
|
|
7724
|
+
return Date.now();
|
|
7725
|
+
}
|
|
7726
|
+
function calcExpiresAt(expiresInSeconds) {
|
|
7727
|
+
return nowMs() + expiresInSeconds * 1000;
|
|
7728
|
+
}
|
|
7648
7729
|
function useAuth() {
|
|
7649
7730
|
const context = useContext(PearHyperliquidContext);
|
|
7650
7731
|
if (!context) {
|
|
7651
|
-
throw new Error(
|
|
7732
|
+
throw new Error('useAuth must be used within a PearHyperliquidProvider');
|
|
7652
7733
|
}
|
|
7653
7734
|
const { apiBaseUrl, clientId } = context;
|
|
7654
|
-
const [isReady, setIsReady] = useState(false);
|
|
7655
7735
|
const accessToken = useUserData((s) => s.accessToken);
|
|
7656
7736
|
const refreshToken$1 = useUserData((s) => s.refreshToken);
|
|
7737
|
+
const isReady = useUserData((s) => s.isReady);
|
|
7738
|
+
const isAuthenticated = useUserData((s) => s.isAuthenticated);
|
|
7657
7739
|
const setAccessToken = useUserData((s) => s.setAccessToken);
|
|
7658
7740
|
const setRefreshToken = useUserData((s) => s.setRefreshToken);
|
|
7659
|
-
const
|
|
7741
|
+
const setIsReady = useUserData((s) => s.setIsReady);
|
|
7660
7742
|
const setIsAuthenticated = useUserData((s) => s.setIsAuthenticated);
|
|
7661
7743
|
const address = useUserData((s) => s.address);
|
|
7662
7744
|
const setAddress = useUserData((s) => s.setAddress);
|
|
7745
|
+
// Ref to prevent concurrent refresh attempts
|
|
7746
|
+
const isRefreshingRef = useRef(false);
|
|
7663
7747
|
useEffect(() => {
|
|
7664
|
-
if (typeof window ==
|
|
7748
|
+
if (typeof window == 'undefined') {
|
|
7665
7749
|
return;
|
|
7666
7750
|
}
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7751
|
+
if (address) {
|
|
7752
|
+
const accessTokenKey = `${address}_accessToken`;
|
|
7753
|
+
const refreshTokenKey = `${address}_refreshToken`;
|
|
7754
|
+
const accessTokenExpiresAtKey = `${address}_accessTokenExpiresAt`;
|
|
7755
|
+
const refreshTokenExpiresAtKey = `${address}_refreshTokenExpiresAt`;
|
|
7756
|
+
const storedAccessToken = localStorage.getItem(accessTokenKey);
|
|
7757
|
+
const storedRefreshToken = localStorage.getItem(refreshTokenKey);
|
|
7758
|
+
const accessExpRaw = localStorage.getItem(accessTokenExpiresAtKey);
|
|
7759
|
+
const refreshExpRaw = localStorage.getItem(refreshTokenExpiresAtKey);
|
|
7760
|
+
const accessExp = accessExpRaw ? Number(accessExpRaw) : 0;
|
|
7761
|
+
const refreshExp = refreshExpRaw ? Number(refreshExpRaw) : 0;
|
|
7762
|
+
const now = nowMs();
|
|
7763
|
+
const accessValid = !!storedAccessToken && accessExp > now;
|
|
7764
|
+
const refreshValid = !!storedRefreshToken && refreshExp > now;
|
|
7765
|
+
if (accessValid && refreshValid) {
|
|
7766
|
+
// Both tokens are valid
|
|
7767
|
+
setAccessToken(storedAccessToken);
|
|
7768
|
+
setRefreshToken(storedRefreshToken);
|
|
7769
|
+
setIsAuthenticated(true);
|
|
7770
|
+
setIsReady(true);
|
|
7771
|
+
}
|
|
7772
|
+
else if (refreshValid) {
|
|
7773
|
+
// Access token expired but refresh still valid → refresh immediately
|
|
7774
|
+
setAccessToken(storedAccessToken);
|
|
7775
|
+
setRefreshToken(storedRefreshToken);
|
|
7776
|
+
(async () => {
|
|
7777
|
+
try {
|
|
7778
|
+
await refreshTokens();
|
|
7779
|
+
}
|
|
7780
|
+
catch (_a) {
|
|
7781
|
+
// Refresh failed → clear tokens
|
|
7782
|
+
setAccessToken(null);
|
|
7783
|
+
setRefreshToken(null);
|
|
7784
|
+
setIsAuthenticated(false);
|
|
7785
|
+
}
|
|
7786
|
+
setIsReady(true);
|
|
7787
|
+
})();
|
|
7788
|
+
return; // setIsReady will be called in the async block
|
|
7789
|
+
}
|
|
7790
|
+
else {
|
|
7791
|
+
// Refresh expired or no tokens → clear
|
|
7792
|
+
setAccessToken(null);
|
|
7793
|
+
setRefreshToken(null);
|
|
7794
|
+
setIsAuthenticated(false);
|
|
7795
|
+
setIsReady(true);
|
|
7796
|
+
}
|
|
7797
|
+
}
|
|
7798
|
+
else {
|
|
7799
|
+
setIsReady(true);
|
|
7800
|
+
}
|
|
7801
|
+
}, [address]);
|
|
7677
7802
|
useEffect(() => {
|
|
7678
7803
|
const cleanup = addAuthInterceptors({
|
|
7679
7804
|
apiBaseUrl,
|
|
7680
7805
|
getAccessToken: () => {
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7806
|
+
if (typeof window === 'undefined')
|
|
7807
|
+
return null;
|
|
7808
|
+
// Read from Zustand state as single source of truth
|
|
7809
|
+
return useUserData.getState().accessToken;
|
|
7684
7810
|
},
|
|
7685
7811
|
refreshTokens: async () => {
|
|
7686
7812
|
const data = await refreshTokens();
|
|
@@ -7694,6 +7820,55 @@ function useAuth() {
|
|
|
7694
7820
|
cleanup();
|
|
7695
7821
|
};
|
|
7696
7822
|
}, [apiBaseUrl]);
|
|
7823
|
+
// Proactive refresh effect: refresh when app regains focus or timer fires
|
|
7824
|
+
useEffect(() => {
|
|
7825
|
+
if (typeof window === 'undefined' || !address || !refreshToken$1)
|
|
7826
|
+
return;
|
|
7827
|
+
const refreshIfNeeded = async () => {
|
|
7828
|
+
// Prevent concurrent refresh attempts
|
|
7829
|
+
if (isRefreshingRef.current)
|
|
7830
|
+
return;
|
|
7831
|
+
// Read fresh expiration values from localStorage (not stale closure)
|
|
7832
|
+
const accessExpRaw = localStorage.getItem(`${address}_accessTokenExpiresAt`);
|
|
7833
|
+
const refreshExpRaw = localStorage.getItem(`${address}_refreshTokenExpiresAt`);
|
|
7834
|
+
const accessExp = accessExpRaw ? Number(accessExpRaw) : 0;
|
|
7835
|
+
const refreshExp = refreshExpRaw ? Number(refreshExpRaw) : 0;
|
|
7836
|
+
const now = nowMs();
|
|
7837
|
+
// If refresh token is already expired, do nothing
|
|
7838
|
+
if (refreshExp <= now)
|
|
7839
|
+
return;
|
|
7840
|
+
// If access token is within buffer window, refresh
|
|
7841
|
+
if (accessExp - now <= ACCESS_TOKEN_BUFFER_MS) {
|
|
7842
|
+
isRefreshingRef.current = true;
|
|
7843
|
+
try {
|
|
7844
|
+
await refreshTokens();
|
|
7845
|
+
}
|
|
7846
|
+
catch (_a) {
|
|
7847
|
+
// Refresh failed, interceptor will handle logout on next API call
|
|
7848
|
+
}
|
|
7849
|
+
finally {
|
|
7850
|
+
isRefreshingRef.current = false;
|
|
7851
|
+
}
|
|
7852
|
+
}
|
|
7853
|
+
};
|
|
7854
|
+
const onVisibilityChange = () => {
|
|
7855
|
+
if (document.visibilityState === 'visible') {
|
|
7856
|
+
refreshIfNeeded();
|
|
7857
|
+
}
|
|
7858
|
+
};
|
|
7859
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
7860
|
+
// Schedule timer for (accessExp - buffer)
|
|
7861
|
+
const accessExpRaw = localStorage.getItem(`${address}_accessTokenExpiresAt`);
|
|
7862
|
+
const accessExp = accessExpRaw ? Number(accessExpRaw) : 0;
|
|
7863
|
+
const delay = Math.max(0, accessExp - nowMs() - ACCESS_TOKEN_BUFFER_MS);
|
|
7864
|
+
const timer = window.setTimeout(() => {
|
|
7865
|
+
refreshIfNeeded();
|
|
7866
|
+
}, delay);
|
|
7867
|
+
return () => {
|
|
7868
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
7869
|
+
clearTimeout(timer);
|
|
7870
|
+
};
|
|
7871
|
+
}, [address, refreshToken$1]);
|
|
7697
7872
|
async function getEip712(address) {
|
|
7698
7873
|
const { data } = await getEIP712Message(apiBaseUrl, address, clientId);
|
|
7699
7874
|
return data;
|
|
@@ -7701,17 +7876,21 @@ function useAuth() {
|
|
|
7701
7876
|
async function loginWithSignedMessage(address, signature, timestamp) {
|
|
7702
7877
|
try {
|
|
7703
7878
|
const { data } = await authenticate(apiBaseUrl, {
|
|
7704
|
-
method:
|
|
7879
|
+
method: 'eip712',
|
|
7705
7880
|
address,
|
|
7706
7881
|
clientId,
|
|
7707
7882
|
details: { signature, timestamp },
|
|
7708
7883
|
});
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7884
|
+
const accessTokenKey = `${address}_accessToken`;
|
|
7885
|
+
const refreshTokenKey = `${address}_refreshToken`;
|
|
7886
|
+
const accessTokenExpiresAtKey = `${address}_accessTokenExpiresAt`;
|
|
7887
|
+
const refreshTokenExpiresAtKey = `${address}_refreshTokenExpiresAt`;
|
|
7888
|
+
window.localStorage.setItem(accessTokenKey, data.accessToken);
|
|
7889
|
+
window.localStorage.setItem(refreshTokenKey, data.refreshToken);
|
|
7890
|
+
window.localStorage.setItem(accessTokenExpiresAtKey, String(calcExpiresAt(data.expiresIn)));
|
|
7891
|
+
window.localStorage.setItem(refreshTokenExpiresAtKey, String(nowMs() + REFRESH_TOKEN_TTL_MS));
|
|
7712
7892
|
setAccessToken(data.accessToken);
|
|
7713
7893
|
setRefreshToken(data.refreshToken);
|
|
7714
|
-
setAddress(address);
|
|
7715
7894
|
setIsAuthenticated(true);
|
|
7716
7895
|
}
|
|
7717
7896
|
catch (e) {
|
|
@@ -7726,12 +7905,16 @@ function useAuth() {
|
|
|
7726
7905
|
appId,
|
|
7727
7906
|
accessToken: privyAccessToken,
|
|
7728
7907
|
});
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7908
|
+
const accessTokenKey = `${address}_accessToken`;
|
|
7909
|
+
const refreshTokenKey = `${address}_refreshToken`;
|
|
7910
|
+
const accessTokenExpiresAtKey = `${address}_accessTokenExpiresAt`;
|
|
7911
|
+
const refreshTokenExpiresAtKey = `${address}_refreshTokenExpiresAt`;
|
|
7912
|
+
window.localStorage.setItem(accessTokenKey, data.accessToken);
|
|
7913
|
+
window.localStorage.setItem(refreshTokenKey, data.refreshToken);
|
|
7914
|
+
window.localStorage.setItem(accessTokenExpiresAtKey, String(calcExpiresAt(data.expiresIn)));
|
|
7915
|
+
window.localStorage.setItem(refreshTokenExpiresAtKey, String(nowMs() + REFRESH_TOKEN_TTL_MS));
|
|
7732
7916
|
setAccessToken(data.accessToken);
|
|
7733
7917
|
setRefreshToken(data.refreshToken);
|
|
7734
|
-
setAddress(address);
|
|
7735
7918
|
setIsAuthenticated(true);
|
|
7736
7919
|
}
|
|
7737
7920
|
catch (e) {
|
|
@@ -7739,35 +7922,56 @@ function useAuth() {
|
|
|
7739
7922
|
}
|
|
7740
7923
|
}
|
|
7741
7924
|
async function refreshTokens() {
|
|
7742
|
-
const
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7925
|
+
const currentAddress = address;
|
|
7926
|
+
const currentRefresh = refreshToken$1;
|
|
7927
|
+
if (!currentRefresh || !currentAddress)
|
|
7928
|
+
throw new Error('No refresh token');
|
|
7929
|
+
const { data } = await refreshToken(apiBaseUrl, currentRefresh);
|
|
7930
|
+
// Update tokens in localStorage
|
|
7931
|
+
const accessTokenKey = `${currentAddress}_accessToken`;
|
|
7932
|
+
const refreshTokenKey = `${currentAddress}_refreshToken`;
|
|
7933
|
+
const accessTokenExpiresAtKey = `${currentAddress}_accessTokenExpiresAt`;
|
|
7934
|
+
const refreshTokenExpiresAtKey = `${currentAddress}_refreshTokenExpiresAt`;
|
|
7935
|
+
window.localStorage.setItem(accessTokenKey, data.accessToken);
|
|
7936
|
+
window.localStorage.setItem(refreshTokenKey, data.refreshToken);
|
|
7937
|
+
window.localStorage.setItem(accessTokenExpiresAtKey, String(calcExpiresAt(data.expiresIn)));
|
|
7938
|
+
window.localStorage.setItem(refreshTokenExpiresAtKey, String(nowMs() + REFRESH_TOKEN_TTL_MS));
|
|
7748
7939
|
setAccessToken(data.accessToken);
|
|
7749
7940
|
setRefreshToken(data.refreshToken);
|
|
7750
7941
|
setIsAuthenticated(true);
|
|
7751
7942
|
return data;
|
|
7752
7943
|
}
|
|
7753
7944
|
async function logout$1() {
|
|
7754
|
-
const
|
|
7755
|
-
|
|
7945
|
+
const currentAddress = address;
|
|
7946
|
+
const currentRefresh = refreshToken$1;
|
|
7947
|
+
if (currentRefresh) {
|
|
7756
7948
|
try {
|
|
7757
|
-
await logout(apiBaseUrl,
|
|
7949
|
+
await logout(apiBaseUrl, currentRefresh);
|
|
7758
7950
|
}
|
|
7759
7951
|
catch (_a) {
|
|
7760
7952
|
/* ignore */
|
|
7761
7953
|
}
|
|
7762
7954
|
}
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7955
|
+
if (currentAddress) {
|
|
7956
|
+
const accessTokenKey = `${currentAddress}_accessToken`;
|
|
7957
|
+
const refreshTokenKey = `${currentAddress}_refreshToken`;
|
|
7958
|
+
const accessTokenExpiresAtKey = `${currentAddress}_accessTokenExpiresAt`;
|
|
7959
|
+
const refreshTokenExpiresAtKey = `${currentAddress}_refreshTokenExpiresAt`;
|
|
7960
|
+
window.localStorage.removeItem(accessTokenKey);
|
|
7961
|
+
window.localStorage.removeItem(refreshTokenKey);
|
|
7962
|
+
window.localStorage.removeItem(accessTokenExpiresAtKey);
|
|
7963
|
+
window.localStorage.removeItem(refreshTokenExpiresAtKey);
|
|
7964
|
+
}
|
|
7766
7965
|
setAccessToken(null);
|
|
7767
7966
|
setRefreshToken(null);
|
|
7768
7967
|
setAddress(null);
|
|
7769
7968
|
setIsAuthenticated(false);
|
|
7770
7969
|
}
|
|
7970
|
+
function clearSession() {
|
|
7971
|
+
setAccessToken(null);
|
|
7972
|
+
setRefreshToken(null);
|
|
7973
|
+
setIsAuthenticated(false);
|
|
7974
|
+
}
|
|
7771
7975
|
return {
|
|
7772
7976
|
isReady,
|
|
7773
7977
|
isAuthenticated,
|
|
@@ -7779,6 +7983,8 @@ function useAuth() {
|
|
|
7779
7983
|
loginWithPrivyToken,
|
|
7780
7984
|
refreshTokens,
|
|
7781
7985
|
logout: logout$1,
|
|
7986
|
+
clearSession,
|
|
7987
|
+
setAddress,
|
|
7782
7988
|
};
|
|
7783
7989
|
}
|
|
7784
7990
|
|
|
@@ -8091,9 +8297,16 @@ const PearHyperliquidContext = createContext(undefined);
|
|
|
8091
8297
|
/**
|
|
8092
8298
|
* React Provider for PearHyperliquidClient
|
|
8093
8299
|
*/
|
|
8094
|
-
const PearHyperliquidProvider = ({ children, apiBaseUrl =
|
|
8300
|
+
const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearprotocol.io", clientId = "PEARPROTOCOLUI", wsUrl = "wss://hl-ui.pearprotocol.io/ws", }) => {
|
|
8095
8301
|
const address = useUserData((s) => s.address);
|
|
8096
|
-
const
|
|
8302
|
+
const clearHyperliquidUserData = useHyperliquidData((state) => state.clearUserData);
|
|
8303
|
+
const prevAddressRef = useRef(null);
|
|
8304
|
+
useEffect(() => {
|
|
8305
|
+
if (prevAddressRef.current !== null && prevAddressRef.current !== address) {
|
|
8306
|
+
clearHyperliquidUserData();
|
|
8307
|
+
}
|
|
8308
|
+
prevAddressRef.current = address;
|
|
8309
|
+
}, [address, clearHyperliquidUserData]);
|
|
8097
8310
|
const perpMetasByDex = useHyperliquidData((state) => state.perpMetasByDex);
|
|
8098
8311
|
const setPerpDexs = useHyperliquidData((state) => state.setPerpDexs);
|
|
8099
8312
|
const setPerpMetasByDex = useHyperliquidData((state) => state.setPerpMetasByDex);
|
|
@@ -8124,20 +8337,20 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8124
8337
|
perpMetas.forEach((item, perpIndex) => {
|
|
8125
8338
|
var _a, _b;
|
|
8126
8339
|
const dexName = perpIndex === 0
|
|
8127
|
-
?
|
|
8340
|
+
? "HYPERLIQUID"
|
|
8128
8341
|
: ((_b = (_a = perpDexs[perpIndex]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : `DEX_${perpIndex}`);
|
|
8129
8342
|
var collateralToken;
|
|
8130
8343
|
if (item.collateralToken === 360) {
|
|
8131
|
-
collateralToken =
|
|
8344
|
+
collateralToken = "USDH";
|
|
8132
8345
|
}
|
|
8133
8346
|
if (item.collateralToken === 0) {
|
|
8134
|
-
collateralToken =
|
|
8347
|
+
collateralToken = "USDC";
|
|
8135
8348
|
}
|
|
8136
8349
|
if (item.collateralToken === 235) {
|
|
8137
|
-
collateralToken =
|
|
8350
|
+
collateralToken = "USDE";
|
|
8138
8351
|
}
|
|
8139
8352
|
if (item.collateralToken === 268) {
|
|
8140
|
-
collateralToken =
|
|
8353
|
+
collateralToken = "USDT0";
|
|
8141
8354
|
}
|
|
8142
8355
|
const universeAssets = item.universe.map((asset) => ({
|
|
8143
8356
|
...asset,
|
|
@@ -8165,8 +8378,6 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8165
8378
|
}), [
|
|
8166
8379
|
apiBaseUrl,
|
|
8167
8380
|
wsUrl,
|
|
8168
|
-
address,
|
|
8169
|
-
setAddress,
|
|
8170
8381
|
isConnected,
|
|
8171
8382
|
lastError,
|
|
8172
8383
|
nativeIsConnected,
|
|
@@ -8181,7 +8392,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8181
8392
|
function usePearHyperliquid() {
|
|
8182
8393
|
const ctx = useContext(PearHyperliquidContext);
|
|
8183
8394
|
if (!ctx)
|
|
8184
|
-
throw new Error(
|
|
8395
|
+
throw new Error("usePearHyperliquid must be used within a PearHyperliquidProvider");
|
|
8185
8396
|
return ctx;
|
|
8186
8397
|
}
|
|
8187
8398
|
|
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;
|
|
@@ -28,6 +28,7 @@ interface HyperliquidDataState {
|
|
|
28
28
|
setPerpDexs: (value: PerpDexsResponse | null) => void;
|
|
29
29
|
setPerpMetasByDex: (value: Map<string, UniverseAsset[]> | null) => void;
|
|
30
30
|
setAssetContextsByDex: (value: Map<string, WebData3AssetCtx[]> | null) => void;
|
|
31
|
+
clearUserData: () => void;
|
|
31
32
|
}
|
|
32
33
|
export declare const useHyperliquidData: import("zustand").UseBoundStore<import("zustand").StoreApi<HyperliquidDataState>>;
|
|
33
34
|
export {};
|
|
@@ -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
|
/**
|