@pear-protocol/hyperliquid-sdk 0.0.9 → 0.0.10

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/index.js CHANGED
@@ -2642,21 +2642,17 @@ const useHyperliquidWebSocket = ({ wsUrl, address }) => {
2642
2642
  accountSummary: null,
2643
2643
  });
2644
2644
  const [lastError, setLastError] = require$$0.useState(null);
2645
+ const [lastSubscribedAddress, setLastSubscribedAddress] = require$$0.useState(null);
2645
2646
  // WebSocket connection
2646
- const { lastMessage, readyState, sendMessage } = useWebSocket(address ? wsUrl : null, // Only connect when address is set
2647
- {
2647
+ const { readyState, sendMessage } = useWebSocket(wsUrl, {
2648
2648
  shouldReconnect: () => true,
2649
2649
  reconnectAttempts: 5,
2650
2650
  reconnectInterval: 3000,
2651
- });
2652
- const isConnected = readyState === dist.ReadyState.OPEN;
2653
- // Handle incoming WebSocket messages
2654
- require$$0.useEffect(() => {
2655
- if (lastMessage !== null) {
2651
+ onMessage: (event) => {
2656
2652
  try {
2657
- const message = JSON.parse(lastMessage.data);
2658
- // Handle subscription responses
2659
- if ('success' in message || 'error' in message) {
2653
+ const message = JSON.parse(event.data);
2654
+ // Handle subscription responses (only if they don't have channel data)
2655
+ if (('success' in message || 'error' in message) && !('channel' in message)) {
2660
2656
  if (message.error) {
2661
2657
  setLastError(message.error);
2662
2658
  }
@@ -2668,30 +2664,42 @@ const useHyperliquidWebSocket = ({ wsUrl, address }) => {
2668
2664
  // Handle channel data messages
2669
2665
  if ('channel' in message && 'data' in message) {
2670
2666
  const dataMessage = message;
2667
+ // Validate data exists and is not null/undefined
2668
+ if (dataMessage.data === null || dataMessage.data === undefined) {
2669
+ return;
2670
+ }
2671
2671
  switch (dataMessage.channel) {
2672
2672
  case 'trade-histories':
2673
- setData(prev => ({
2674
- ...prev,
2675
- tradeHistories: dataMessage.data
2676
- }));
2673
+ if (Array.isArray(dataMessage.data)) {
2674
+ setData(prev => ({
2675
+ ...prev,
2676
+ tradeHistories: dataMessage.data
2677
+ }));
2678
+ }
2677
2679
  break;
2678
2680
  case 'open-positions':
2679
- setData(prev => ({
2680
- ...prev,
2681
- openPositions: dataMessage.data
2682
- }));
2681
+ if (Array.isArray(dataMessage.data)) {
2682
+ setData(prev => ({
2683
+ ...prev,
2684
+ openPositions: dataMessage.data
2685
+ }));
2686
+ }
2683
2687
  break;
2684
2688
  case 'open-orders':
2685
- setData(prev => ({
2686
- ...prev,
2687
- openOrders: dataMessage.data
2688
- }));
2689
+ if (Array.isArray(dataMessage.data)) {
2690
+ setData(prev => ({
2691
+ ...prev,
2692
+ openOrders: dataMessage.data
2693
+ }));
2694
+ }
2689
2695
  break;
2690
2696
  case 'account-summary':
2691
- setData(prev => ({
2692
- ...prev,
2693
- accountSummary: dataMessage.data
2694
- }));
2697
+ if (typeof dataMessage.data === 'object' && dataMessage.data !== null) {
2698
+ setData(prev => ({
2699
+ ...prev,
2700
+ accountSummary: dataMessage.data
2701
+ }));
2702
+ }
2695
2703
  break;
2696
2704
  }
2697
2705
  }
@@ -2700,34 +2708,47 @@ const useHyperliquidWebSocket = ({ wsUrl, address }) => {
2700
2708
  setLastError(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`);
2701
2709
  }
2702
2710
  }
2703
- }, [lastMessage]);
2704
- // Handle address changes - subscribe/unsubscribe
2711
+ });
2712
+ const isConnected = readyState === dist.ReadyState.OPEN;
2713
+ // Handle subscription management
2705
2714
  require$$0.useEffect(() => {
2706
- if (isConnected && address) {
2715
+ if (isConnected && address && address !== lastSubscribedAddress) {
2716
+ // Unsubscribe from previous address if exists
2717
+ if (lastSubscribedAddress) {
2718
+ sendMessage(JSON.stringify({
2719
+ action: 'unsubscribe',
2720
+ address: lastSubscribedAddress
2721
+ }));
2722
+ }
2707
2723
  // Subscribe to new address
2708
2724
  sendMessage(JSON.stringify({
2709
2725
  action: 'subscribe',
2710
2726
  address: address
2711
2727
  }));
2712
- // Clear previous data
2713
- setData({
2714
- tradeHistories: null,
2715
- openPositions: null,
2716
- openOrders: null,
2717
- accountSummary: null,
2718
- });
2728
+ setLastSubscribedAddress(address);
2719
2729
  setLastError(null);
2720
2730
  }
2721
- else if (isConnected && !address) {
2722
- // Clear data when address is removed
2731
+ else if (isConnected && !address && lastSubscribedAddress) {
2732
+ // Send unsubscribe action when address is removed
2733
+ sendMessage(JSON.stringify({
2734
+ action: 'unsubscribe',
2735
+ address: lastSubscribedAddress
2736
+ }));
2737
+ setLastSubscribedAddress(null);
2738
+ }
2739
+ }, [isConnected, address, lastSubscribedAddress, sendMessage]);
2740
+ // Clear data when address changes
2741
+ require$$0.useEffect(() => {
2742
+ if (address !== lastSubscribedAddress) {
2723
2743
  setData({
2724
2744
  tradeHistories: null,
2725
2745
  openPositions: null,
2726
2746
  openOrders: null,
2727
2747
  accountSummary: null,
2728
2748
  });
2749
+ setLastError(null);
2729
2750
  }
2730
- }, [isConnected, address, sendMessage]);
2751
+ }, [address, lastSubscribedAddress]);
2731
2752
  return {
2732
2753
  data,
2733
2754
  connectionStatus: readyState,
@@ -2743,7 +2764,7 @@ const useHyperliquidNativeWebSocket = ({ address }) => {
2743
2764
  const [subscribedAddress, setSubscribedAddress] = require$$0.useState(null);
2744
2765
  const pingIntervalRef = require$$0.useRef(null);
2745
2766
  // WebSocket connection to HyperLiquid native API
2746
- const { lastMessage, readyState, sendJsonMessage } = useWebSocket('wss://api.hyperliquid.xyz/ws', {
2767
+ const { readyState, sendJsonMessage } = useWebSocket('wss://api.hyperliquid.xyz/ws', {
2747
2768
  shouldReconnect: () => true,
2748
2769
  reconnectAttempts: 5,
2749
2770
  reconnectInterval: 3000,
@@ -2751,6 +2772,41 @@ const useHyperliquidNativeWebSocket = ({ address }) => {
2751
2772
  onClose: () => { },
2752
2773
  onError: (event) => console.error('[HyperLiquid WS] Connection error:', event),
2753
2774
  onReconnectStop: () => console.error('[HyperLiquid WS] Reconnection stopped after 5 attempts'),
2775
+ onMessage: (event) => {
2776
+ try {
2777
+ const message = JSON.parse(event.data);
2778
+ // Handle subscription responses
2779
+ if ('success' in message || 'error' in message) {
2780
+ if (message.error) {
2781
+ console.error('[HyperLiquid WS] Subscription error:', message.error);
2782
+ setLastError(message.error);
2783
+ }
2784
+ else {
2785
+ setLastError(null);
2786
+ }
2787
+ return;
2788
+ }
2789
+ // Handle channel data messages
2790
+ if ('channel' in message && 'data' in message) {
2791
+ const response = message;
2792
+ switch (response.channel) {
2793
+ case 'webData2':
2794
+ setWebData2(response.data);
2795
+ break;
2796
+ case 'allMids':
2797
+ setAllMids(response.data);
2798
+ break;
2799
+ default:
2800
+ console.warn(`[HyperLiquid WS] Unknown channel: ${response.channel}`);
2801
+ }
2802
+ }
2803
+ }
2804
+ catch (error) {
2805
+ const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
2806
+ console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', event.data);
2807
+ setLastError(errorMessage);
2808
+ }
2809
+ }
2754
2810
  });
2755
2811
  const isConnected = readyState === dist.ReadyState.OPEN;
2756
2812
  // Setup ping mechanism
@@ -2816,44 +2872,6 @@ const useHyperliquidNativeWebSocket = ({ address }) => {
2816
2872
  setWebData2(null);
2817
2873
  }
2818
2874
  }, [isConnected, address, subscribedAddress, sendJsonMessage]);
2819
- // Handle incoming WebSocket messages
2820
- require$$0.useEffect(() => {
2821
- if (!lastMessage)
2822
- return;
2823
- try {
2824
- const message = JSON.parse(lastMessage.data);
2825
- // Handle subscription responses
2826
- if ('success' in message || 'error' in message) {
2827
- if (message.error) {
2828
- console.error('[HyperLiquid WS] Subscription error:', message.error);
2829
- setLastError(message.error);
2830
- }
2831
- else {
2832
- setLastError(null);
2833
- }
2834
- return;
2835
- }
2836
- // Handle channel data messages
2837
- if ('channel' in message && 'data' in message) {
2838
- const response = message;
2839
- switch (response.channel) {
2840
- case 'webData2':
2841
- setWebData2(response.data);
2842
- break;
2843
- case 'allMids':
2844
- setAllMids(response.data);
2845
- break;
2846
- default:
2847
- console.warn(`[HyperLiquid WS] Unknown channel: ${response.channel}`);
2848
- }
2849
- }
2850
- }
2851
- catch (error) {
2852
- const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
2853
- console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', lastMessage.data);
2854
- setLastError(errorMessage);
2855
- }
2856
- }, [lastMessage]);
2857
2875
  return {
2858
2876
  webData2,
2859
2877
  allMids,
@@ -3184,32 +3202,24 @@ class PositionProcessor {
3184
3202
  };
3185
3203
  }
3186
3204
  calculateEntryRatio(syncResults) {
3205
+ var _a, _b;
3187
3206
  const longResults = syncResults.filter(result => result.asset.side === PositionSide.LONG);
3188
3207
  const shortResults = syncResults.filter(result => result.asset.side === PositionSide.SHORT);
3189
3208
  if (longResults.length === 0 || shortResults.length === 0)
3190
3209
  return 0;
3191
- const longValue = longResults.reduce((sum, result) => {
3192
- return sum + (Number(result.asset.size || 0) * Number(result.asset.entryPrice || 0));
3193
- }, 0);
3194
- const shortValue = shortResults.reduce((sum, result) => {
3195
- return sum + (Number(result.asset.size || 0) * Number(result.asset.entryPrice || 0));
3196
- }, 0);
3197
- return shortValue > 0 ? longValue / shortValue : 0;
3210
+ const longEntryPrice = ((_a = longResults[0]) === null || _a === void 0 ? void 0 : _a.asset.entryPrice) ? Number(longResults[0].asset.entryPrice) : 0;
3211
+ const shortEntryPrice = ((_b = shortResults[0]) === null || _b === void 0 ? void 0 : _b.asset.entryPrice) ? Number(shortResults[0].asset.entryPrice) : 0;
3212
+ return shortEntryPrice > 0 ? longEntryPrice / shortEntryPrice : 0;
3198
3213
  }
3199
3214
  calculateMarkRatio(syncResults) {
3215
+ var _a, _b;
3200
3216
  const longResults = syncResults.filter(result => result.asset.side === PositionSide.LONG);
3201
3217
  const shortResults = syncResults.filter(result => result.asset.side === PositionSide.SHORT);
3202
3218
  if (longResults.length === 0 || shortResults.length === 0)
3203
3219
  return 0;
3204
- const longValue = longResults.reduce((sum, result) => {
3205
- const currentPrice = this.getMarketPrice(result.asset.coin);
3206
- return sum + (result.actualSize * currentPrice);
3207
- }, 0);
3208
- const shortValue = shortResults.reduce((sum, result) => {
3209
- const currentPrice = this.getMarketPrice(result.asset.coin);
3210
- return sum + (result.actualSize * currentPrice);
3211
- }, 0);
3212
- return shortValue > 0 ? longValue / shortValue : 0;
3220
+ const longMarkPrice = ((_a = longResults[0]) === null || _a === void 0 ? void 0 : _a.asset.coin) ? this.getMarketPrice(longResults[0].asset.coin) : 0;
3221
+ const shortMarkPrice = ((_b = shortResults[0]) === null || _b === void 0 ? void 0 : _b.asset.coin) ? this.getMarketPrice(shortResults[0].asset.coin) : 0;
3222
+ return shortMarkPrice > 0 ? longMarkPrice / shortMarkPrice : 0;
3213
3223
  }
3214
3224
  calculateNetFundingFromSyncResults(syncResults) {
3215
3225
  const netFunding = syncResults.reduce((sum, result) => {
@@ -3352,17 +3362,24 @@ const useCalculatedAccountSummary = (platformAccountSummary, platformOpenOrders,
3352
3362
  };
3353
3363
 
3354
3364
  /**
3355
- * Hook to access trade histories
3365
+ * Hook to access trade histories with loading state
3356
3366
  */
3357
3367
  const useTradeHistories = () => {
3358
3368
  const context = require$$0.useContext(PearHyperliquidContext);
3359
3369
  if (!context) {
3360
3370
  throw new Error('useTradeHistories must be used within a PearHyperliquidProvider');
3361
3371
  }
3362
- return context.data.tradeHistories;
3372
+ const isLoading = require$$0.useMemo(() => {
3373
+ // Loading is true initially and becomes false once we get the first data
3374
+ return context.data.tradeHistories === null && context.isConnected;
3375
+ }, [context.data.tradeHistories, context.isConnected]);
3376
+ return {
3377
+ data: context.data.tradeHistories,
3378
+ isLoading
3379
+ };
3363
3380
  };
3364
3381
  /**
3365
- * Hook to access open positions with real-time calculations
3382
+ * Hook to access open positions with real-time calculations and loading state
3366
3383
  */
3367
3384
  const useOpenPositions = () => {
3368
3385
  const context = require$$0.useContext(PearHyperliquidContext);
@@ -3371,20 +3388,38 @@ const useOpenPositions = () => {
3371
3388
  }
3372
3389
  // Use calculated positions that sync platform data with HyperLiquid real-time data
3373
3390
  const calculatedPositions = useCalculatedOpenPositions(context.data.openPositions, context.webData2, context.allMids);
3374
- return calculatedPositions;
3391
+ const isLoading = require$$0.useMemo(() => {
3392
+ // Loading is true initially and becomes false only when:
3393
+ // 1. WebSocket has open-positions data
3394
+ // 2. webData2 and allMids are ready (required for calculations)
3395
+ // 3. Connection is established
3396
+ return (context.isConnected &&
3397
+ (context.data.openPositions === null || !context.webData2 || !context.allMids));
3398
+ }, [context.data.openPositions, context.webData2, context.allMids, context.isConnected]);
3399
+ return {
3400
+ data: calculatedPositions,
3401
+ isLoading
3402
+ };
3375
3403
  };
3376
3404
  /**
3377
- * Hook to access open orders
3405
+ * Hook to access open orders with loading state
3378
3406
  */
3379
3407
  const useOpenOrders = () => {
3380
3408
  const context = require$$0.useContext(PearHyperliquidContext);
3381
3409
  if (!context) {
3382
3410
  throw new Error('useOpenOrders must be used within a PearHyperliquidProvider');
3383
3411
  }
3384
- return context.data.openOrders;
3412
+ const isLoading = require$$0.useMemo(() => {
3413
+ // Loading is true initially and becomes false once we get the first data
3414
+ return context.data.openOrders === null && context.isConnected;
3415
+ }, [context.data.openOrders, context.isConnected]);
3416
+ return {
3417
+ data: context.data.openOrders,
3418
+ isLoading
3419
+ };
3385
3420
  };
3386
3421
  /**
3387
- * Hook to access account summary with real-time calculations
3422
+ * Hook to access account summary with real-time calculations and loading state
3388
3423
  */
3389
3424
  const useAccountSummary = () => {
3390
3425
  var _a, _b, _c, _d;
@@ -3394,7 +3429,14 @@ const useAccountSummary = () => {
3394
3429
  }
3395
3430
  // Use calculated account summary that syncs platform data with HyperLiquid real-time data
3396
3431
  const calculatedAccountSummary = useCalculatedAccountSummary(context.data.accountSummary, context.data.openOrders, context.webData2, (_b = (_a = context.data.accountSummary) === null || _a === void 0 ? void 0 : _a.agentWallet) === null || _b === void 0 ? void 0 : _b.address, (_d = (_c = context.data.accountSummary) === null || _c === void 0 ? void 0 : _c.agentWallet) === null || _d === void 0 ? void 0 : _d.status);
3397
- return calculatedAccountSummary;
3432
+ const isLoading = require$$0.useMemo(() => {
3433
+ // Loading is true initially and becomes false once we get the first data
3434
+ return context.data.accountSummary === null && context.isConnected;
3435
+ }, [context.data.accountSummary, context.isConnected]);
3436
+ return {
3437
+ data: calculatedAccountSummary,
3438
+ isLoading
3439
+ };
3398
3440
  };
3399
3441
 
3400
3442
  exports.AccountSummaryCalculator = AccountSummaryCalculator;