@pear-protocol/hyperliquid-sdk 0.1.14 → 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 +3 -0
- package/dist/index.js +391 -164
- 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/types.d.ts +1 -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
|
@@ -228,6 +228,7 @@ interface TwapMonitoringDto {
|
|
|
228
228
|
interface TradeHistoryAssetDataDto {
|
|
229
229
|
coin: string;
|
|
230
230
|
entryWeight: number;
|
|
231
|
+
closeWeight: number;
|
|
231
232
|
entryPrice: number;
|
|
232
233
|
limitPrice: number;
|
|
233
234
|
leverage: number;
|
|
@@ -1430,6 +1431,8 @@ declare function useAuth(): {
|
|
|
1430
1431
|
readonly loginWithPrivyToken: (address: string, appId: string, privyAccessToken: string) => Promise<void>;
|
|
1431
1432
|
readonly refreshTokens: () => Promise<RefreshTokenResponse>;
|
|
1432
1433
|
readonly logout: () => Promise<void>;
|
|
1434
|
+
readonly clearSession: () => void;
|
|
1435
|
+
readonly setAddress: (address: string | null) => void;
|
|
1433
1436
|
};
|
|
1434
1437
|
|
|
1435
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
|
},
|
|
@@ -1566,17 +1583,33 @@ const useTradeHistories = () => {
|
|
|
1566
1583
|
const enrichedTradeHistories = useMemo(() => {
|
|
1567
1584
|
if (!tradeHistories)
|
|
1568
1585
|
return null;
|
|
1569
|
-
return tradeHistories.map((history) =>
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1586
|
+
return tradeHistories.map((history) => {
|
|
1587
|
+
const totalClosePositionSize = history.closedLongAssets.reduce((acc, asset) => acc + asset.limitPrice * asset.size, 0) +
|
|
1588
|
+
history.closedShortAssets.reduce((acc, asset) => acc + asset.limitPrice * asset.size, 0);
|
|
1589
|
+
return {
|
|
1590
|
+
...history,
|
|
1591
|
+
closedLongAssets: history.closedLongAssets.map((asset) => {
|
|
1592
|
+
const closeNotional = asset.limitPrice * asset.size;
|
|
1593
|
+
return {
|
|
1594
|
+
...asset,
|
|
1595
|
+
closeWeight: totalClosePositionSize > 0
|
|
1596
|
+
? closeNotional / totalClosePositionSize
|
|
1597
|
+
: 0,
|
|
1598
|
+
metadata: getAssetByName(asset.coin),
|
|
1599
|
+
};
|
|
1600
|
+
}),
|
|
1601
|
+
closedShortAssets: history.closedShortAssets.map((asset) => {
|
|
1602
|
+
const closeNotional = asset.limitPrice * asset.size;
|
|
1603
|
+
return {
|
|
1604
|
+
...asset,
|
|
1605
|
+
closeWeight: totalClosePositionSize > 0
|
|
1606
|
+
? closeNotional / totalClosePositionSize
|
|
1607
|
+
: 0,
|
|
1608
|
+
metadata: getAssetByName(asset.coin),
|
|
1609
|
+
};
|
|
1610
|
+
}),
|
|
1611
|
+
};
|
|
1612
|
+
});
|
|
1580
1613
|
}, [tradeHistories, getAssetByName]);
|
|
1581
1614
|
const isLoading = useMemo(() => {
|
|
1582
1615
|
return tradeHistories === null && context.isConnected;
|
|
@@ -5926,10 +5959,10 @@ function toApiError(error) {
|
|
|
5926
5959
|
var _a;
|
|
5927
5960
|
const axiosError = error;
|
|
5928
5961
|
const payload = (axiosError && axiosError.response ? axiosError.response.data : undefined);
|
|
5929
|
-
const message = typeof payload ===
|
|
5962
|
+
const message = typeof payload === "object" && payload && "message" in payload
|
|
5930
5963
|
? String(payload.message)
|
|
5931
|
-
: (axiosError === null || axiosError === void 0 ? void 0 : axiosError.message) ||
|
|
5932
|
-
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
|
|
5933
5966
|
? String(payload.error)
|
|
5934
5967
|
: undefined;
|
|
5935
5968
|
return {
|
|
@@ -5939,8 +5972,8 @@ function toApiError(error) {
|
|
|
5939
5972
|
};
|
|
5940
5973
|
}
|
|
5941
5974
|
function joinUrl(baseUrl, path) {
|
|
5942
|
-
const cleanBase = baseUrl.replace(/\/$/,
|
|
5943
|
-
const cleanPath = path.startsWith(
|
|
5975
|
+
const cleanBase = baseUrl.replace(/\/$/, "");
|
|
5976
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
5944
5977
|
return `${cleanBase}${cleanPath}`;
|
|
5945
5978
|
}
|
|
5946
5979
|
/**
|
|
@@ -5969,7 +6002,7 @@ function addAuthInterceptors(params) {
|
|
|
5969
6002
|
pendingRequests = [];
|
|
5970
6003
|
}
|
|
5971
6004
|
const isOurApiUrl = (url) => Boolean(url && url.startsWith(apiBaseUrl));
|
|
5972
|
-
const isRefreshUrl = (url) => Boolean(url && url.startsWith(joinUrl(apiBaseUrl,
|
|
6005
|
+
const isRefreshUrl = (url) => Boolean(url && url.startsWith(joinUrl(apiBaseUrl, "/auth/refresh")));
|
|
5973
6006
|
const reqId = apiClient.interceptors.request.use((config) => {
|
|
5974
6007
|
var _a;
|
|
5975
6008
|
try {
|
|
@@ -5977,11 +6010,12 @@ function addAuthInterceptors(params) {
|
|
|
5977
6010
|
const token = getAccessToken();
|
|
5978
6011
|
if (token) {
|
|
5979
6012
|
config.headers = (_a = config.headers) !== null && _a !== void 0 ? _a : {};
|
|
5980
|
-
|
|
6013
|
+
config.headers["Authorization"] = `Bearer ${token}`;
|
|
5981
6014
|
}
|
|
5982
6015
|
}
|
|
5983
6016
|
}
|
|
5984
|
-
catch (
|
|
6017
|
+
catch (err) {
|
|
6018
|
+
console.error("[Auth Interceptor] Request interceptor error:", err);
|
|
5985
6019
|
}
|
|
5986
6020
|
return config;
|
|
5987
6021
|
});
|
|
@@ -5992,22 +6026,36 @@ function addAuthInterceptors(params) {
|
|
|
5992
6026
|
const url = originalRequest === null || originalRequest === void 0 ? void 0 : originalRequest.url;
|
|
5993
6027
|
// If not our API or not 401, just reject
|
|
5994
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
|
+
}
|
|
5995
6036
|
return Promise.reject(error);
|
|
5996
6037
|
}
|
|
6038
|
+
console.log("[Auth Interceptor] 401 detected, attempting token refresh for URL:", url);
|
|
5997
6039
|
// If the 401 is from refresh endpoint itself -> force logout
|
|
5998
6040
|
if (isRefreshUrl(url)) {
|
|
6041
|
+
console.warn("[Auth Interceptor] Refresh endpoint returned 401, logging out");
|
|
5999
6042
|
try {
|
|
6000
6043
|
await logout();
|
|
6001
6044
|
}
|
|
6002
|
-
catch (
|
|
6045
|
+
catch (err) {
|
|
6046
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6047
|
+
}
|
|
6003
6048
|
return Promise.reject(error);
|
|
6004
6049
|
}
|
|
6005
6050
|
// Prevent infinite loop
|
|
6006
6051
|
if (originalRequest && originalRequest._retry) {
|
|
6052
|
+
console.warn("[Auth Interceptor] Request already retried, logging out");
|
|
6007
6053
|
try {
|
|
6008
6054
|
await logout();
|
|
6009
6055
|
}
|
|
6010
|
-
catch (
|
|
6056
|
+
catch (err) {
|
|
6057
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6058
|
+
}
|
|
6011
6059
|
return Promise.reject(error);
|
|
6012
6060
|
}
|
|
6013
6061
|
// Mark so we don't retry twice
|
|
@@ -6021,31 +6069,45 @@ function addAuthInterceptors(params) {
|
|
|
6021
6069
|
if (!newToken || !originalRequest)
|
|
6022
6070
|
return reject(error);
|
|
6023
6071
|
originalRequest.headers = (_a = originalRequest.headers) !== null && _a !== void 0 ? _a : {};
|
|
6024
|
-
originalRequest.headers[
|
|
6072
|
+
originalRequest.headers["Authorization"] =
|
|
6073
|
+
`Bearer ${newToken}`;
|
|
6025
6074
|
resolve(apiClient.request(originalRequest));
|
|
6026
6075
|
});
|
|
6027
6076
|
});
|
|
6028
6077
|
}
|
|
6029
6078
|
isRefreshing = true;
|
|
6030
6079
|
try {
|
|
6080
|
+
console.log("[Auth Interceptor] Refreshing tokens...");
|
|
6031
6081
|
const refreshed = await refreshTokens();
|
|
6032
|
-
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
|
+
}
|
|
6033
6090
|
resolvePendingRequests(newAccessToken);
|
|
6034
6091
|
if (originalRequest) {
|
|
6035
6092
|
originalRequest.headers = (_c = originalRequest.headers) !== null && _c !== void 0 ? _c : {};
|
|
6036
6093
|
if (newAccessToken)
|
|
6037
|
-
|
|
6094
|
+
originalRequest.headers["Authorization"] =
|
|
6095
|
+
`Bearer ${newAccessToken}`;
|
|
6096
|
+
console.log("[Auth Interceptor] Retrying original request with new token");
|
|
6038
6097
|
const resp = await apiClient.request(originalRequest);
|
|
6039
6098
|
return resp;
|
|
6040
6099
|
}
|
|
6041
6100
|
return Promise.reject(error);
|
|
6042
6101
|
}
|
|
6043
6102
|
catch (refreshErr) {
|
|
6103
|
+
console.error("[Auth Interceptor] Token refresh failed:", refreshErr);
|
|
6044
6104
|
resolvePendingRequests(null);
|
|
6045
6105
|
try {
|
|
6046
6106
|
await logout();
|
|
6047
6107
|
}
|
|
6048
|
-
catch (
|
|
6108
|
+
catch (err) {
|
|
6109
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6110
|
+
}
|
|
6049
6111
|
return Promise.reject(refreshErr);
|
|
6050
6112
|
}
|
|
6051
6113
|
finally {
|
|
@@ -6056,11 +6118,15 @@ function addAuthInterceptors(params) {
|
|
|
6056
6118
|
try {
|
|
6057
6119
|
apiClient.interceptors.request.eject(reqId);
|
|
6058
6120
|
}
|
|
6059
|
-
catch (
|
|
6121
|
+
catch (err) {
|
|
6122
|
+
console.error("[Auth Interceptor] Failed to eject request interceptor:", err);
|
|
6123
|
+
}
|
|
6060
6124
|
try {
|
|
6061
6125
|
apiClient.interceptors.response.eject(resId);
|
|
6062
6126
|
}
|
|
6063
|
-
catch (
|
|
6127
|
+
catch (err) {
|
|
6128
|
+
console.error("[Auth Interceptor] Failed to eject response interceptor:", err);
|
|
6129
|
+
}
|
|
6064
6130
|
};
|
|
6065
6131
|
}
|
|
6066
6132
|
|
|
@@ -7577,20 +7643,34 @@ function usePortfolio() {
|
|
|
7577
7643
|
}
|
|
7578
7644
|
|
|
7579
7645
|
async function getEIP712Message(baseUrl, address, clientId) {
|
|
7580
|
-
const url = joinUrl(baseUrl,
|
|
7646
|
+
const url = joinUrl(baseUrl, "/auth/eip712-message");
|
|
7581
7647
|
try {
|
|
7582
|
-
const resp = await
|
|
7583
|
-
|
|
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
|
+
};
|
|
7584
7657
|
}
|
|
7585
7658
|
catch (error) {
|
|
7586
7659
|
throw toApiError(error);
|
|
7587
7660
|
}
|
|
7588
7661
|
}
|
|
7589
7662
|
async function authenticate(baseUrl, body) {
|
|
7590
|
-
const url = joinUrl(baseUrl,
|
|
7663
|
+
const url = joinUrl(baseUrl, "/auth/login");
|
|
7591
7664
|
try {
|
|
7592
|
-
const resp = await
|
|
7593
|
-
|
|
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
|
+
};
|
|
7594
7674
|
}
|
|
7595
7675
|
catch (error) {
|
|
7596
7676
|
throw toApiError(error);
|
|
@@ -7601,7 +7681,7 @@ async function authenticate(baseUrl, body) {
|
|
|
7601
7681
|
*/
|
|
7602
7682
|
async function authenticateWithPrivy(baseUrl, params) {
|
|
7603
7683
|
const body = {
|
|
7604
|
-
method:
|
|
7684
|
+
method: "privy_access_token",
|
|
7605
7685
|
address: params.address,
|
|
7606
7686
|
clientId: params.clientId,
|
|
7607
7687
|
details: { appId: params.appId, accessToken: params.accessToken },
|
|
@@ -7609,62 +7689,124 @@ async function authenticateWithPrivy(baseUrl, params) {
|
|
|
7609
7689
|
return authenticate(baseUrl, body);
|
|
7610
7690
|
}
|
|
7611
7691
|
async function refreshToken(baseUrl, refreshTokenVal) {
|
|
7612
|
-
const url = joinUrl(baseUrl,
|
|
7692
|
+
const url = joinUrl(baseUrl, "/auth/refresh");
|
|
7613
7693
|
try {
|
|
7614
|
-
const resp = await
|
|
7615
|
-
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
|
+
};
|
|
7616
7700
|
}
|
|
7617
7701
|
catch (error) {
|
|
7618
7702
|
throw toApiError(error);
|
|
7619
7703
|
}
|
|
7620
7704
|
}
|
|
7621
7705
|
async function logout(baseUrl, refreshTokenVal) {
|
|
7622
|
-
const url = joinUrl(baseUrl,
|
|
7706
|
+
const url = joinUrl(baseUrl, "/auth/logout");
|
|
7623
7707
|
try {
|
|
7624
|
-
const resp = await
|
|
7625
|
-
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
|
+
};
|
|
7626
7714
|
}
|
|
7627
7715
|
catch (error) {
|
|
7628
7716
|
throw toApiError(error);
|
|
7629
7717
|
}
|
|
7630
7718
|
}
|
|
7631
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
|
+
}
|
|
7632
7729
|
function useAuth() {
|
|
7633
7730
|
const context = useContext(PearHyperliquidContext);
|
|
7634
7731
|
if (!context) {
|
|
7635
|
-
throw new Error(
|
|
7732
|
+
throw new Error('useAuth must be used within a PearHyperliquidProvider');
|
|
7636
7733
|
}
|
|
7637
7734
|
const { apiBaseUrl, clientId } = context;
|
|
7638
|
-
const [isReady, setIsReady] = useState(false);
|
|
7639
7735
|
const accessToken = useUserData((s) => s.accessToken);
|
|
7640
7736
|
const refreshToken$1 = useUserData((s) => s.refreshToken);
|
|
7737
|
+
const isReady = useUserData((s) => s.isReady);
|
|
7738
|
+
const isAuthenticated = useUserData((s) => s.isAuthenticated);
|
|
7641
7739
|
const setAccessToken = useUserData((s) => s.setAccessToken);
|
|
7642
7740
|
const setRefreshToken = useUserData((s) => s.setRefreshToken);
|
|
7643
|
-
const
|
|
7741
|
+
const setIsReady = useUserData((s) => s.setIsReady);
|
|
7644
7742
|
const setIsAuthenticated = useUserData((s) => s.setIsAuthenticated);
|
|
7645
7743
|
const address = useUserData((s) => s.address);
|
|
7646
7744
|
const setAddress = useUserData((s) => s.setAddress);
|
|
7745
|
+
// Ref to prevent concurrent refresh attempts
|
|
7746
|
+
const isRefreshingRef = useRef(false);
|
|
7647
7747
|
useEffect(() => {
|
|
7648
|
-
if (typeof window ==
|
|
7748
|
+
if (typeof window == 'undefined') {
|
|
7649
7749
|
return;
|
|
7650
7750
|
}
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
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]);
|
|
7661
7802
|
useEffect(() => {
|
|
7662
7803
|
const cleanup = addAuthInterceptors({
|
|
7663
7804
|
apiBaseUrl,
|
|
7664
7805
|
getAccessToken: () => {
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7806
|
+
if (typeof window === 'undefined')
|
|
7807
|
+
return null;
|
|
7808
|
+
// Read from Zustand state as single source of truth
|
|
7809
|
+
return useUserData.getState().accessToken;
|
|
7668
7810
|
},
|
|
7669
7811
|
refreshTokens: async () => {
|
|
7670
7812
|
const data = await refreshTokens();
|
|
@@ -7678,6 +7820,55 @@ function useAuth() {
|
|
|
7678
7820
|
cleanup();
|
|
7679
7821
|
};
|
|
7680
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]);
|
|
7681
7872
|
async function getEip712(address) {
|
|
7682
7873
|
const { data } = await getEIP712Message(apiBaseUrl, address, clientId);
|
|
7683
7874
|
return data;
|
|
@@ -7685,17 +7876,21 @@ function useAuth() {
|
|
|
7685
7876
|
async function loginWithSignedMessage(address, signature, timestamp) {
|
|
7686
7877
|
try {
|
|
7687
7878
|
const { data } = await authenticate(apiBaseUrl, {
|
|
7688
|
-
method:
|
|
7879
|
+
method: 'eip712',
|
|
7689
7880
|
address,
|
|
7690
7881
|
clientId,
|
|
7691
7882
|
details: { signature, timestamp },
|
|
7692
7883
|
});
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
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));
|
|
7696
7892
|
setAccessToken(data.accessToken);
|
|
7697
7893
|
setRefreshToken(data.refreshToken);
|
|
7698
|
-
setAddress(address);
|
|
7699
7894
|
setIsAuthenticated(true);
|
|
7700
7895
|
}
|
|
7701
7896
|
catch (e) {
|
|
@@ -7710,12 +7905,16 @@ function useAuth() {
|
|
|
7710
7905
|
appId,
|
|
7711
7906
|
accessToken: privyAccessToken,
|
|
7712
7907
|
});
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
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));
|
|
7716
7916
|
setAccessToken(data.accessToken);
|
|
7717
7917
|
setRefreshToken(data.refreshToken);
|
|
7718
|
-
setAddress(address);
|
|
7719
7918
|
setIsAuthenticated(true);
|
|
7720
7919
|
}
|
|
7721
7920
|
catch (e) {
|
|
@@ -7723,35 +7922,56 @@ function useAuth() {
|
|
|
7723
7922
|
}
|
|
7724
7923
|
}
|
|
7725
7924
|
async function refreshTokens() {
|
|
7726
|
-
const
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
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));
|
|
7732
7939
|
setAccessToken(data.accessToken);
|
|
7733
7940
|
setRefreshToken(data.refreshToken);
|
|
7734
7941
|
setIsAuthenticated(true);
|
|
7735
7942
|
return data;
|
|
7736
7943
|
}
|
|
7737
7944
|
async function logout$1() {
|
|
7738
|
-
const
|
|
7739
|
-
|
|
7945
|
+
const currentAddress = address;
|
|
7946
|
+
const currentRefresh = refreshToken$1;
|
|
7947
|
+
if (currentRefresh) {
|
|
7740
7948
|
try {
|
|
7741
|
-
await logout(apiBaseUrl,
|
|
7949
|
+
await logout(apiBaseUrl, currentRefresh);
|
|
7742
7950
|
}
|
|
7743
7951
|
catch (_a) {
|
|
7744
7952
|
/* ignore */
|
|
7745
7953
|
}
|
|
7746
7954
|
}
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
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
|
+
}
|
|
7750
7965
|
setAccessToken(null);
|
|
7751
7966
|
setRefreshToken(null);
|
|
7752
7967
|
setAddress(null);
|
|
7753
7968
|
setIsAuthenticated(false);
|
|
7754
7969
|
}
|
|
7970
|
+
function clearSession() {
|
|
7971
|
+
setAccessToken(null);
|
|
7972
|
+
setRefreshToken(null);
|
|
7973
|
+
setIsAuthenticated(false);
|
|
7974
|
+
}
|
|
7755
7975
|
return {
|
|
7756
7976
|
isReady,
|
|
7757
7977
|
isAuthenticated,
|
|
@@ -7763,6 +7983,8 @@ function useAuth() {
|
|
|
7763
7983
|
loginWithPrivyToken,
|
|
7764
7984
|
refreshTokens,
|
|
7765
7985
|
logout: logout$1,
|
|
7986
|
+
clearSession,
|
|
7987
|
+
setAddress,
|
|
7766
7988
|
};
|
|
7767
7989
|
}
|
|
7768
7990
|
|
|
@@ -8075,9 +8297,16 @@ const PearHyperliquidContext = createContext(undefined);
|
|
|
8075
8297
|
/**
|
|
8076
8298
|
* React Provider for PearHyperliquidClient
|
|
8077
8299
|
*/
|
|
8078
|
-
const PearHyperliquidProvider = ({ children, apiBaseUrl =
|
|
8300
|
+
const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearprotocol.io", clientId = "PEARPROTOCOLUI", wsUrl = "wss://hl-ui.pearprotocol.io/ws", }) => {
|
|
8079
8301
|
const address = useUserData((s) => s.address);
|
|
8080
|
-
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]);
|
|
8081
8310
|
const perpMetasByDex = useHyperliquidData((state) => state.perpMetasByDex);
|
|
8082
8311
|
const setPerpDexs = useHyperliquidData((state) => state.setPerpDexs);
|
|
8083
8312
|
const setPerpMetasByDex = useHyperliquidData((state) => state.setPerpMetasByDex);
|
|
@@ -8108,20 +8337,20 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8108
8337
|
perpMetas.forEach((item, perpIndex) => {
|
|
8109
8338
|
var _a, _b;
|
|
8110
8339
|
const dexName = perpIndex === 0
|
|
8111
|
-
?
|
|
8340
|
+
? "HYPERLIQUID"
|
|
8112
8341
|
: ((_b = (_a = perpDexs[perpIndex]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : `DEX_${perpIndex}`);
|
|
8113
8342
|
var collateralToken;
|
|
8114
8343
|
if (item.collateralToken === 360) {
|
|
8115
|
-
collateralToken =
|
|
8344
|
+
collateralToken = "USDH";
|
|
8116
8345
|
}
|
|
8117
8346
|
if (item.collateralToken === 0) {
|
|
8118
|
-
collateralToken =
|
|
8347
|
+
collateralToken = "USDC";
|
|
8119
8348
|
}
|
|
8120
8349
|
if (item.collateralToken === 235) {
|
|
8121
|
-
collateralToken =
|
|
8350
|
+
collateralToken = "USDE";
|
|
8122
8351
|
}
|
|
8123
8352
|
if (item.collateralToken === 268) {
|
|
8124
|
-
collateralToken =
|
|
8353
|
+
collateralToken = "USDT0";
|
|
8125
8354
|
}
|
|
8126
8355
|
const universeAssets = item.universe.map((asset) => ({
|
|
8127
8356
|
...asset,
|
|
@@ -8149,8 +8378,6 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8149
8378
|
}), [
|
|
8150
8379
|
apiBaseUrl,
|
|
8151
8380
|
wsUrl,
|
|
8152
|
-
address,
|
|
8153
|
-
setAddress,
|
|
8154
8381
|
isConnected,
|
|
8155
8382
|
lastError,
|
|
8156
8383
|
nativeIsConnected,
|
|
@@ -8165,7 +8392,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8165
8392
|
function usePearHyperliquid() {
|
|
8166
8393
|
const ctx = useContext(PearHyperliquidContext);
|
|
8167
8394
|
if (!ctx)
|
|
8168
|
-
throw new Error(
|
|
8395
|
+
throw new Error("usePearHyperliquid must be used within a PearHyperliquidProvider");
|
|
8169
8396
|
return ctx;
|
|
8170
8397
|
}
|
|
8171
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/types.d.ts
CHANGED
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
|
/**
|