@pear-protocol/hyperliquid-sdk 0.1.12 → 0.1.13-beta.1

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