@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.
@@ -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
@@ -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
- 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
- }
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
- address: null,
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 === 'function' ? value(state.activeAssetData) : 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 = { ...((_a = state.activeAssetData) !== null && _a !== void 0 ? _a : {}), [key]: value };
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 ('success' in message || 'error' in message) {
937
+ if ("success" in message || "error" in message) {
921
938
  if (message.error) {
922
- console.error('[HyperLiquid WS] Subscription error:', message.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 ('channel' in message && 'data' in message) {
948
+ if ("channel" in message && "data" in message) {
932
949
  const response = message;
933
950
  switch (response.channel) {
934
- case 'userFills':
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('[HyperLiquid WS] userFills callback error', err));
955
+ maybePromise.catch((err) => console.error("[HyperLiquid WS] userFills callback error", err));
939
956
  }
940
957
  }
941
958
  break;
942
- case 'webData3':
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 'allDexsAssetCtxs':
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 === '' ? 'HYPERLIQUID' : 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 'allDexsClearinghouseState':
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 || '0') || 0), 0);
968
- const toStr = (n) => Number.isFinite(n) ? n.toString() : '0';
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 'allMids':
1015
+ case "allMids":
999
1016
  {
1000
1017
  const data = response.data;
1001
1018
  setAllMids(data);
1002
1019
  }
1003
1020
  break;
1004
- case 'activeAssetData':
1021
+ case "activeAssetData":
1005
1022
  {
1006
1023
  const assetData = response.data;
1007
1024
  upsertActiveAssetData(assetData.coin, assetData);
1008
1025
  }
1009
1026
  break;
1010
- case 'candle':
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 'spotState':
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('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', event.data);
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('[HyperLiquid WS] connect() called, enabled:', enabled);
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('[HyperLiquid WS] Creating new WebSocket connection');
1079
+ console.log("[HyperLiquid WS] Creating new WebSocket connection");
1063
1080
  manualCloseRef.current = false;
1064
1081
  setReadyState(ReadyState.CONNECTING);
1065
- const ws = new WebSocket('wss://api.hyperliquid.xyz/ws');
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('[HyperLiquid WS] Connection error:', event);
1075
- setLastError('WebSocket error');
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: 'ping' });
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 = '0x0000000000000000000000000000000000000000';
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('[HyperLiquid WS] Address subscription effect running');
1189
- console.log('[HyperLiquid WS] address:', address, 'userAddress:', userAddress, 'subscribedAddress:', subscribedAddress, 'normalizedSubscribedAddress:', normalizedSubscribedAddress);
1190
- console.log('[HyperLiquid WS] isConnected:', isConnected);
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('[HyperLiquid WS] Address unchanged, skipping subscription update');
1209
+ console.log("[HyperLiquid WS] Address unchanged, skipping subscription update");
1193
1210
  return;
1194
1211
  }
1195
1212
  if (!isConnected) {
1196
- console.log('[HyperLiquid WS] Not connected, skipping subscription update');
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('[HyperLiquid WS] Unsubscribing from previous address:', subscribedAddress);
1218
+ console.log("[HyperLiquid WS] Unsubscribing from previous address:", subscribedAddress);
1202
1219
  const unsubscribeMessage = {
1203
- method: 'unsubscribe',
1220
+ method: "unsubscribe",
1204
1221
  subscription: {
1205
- type: 'webData3',
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: 'unsubscribe',
1230
+ method: "unsubscribe",
1214
1231
  subscription: {
1215
- type: 'spotState',
1232
+ type: "spotState",
1216
1233
  user: subscribedAddress,
1217
1234
  },
1218
1235
  };
1219
1236
  sendJsonMessage(unsubscribeSpotState);
1220
1237
  }
1221
1238
  const unsubscribeAllDexsClearinghouseState = {
1222
- method: 'unsubscribe',
1239
+ method: "unsubscribe",
1223
1240
  subscription: {
1224
- type: 'allDexsClearinghouseState',
1241
+ type: "allDexsClearinghouseState",
1225
1242
  user: subscribedAddress,
1226
1243
  },
1227
1244
  };
1228
1245
  sendJsonMessage(unsubscribeAllDexsClearinghouseState);
1229
1246
  const unsubscribeUserFills = {
1230
- method: 'unsubscribe',
1247
+ method: "unsubscribe",
1231
1248
  subscription: {
1232
- type: 'userFills',
1249
+ type: "userFills",
1233
1250
  user: subscribedAddress,
1234
1251
  },
1235
1252
  };
1236
1253
  sendJsonMessage(unsubscribeUserFills);
1237
1254
  }
