@pear-protocol/hyperliquid-sdk 0.1.11 → 0.1.13-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clients/auth.d.ts +1 -1
- package/dist/hooks/useAllUserBalances.d.ts +2 -1
- package/dist/hooks/useAuth.d.ts +3 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +413 -170
- package/dist/provider.d.ts +1 -1
- package/dist/store/hyperliquidDataStore.d.ts +2 -2
- package/dist/store/userDataStore.d.ts +5 -1
- package/dist/types.d.ts +2 -0
- package/dist/utils/http.d.ts +2 -2
- package/package.json +1 -1
package/dist/clients/auth.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApiResponse, GetEIP712MessageResponse, AuthenticateRequest, AuthenticateResponse, RefreshTokenResponse, LogoutResponse } from
|
|
1
|
+
import type { ApiResponse, GetEIP712MessageResponse, AuthenticateRequest, AuthenticateResponse, RefreshTokenResponse, LogoutResponse } from "../types";
|
|
2
2
|
export declare function getEIP712Message(baseUrl: string, address: string, clientId: string): Promise<ApiResponse<GetEIP712MessageResponse>>;
|
|
3
3
|
export declare function authenticate(baseUrl: string, body: AuthenticateRequest): Promise<ApiResponse<AuthenticateResponse>>;
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AvailableToTrades, SpotBalances, CollateralToken } from '../types';
|
|
1
|
+
import { AvailableToTrades, SpotBalances, CollateralToken, UserAbstraction } from '../types';
|
|
2
2
|
export interface MarginRequiredPerCollateral {
|
|
3
3
|
collateral: CollateralToken;
|
|
4
4
|
marginRequired: number;
|
|
@@ -16,6 +16,7 @@ interface AllUserBalancesResult {
|
|
|
16
16
|
spotBalances: SpotBalances;
|
|
17
17
|
availableToTrades: AvailableToTrades;
|
|
18
18
|
isLoading: boolean;
|
|
19
|
+
abstractionMode: UserAbstraction | null;
|
|
19
20
|
getMarginRequired: (assetsLeverage: Record<string, number>, size: number) => MarginRequiredResult;
|
|
20
21
|
getMaxSize: (assetsLeverage: Record<string, number>) => number;
|
|
21
22
|
}
|
package/dist/hooks/useAuth.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GetEIP712MessageResponse } from
|
|
1
|
+
import type { GetEIP712MessageResponse } from '../types';
|
|
2
2
|
export declare function useAuth(): {
|
|
3
3
|
readonly isReady: boolean;
|
|
4
4
|
readonly isAuthenticated: boolean;
|
|
@@ -10,4 +10,6 @@ export declare function useAuth(): {
|
|
|
10
10
|
readonly loginWithPrivyToken: (address: string, appId: string, privyAccessToken: string) => Promise<void>;
|
|
11
11
|
readonly refreshTokens: () => Promise<import("../types").RefreshTokenResponse>;
|
|
12
12
|
readonly logout: () => Promise<void>;
|
|
13
|
+
readonly clearSession: () => void;
|
|
14
|
+
readonly setAddress: (address: string | null) => void;
|
|
13
15
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -591,6 +591,7 @@ interface HLChannelDataMap {
|
|
|
591
591
|
allDexsAssetCtxs: AllDexsAssetCtxsData;
|
|
592
592
|
userFills: any;
|
|
593
593
|
}
|
|
594
|
+
type UserAbstraction = 'dexAbstraction' | 'disabled' | 'unifiedAccount';
|
|
594
595
|
interface WebData3UserState {
|
|
595
596
|
agentAddress?: string;
|
|
596
597
|
agentValidUntil?: number;
|
|
@@ -599,6 +600,7 @@ interface WebData3UserState {
|
|
|
599
600
|
isVault: boolean;
|
|
600
601
|
user: string;
|
|
601
602
|
dexAbstractionEnabled?: boolean;
|
|
603
|
+
abstraction: UserAbstraction;
|
|
602
604
|
}
|
|
603
605
|
interface WebData3AssetCtx {
|
|
604
606
|
funding: string;
|
|
@@ -1427,6 +1429,8 @@ declare function useAuth(): {
|
|
|
1427
1429
|
readonly loginWithPrivyToken: (address: string, appId: string, privyAccessToken: string) => Promise<void>;
|
|
1428
1430
|
readonly refreshTokens: () => Promise<RefreshTokenResponse>;
|
|
1429
1431
|
readonly logout: () => Promise<void>;
|
|
1432
|
+
readonly clearSession: () => void;
|
|
1433
|
+
readonly setAddress: (address: string | null) => void;
|
|
1430
1434
|
};
|
|
1431
1435
|
|
|
1432
1436
|
interface MarginRequiredPerCollateral {
|
|
@@ -1446,6 +1450,7 @@ interface AllUserBalancesResult {
|
|
|
1446
1450
|
spotBalances: SpotBalances;
|
|
1447
1451
|
availableToTrades: AvailableToTrades;
|
|
1448
1452
|
isLoading: boolean;
|
|
1453
|
+
abstractionMode: UserAbstraction | null;
|
|
1449
1454
|
getMarginRequired: (assetsLeverage: Record<string, number>, size: number) => MarginRequiredResult;
|
|
1450
1455
|
getMaxSize: (assetsLeverage: Record<string, number>) => number;
|
|
1451
1456
|
}
|
|
@@ -1743,4 +1748,4 @@ interface MarketDataState {
|
|
|
1743
1748
|
declare const useMarketData: zustand.UseBoundStore<zustand.StoreApi<MarketDataState>>;
|
|
1744
1749
|
|
|
1745
1750
|
export { ConflictDetector, MAX_ASSETS_PER_LEG, MINIMUM_ASSET_USD_VALUE, MaxAssetsPerLegError, MinimumPositionSizeError, PearHyperliquidProvider, ReadyState, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, executeSpotOrder, getCompleteTimestamps, getKalshiMarkets, getOrderDirection, getOrderLadderConfig, getOrderLeverage, getOrderReduceOnly, getOrderTpSlTriggerType, getOrderTrailingInfo, getOrderTriggerType, getOrderTriggerValue, getOrderTwapDuration, getOrderUsdValue, getPortfolio, isBtcDomOrder, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toggleWatchlist, updateLeverage, updateRiskParameters, useAccountSummary, useAgentWallet, useAllUserBalances, useAuth, useBasketCandles, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidUserFills, useMarket, useMarketData, useMarketDataHook, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePortfolio, usePosition, useSpotOrder, useTokenSelectionMetadata, useTradeHistories, useTwap, useUserSelection, useWatchlist, validateMaxAssetsPerLeg, validateMinimumAssetSize, validatePositionSize };
|
|
1746
|
-
export type { AccountSummaryResponseDto, ActiveAssetData, ActiveAssetGroupItem, ActiveAssetsAllResponse, ActiveAssetsResponse, AddressState, AdjustAdvanceAssetInput, AdjustAdvanceItemInput, AdjustAdvanceResponseDto, AdjustExecutionType, AdjustOrderRequestInput, AdjustOrderResponseDto, AdjustPositionRequestInput, AdjustPositionResponseDto, AgentWalletDto, AgentWalletState, AgentWalletStatus, AllDexsAssetCtxsData, AllDexsClearinghouseStateData, AllPerpMetasItem, AllPerpMetasResponse, ApiErrorResponse, ApiResponse, AssetCtx, AssetInformationDetail, AssetMarketData, AssetPosition, AuthenticateRequest, AuthenticateResponse, AvailableToTrades, BalanceSummaryDto, BaseTriggerOrderNotificationParams, BtcDomTriggerParams, CancelOrderResponseDto, CancelTwapResponseDto, CandleChartData, CandleData, CandleInterval, CandleSnapshotRequest, ChunkFillDto, ClearinghouseState, CloseAllPositionsResponseDto, CloseAllPositionsResultDto, CloseExecutionType, ClosePositionRequestInput, ClosePositionResponseDto, CollateralToken, CreateAgentWalletResponseDto, CreatePositionRequestInput, CreatePositionResponseDto, CrossAssetPriceTriggerParams, CrossMarginSummaryDto, CumFundingDto, EIP712AuthDetails, ExecutionType, ExternalFillDto, ExternalLiquidationDto, ExtraAgent, GetAgentWalletResponseDto, GetEIP712MessageResponse, GetKalshiMarketsParams, HLChannel, HLChannelDataMap, HLWebSocketResponse, HistoricalRange, KalshiMarket, KalshiMarketsResponse, KalshiMveLeg, KalshiPriceRange, LadderConfigInput, LadderOrderParameters, LeverageInfo, LogoutRequest, LogoutResponse, MarginRequiredPerCollateral, MarginRequiredResult, MarginSummaryDto, MarginTableDef, MarginTablesEntry, MarginTier, MarketOrderParameters, NotificationCategory, NotificationDto, OpenLimitOrderDto, OpenPositionDto, OrderAssetDto, OrderDirection, OrderParameters, OrderStatus, OrderType, PairAssetDto, PairAssetInput, PerformanceOverlay, PerpDex, PerpDexsResponse, PerpMetaAsset, PlatformAccountSummaryResponseDto, PortfolioBucketDto, PortfolioInterval, PortfolioIntervalsDto, PortfolioOverallDto, PortfolioResponseDto, PositionAdjustmentType, PositionAssetDetailDto, PredictionMarketOutcomeTriggerParams, PriceRatioTriggerParams, PriceTriggerParams, PrivyAuthDetails, RawAssetDto, RawPositionDto, RealtimeBar, RealtimeBarsCallback, RefreshTokenRequest, RefreshTokenResponse, SpotBalance, SpotBalances, SpotOrderFilledStatus, SpotOrderHyperliquidData, SpotOrderHyperliquidResult, SpotOrderRequestInput, SpotOrderResponseDto, SpotState, SyncFillsRequestDto, SyncFillsResponseDto, ToggleWatchlistResponseDto, TokenConflict, TokenEntry, TokenHistoricalPriceData, TokenMetadata, TokenSelection, TokenSelectorConfig, TpSlOrderParameters, TpSlThreshold, TpSlThresholdInput, TpSlThresholdType, TpSlTriggerType, TradeHistoryAssetDataDto, TradeHistoryDataDto, TriggerOrderNotificationAsset, TriggerOrderNotificationParams, TriggerOrderNotificationType, TriggerOrderParameters, TriggerType, TwapChunkStatus, TwapChunkStatusDto, TwapMonitoringDto, TwapOrderOverallStatus, TwapOrderParameters, TwapSliceFillResponseItem, UniverseAsset, UpdateLeverageRequestInput, UpdateLeverageResponseDto, UpdateRiskParametersRequestInput, UpdateRiskParametersResponseDto, UseAuthOptions, UseBasketCandlesReturn, UseHistoricalPriceDataReturn, UseHyperliquidUserFillsOptions, UseHyperliquidUserFillsState, UseNotificationsResult, UsePerformanceOverlaysReturn, UsePortfolioResult, UseSpotOrderResult, UseTokenSelectionMetadataReturn, UserProfile, UserSelectionState, WatchlistAssetDto, WatchlistItemDto, WebData3AssetCtx, WebData3PerpDexState, WebData3Response, WebData3UserState, WebSocketAckResponse, WebSocketChannel, WebSocketConnectionState, WebSocketDataMessage, WebSocketMessage, WebSocketSubscribeMessage, WsAllMidsData };
|
|
1751
|
+
export type { AccountSummaryResponseDto, ActiveAssetData, ActiveAssetGroupItem, ActiveAssetsAllResponse, ActiveAssetsResponse, AddressState, AdjustAdvanceAssetInput, AdjustAdvanceItemInput, AdjustAdvanceResponseDto, AdjustExecutionType, AdjustOrderRequestInput, AdjustOrderResponseDto, AdjustPositionRequestInput, AdjustPositionResponseDto, AgentWalletDto, AgentWalletState, AgentWalletStatus, AllDexsAssetCtxsData, AllDexsClearinghouseStateData, AllPerpMetasItem, AllPerpMetasResponse, ApiErrorResponse, ApiResponse, AssetCtx, AssetInformationDetail, AssetMarketData, AssetPosition, AuthenticateRequest, AuthenticateResponse, AvailableToTrades, BalanceSummaryDto, BaseTriggerOrderNotificationParams, BtcDomTriggerParams, CancelOrderResponseDto, CancelTwapResponseDto, CandleChartData, CandleData, CandleInterval, CandleSnapshotRequest, ChunkFillDto, ClearinghouseState, CloseAllPositionsResponseDto, CloseAllPositionsResultDto, CloseExecutionType, ClosePositionRequestInput, ClosePositionResponseDto, CollateralToken, CreateAgentWalletResponseDto, CreatePositionRequestInput, CreatePositionResponseDto, CrossAssetPriceTriggerParams, CrossMarginSummaryDto, CumFundingDto, EIP712AuthDetails, ExecutionType, ExternalFillDto, ExternalLiquidationDto, ExtraAgent, GetAgentWalletResponseDto, GetEIP712MessageResponse, GetKalshiMarketsParams, HLChannel, HLChannelDataMap, HLWebSocketResponse, HistoricalRange, KalshiMarket, KalshiMarketsResponse, KalshiMveLeg, KalshiPriceRange, LadderConfigInput, LadderOrderParameters, LeverageInfo, LogoutRequest, LogoutResponse, MarginRequiredPerCollateral, MarginRequiredResult, MarginSummaryDto, MarginTableDef, MarginTablesEntry, MarginTier, MarketOrderParameters, NotificationCategory, NotificationDto, OpenLimitOrderDto, OpenPositionDto, OrderAssetDto, OrderDirection, OrderParameters, OrderStatus, OrderType, PairAssetDto, PairAssetInput, PerformanceOverlay, PerpDex, PerpDexsResponse, PerpMetaAsset, PlatformAccountSummaryResponseDto, PortfolioBucketDto, PortfolioInterval, PortfolioIntervalsDto, PortfolioOverallDto, PortfolioResponseDto, PositionAdjustmentType, PositionAssetDetailDto, PredictionMarketOutcomeTriggerParams, PriceRatioTriggerParams, PriceTriggerParams, PrivyAuthDetails, RawAssetDto, RawPositionDto, RealtimeBar, RealtimeBarsCallback, RefreshTokenRequest, RefreshTokenResponse, SpotBalance, SpotBalances, SpotOrderFilledStatus, SpotOrderHyperliquidData, SpotOrderHyperliquidResult, SpotOrderRequestInput, SpotOrderResponseDto, SpotState, SyncFillsRequestDto, SyncFillsResponseDto, ToggleWatchlistResponseDto, TokenConflict, TokenEntry, TokenHistoricalPriceData, TokenMetadata, TokenSelection, TokenSelectorConfig, TpSlOrderParameters, TpSlThreshold, TpSlThresholdInput, TpSlThresholdType, TpSlTriggerType, TradeHistoryAssetDataDto, TradeHistoryDataDto, TriggerOrderNotificationAsset, TriggerOrderNotificationParams, TriggerOrderNotificationType, TriggerOrderParameters, TriggerType, TwapChunkStatus, TwapChunkStatusDto, TwapMonitoringDto, TwapOrderOverallStatus, TwapOrderParameters, TwapSliceFillResponseItem, UniverseAsset, UpdateLeverageRequestInput, UpdateLeverageResponseDto, UpdateRiskParametersRequestInput, UpdateRiskParametersResponseDto, UseAuthOptions, UseBasketCandlesReturn, UseHistoricalPriceDataReturn, UseHyperliquidUserFillsOptions, UseHyperliquidUserFillsState, UseNotificationsResult, UsePerformanceOverlaysReturn, UsePortfolioResult, UseSpotOrderResult, UseTokenSelectionMetadataReturn, UserAbstraction, UserProfile, UserSelectionState, WatchlistAssetDto, WatchlistItemDto, WebData3AssetCtx, WebData3PerpDexState, WebData3Response, WebData3UserState, WebSocketAckResponse, WebSocketChannel, WebSocketConnectionState, WebSocketDataMessage, WebSocketMessage, WebSocketSubscribeMessage, WsAllMidsData };
|
package/dist/index.js
CHANGED
|
@@ -64,20 +64,16 @@ const useUserData = create((set) => ({
|
|
|
64
64
|
notifications: null,
|
|
65
65
|
userExtraAgents: null,
|
|
66
66
|
spotState: null,
|
|
67
|
+
userAbstractionMode: null,
|
|
68
|
+
isReady: false,
|
|
69
|
+
setUserAbstractionMode: (value) => set({ userAbstractionMode: value }),
|
|
67
70
|
setAccessToken: (token) => set({ accessToken: token }),
|
|
68
71
|
setRefreshToken: (token) => set({ refreshToken: token }),
|
|
69
72
|
setIsAuthenticated: (value) => set({ isAuthenticated: value }),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
window.localStorage.removeItem('address');
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return { address };
|
|
80
|
-
}),
|
|
73
|
+
setIsReady: (value) => set({ isReady: value }),
|
|
74
|
+
setAddress: (address) => {
|
|
75
|
+
set({ address });
|
|
76
|
+
},
|
|
81
77
|
setTradeHistories: (value) => set({ tradeHistories: value }),
|
|
82
78
|
setRawOpenPositions: (value) => set({ rawOpenPositions: value }),
|
|
83
79
|
setOpenOrders: (value) => set({ openOrders: value }),
|
|
@@ -86,10 +82,11 @@ const useUserData = create((set) => ({
|
|
|
86
82
|
setNotifications: (value) => set({ notifications: value }),
|
|
87
83
|
setSpotState: (value) => set({ spotState: value }),
|
|
88
84
|
clean: () => set({
|
|
89
|
-
accessToken: null,
|
|
90
|
-
refreshToken: null,
|
|
91
|
-
isAuthenticated: false,
|
|
92
|
-
|
|
85
|
+
// accessToken: null,
|
|
86
|
+
// refreshToken: null,
|
|
87
|
+
// isAuthenticated: false,
|
|
88
|
+
// isReady: false,
|
|
89
|
+
// address: null,
|
|
93
90
|
tradeHistories: null,
|
|
94
91
|
rawOpenPositions: null,
|
|
95
92
|
openOrders: null,
|
|
@@ -382,19 +379,19 @@ class TokenMetadataExtractor {
|
|
|
382
379
|
if (!assetCtx) {
|
|
383
380
|
return null;
|
|
384
381
|
}
|
|
385
|
-
// Get current price - prefer
|
|
386
|
-
// fall back to
|
|
382
|
+
// Get current price - prefer allMids (real-time WebSocket data),
|
|
383
|
+
// fall back to assetCtx.midPx if not available
|
|
387
384
|
const actualSymbol = foundAsset.name;
|
|
388
385
|
let currentPrice = 0;
|
|
389
|
-
// Fallback: assetCtx.midPx (
|
|
390
|
-
if (!currentPrice || isNaN(currentPrice)) {
|
|
391
|
-
const currentPriceStr = allMids.mids[actualSymbol] || allMids.mids[symbol];
|
|
392
|
-
currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
|
|
393
|
-
}
|
|
394
|
-
// Primary source: allMids lookup
|
|
386
|
+
// Fallback: assetCtx.midPx (from REST API, less frequent)
|
|
395
387
|
if (assetCtx.midPx) {
|
|
396
388
|
currentPrice = parseFloat(assetCtx.midPx);
|
|
397
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
|
+
}
|
|
398
395
|
// Get previous day price
|
|
399
396
|
const prevDayPrice = parseFloat(assetCtx.prevDayPx);
|
|
400
397
|
// Calculate 24h price change
|
|
@@ -549,7 +546,7 @@ const useHyperliquidData = create((set) => ({
|
|
|
549
546
|
tokenMetadata: refreshTokenMetadata(state, { allMids: value }),
|
|
550
547
|
})),
|
|
551
548
|
setActiveAssetData: (value) => set((state) => {
|
|
552
|
-
const activeAssetData = typeof value ===
|
|
549
|
+
const activeAssetData = typeof value === "function" ? value(state.activeAssetData) : value;
|
|
553
550
|
return {
|
|
554
551
|
activeAssetData,
|
|
555
552
|
tokenMetadata: refreshTokenMetadata(state, { activeAssetData }),
|
|
@@ -589,7 +586,10 @@ const useHyperliquidData = create((set) => ({
|
|
|
589
586
|
setCandleData: (value) => set({ candleData: value }),
|
|
590
587
|
upsertActiveAssetData: (key, value) => set((state) => {
|
|
591
588
|
var _a;
|
|
592
|
-
const activeAssetData = {
|
|
589
|
+
const activeAssetData = {
|
|
590
|
+
...((_a = state.activeAssetData) !== null && _a !== void 0 ? _a : {}),
|
|
591
|
+
[key]: value,
|
|
592
|
+
};
|
|
593
593
|
return {
|
|
594
594
|
activeAssetData,
|
|
595
595
|
tokenMetadata: refreshTokenMetadata(state, { activeAssetData }, { symbols: [key] }),
|
|
@@ -887,7 +887,7 @@ const useUserSelection$1 = create((set, get) => ({
|
|
|
887
887
|
|
|
888
888
|
const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }) => {
|
|
889
889
|
const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAtOICaps, setAggregatedClearingHouseState, setRawClearinghouseStates, setAssetContextsByDex, } = useHyperliquidData();
|
|
890
|
-
const { setSpotState } = useUserData();
|
|
890
|
+
const { setSpotState, setUserAbstractionMode } = useUserData();
|
|
891
891
|
const { candleInterval } = useUserSelection$1();
|
|
892
892
|
const userSummary = useUserData((state) => state.accountSummary);
|
|
893
893
|
const longTokens = useUserSelection$1((s) => s.longTokens);
|
|
@@ -904,6 +904,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
904
904
|
const reconnectAttemptsRef = useRef(0);
|
|
905
905
|
const manualCloseRef = useRef(false);
|
|
906
906
|
const onUserFillsRef = useRef(onUserFills);
|
|
907
|
+
const reconnectTimeoutRef = useRef(null);
|
|
907
908
|
const [readyState, setReadyState] = useState(ReadyState.CONNECTING);
|
|
908
909
|
// Keep the ref updated with the latest callback
|
|
909
910
|
useEffect(() => {
|
|
@@ -914,9 +915,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
914
915
|
try {
|
|
915
916
|
const message = JSON.parse(event.data);
|
|
916
917
|
// Handle subscription responses
|
|
917
|
-
if (
|
|
918
|
+
if ("success" in message || "error" in message) {
|
|
918
919
|
if (message.error) {
|
|
919
|
-
console.error(
|
|
920
|
+
console.error("[HyperLiquid WS] Subscription error:", message.error);
|
|
920
921
|
setLastError(message.error);
|
|
921
922
|
}
|
|
922
923
|
else {
|
|
@@ -925,43 +926,44 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
925
926
|
return;
|
|
926
927
|
}
|
|
927
928
|
// Handle channel data messages
|
|
928
|
-
if (
|
|
929
|
+
if ("channel" in message && "data" in message) {
|
|
929
930
|
const response = message;
|
|
930
931
|
switch (response.channel) {
|
|
931
|
-
case
|
|
932
|
+
case "userFills":
|
|
932
933
|
{
|
|
933
934
|
const maybePromise = (_a = onUserFillsRef.current) === null || _a === void 0 ? void 0 : _a.call(onUserFillsRef);
|
|
934
935
|
if (maybePromise instanceof Promise) {
|
|
935
|
-
maybePromise.catch((err) => console.error(
|
|
936
|
+
maybePromise.catch((err) => console.error("[HyperLiquid WS] userFills callback error", err));
|
|
936
937
|
}
|
|
937
938
|
}
|
|
938
939
|
break;
|
|
939
|
-
case
|
|
940
|
+
case "webData3":
|
|
940
941
|
const webData3 = response.data;
|
|
941
942
|
// finalAssetContexts now sourced from allDexsAssetCtxs channel
|
|
942
943
|
const finalAtOICaps = webData3.perpDexStates.flatMap((dex) => dex.perpsAtOpenInterestCap);
|
|
943
944
|
setFinalAtOICaps(finalAtOICaps);
|
|
945
|
+
setUserAbstractionMode(webData3.userState.abstraction || null);
|
|
944
946
|
break;
|
|
945
|
-
case
|
|
947
|
+
case "allDexsAssetCtxs":
|
|
946
948
|
{
|
|
947
949
|
const data = response.data;
|
|
948
950
|
// Store by DEX name, mapping '' to 'HYPERLIQUID'
|
|
949
951
|
const assetContextsByDex = new Map();
|
|
950
952
|
data.ctxs.forEach(([dexKey, ctxs]) => {
|
|
951
|
-
const dexName = dexKey ===
|
|
953
|
+
const dexName = dexKey === "" ? "HYPERLIQUID" : dexKey;
|
|
952
954
|
assetContextsByDex.set(dexName, ctxs || []);
|
|
953
955
|
});
|
|
954
956
|
setAssetContextsByDex(assetContextsByDex);
|
|
955
957
|
}
|
|
956
958
|
break;
|
|
957
|
-
case
|
|
959
|
+
case "allDexsClearinghouseState":
|
|
958
960
|
{
|
|
959
961
|
const data = response.data;
|
|
960
962
|
const states = (data.clearinghouseStates || [])
|
|
961
963
|
.map(([, s]) => s)
|
|
962
964
|
.filter(Boolean);
|
|
963
|
-
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v ||
|
|
964
|
-
const toStr = (n) => Number.isFinite(n) ? n.toString() :
|
|
965
|
+
const sum = (values) => values.reduce((acc, v) => acc + (parseFloat(v || "0") || 0), 0);
|
|
966
|
+
const toStr = (n) => Number.isFinite(n) ? n.toString() : "0";
|
|
965
967
|
const assetPositions = states.flatMap((s) => s.assetPositions || []);
|
|
966
968
|
const crossMaintenanceMarginUsed = toStr(sum(states.map((s) => s.crossMaintenanceMarginUsed)));
|
|
967
969
|
const crossMarginSummary = {
|
|
@@ -991,26 +993,26 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
991
993
|
setClearinghouseStateReceived(true);
|
|
992
994
|
}
|
|
993
995
|
break;
|
|
994
|
-
case
|
|
996
|
+
case "allMids":
|
|
995
997
|
{
|
|
996
998
|
const data = response.data;
|
|
997
999
|
setAllMids(data);
|
|
998
1000
|
}
|
|
999
1001
|
break;
|
|
1000
|
-
case
|
|
1002
|
+
case "activeAssetData":
|
|
1001
1003
|
{
|
|
1002
1004
|
const assetData = response.data;
|
|
1003
1005
|
upsertActiveAssetData(assetData.coin, assetData);
|
|
1004
1006
|
}
|
|
1005
1007
|
break;
|
|
1006
|
-
case
|
|
1008
|
+
case "candle":
|
|
1007
1009
|
{
|
|
1008
1010
|
const candleDataItem = response.data;
|
|
1009
|
-
const symbol = candleDataItem.s ||
|
|
1011
|
+
const symbol = candleDataItem.s || "";
|
|
1010
1012
|
addCandleData(symbol, candleDataItem);
|
|
1011
1013
|
}
|
|
1012
1014
|
break;
|
|
1013
|
-
case
|
|
1015
|
+
case "spotState":
|
|
1014
1016
|
{
|
|
1015
1017
|
const spotStateData = response.data;
|
|
1016
1018
|
if (spotStateData === null || spotStateData === void 0 ? void 0 : spotStateData.spotState) {
|
|
@@ -1025,7 +1027,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1025
1027
|
}
|
|
1026
1028
|
catch (error) {
|
|
1027
1029
|
const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
|
|
1028
|
-
console.error(
|
|
1030
|
+
console.error("[HyperLiquid WS] Parse error:", errorMessage, "Raw message:", event.data);
|
|
1029
1031
|
setLastError(errorMessage);
|
|
1030
1032
|
}
|
|
1031
1033
|
}, [
|
|
@@ -1039,9 +1041,14 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1039
1041
|
setSpotState,
|
|
1040
1042
|
]);
|
|
1041
1043
|
const connect = useCallback(() => {
|
|
1042
|
-
console.log(
|
|
1044
|
+
console.log("[HyperLiquid WS] connect() called, enabled:", enabled);
|
|
1043
1045
|
if (!enabled)
|
|
1044
1046
|
return;
|
|
1047
|
+
// Clear any pending reconnect timeout
|
|
1048
|
+
if (reconnectTimeoutRef.current) {
|
|
1049
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
1050
|
+
reconnectTimeoutRef.current = null;
|
|
1051
|
+
}
|
|
1045
1052
|
try {
|
|
1046
1053
|
// Avoid opening multiple sockets if one is already active or connecting
|
|
1047
1054
|
if (wsRef.current &&
|
|
@@ -1050,10 +1057,10 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1050
1057
|
console.log('[HyperLiquid WS] connect() returning early - socket already exists, readyState:', wsRef.current.readyState);
|
|
1051
1058
|
return;
|
|
1052
1059
|
}
|
|
1053
|
-
console.log(
|
|
1060
|
+
console.log("[HyperLiquid WS] Creating new WebSocket connection");
|
|
1054
1061
|
manualCloseRef.current = false;
|
|
1055
1062
|
setReadyState(ReadyState.CONNECTING);
|
|
1056
|
-
const ws = new WebSocket(
|
|
1063
|
+
const ws = new WebSocket("wss://api.hyperliquid.xyz/ws");
|
|
1057
1064
|
wsRef.current = ws;
|
|
1058
1065
|
ws.onopen = () => {
|
|
1059
1066
|
reconnectAttemptsRef.current = 0;
|
|
@@ -1062,17 +1069,22 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1062
1069
|
};
|
|
1063
1070
|
ws.onmessage = handleMessage;
|
|
1064
1071
|
ws.onerror = (event) => {
|
|
1065
|
-
console.error(
|
|
1066
|
-
setLastError(
|
|
1072
|
+
console.error("[HyperLiquid WS] Connection error:", event);
|
|
1073
|
+
setLastError("WebSocket error");
|
|
1067
1074
|
};
|
|
1068
1075
|
ws.onclose = () => {
|
|
1069
1076
|
setReadyState(ReadyState.CLOSED);
|
|
1070
|
-
|
|
1077
|
+
// Reset subscription state so effects will resubscribe on reconnect
|
|
1078
|
+
setSubscribedAddress(null);
|
|
1079
|
+
setSubscribedTokens([]);
|
|
1080
|
+
setSubscribedCandleTokens([]);
|
|
1081
|
+
setClearinghouseStateReceived(false);
|
|
1082
|
+
if (!manualCloseRef.current) {
|
|
1071
1083
|
reconnectAttemptsRef.current += 1;
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
}
|
|
1075
|
-
setTimeout(() => connect(),
|
|
1084
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s (max), then stay at 30s
|
|
1085
|
+
const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current - 1), 30000);
|
|
1086
|
+
console.log(`[HyperLiquid WS] Reconnecting in ${delay}ms (attempt ${reconnectAttemptsRef.current})`);
|
|
1087
|
+
reconnectTimeoutRef.current = setTimeout(() => connect(), delay);
|
|
1076
1088
|
}
|
|
1077
1089
|
};
|
|
1078
1090
|
}
|
|
@@ -1083,9 +1095,53 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1083
1095
|
useEffect(() => {
|
|
1084
1096
|
console.log('[HyperLiquid WS] Connection effect running - calling connect()');
|
|
1085
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);
|
|
1086
1136
|
return () => {
|
|
1087
1137
|
console.log('[HyperLiquid WS] Connection effect cleanup - closing existing connection');
|
|
1138
|
+
window.removeEventListener('online', handleOnline);
|
|
1139
|
+
window.removeEventListener('offline', handleOffline);
|
|
1088
1140
|
manualCloseRef.current = true;
|
|
1141
|
+
if (reconnectTimeoutRef.current) {
|
|
1142
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
1143
|
+
reconnectTimeoutRef.current = null;
|
|
1144
|
+
}
|
|
1089
1145
|
if (wsRef.current) {
|
|
1090
1146
|
try {
|
|
1091
1147
|
wsRef.current.close();
|
|
@@ -1106,7 +1162,7 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1106
1162
|
if (isConnected) {
|
|
1107
1163
|
// Send ping every 30 seconds
|
|
1108
1164
|
pingIntervalRef.current = setInterval(() => {
|
|
1109
|
-
sendJsonMessage({ method:
|
|
1165
|
+
sendJsonMessage({ method: "ping" });
|
|
1110
1166
|
}, 30000);
|
|
1111
1167
|
}
|
|
1112
1168
|
else {
|
|
@@ -1124,27 +1180,27 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1124
1180
|
}, [isConnected, sendJsonMessage]);
|
|
1125
1181
|
// Handle address subscription changes
|
|
1126
1182
|
useEffect(() => {
|
|
1127
|
-
const DEFAULT_ADDRESS =
|
|
1183
|
+
const DEFAULT_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
1128
1184
|
const userAddress = (address || DEFAULT_ADDRESS).toLowerCase();
|
|
1129
1185
|
const normalizedSubscribedAddress = (subscribedAddress === null || subscribedAddress === void 0 ? void 0 : subscribedAddress.toLowerCase()) || null;
|
|
1130
|
-
console.log(
|
|
1131
|
-
console.log(
|
|
1132
|
-
console.log(
|
|
1186
|
+
console.log("[HyperLiquid WS] Address subscription effect running");
|
|
1187
|
+
console.log("[HyperLiquid WS] address:", address, "userAddress:", userAddress, "subscribedAddress:", subscribedAddress, "normalizedSubscribedAddress:", normalizedSubscribedAddress);
|
|
1188
|
+
console.log("[HyperLiquid WS] isConnected:", isConnected);
|
|
1133
1189
|
if (normalizedSubscribedAddress === userAddress) {
|
|
1134
|
-
console.log(
|
|
1190
|
+
console.log("[HyperLiquid WS] Address unchanged, skipping subscription update");
|
|
1135
1191
|
return;
|
|
1136
1192
|
}
|
|
1137
1193
|
if (!isConnected) {
|
|
1138
|
-
console.log(
|
|
1194
|
+
console.log("[HyperLiquid WS] Not connected, skipping subscription update");
|
|
1139
1195
|
return;
|
|
1140
1196
|
}
|
|
1141
1197
|
// Unsubscribe from previous address if exists
|
|
1142
1198
|
if (subscribedAddress) {
|
|
1143
|
-
console.log(
|
|
1199
|
+
console.log("[HyperLiquid WS] Unsubscribing from previous address:", subscribedAddress);
|
|
1144
1200
|
const unsubscribeMessage = {
|
|
1145
|
-
method:
|
|
1201
|
+
method: "unsubscribe",
|
|
1146
1202
|
subscription: {
|
|
1147
|
-
type:
|
|
1203
|
+
type: "webData3",
|
|
1148
1204
|
user: subscribedAddress,
|
|
1149
1205
|
},
|
|
1150
1206
|
};
|
|
@@ -1152,54 +1208,54 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1152
1208
|
// Unsubscribe from spotState for previous address
|
|
1153
1209
|
if (subscribedAddress !== DEFAULT_ADDRESS) {
|
|
1154
1210
|
const unsubscribeSpotState = {
|
|
1155
|
-
method:
|
|
1211
|
+
method: "unsubscribe",
|
|
1156
1212
|
subscription: {
|
|
1157
|
-
type:
|
|
1213
|
+
type: "spotState",
|
|
1158
1214
|
user: subscribedAddress,
|
|
1159
1215
|
},
|
|
1160
1216
|
};
|
|
1161
1217
|
sendJsonMessage(unsubscribeSpotState);
|
|
1162
1218
|
}
|
|
1163
1219
|
const unsubscribeAllDexsClearinghouseState = {
|
|
1164
|
-
method:
|
|
1220
|
+
method: "unsubscribe",
|
|
1165
1221
|
subscription: {
|
|
1166
|
-
type:
|
|
1222
|
+
type: "allDexsClearinghouseState",
|
|
1167
1223
|
user: subscribedAddress,
|
|
1168
1224
|
},
|
|
1169
1225
|
};
|
|
1170
1226
|
sendJsonMessage(unsubscribeAllDexsClearinghouseState);
|
|
1171
1227
|
const unsubscribeUserFills = {
|
|
1172
|
-
method:
|
|
1228
|
+
method: "unsubscribe",
|
|
1173
1229
|
subscription: {
|
|
1174
|
-
type:
|
|
1230
|
+
type: "userFills",
|
|
1175
1231
|
user: subscribedAddress,
|
|
1176
1232
|
},
|
|
1177
1233
|
};
|
|
1178
1234
|
sendJsonMessage(unsubscribeUserFills);
|
|
1179
1235
|
}
|
|
1180
1236
|
const subscribeWebData3 = {
|
|
1181
|
-
method:
|
|
1237
|
+
method: "subscribe",
|
|
1182
1238
|
subscription: {
|
|
1183
|
-
type:
|
|
1239
|
+
type: "webData3",
|
|
1184
1240
|
user: userAddress,
|
|
1185
1241
|
},
|
|
1186
1242
|
};
|
|
1187
1243
|
// Subscribe to allMids
|
|
1188
1244
|
const subscribeAllMids = {
|
|
1189
|
-
method:
|
|
1245
|
+
method: "subscribe",
|
|
1190
1246
|
subscription: {
|
|
1191
|
-
type:
|
|
1192
|
-
dex:
|
|
1247
|
+
type: "allMids",
|
|
1248
|
+
dex: "ALL_DEXS",
|
|
1193
1249
|
},
|
|
1194
1250
|
};
|
|
1195
1251
|
// Subscribe to allDexsAssetCtxs (no payload params, global feed)
|
|
1196
1252
|
const subscribeAllDexsAssetCtxs = {
|
|
1197
|
-
method:
|
|
1253
|
+
method: "subscribe",
|
|
1198
1254
|
subscription: {
|
|
1199
|
-
type:
|
|
1255
|
+
type: "allDexsAssetCtxs",
|
|
1200
1256
|
},
|
|
1201
1257
|
};
|
|
1202
|
-
console.log(
|
|
1258
|
+
console.log("[HyperLiquid WS] Subscribing to new address:", userAddress);
|
|
1203
1259
|
sendJsonMessage(subscribeWebData3);
|
|
1204
1260
|
sendJsonMessage(subscribeAllMids);
|
|
1205
1261
|
sendJsonMessage(subscribeAllDexsAssetCtxs);
|
|
@@ -1207,9 +1263,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1207
1263
|
// Only subscribe if we have a real user address (not the default)
|
|
1208
1264
|
if (userAddress !== DEFAULT_ADDRESS.toLowerCase()) {
|
|
1209
1265
|
const subscribeSpotState = {
|
|
1210
|
-
method:
|
|
1266
|
+
method: "subscribe",
|
|
1211
1267
|
subscription: {
|
|
1212
|
-
type:
|
|
1268
|
+
type: "spotState",
|
|
1213
1269
|
user: userAddress,
|
|
1214
1270
|
},
|
|
1215
1271
|
};
|
|
@@ -1219,9 +1275,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1219
1275
|
// Only subscribe if we have a real user address (not the default)
|
|
1220
1276
|
if (userAddress !== DEFAULT_ADDRESS.toLowerCase()) {
|
|
1221
1277
|
const subscribeAllDexsClearinghouseState = {
|
|
1222
|
-
method:
|
|
1278
|
+
method: "subscribe",
|
|
1223
1279
|
subscription: {
|
|
1224
|
-
type:
|
|
1280
|
+
type: "allDexsClearinghouseState",
|
|
1225
1281
|
user: userAddress,
|
|
1226
1282
|
},
|
|
1227
1283
|
};
|
|
@@ -1255,9 +1311,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1255
1311
|
!userSummary)
|
|
1256
1312
|
return;
|
|
1257
1313
|
const subscribeUserFills = {
|
|
1258
|
-
method:
|
|
1314
|
+
method: "subscribe",
|
|
1259
1315
|
subscription: {
|
|
1260
|
-
type:
|
|
1316
|
+
type: "userFills",
|
|
1261
1317
|
user: subscribedAddress,
|
|
1262
1318
|
},
|
|
1263
1319
|
};
|
|
@@ -1279,9 +1335,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1279
1335
|
// Unsubscribe from tokens no longer in the list
|
|
1280
1336
|
tokensToUnsubscribe.forEach((token) => {
|
|
1281
1337
|
const unsubscribeMessage = {
|
|
1282
|
-
method:
|
|
1338
|
+
method: "unsubscribe",
|
|
1283
1339
|
subscription: {
|
|
1284
|
-
type:
|
|
1340
|
+
type: "activeAssetData",
|
|
1285
1341
|
user: address,
|
|
1286
1342
|
coin: token,
|
|
1287
1343
|
},
|
|
@@ -1291,9 +1347,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1291
1347
|
// Subscribe to new tokens
|
|
1292
1348
|
tokensToSubscribe.forEach((token) => {
|
|
1293
1349
|
const subscribeMessage = {
|
|
1294
|
-
method:
|
|
1350
|
+
method: "subscribe",
|
|
1295
1351
|
subscription: {
|
|
1296
|
-
type:
|
|
1352
|
+
type: "activeAssetData",
|
|
1297
1353
|
user: address,
|
|
1298
1354
|
coin: token,
|
|
1299
1355
|
},
|
|
@@ -1322,9 +1378,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1322
1378
|
if (prevInterval && prevInterval !== candleInterval) {
|
|
1323
1379
|
subscribedCandleTokens.forEach((token) => {
|
|
1324
1380
|
const unsubscribeMessage = {
|
|
1325
|
-
method:
|
|
1381
|
+
method: "unsubscribe",
|
|
1326
1382
|
subscription: {
|
|
1327
|
-
type:
|
|
1383
|
+
type: "candle",
|
|
1328
1384
|
coin: token,
|
|
1329
1385
|
interval: prevInterval,
|
|
1330
1386
|
},
|
|
@@ -1339,9 +1395,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1339
1395
|
// Unsubscribe from tokens no longer in the list
|
|
1340
1396
|
tokensToUnsubscribe.forEach((token) => {
|
|
1341
1397
|
const unsubscribeMessage = {
|
|
1342
|
-
method:
|
|
1398
|
+
method: "unsubscribe",
|
|
1343
1399
|
subscription: {
|
|
1344
|
-
type:
|
|
1400
|
+
type: "candle",
|
|
1345
1401
|
coin: token,
|
|
1346
1402
|
interval: candleInterval,
|
|
1347
1403
|
},
|
|
@@ -1351,9 +1407,9 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
|
|
|
1351
1407
|
// Subscribe to new tokens
|
|
1352
1408
|
tokensToSubscribe.forEach((token) => {
|
|
1353
1409
|
const subscribeMessage = {
|
|
1354
|
-
method:
|
|
1410
|
+
method: "subscribe",
|
|
1355
1411
|
subscription: {
|
|
1356
|
-
type:
|
|
1412
|
+
type: "candle",
|
|
1357
1413
|
coin: token,
|
|
1358
1414
|
interval: candleInterval,
|
|
1359
1415
|
},
|
|
@@ -5868,10 +5924,10 @@ function toApiError(error) {
|
|
|
5868
5924
|
var _a;
|
|
5869
5925
|
const axiosError = error;
|
|
5870
5926
|
const payload = (axiosError && axiosError.response ? axiosError.response.data : undefined);
|
|
5871
|
-
const message = typeof payload ===
|
|
5927
|
+
const message = typeof payload === "object" && payload && "message" in payload
|
|
5872
5928
|
? String(payload.message)
|
|
5873
|
-
: (axiosError === null || axiosError === void 0 ? void 0 : axiosError.message) ||
|
|
5874
|
-
const errField = typeof payload ===
|
|
5929
|
+
: (axiosError === null || axiosError === void 0 ? void 0 : axiosError.message) || "Request failed";
|
|
5930
|
+
const errField = typeof payload === "object" && payload && "error" in payload
|
|
5875
5931
|
? String(payload.error)
|
|
5876
5932
|
: undefined;
|
|
5877
5933
|
return {
|
|
@@ -5881,8 +5937,8 @@ function toApiError(error) {
|
|
|
5881
5937
|
};
|
|
5882
5938
|
}
|
|
5883
5939
|
function joinUrl(baseUrl, path) {
|
|
5884
|
-
const cleanBase = baseUrl.replace(/\/$/,
|
|
5885
|
-
const cleanPath = path.startsWith(
|
|
5940
|
+
const cleanBase = baseUrl.replace(/\/$/, "");
|
|
5941
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
5886
5942
|
return `${cleanBase}${cleanPath}`;
|
|
5887
5943
|
}
|
|
5888
5944
|
/**
|
|
@@ -5911,7 +5967,7 @@ function addAuthInterceptors(params) {
|
|
|
5911
5967
|
pendingRequests = [];
|
|
5912
5968
|
}
|
|
5913
5969
|
const isOurApiUrl = (url) => Boolean(url && url.startsWith(apiBaseUrl));
|
|
5914
|
-
const isRefreshUrl = (url) => Boolean(url && url.startsWith(joinUrl(apiBaseUrl,
|
|
5970
|
+
const isRefreshUrl = (url) => Boolean(url && url.startsWith(joinUrl(apiBaseUrl, "/auth/refresh")));
|
|
5915
5971
|
const reqId = apiClient.interceptors.request.use((config) => {
|
|
5916
5972
|
var _a;
|
|
5917
5973
|
try {
|
|
@@ -5919,11 +5975,12 @@ function addAuthInterceptors(params) {
|
|
|
5919
5975
|
const token = getAccessToken();
|
|
5920
5976
|
if (token) {
|
|
5921
5977
|
config.headers = (_a = config.headers) !== null && _a !== void 0 ? _a : {};
|
|
5922
|
-
|
|
5978
|
+
config.headers["Authorization"] = `Bearer ${token}`;
|
|
5923
5979
|
}
|
|
5924
5980
|
}
|
|
5925
5981
|
}
|
|
5926
|
-
catch (
|
|
5982
|
+
catch (err) {
|
|
5983
|
+
console.error("[Auth Interceptor] Request interceptor error:", err);
|
|
5927
5984
|
}
|
|
5928
5985
|
return config;
|
|
5929
5986
|
});
|
|
@@ -5934,22 +5991,36 @@ function addAuthInterceptors(params) {
|
|
|
5934
5991
|
const url = originalRequest === null || originalRequest === void 0 ? void 0 : originalRequest.url;
|
|
5935
5992
|
// If not our API or not 401, just reject
|
|
5936
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
|
+
}
|
|
5937
6001
|
return Promise.reject(error);
|
|
5938
6002
|
}
|
|
6003
|
+
console.log("[Auth Interceptor] 401 detected, attempting token refresh for URL:", url);
|
|
5939
6004
|
// If the 401 is from refresh endpoint itself -> force logout
|
|
5940
6005
|
if (isRefreshUrl(url)) {
|
|
6006
|
+
console.warn("[Auth Interceptor] Refresh endpoint returned 401, logging out");
|
|
5941
6007
|
try {
|
|
5942
6008
|
await logout();
|
|
5943
6009
|
}
|
|
5944
|
-
catch (
|
|
6010
|
+
catch (err) {
|
|
6011
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6012
|
+
}
|
|
5945
6013
|
return Promise.reject(error);
|
|
5946
6014
|
}
|
|
5947
6015
|
// Prevent infinite loop
|
|
5948
6016
|
if (originalRequest && originalRequest._retry) {
|
|
6017
|
+
console.warn("[Auth Interceptor] Request already retried, logging out");
|
|
5949
6018
|
try {
|
|
5950
6019
|
await logout();
|
|
5951
6020
|
}
|
|
5952
|
-
catch (
|
|
6021
|
+
catch (err) {
|
|
6022
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6023
|
+
}
|
|
5953
6024
|
return Promise.reject(error);
|
|
5954
6025
|
}
|
|
5955
6026
|
// Mark so we don't retry twice
|
|
@@ -5963,31 +6034,45 @@ function addAuthInterceptors(params) {
|
|
|
5963
6034
|
if (!newToken || !originalRequest)
|
|
5964
6035
|
return reject(error);
|
|
5965
6036
|
originalRequest.headers = (_a = originalRequest.headers) !== null && _a !== void 0 ? _a : {};
|
|
5966
|
-
originalRequest.headers[
|
|
6037
|
+
originalRequest.headers["Authorization"] =
|
|
6038
|
+
`Bearer ${newToken}`;
|
|
5967
6039
|
resolve(apiClient.request(originalRequest));
|
|
5968
6040
|
});
|
|
5969
6041
|
});
|
|
5970
6042
|
}
|
|
5971
6043
|
isRefreshing = true;
|
|
5972
6044
|
try {
|
|
6045
|
+
console.log("[Auth Interceptor] Refreshing tokens...");
|
|
5973
6046
|
const refreshed = await refreshTokens();
|
|
5974
|
-
const newAccessToken = (_b = (refreshed &&
|
|
6047
|
+
const newAccessToken = (_b = (refreshed &&
|
|
6048
|
+
(refreshed.accessToken || ((_a = refreshed.data) === null || _a === void 0 ? void 0 : _a.accessToken)))) !== null && _b !== void 0 ? _b : null;
|
|
6049
|
+
if (!newAccessToken) {
|
|
6050
|
+
console.error("[Auth Interceptor] Token refresh succeeded but no access token in response:", refreshed);
|
|
6051
|
+
}
|
|
6052
|
+
else {
|
|
6053
|
+
console.log("[Auth Interceptor] Token refresh successful");
|
|
6054
|
+
}
|
|
5975
6055
|
resolvePendingRequests(newAccessToken);
|
|
5976
6056
|
if (originalRequest) {
|
|
5977
6057
|
originalRequest.headers = (_c = originalRequest.headers) !== null && _c !== void 0 ? _c : {};
|
|
5978
6058
|
if (newAccessToken)
|
|
5979
|
-
|
|
6059
|
+
originalRequest.headers["Authorization"] =
|
|
6060
|
+
`Bearer ${newAccessToken}`;
|
|
6061
|
+
console.log("[Auth Interceptor] Retrying original request with new token");
|
|
5980
6062
|
const resp = await apiClient.request(originalRequest);
|
|
5981
6063
|
return resp;
|
|
5982
6064
|
}
|
|
5983
6065
|
return Promise.reject(error);
|
|
5984
6066
|
}
|
|
5985
6067
|
catch (refreshErr) {
|
|
6068
|
+
console.error("[Auth Interceptor] Token refresh failed:", refreshErr);
|
|
5986
6069
|
resolvePendingRequests(null);
|
|
5987
6070
|
try {
|
|
5988
6071
|
await logout();
|
|
5989
6072
|
}
|
|
5990
|
-
catch (
|
|
6073
|
+
catch (err) {
|
|
6074
|
+
console.error("[Auth Interceptor] Logout failed:", err);
|
|
6075
|
+
}
|
|
5991
6076
|
return Promise.reject(refreshErr);
|
|
5992
6077
|
}
|
|
5993
6078
|
finally {
|
|
@@ -5998,11 +6083,15 @@ function addAuthInterceptors(params) {
|
|
|
5998
6083
|
try {
|
|
5999
6084
|
apiClient.interceptors.request.eject(reqId);
|
|
6000
6085
|
}
|
|
6001
|
-
catch (
|
|
6086
|
+
catch (err) {
|
|
6087
|
+
console.error("[Auth Interceptor] Failed to eject request interceptor:", err);
|
|
6088
|
+
}
|
|
6002
6089
|
try {
|
|
6003
6090
|
apiClient.interceptors.response.eject(resId);
|
|
6004
6091
|
}
|
|
6005
|
-
catch (
|
|
6092
|
+
catch (err) {
|
|
6093
|
+
console.error("[Auth Interceptor] Failed to eject response interceptor:", err);
|
|
6094
|
+
}
|
|
6006
6095
|
};
|
|
6007
6096
|
}
|
|
6008
6097
|
|
|
@@ -7508,20 +7597,34 @@ function usePortfolio() {
|
|
|
7508
7597
|
}
|
|
7509
7598
|
|
|
7510
7599
|
async function getEIP712Message(baseUrl, address, clientId) {
|
|
7511
|
-
const url = joinUrl(baseUrl,
|
|
7600
|
+
const url = joinUrl(baseUrl, "/auth/eip712-message");
|
|
7512
7601
|
try {
|
|
7513
|
-
const resp = await
|
|
7514
|
-
|
|
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
|
+
};
|
|
7515
7611
|
}
|
|
7516
7612
|
catch (error) {
|
|
7517
7613
|
throw toApiError(error);
|
|
7518
7614
|
}
|
|
7519
7615
|
}
|
|
7520
7616
|
async function authenticate(baseUrl, body) {
|
|
7521
|
-
const url = joinUrl(baseUrl,
|
|
7617
|
+
const url = joinUrl(baseUrl, "/auth/login");
|
|
7522
7618
|
try {
|
|
7523
|
-
const resp = await
|
|
7524
|
-
|
|
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
|
+
};
|
|
7525
7628
|
}
|
|
7526
7629
|
catch (error) {
|
|
7527
7630
|
throw toApiError(error);
|
|
@@ -7532,7 +7635,7 @@ async function authenticate(baseUrl, body) {
|
|
|
7532
7635
|
*/
|
|
7533
7636
|
async function authenticateWithPrivy(baseUrl, params) {
|
|
7534
7637
|
const body = {
|
|
7535
|
-
method:
|
|
7638
|
+
method: "privy_access_token",
|
|
7536
7639
|
address: params.address,
|
|
7537
7640
|
clientId: params.clientId,
|
|
7538
7641
|
details: { appId: params.appId, accessToken: params.accessToken },
|
|
@@ -7540,62 +7643,124 @@ async function authenticateWithPrivy(baseUrl, params) {
|
|
|
7540
7643
|
return authenticate(baseUrl, body);
|
|
7541
7644
|
}
|
|
7542
7645
|
async function refreshToken(baseUrl, refreshTokenVal) {
|
|
7543
|
-
const url = joinUrl(baseUrl,
|
|
7646
|
+
const url = joinUrl(baseUrl, "/auth/refresh");
|
|
7544
7647
|
try {
|
|
7545
|
-
const resp = await
|
|
7546
|
-
return {
|
|
7648
|
+
const resp = await apiClient.post(url, { refreshToken: refreshTokenVal }, { headers: { "Content-Type": "application/json" }, timeout: 30000 });
|
|
7649
|
+
return {
|
|
7650
|
+
data: resp.data,
|
|
7651
|
+
status: resp.status,
|
|
7652
|
+
headers: resp.headers,
|
|
7653
|
+
};
|
|
7547
7654
|
}
|
|
7548
7655
|
catch (error) {
|
|
7549
7656
|
throw toApiError(error);
|
|
7550
7657
|
}
|
|
7551
7658
|
}
|
|
7552
7659
|
async function logout(baseUrl, refreshTokenVal) {
|
|
7553
|
-
const url = joinUrl(baseUrl,
|
|
7660
|
+
const url = joinUrl(baseUrl, "/auth/logout");
|
|
7554
7661
|
try {
|
|
7555
|
-
const resp = await
|
|
7556
|
-
return {
|
|
7662
|
+
const resp = await apiClient.post(url, { refreshToken: refreshTokenVal }, { headers: { "Content-Type": "application/json" }, timeout: 30000 });
|
|
7663
|
+
return {
|
|
7664
|
+
data: resp.data,
|
|
7665
|
+
status: resp.status,
|
|
7666
|
+
headers: resp.headers,
|
|
7667
|
+
};
|
|
7557
7668
|
}
|
|
7558
7669
|
catch (error) {
|
|
7559
7670
|
throw toApiError(error);
|
|
7560
7671
|
}
|
|
7561
7672
|
}
|
|
7562
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
|
+
}
|
|
7563
7683
|
function useAuth() {
|
|
7564
7684
|
const context = useContext(PearHyperliquidContext);
|
|
7565
7685
|
if (!context) {
|
|
7566
|
-
throw new Error(
|
|
7686
|
+
throw new Error('useAuth must be used within a PearHyperliquidProvider');
|
|
7567
7687
|
}
|
|
7568
7688
|
const { apiBaseUrl, clientId } = context;
|
|
7569
|
-
const [isReady, setIsReady] = useState(false);
|
|
7570
7689
|
const accessToken = useUserData((s) => s.accessToken);
|
|
7571
7690
|
const refreshToken$1 = useUserData((s) => s.refreshToken);
|
|
7691
|
+
const isReady = useUserData((s) => s.isReady);
|
|
7692
|
+
const isAuthenticated = useUserData((s) => s.isAuthenticated);
|
|
7572
7693
|
const setAccessToken = useUserData((s) => s.setAccessToken);
|
|
7573
7694
|
const setRefreshToken = useUserData((s) => s.setRefreshToken);
|
|
7574
|
-
const
|
|
7695
|
+
const setIsReady = useUserData((s) => s.setIsReady);
|
|
7575
7696
|
const setIsAuthenticated = useUserData((s) => s.setIsAuthenticated);
|
|
7576
7697
|
const address = useUserData((s) => s.address);
|
|
7577
7698
|
const setAddress = useUserData((s) => s.setAddress);
|
|
7699
|
+
// Ref to prevent concurrent refresh attempts
|
|
7700
|
+
const isRefreshingRef = useRef(false);
|
|
7578
7701
|
useEffect(() => {
|
|
7579
|
-
if (typeof window ==
|
|
7702
|
+
if (typeof window == 'undefined') {
|
|
7580
7703
|
return;
|
|
7581
7704
|
}
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
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]);
|
|
7592
7756
|
useEffect(() => {
|
|
7593
7757
|
const cleanup = addAuthInterceptors({
|
|
7594
7758
|
apiBaseUrl,
|
|
7595
7759
|
getAccessToken: () => {
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7760
|
+
if (typeof window === 'undefined')
|
|
7761
|
+
return null;
|
|
7762
|
+
// Read from Zustand state as single source of truth
|
|
7763
|
+
return useUserData.getState().accessToken;
|
|
7599
7764
|
},
|
|
7600
7765
|
refreshTokens: async () => {
|
|
7601
7766
|
const data = await refreshTokens();
|
|
@@ -7609,6 +7774,55 @@ function useAuth() {
|
|
|
7609
7774
|
cleanup();
|
|
7610
7775
|
};
|
|
7611
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]);
|
|
7612
7826
|
async function getEip712(address) {
|
|
7613
7827
|
const { data } = await getEIP712Message(apiBaseUrl, address, clientId);
|
|
7614
7828
|
return data;
|
|
@@ -7616,17 +7830,21 @@ function useAuth() {
|
|
|
7616
7830
|
async function loginWithSignedMessage(address, signature, timestamp) {
|
|
7617
7831
|
try {
|
|
7618
7832
|
const { data } = await authenticate(apiBaseUrl, {
|
|
7619
|
-
method:
|
|
7833
|
+
method: 'eip712',
|
|
7620
7834
|
address,
|
|
7621
7835
|
clientId,
|
|
7622
7836
|
details: { signature, timestamp },
|
|
7623
7837
|
});
|
|
7624
|
-
|
|
7625
|
-
|
|
7626
|
-
|
|
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));
|
|
7627
7846
|
setAccessToken(data.accessToken);
|
|
7628
7847
|
setRefreshToken(data.refreshToken);
|
|
7629
|
-
setAddress(address);
|
|
7630
7848
|
setIsAuthenticated(true);
|
|
7631
7849
|
}
|
|
7632
7850
|
catch (e) {
|
|
@@ -7641,12 +7859,16 @@ function useAuth() {
|
|
|
7641
7859
|
appId,
|
|
7642
7860
|
accessToken: privyAccessToken,
|
|
7643
7861
|
});
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
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));
|
|
7647
7870
|
setAccessToken(data.accessToken);
|
|
7648
7871
|
setRefreshToken(data.refreshToken);
|
|
7649
|
-
setAddress(address);
|
|
7650
7872
|
setIsAuthenticated(true);
|
|
7651
7873
|
}
|
|
7652
7874
|
catch (e) {
|
|
@@ -7654,35 +7876,56 @@ function useAuth() {
|
|
|
7654
7876
|
}
|
|
7655
7877
|
}
|
|
7656
7878
|
async function refreshTokens() {
|
|
7657
|
-
const
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
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));
|
|
7663
7893
|
setAccessToken(data.accessToken);
|
|
7664
7894
|
setRefreshToken(data.refreshToken);
|
|
7665
7895
|
setIsAuthenticated(true);
|
|
7666
7896
|
return data;
|
|
7667
7897
|
}
|
|
7668
7898
|
async function logout$1() {
|
|
7669
|
-
const
|
|
7670
|
-
|
|
7899
|
+
const currentAddress = address;
|
|
7900
|
+
const currentRefresh = refreshToken$1;
|
|
7901
|
+
if (currentRefresh) {
|
|
7671
7902
|
try {
|
|
7672
|
-
await logout(apiBaseUrl,
|
|
7903
|
+
await logout(apiBaseUrl, currentRefresh);
|
|
7673
7904
|
}
|
|
7674
7905
|
catch (_a) {
|
|
7675
7906
|
/* ignore */
|
|
7676
7907
|
}
|
|
7677
7908
|
}
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
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
|
+
}
|
|
7681
7919
|
setAccessToken(null);
|
|
7682
7920
|
setRefreshToken(null);
|
|
7683
7921
|
setAddress(null);
|
|
7684
7922
|
setIsAuthenticated(false);
|
|
7685
7923
|
}
|
|
7924
|
+
function clearSession() {
|
|
7925
|
+
setAccessToken(null);
|
|
7926
|
+
setRefreshToken(null);
|
|
7927
|
+
setIsAuthenticated(false);
|
|
7928
|
+
}
|
|
7686
7929
|
return {
|
|
7687
7930
|
isReady,
|
|
7688
7931
|
isAuthenticated,
|
|
@@ -7694,6 +7937,8 @@ function useAuth() {
|
|
|
7694
7937
|
loginWithPrivyToken,
|
|
7695
7938
|
refreshTokens,
|
|
7696
7939
|
logout: logout$1,
|
|
7940
|
+
clearSession,
|
|
7941
|
+
setAddress,
|
|
7697
7942
|
};
|
|
7698
7943
|
}
|
|
7699
7944
|
|
|
@@ -7861,6 +8106,7 @@ const useAllUserBalances = () => {
|
|
|
7861
8106
|
availableToTrades,
|
|
7862
8107
|
]);
|
|
7863
8108
|
return {
|
|
8109
|
+
abstractionMode: useUserData((state) => state.userAbstractionMode),
|
|
7864
8110
|
spotBalances,
|
|
7865
8111
|
availableToTrades,
|
|
7866
8112
|
isLoading,
|
|
@@ -8005,9 +8251,8 @@ const PearHyperliquidContext = createContext(undefined);
|
|
|
8005
8251
|
/**
|
|
8006
8252
|
* React Provider for PearHyperliquidClient
|
|
8007
8253
|
*/
|
|
8008
|
-
const PearHyperliquidProvider = ({ children, apiBaseUrl =
|
|
8254
|
+
const PearHyperliquidProvider = ({ children, apiBaseUrl = "https://hl-ui.pearprotocol.io", clientId = "PEARPROTOCOLUI", wsUrl = "wss://hl-ui.pearprotocol.io/ws", }) => {
|
|
8009
8255
|
const address = useUserData((s) => s.address);
|
|
8010
|
-
const setAddress = useUserData((s) => s.setAddress);
|
|
8011
8256
|
const perpMetasByDex = useHyperliquidData((state) => state.perpMetasByDex);
|
|
8012
8257
|
const setPerpDexs = useHyperliquidData((state) => state.setPerpDexs);
|
|
8013
8258
|
const setPerpMetasByDex = useHyperliquidData((state) => state.setPerpMetasByDex);
|
|
@@ -8038,20 +8283,20 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8038
8283
|
perpMetas.forEach((item, perpIndex) => {
|
|
8039
8284
|
var _a, _b;
|
|
8040
8285
|
const dexName = perpIndex === 0
|
|
8041
|
-
?
|
|
8286
|
+
? "HYPERLIQUID"
|
|
8042
8287
|
: ((_b = (_a = perpDexs[perpIndex]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : `DEX_${perpIndex}`);
|
|
8043
8288
|
var collateralToken;
|
|
8044
8289
|
if (item.collateralToken === 360) {
|
|
8045
|
-
collateralToken =
|
|
8290
|
+
collateralToken = "USDH";
|
|
8046
8291
|
}
|
|
8047
8292
|
if (item.collateralToken === 0) {
|
|
8048
|
-
collateralToken =
|
|
8293
|
+
collateralToken = "USDC";
|
|
8049
8294
|
}
|
|
8050
8295
|
if (item.collateralToken === 235) {
|
|
8051
|
-
collateralToken =
|
|
8296
|
+
collateralToken = "USDE";
|
|
8052
8297
|
}
|
|
8053
8298
|
if (item.collateralToken === 268) {
|
|
8054
|
-
collateralToken =
|
|
8299
|
+
collateralToken = "USDT0";
|
|
8055
8300
|
}
|
|
8056
8301
|
const universeAssets = item.universe.map((asset) => ({
|
|
8057
8302
|
...asset,
|
|
@@ -8079,8 +8324,6 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8079
8324
|
}), [
|
|
8080
8325
|
apiBaseUrl,
|
|
8081
8326
|
wsUrl,
|
|
8082
|
-
address,
|
|
8083
|
-
setAddress,
|
|
8084
8327
|
isConnected,
|
|
8085
8328
|
lastError,
|
|
8086
8329
|
nativeIsConnected,
|
|
@@ -8095,7 +8338,7 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
|
|
|
8095
8338
|
function usePearHyperliquid() {
|
|
8096
8339
|
const ctx = useContext(PearHyperliquidContext);
|
|
8097
8340
|
if (!ctx)
|
|
8098
|
-
throw new Error(
|
|
8341
|
+
throw new Error("usePearHyperliquid must be used within a PearHyperliquidProvider");
|
|
8099
8342
|
return ctx;
|
|
8100
8343
|
}
|
|
8101
8344
|
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ActiveAssetData, CandleChartData, CandleData, ClearinghouseState, UniverseAsset, WebData3AssetCtx, WsAllMidsData, PerpDexsResponse } from
|
|
2
|
-
import { TokenMetadataBySymbol } from
|
|
1
|
+
import { ActiveAssetData, CandleChartData, CandleData, ClearinghouseState, UniverseAsset, WebData3AssetCtx, WsAllMidsData, PerpDexsResponse } from "../types";
|
|
2
|
+
import { TokenMetadataBySymbol } from "../utils/token-metadata-extractor";
|
|
3
3
|
interface HyperliquidDataState {
|
|
4
4
|
allMids: WsAllMidsData | null;
|
|
5
5
|
activeAssetData: Record<string, ActiveAssetData> | null;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { PlatformAccountSummaryResponseDto, OpenLimitOrderDto, RawPositionDto, TradeHistoryDataDto, TwapMonitoringDto, NotificationDto, ExtraAgent, SpotState } from "../types";
|
|
1
|
+
import { PlatformAccountSummaryResponseDto, OpenLimitOrderDto, RawPositionDto, TradeHistoryDataDto, TwapMonitoringDto, NotificationDto, ExtraAgent, SpotState, UserAbstraction } from "../types";
|
|
2
2
|
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;
|
|
@@ -12,6 +13,7 @@ interface UserDataState {
|
|
|
12
13
|
notifications: NotificationDto[] | null;
|
|
13
14
|
userExtraAgents: ExtraAgent[] | null;
|
|
14
15
|
spotState: SpotState | null;
|
|
16
|
+
userAbstractionMode: UserAbstraction | null;
|
|
15
17
|
setAccessToken: (token: string | null) => void;
|
|
16
18
|
setRefreshToken: (token: string | null) => void;
|
|
17
19
|
setIsAuthenticated: (value: boolean) => void;
|
|
@@ -24,6 +26,8 @@ interface UserDataState {
|
|
|
24
26
|
setTwapDetails: (value: TwapMonitoringDto[] | null) => void;
|
|
25
27
|
setNotifications: (value: NotificationDto[] | null) => void;
|
|
26
28
|
setSpotState: (value: SpotState | null) => void;
|
|
29
|
+
setUserAbstractionMode: (value: UserAbstraction | null) => void;
|
|
30
|
+
setIsReady: (value: boolean) => void;
|
|
27
31
|
clean: () => void;
|
|
28
32
|
}
|
|
29
33
|
export declare const useUserData: import("zustand").UseBoundStore<import("zustand").StoreApi<UserDataState>>;
|
package/dist/types.d.ts
CHANGED
|
@@ -563,6 +563,7 @@ export interface HLChannelDataMap {
|
|
|
563
563
|
allDexsAssetCtxs: AllDexsAssetCtxsData;
|
|
564
564
|
userFills: any;
|
|
565
565
|
}
|
|
566
|
+
export type UserAbstraction = 'dexAbstraction' | 'disabled' | 'unifiedAccount';
|
|
566
567
|
export interface WebData3UserState {
|
|
567
568
|
agentAddress?: string;
|
|
568
569
|
agentValidUntil?: number;
|
|
@@ -571,6 +572,7 @@ export interface WebData3UserState {
|
|
|
571
572
|
isVault: boolean;
|
|
572
573
|
user: string;
|
|
573
574
|
dexAbstractionEnabled?: boolean;
|
|
575
|
+
abstraction: UserAbstraction;
|
|
574
576
|
}
|
|
575
577
|
export interface WebData3AssetCtx {
|
|
576
578
|
funding: string;
|
package/dist/utils/http.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AxiosInstance } from
|
|
2
|
-
import { ApiErrorResponse } from
|
|
1
|
+
import type { AxiosInstance } from "axios";
|
|
2
|
+
import { ApiErrorResponse } from "../types";
|
|
3
3
|
export declare function toApiError(error: unknown): ApiErrorResponse;
|
|
4
4
|
export declare function joinUrl(baseUrl: string, path: string): string;
|
|
5
5
|
/**
|