1238
1255
  const subscribeWebData3 = {
1239
- method: 'subscribe',
1256
+ method: "subscribe",
1240
1257
  subscription: {
1241
- type: 'webData3',
1258
+ type: "webData3",
1242
1259
  user: userAddress,
1243
1260
  },
1244
1261
  };
1245
1262
  // Subscribe to allMids
1246
1263
  const subscribeAllMids = {
1247
- method: 'subscribe',
1264
+ method: "subscribe",
1248
1265
  subscription: {
1249
- type: 'allMids',
1250
- dex: 'ALL_DEXS',
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: 'subscribe',
1272
+ method: "subscribe",
1256
1273
  subscription: {
1257
- type: 'allDexsAssetCtxs',
1274
+ type: "allDexsAssetCtxs",
1258
1275
  },
1259
1276
  };
1260
- console.log('[HyperLiquid WS] Subscribing to new address:', userAddress);
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: 'subscribe',
1285
+ method: "subscribe",
1269
1286
  subscription: {
1270
- type: 'spotState',
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: 'subscribe',
1297
+ method: "subscribe",
1281
1298
  subscription: {
1282
- type: 'allDexsClearinghouseState',
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: 'subscribe',
1333
+ method: "subscribe",
1317
1334
  subscription: {
1318
- type: 'userFills',
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: 'unsubscribe',
1357
+ method: "unsubscribe",
1341
1358
  subscription: {
1342
- type: 'activeAssetData',
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: 'subscribe',
1369
+ method: "subscribe",
1353
1370
  subscription: {
1354
- type: 'activeAssetData',
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: 'unsubscribe',
1400
+ method: "unsubscribe",
1384
1401
  subscription: {
1385
- type: 'candle',
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: 'unsubscribe',
1417
+ method: "unsubscribe",
1401
1418
  subscription: {
1402
- type: 'candle',
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: 'subscribe',
1429
+ method: "subscribe",
1413
1430
  subscription: {
1414
- type: 'candle',
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
- ...history,
1571
- closedLongAssets: history.closedLongAssets.map((asset) => ({
1572
- ...asset,
1573
- metadata: getAssetByName(asset.coin),
1574
- })),
1575
- closedShortAssets: history.closedShortAssets.map((asset) => ({
1576
- ...asset,
1577
- metadata: getAssetByName(asset.coin),
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 === 'object' && payload && 'message' in 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) || 'Request failed';
5932
- const errField = typeof payload === 'object' && payload && 'error' in 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('/') ? path : `/${path}`;
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, '/auth/refresh')));
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
- (config.headers)['Authorization'] = `Bearer ${token}`;
6013
+ config.headers["Authorization"] = `Bearer ${token}`;
5981
6014
  }
5982
6015
  }
5983
6016
  }
5984
- catch (_b) {
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 (_d) { }
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 (_e) { }
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['Authorization'] = `Bearer ${newToken}`;
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 && (refreshed.accessToken || ((_a = refreshed.data) === null || _a === void 0 ? void 0 : _a.accessToken)))) !== null && _b !== void 0 ? _b : null;
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
- (originalRequest.headers)['Authorization'] = `Bearer ${newAccessToken}`;
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 (_f) { }
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 (_a) { }
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 (_b) { }
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, '/auth/eip712-message');
7646
+ const url = joinUrl(baseUrl, "/auth/eip712-message");
7581
7647
  try {
7582
- const resp = await axios$1.get(url, { params: { address, clientId }, timeout: 30000 });
7583
- return { data: resp.data, status: resp.status, headers: resp.headers };
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, '/auth/login');
7663
+ const url = joinUrl(baseUrl, "/auth/login");
7591
7664
  try {
7592
- const resp = await axios$1.post(url, body, { headers: { 'Content-Type': 'application/json' }, timeout: 30000 });
7593
- return { data: resp.data, status: resp.status, headers: resp.headers };
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: 'privy_access_token',
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, '/auth/refresh');
7692
+ const url = joinUrl(baseUrl, "/auth/refresh");
7613
7693
  try {
7614
- const resp = await axios$1.post(url, { refreshToken: refreshTokenVal }, { headers: { 'Content-Type': 'application/json' }, timeout: 30000 });
7615
- return { data: resp.data, status: resp.status, headers: resp.headers };
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, '/auth/logout');
7706
+ const url = joinUrl(baseUrl, "/auth/logout");
7623
7707
  try {
7624
- const resp = await axios$1.post(url, { refreshToken: refreshTokenVal }, { headers: { 'Content-Type': 'application/json' }, timeout: 30000 });
7625
- return { data: resp.data, status: resp.status, headers: resp.headers };
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("usePortfolio must be used within a PearHyperliquidProvider");
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 isAuthenticated = useUserData((s) => s.isAuthenticated);
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 == "undefined") {
7748
+ if (typeof window == 'undefined') {
7649
7749
  return;
7650
7750
  }
7651
- const access = localStorage.getItem("accessToken");
7652
- const refresh = localStorage.getItem("refreshToken");
7653
- const addr = localStorage.getItem("address");
7654
- setAccessToken(access);
7655
- setRefreshToken(refresh);
7656
- setAddress(addr);
7657
- const authed = Boolean(access && addr);
7658
- setIsAuthenticated(authed);
7659
- setIsReady(true);
7660
- }, [setAccessToken, setRefreshToken, setIsAuthenticated, setAddress]);
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
- return typeof window !== "undefined"
7666
- ? window.localStorage.getItem("accessToken")
7667
- : null;
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: "eip712",
7879
+ method: 'eip712',
7689
7880
  address,
7690
7881
  clientId,
7691
7882
  details: { signature, timestamp },
7692
7883
  });
7693
- window.localStorage.setItem("accessToken", data.accessToken);
7694
- window.localStorage.setItem("refreshToken", data.refreshToken);
7695
- window.localStorage.setItem("address", address);
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
- window.localStorage.setItem("accessToken", data.accessToken);
7714
- window.localStorage.setItem("refreshToken", data.refreshToken);
7715
- window.localStorage.setItem("address", address);
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 refresh = window.localStorage.getItem("refreshToken");
7727
- if (!refresh)
7728
- throw new Error("No refresh token");
7729
- const { data } = await refreshToken(apiBaseUrl, refresh);
7730
- window.localStorage.setItem("accessToken", data.accessToken);
7731
- window.localStorage.setItem("refreshToken", data.refreshToken);
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 refresh = window.localStorage.getItem("refreshToken");
7739
- if (refresh) {
7945
+ const currentAddress = address;
7946
+ const currentRefresh = refreshToken$1;
7947
+ if (currentRefresh) {
7740
7948
  try {
7741
- await logout(apiBaseUrl, refresh);
7949
+ await logout(apiBaseUrl, currentRefresh);
7742
7950
  }
7743
7951
  catch (_a) {
7744
7952
  /* ignore */
7745
7953
  }
7746
7954
  }
7747
- window.localStorage.removeItem("accessToken");
7748
- window.localStorage.removeItem("refreshToken");
7749
- window.localStorage.removeItem("address");
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 = 'https://hl-ui.pearprotocol.io', clientId = 'PEARPROTOCOLUI', wsUrl = 'wss://hl-ui.pearprotocol.io/ws', }) => {
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 setAddress = useUserData((s) => s.setAddress);
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
- ? 'HYPERLIQUID'
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 = 'USDH';
8344
+ collateralToken = "USDH";
8116
8345
  }
8117
8346
  if (item.collateralToken === 0) {
8118
- collateralToken = 'USDC';
8347
+ collateralToken = "USDC";
8119
8348
  }
8120
8349
  if (item.collateralToken === 235) {
8121
- collateralToken = 'USDE';
8350
+ collateralToken = "USDE";
8122
8351
  }
8123
8352
  if (item.collateralToken === 268) {
8124
- collateralToken = 'USDT0';
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('usePearHyperliquid must be used within a PearHyperliquidProvider');
8395
+ throw new Error("usePearHyperliquid must be used within a PearHyperliquidProvider");
8169
8396
  return ctx;
8170
8397
  }
8171
8398
 
@@ -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;
@@ -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
@@ -200,6 +200,7 @@ export interface TwapMonitoringDto {
200
200
  export interface TradeHistoryAssetDataDto {
201
201
  coin: string;
202
202
  entryWeight: number;
203
+ closeWeight: number;
203
204
  entryPrice: number;
204
205
  limitPrice: number;
205
206
  leverage: number;
@@ -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.14",
3
+ "version": "0.1.16",
4
4
  "description": "React SDK for Pear Protocol Hyperliquid API integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",