@pear-protocol/hyperliquid-sdk 0.0.9 → 0.0.11

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.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import axios from 'axios';
2
- import require$$0, { useState, useEffect, useRef, createContext, useMemo, useContext } from 'react';
2
+ import require$$0, { useState, useEffect, useRef, createContext, useMemo, useContext, useCallback } from 'react';
3
3
  import require$$1 from 'react-dom';
4
4
 
5
5
  /**
@@ -2638,21 +2638,17 @@ const useHyperliquidWebSocket = ({ wsUrl, address }) => {
2638
2638
  accountSummary: null,
2639
2639
  });
2640
2640
  const [lastError, setLastError] = useState(null);
2641
+ const [lastSubscribedAddress, setLastSubscribedAddress] = useState(null);
2641
2642
  // WebSocket connection
2642
- const { lastMessage, readyState, sendMessage } = useWebSocket(address ? wsUrl : null, // Only connect when address is set
2643
- {
2643
+ const { readyState, sendMessage } = useWebSocket(wsUrl, {
2644
2644
  shouldReconnect: () => true,
2645
2645
  reconnectAttempts: 5,
2646
2646
  reconnectInterval: 3000,
2647
- });
2648
- const isConnected = readyState === dist.ReadyState.OPEN;
2649
- // Handle incoming WebSocket messages
2650
- useEffect(() => {
2651
- if (lastMessage !== null) {
2647
+ onMessage: (event) => {
2652
2648
  try {
2653
- const message = JSON.parse(lastMessage.data);
2654
- // Handle subscription responses
2655
- if ('success' in message || 'error' in message) {
2649
+ const message = JSON.parse(event.data);
2650
+ // Handle subscription responses (only if they don't have channel data)
2651
+ if (('success' in message || 'error' in message) && !('channel' in message)) {
2656
2652
  if (message.error) {
2657
2653
  setLastError(message.error);
2658
2654
  }
@@ -2664,30 +2660,42 @@ const useHyperliquidWebSocket = ({ wsUrl, address }) => {
2664
2660
  // Handle channel data messages
2665
2661
  if ('channel' in message && 'data' in message) {
2666
2662
  const dataMessage = message;
2663
+ // Validate data exists and is not null/undefined
2664
+ if (dataMessage.data === null || dataMessage.data === undefined) {
2665
+ return;
2666
+ }
2667
2667
  switch (dataMessage.channel) {
2668
2668
  case 'trade-histories':
2669
- setData(prev => ({
2670
- ...prev,
2671
- tradeHistories: dataMessage.data
2672
- }));
2669
+ if (Array.isArray(dataMessage.data)) {
2670
+ setData(prev => ({
2671
+ ...prev,
2672
+ tradeHistories: dataMessage.data
2673
+ }));
2674
+ }
2673
2675
  break;
2674
2676
  case 'open-positions':
2675
- setData(prev => ({
2676
- ...prev,
2677
- openPositions: dataMessage.data
2678
- }));
2677
+ if (Array.isArray(dataMessage.data)) {
2678
+ setData(prev => ({
2679
+ ...prev,
2680
+ openPositions: dataMessage.data
2681
+ }));
2682
+ }
2679
2683
  break;
2680
2684
  case 'open-orders':
2681
- setData(prev => ({
2682
- ...prev,
2683
- openOrders: dataMessage.data
2684
- }));
2685
+ if (Array.isArray(dataMessage.data)) {
2686
+ setData(prev => ({
2687
+ ...prev,
2688
+ openOrders: dataMessage.data
2689
+ }));
2690
+ }
2685
2691
  break;
2686
2692
  case 'account-summary':
2687
- setData(prev => ({
2688
- ...prev,
2689
- accountSummary: dataMessage.data
2690
- }));
2693
+ if (typeof dataMessage.data === 'object' && dataMessage.data !== null) {
2694
+ setData(prev => ({
2695
+ ...prev,
2696
+ accountSummary: dataMessage.data
2697
+ }));
2698
+ }
2691
2699
  break;
2692
2700
  }
2693
2701
  }
@@ -2696,34 +2704,47 @@ const useHyperliquidWebSocket = ({ wsUrl, address }) => {
2696
2704
  setLastError(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`);
2697
2705
  }
2698
2706
  }
2699
- }, [lastMessage]);
2700
- // Handle address changes - subscribe/unsubscribe
2707
+ });
2708
+ const isConnected = readyState === dist.ReadyState.OPEN;
2709
+ // Handle subscription management
2701
2710
  useEffect(() => {
2702
- if (isConnected && address) {
2711
+ if (isConnected && address && address !== lastSubscribedAddress) {
2712
+ // Unsubscribe from previous address if exists
2713
+ if (lastSubscribedAddress) {
2714
+ sendMessage(JSON.stringify({
2715
+ action: 'unsubscribe',
2716
+ address: lastSubscribedAddress
2717
+ }));
2718
+ }
2703
2719
  // Subscribe to new address
2704
2720
  sendMessage(JSON.stringify({
2705
2721
  action: 'subscribe',
2706
2722
  address: address
2707
2723
  }));
2708
- // Clear previous data
2709
- setData({
2710
- tradeHistories: null,
2711
- openPositions: null,
2712
- openOrders: null,
2713
- accountSummary: null,
2714
- });
2724
+ setLastSubscribedAddress(address);
2715
2725
  setLastError(null);
2716
2726
  }
2717
- else if (isConnected && !address) {
2718
- // Clear data when address is removed
2727
+ else if (isConnected && !address && lastSubscribedAddress) {
2728
+ // Send unsubscribe action when address is removed
2729
+ sendMessage(JSON.stringify({
2730
+ action: 'unsubscribe',
2731
+ address: lastSubscribedAddress
2732
+ }));
2733
+ setLastSubscribedAddress(null);
2734
+ }
2735
+ }, [isConnected, address, lastSubscribedAddress, sendMessage]);
2736
+ // Clear data when address changes
2737
+ useEffect(() => {
2738
+ if (address !== lastSubscribedAddress) {
2719
2739
  setData({
2720
2740
  tradeHistories: null,
2721
2741
  openPositions: null,
2722
2742
  openOrders: null,
2723
2743
  accountSummary: null,
2724
2744
  });
2745
+ setLastError(null);
2725
2746
  }
2726
- }, [isConnected, address, sendMessage]);
2747
+ }, [address, lastSubscribedAddress]);
2727
2748
  return {
2728
2749
  data,
2729
2750
  connectionStatus: readyState,
@@ -2739,7 +2760,7 @@ const useHyperliquidNativeWebSocket = ({ address }) => {
2739
2760
  const [subscribedAddress, setSubscribedAddress] = useState(null);
2740
2761
  const pingIntervalRef = useRef(null);
2741
2762
  // WebSocket connection to HyperLiquid native API
2742
- const { lastMessage, readyState, sendJsonMessage } = useWebSocket('wss://api.hyperliquid.xyz/ws', {
2763
+ const { readyState, sendJsonMessage } = useWebSocket('wss://api.hyperliquid.xyz/ws', {
2743
2764
  shouldReconnect: () => true,
2744
2765
  reconnectAttempts: 5,
2745
2766
  reconnectInterval: 3000,
@@ -2747,6 +2768,41 @@ const useHyperliquidNativeWebSocket = ({ address }) => {
2747
2768
  onClose: () => { },
2748
2769
  onError: (event) => console.error('[HyperLiquid WS] Connection error:', event),
2749
2770
  onReconnectStop: () => console.error('[HyperLiquid WS] Reconnection stopped after 5 attempts'),
2771
+ onMessage: (event) => {
2772
+ try {
2773
+ const message = JSON.parse(event.data);
2774
+ // Handle subscription responses
2775
+ if ('success' in message || 'error' in message) {
2776
+ if (message.error) {
2777
+ console.error('[HyperLiquid WS] Subscription error:', message.error);
2778
+ setLastError(message.error);
2779
+ }
2780
+ else {
2781
+ setLastError(null);
2782
+ }
2783
+ return;
2784
+ }
2785
+ // Handle channel data messages
2786
+ if ('channel' in message && 'data' in message) {
2787
+ const response = message;
2788
+ switch (response.channel) {
2789
+ case 'webData2':
2790
+ setWebData2(response.data);
2791
+ break;
2792
+ case 'allMids':
2793
+ setAllMids(response.data);
2794
+ break;
2795
+ default:
2796
+ console.warn(`[HyperLiquid WS] Unknown channel: ${response.channel}`);
2797
+ }
2798
+ }
2799
+ }
2800
+ catch (error) {
2801
+ const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
2802
+ console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', event.data);
2803
+ setLastError(errorMessage);
2804
+ }
2805
+ }
2750
2806
  });
2751
2807
  const isConnected = readyState === dist.ReadyState.OPEN;
2752
2808
  // Setup ping mechanism
@@ -2812,44 +2868,6 @@ const useHyperliquidNativeWebSocket = ({ address }) => {
2812
2868
  setWebData2(null);
2813
2869
  }
2814
2870
  }, [isConnected, address, subscribedAddress, sendJsonMessage]);
2815
- // Handle incoming WebSocket messages
2816
- useEffect(() => {
2817
- if (!lastMessage)
2818
- return;
2819
- try {
2820
- const message = JSON.parse(lastMessage.data);
2821
- // Handle subscription responses
2822
- if ('success' in message || 'error' in message) {
2823
- if (message.error) {
2824
- console.error('[HyperLiquid WS] Subscription error:', message.error);
2825
- setLastError(message.error);
2826
- }
2827
- else {
2828
- setLastError(null);
2829
- }
2830
- return;
2831
- }
2832
- // Handle channel data messages
2833
- if ('channel' in message && 'data' in message) {
2834
- const response = message;
2835
- switch (response.channel) {
2836
- case 'webData2':
2837
- setWebData2(response.data);
2838
- break;
2839
- case 'allMids':
2840
- setAllMids(response.data);
2841
- break;
2842
- default:
2843
- console.warn(`[HyperLiquid WS] Unknown channel: ${response.channel}`);
2844
- }
2845
- }
2846
- }
2847
- catch (error) {
2848
- const errorMessage = `Failed to parse message: ${error instanceof Error ? error.message : String(error)}`;
2849
- console.error('[HyperLiquid WS] Parse error:', errorMessage, 'Raw message:', lastMessage.data);
2850
- setLastError(errorMessage);
2851
- }
2852
- }, [lastMessage]);
2853
2871
  return {
2854
2872
  webData2,
2855
2873
  allMids,
@@ -2859,6 +2877,23 @@ const useHyperliquidNativeWebSocket = ({ address }) => {
2859
2877
  };
2860
2878
  };
2861
2879
 
2880
+ const DEFAULT_TOKEN_SELECTION_STATE = {
2881
+ longTokens: [
2882
+ { symbol: 'HYPE', weight: 25 },
2883
+ { symbol: 'BTC', weight: 25 }
2884
+ ],
2885
+ shortTokens: [
2886
+ { symbol: 'AVAX', weight: 10 },
2887
+ { symbol: 'SEI', weight: 10 },
2888
+ { symbol: 'ADA', weight: 10 },
2889
+ { symbol: 'TRUMP', weight: 10 },
2890
+ { symbol: 'SUI', weight: 10 }
2891
+ ],
2892
+ openTokenSelector: false,
2893
+ selectorConfig: null,
2894
+ openConflictModal: false,
2895
+ conflicts: [],
2896
+ };
2862
2897
  const PearHyperliquidContext = createContext(undefined);
2863
2898
  /**
2864
2899
  * React Provider for PearHyperliquidClient
@@ -2868,6 +2903,8 @@ const PearHyperliquidProvider = ({ config, wsUrl = 'wss://hl-v2.pearprotocol.io/
2868
2903
  const migrationSDK = useMemo(() => new PearMigrationSDK(client), [client]);
2869
2904
  // Address state
2870
2905
  const [address, setAddress] = useState(null);
2906
+ // Token selection state
2907
+ const [tokenSelection, setTokenSelection] = useState(DEFAULT_TOKEN_SELECTION_STATE);
2871
2908
  // WebSocket connection and data (Pear API)
2872
2909
  const { data, connectionStatus, isConnected, lastError } = useHyperliquidWebSocket({
2873
2910
  wsUrl,
@@ -2897,7 +2934,10 @@ const PearHyperliquidProvider = ({ config, wsUrl = 'wss://hl-v2.pearprotocol.io/
2897
2934
  // HyperLiquid native WebSocket data
2898
2935
  webData2,
2899
2936
  allMids,
2900
- }), [client, migrationSDK, address, connectionStatus, isConnected, data, lastError, nativeConnectionStatus, nativeIsConnected, nativeLastError, webData2, allMids]);
2937
+ // Token selection state
2938
+ tokenSelection,
2939
+ setTokenSelection,
2940
+ }), [client, migrationSDK, address, connectionStatus, isConnected, data, lastError, nativeConnectionStatus, nativeIsConnected, nativeLastError, webData2, allMids, tokenSelection, setTokenSelection]);
2901
2941
  return (jsxRuntimeExports.jsx(PearHyperliquidContext.Provider, { value: contextValue, children: children }));
2902
2942
  };
2903
2943
  /**
@@ -3180,32 +3220,24 @@ class PositionProcessor {
3180
3220
  };
3181
3221
  }
3182
3222
  calculateEntryRatio(syncResults) {
3223
+ var _a, _b;
3183
3224
  const longResults = syncResults.filter(result => result.asset.side === PositionSide.LONG);
3184
3225
  const shortResults = syncResults.filter(result => result.asset.side === PositionSide.SHORT);
3185
3226
  if (longResults.length === 0 || shortResults.length === 0)
3186
3227
  return 0;
3187
- const longValue = longResults.reduce((sum, result) => {
3188
- return sum + (Number(result.asset.size || 0) * Number(result.asset.entryPrice || 0));
3189
- }, 0);
3190
- const shortValue = shortResults.reduce((sum, result) => {
3191
- return sum + (Number(result.asset.size || 0) * Number(result.asset.entryPrice || 0));
3192
- }, 0);
3193
- return shortValue > 0 ? longValue / shortValue : 0;
3228
+ const longEntryPrice = ((_a = longResults[0]) === null || _a === void 0 ? void 0 : _a.asset.entryPrice) ? Number(longResults[0].asset.entryPrice) : 0;
3229
+ const shortEntryPrice = ((_b = shortResults[0]) === null || _b === void 0 ? void 0 : _b.asset.entryPrice) ? Number(shortResults[0].asset.entryPrice) : 0;
3230
+ return shortEntryPrice > 0 ? longEntryPrice / shortEntryPrice : 0;
3194
3231
  }
3195
3232
  calculateMarkRatio(syncResults) {
3233
+ var _a, _b;
3196
3234
  const longResults = syncResults.filter(result => result.asset.side === PositionSide.LONG);
3197
3235
  const shortResults = syncResults.filter(result => result.asset.side === PositionSide.SHORT);
3198
3236
  if (longResults.length === 0 || shortResults.length === 0)
3199
3237
  return 0;
3200
- const longValue = longResults.reduce((sum, result) => {
3201
- const currentPrice = this.getMarketPrice(result.asset.coin);
3202
- return sum + (result.actualSize * currentPrice);
3203
- }, 0);
3204
- const shortValue = shortResults.reduce((sum, result) => {
3205
- const currentPrice = this.getMarketPrice(result.asset.coin);
3206
- return sum + (result.actualSize * currentPrice);
3207
- }, 0);
3208
- return shortValue > 0 ? longValue / shortValue : 0;
3238
+ const longMarkPrice = ((_a = longResults[0]) === null || _a === void 0 ? void 0 : _a.asset.coin) ? this.getMarketPrice(longResults[0].asset.coin) : 0;
3239
+ const shortMarkPrice = ((_b = shortResults[0]) === null || _b === void 0 ? void 0 : _b.asset.coin) ? this.getMarketPrice(shortResults[0].asset.coin) : 0;
3240
+ return shortMarkPrice > 0 ? longMarkPrice / shortMarkPrice : 0;
3209
3241
  }
3210
3242
  calculateNetFundingFromSyncResults(syncResults) {
3211
3243
  const netFunding = syncResults.reduce((sum, result) => {
@@ -3348,17 +3380,24 @@ const useCalculatedAccountSummary = (platformAccountSummary, platformOpenOrders,
3348
3380
  };
3349
3381
 
3350
3382
  /**
3351
- * Hook to access trade histories
3383
+ * Hook to access trade histories with loading state
3352
3384
  */
3353
3385
  const useTradeHistories = () => {
3354
3386
  const context = useContext(PearHyperliquidContext);
3355
3387
  if (!context) {
3356
3388
  throw new Error('useTradeHistories must be used within a PearHyperliquidProvider');
3357
3389
  }
3358
- return context.data.tradeHistories;
3390
+ const isLoading = useMemo(() => {
3391
+ // Loading is true initially and becomes false once we get the first data
3392
+ return context.data.tradeHistories === null && context.isConnected;
3393
+ }, [context.data.tradeHistories, context.isConnected]);
3394
+ return {
3395
+ data: context.data.tradeHistories,
3396
+ isLoading
3397
+ };
3359
3398
  };
3360
3399
  /**
3361
- * Hook to access open positions with real-time calculations
3400
+ * Hook to access open positions with real-time calculations and loading state
3362
3401
  */
3363
3402
  const useOpenPositions = () => {
3364
3403
  const context = useContext(PearHyperliquidContext);
@@ -3367,20 +3406,38 @@ const useOpenPositions = () => {
3367
3406
  }
3368
3407
  // Use calculated positions that sync platform data with HyperLiquid real-time data
3369
3408
  const calculatedPositions = useCalculatedOpenPositions(context.data.openPositions, context.webData2, context.allMids);
3370
- return calculatedPositions;
3409
+ const isLoading = useMemo(() => {
3410
+ // Loading is true initially and becomes false only when:
3411
+ // 1. WebSocket has open-positions data
3412
+ // 2. webData2 and allMids are ready (required for calculations)
3413
+ // 3. Connection is established
3414
+ return (context.isConnected &&
3415
+ (context.data.openPositions === null || !context.webData2 || !context.allMids));
3416
+ }, [context.data.openPositions, context.webData2, context.allMids, context.isConnected]);
3417
+ return {
3418
+ data: calculatedPositions,
3419
+ isLoading
3420
+ };
3371
3421
  };
3372
3422
  /**
3373
- * Hook to access open orders
3423
+ * Hook to access open orders with loading state
3374
3424
  */
3375
3425
  const useOpenOrders = () => {
3376
3426
  const context = useContext(PearHyperliquidContext);
3377
3427
  if (!context) {
3378
3428
  throw new Error('useOpenOrders must be used within a PearHyperliquidProvider');
3379
3429
  }
3380
- return context.data.openOrders;
3430
+ const isLoading = useMemo(() => {
3431
+ // Loading is true initially and becomes false once we get the first data
3432
+ return context.data.openOrders === null && context.isConnected;
3433
+ }, [context.data.openOrders, context.isConnected]);
3434
+ return {
3435
+ data: context.data.openOrders,
3436
+ isLoading
3437
+ };
3381
3438
  };
3382
3439
  /**
3383
- * Hook to access account summary with real-time calculations
3440
+ * Hook to access account summary with real-time calculations and loading state
3384
3441
  */
3385
3442
  const useAccountSummary = () => {
3386
3443
  var _a, _b, _c, _d;
@@ -3390,8 +3447,462 @@ const useAccountSummary = () => {
3390
3447
  }
3391
3448
  // Use calculated account summary that syncs platform data with HyperLiquid real-time data
3392
3449
  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);
3393
- return calculatedAccountSummary;
3450
+ const isLoading = useMemo(() => {
3451
+ // Loading is true initially and becomes false once we get the first data
3452
+ return context.data.accountSummary === null && context.isConnected;
3453
+ }, [context.data.accountSummary, context.isConnected]);
3454
+ return {
3455
+ data: calculatedAccountSummary,
3456
+ isLoading
3457
+ };
3458
+ };
3459
+
3460
+ /**
3461
+ * Extracts token metadata from WebData2 and AllMids data
3462
+ */
3463
+ class TokenMetadataExtractor {
3464
+ /**
3465
+ * Extracts comprehensive token metadata
3466
+ * @param symbol - Token symbol
3467
+ * @param webData2 - WebData2 response containing asset context and universe data
3468
+ * @param allMids - AllMids data containing current prices
3469
+ * @returns TokenMetadata or null if token not found
3470
+ */
3471
+ static extractTokenMetadata(symbol, webData2, allMids) {
3472
+ if (!webData2 || !allMids) {
3473
+ return null;
3474
+ }
3475
+ // Find token index in universe
3476
+ const universeIndex = webData2.meta.universe.findIndex(asset => asset.name === symbol);
3477
+ if (universeIndex === -1) {
3478
+ return null;
3479
+ }
3480
+ const universeAsset = webData2.meta.universe[universeIndex];
3481
+ const assetCtx = webData2.assetCtxs[universeIndex];
3482
+ if (!assetCtx) {
3483
+ return null;
3484
+ }
3485
+ // Get current price from allMids
3486
+ const currentPriceStr = allMids.mids[symbol];
3487
+ const currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
3488
+ // Get previous day price
3489
+ const prevDayPrice = parseFloat(assetCtx.prevDayPx);
3490
+ // Calculate 24h price change
3491
+ const priceChange24h = currentPrice - prevDayPrice;
3492
+ const priceChange24hPercent = prevDayPrice !== 0 ? (priceChange24h / prevDayPrice) * 100 : 0;
3493
+ // Parse other metadata
3494
+ const netFunding = parseFloat(assetCtx.funding);
3495
+ const markPrice = parseFloat(assetCtx.markPx);
3496
+ const oraclePrice = parseFloat(assetCtx.oraclePx);
3497
+ return {
3498
+ currentPrice,
3499
+ prevDayPrice,
3500
+ priceChange24h,
3501
+ priceChange24hPercent,
3502
+ netFunding,
3503
+ maxLeverage: universeAsset.maxLeverage,
3504
+ markPrice,
3505
+ oraclePrice,
3506
+ openInterest: assetCtx.openInterest,
3507
+ dayVolume: assetCtx.dayNtlVlm,
3508
+ };
3509
+ }
3510
+ /**
3511
+ * Extracts metadata for multiple tokens
3512
+ * @param symbols - Array of token symbols
3513
+ * @param webData2 - WebData2 response
3514
+ * @param allMids - AllMids data
3515
+ * @returns Record of symbol to TokenMetadata
3516
+ */
3517
+ static extractMultipleTokensMetadata(symbols, webData2, allMids) {
3518
+ const result = {};
3519
+ for (const symbol of symbols) {
3520
+ result[symbol] = this.extractTokenMetadata(symbol, webData2, allMids);
3521
+ }
3522
+ return result;
3523
+ }
3524
+ /**
3525
+ * Checks if token data is available in WebData2
3526
+ * @param symbol - Token symbol
3527
+ * @param webData2 - WebData2 response
3528
+ * @returns boolean indicating if token exists in universe
3529
+ */
3530
+ static isTokenAvailable(symbol, webData2) {
3531
+ if (!webData2)
3532
+ return false;
3533
+ return webData2.meta.universe.some(asset => asset.name === symbol);
3534
+ }
3535
+ }
3536
+
3537
+ /**
3538
+ * Hook to access token selection state using provider's WebSocket data
3539
+ */
3540
+ const useTokenSelection = () => {
3541
+ var _a;
3542
+ const context = useContext(PearHyperliquidContext);
3543
+ if (!context) {
3544
+ throw new Error('useTokenSelection must be used within PearHyperliquidProvider');
3545
+ }
3546
+ const { webData2, allMids, tokenSelection, setTokenSelection } = context;
3547
+ // Loading states
3548
+ const isPriceDataReady = useMemo(() => {
3549
+ return !!(webData2 && allMids);
3550
+ }, [webData2, allMids]);
3551
+ const isLoading = useMemo(() => {
3552
+ // Check if any tokens have missing metadata when price data is available
3553
+ if (!isPriceDataReady)
3554
+ return true;
3555
+ const allTokens = [...tokenSelection.longTokens, ...tokenSelection.shortTokens];
3556
+ if (allTokens.length === 0)
3557
+ return false;
3558
+ // Loading if any token is missing metadata
3559
+ return allTokens.some(token => !token.metadata);
3560
+ }, [isPriceDataReady, tokenSelection.longTokens, tokenSelection.shortTokens]);
3561
+ // Auto-update token metadata when WebSocket data changes
3562
+ useEffect(() => {
3563
+ if (!webData2 || !allMids)
3564
+ return;
3565
+ const allTokenSymbols = [...tokenSelection.longTokens, ...tokenSelection.shortTokens].map(t => t.symbol);
3566
+ if (allTokenSymbols.length === 0)
3567
+ return;
3568
+ const metadataMap = TokenMetadataExtractor.extractMultipleTokensMetadata(allTokenSymbols, webData2, allMids);
3569
+ setTokenSelection(prev => ({
3570
+ ...prev,
3571
+ longTokens: prev.longTokens.map(token => ({
3572
+ ...token,
3573
+ metadata: metadataMap[token.symbol] || undefined
3574
+ })),
3575
+ shortTokens: prev.shortTokens.map(token => ({
3576
+ ...token,
3577
+ metadata: metadataMap[token.symbol] || undefined
3578
+ }))
3579
+ }));
3580
+ }, [webData2, allMids, setTokenSelection]);
3581
+ // Calculate weighted ratio: LONG^WEIGHT * SHORT^-WEIGHT
3582
+ const weightedRatio = useMemo(() => {
3583
+ let longProduct = 1;
3584
+ let shortProduct = 1;
3585
+ // Calculate long side product: PRICE^(WEIGHT/100)
3586
+ tokenSelection.longTokens.forEach(token => {
3587
+ var _a;
3588
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.currentPrice) && token.weight > 0) {
3589
+ const weightFactor = token.weight / 100;
3590
+ longProduct *= Math.pow(token.metadata.currentPrice, weightFactor);
3591
+ }
3592
+ });
3593
+ // Calculate short side product: PRICE^-(WEIGHT/100)
3594
+ tokenSelection.shortTokens.forEach(token => {
3595
+ var _a;
3596
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.currentPrice) && token.weight > 0) {
3597
+ const weightFactor = token.weight / 100;
3598
+ shortProduct *= Math.pow(token.metadata.currentPrice, -weightFactor);
3599
+ }
3600
+ });
3601
+ return longProduct * shortProduct;
3602
+ }, [tokenSelection.longTokens, tokenSelection.shortTokens]);
3603
+ // Calculate 24h weighted ratio using previous day prices
3604
+ const weightedRatio24h = useMemo(() => {
3605
+ let longProduct = 1;
3606
+ let shortProduct = 1;
3607
+ // Calculate long side product: PREV_PRICE^(WEIGHT/100)
3608
+ tokenSelection.longTokens.forEach(token => {
3609
+ var _a;
3610
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.prevDayPrice) && token.weight > 0) {
3611
+ const weightFactor = token.weight / 100;
3612
+ longProduct *= Math.pow(token.metadata.prevDayPrice, weightFactor);
3613
+ }
3614
+ });
3615
+ // Calculate short side product: PREV_PRICE^-(WEIGHT/100)
3616
+ tokenSelection.shortTokens.forEach(token => {
3617
+ var _a;
3618
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.prevDayPrice) && token.weight > 0) {
3619
+ const weightFactor = token.weight / 100;
3620
+ shortProduct *= Math.pow(token.metadata.prevDayPrice, -weightFactor);
3621
+ }
3622
+ });
3623
+ return longProduct * shortProduct;
3624
+ }, [tokenSelection.longTokens, tokenSelection.shortTokens]);
3625
+ // Calculate sum of weighted net funding
3626
+ const sumNetFunding = useMemo(() => {
3627
+ let totalFunding = 0;
3628
+ // Add long funding weighted by allocation
3629
+ tokenSelection.longTokens.forEach(token => {
3630
+ var _a;
3631
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.netFunding) && token.weight > 0) {
3632
+ const weightFactor = token.weight / 100;
3633
+ totalFunding += token.metadata.netFunding * weightFactor;
3634
+ }
3635
+ });
3636
+ // Subtract short funding weighted by allocation (since we're short)
3637
+ tokenSelection.shortTokens.forEach(token => {
3638
+ var _a;
3639
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.netFunding) && token.weight > 0) {
3640
+ const weightFactor = token.weight / 100;
3641
+ totalFunding -= token.metadata.netFunding * weightFactor;
3642
+ }
3643
+ });
3644
+ return totalFunding;
3645
+ }, [tokenSelection.longTokens, tokenSelection.shortTokens]);
3646
+ // Calculate max leverage (minimum of all token maxLeverages)
3647
+ const maxLeverage = useMemo(() => {
3648
+ var _a;
3649
+ if (!((_a = webData2 === null || webData2 === void 0 ? void 0 : webData2.meta) === null || _a === void 0 ? void 0 : _a.universe))
3650
+ return 0;
3651
+ const allTokenSymbols = [...tokenSelection.longTokens, ...tokenSelection.shortTokens].map(t => t.symbol);
3652
+ if (allTokenSymbols.length === 0)
3653
+ return 0;
3654
+ let minLeverage = Infinity;
3655
+ allTokenSymbols.forEach(symbol => {
3656
+ const tokenUniverse = webData2.meta.universe.find(u => u.name === symbol);
3657
+ if (tokenUniverse === null || tokenUniverse === void 0 ? void 0 : tokenUniverse.maxLeverage) {
3658
+ minLeverage = Math.min(minLeverage, tokenUniverse.maxLeverage);
3659
+ }
3660
+ });
3661
+ return minLeverage === Infinity ? 0 : minLeverage;
3662
+ }, [(_a = webData2 === null || webData2 === void 0 ? void 0 : webData2.meta) === null || _a === void 0 ? void 0 : _a.universe, tokenSelection.longTokens, tokenSelection.shortTokens]);
3663
+ // Calculate minimum margin (10 * total number of tokens)
3664
+ const minMargin = useMemo(() => {
3665
+ const totalTokenCount = tokenSelection.longTokens.length + tokenSelection.shortTokens.length;
3666
+ return 10 * totalTokenCount;
3667
+ }, [tokenSelection.longTokens.length, tokenSelection.shortTokens.length]);
3668
+ // Actions
3669
+ const setLongTokens = useCallback((tokens) => {
3670
+ setTokenSelection(prev => ({ ...prev, longTokens: tokens }));
3671
+ }, [setTokenSelection]);
3672
+ const setShortTokens = useCallback((tokens) => {
3673
+ setTokenSelection(prev => ({ ...prev, shortTokens: tokens }));
3674
+ }, [setTokenSelection]);
3675
+ const updateTokenWeight = useCallback((isLong, index, newWeight) => {
3676
+ const clampedWeight = Math.max(1, Math.min(100, newWeight));
3677
+ setTokenSelection(prev => {
3678
+ var _a, _b;
3679
+ const currentLongTotal = prev.longTokens.reduce((sum, token) => sum + token.weight, 0);
3680
+ const currentShortTotal = prev.shortTokens.reduce((sum, token) => sum + token.weight, 0);
3681
+ if (isLong) {
3682
+ const oldWeight = ((_a = prev.longTokens[index]) === null || _a === void 0 ? void 0 : _a.weight) || 0;
3683
+ const weightDiff = clampedWeight - oldWeight;
3684
+ const newLongTotal = currentLongTotal + weightDiff;
3685
+ if (newLongTotal + currentShortTotal > 100) {
3686
+ const maxAllowedWeight = Math.max(1, 100 - currentShortTotal - (currentLongTotal - oldWeight));
3687
+ const updated = [...prev.longTokens];
3688
+ updated[index] = { ...updated[index], weight: maxAllowedWeight };
3689
+ return { ...prev, longTokens: updated };
3690
+ }
3691
+ else {
3692
+ const updated = [...prev.longTokens];
3693
+ updated[index] = { ...updated[index], weight: clampedWeight };
3694
+ return { ...prev, longTokens: updated };
3695
+ }
3696
+ }
3697
+ else {
3698
+ const oldWeight = ((_b = prev.shortTokens[index]) === null || _b === void 0 ? void 0 : _b.weight) || 0;
3699
+ const weightDiff = clampedWeight - oldWeight;
3700
+ const newShortTotal = currentShortTotal + weightDiff;
3701
+ if (currentLongTotal + newShortTotal > 100) {
3702
+ const maxAllowedWeight = Math.max(1, 100 - currentLongTotal - (currentShortTotal - oldWeight));
3703
+ const updated = [...prev.shortTokens];
3704
+ updated[index] = { ...updated[index], weight: maxAllowedWeight };
3705
+ return { ...prev, shortTokens: updated };
3706
+ }
3707
+ else {
3708
+ const updated = [...prev.shortTokens];
3709
+ updated[index] = { ...updated[index], weight: clampedWeight };
3710
+ return { ...prev, shortTokens: updated };
3711
+ }
3712
+ }
3713
+ });
3714
+ }, [setTokenSelection]);
3715
+ const addToken = useCallback((isLong) => {
3716
+ const currentTokens = isLong ? tokenSelection.longTokens : tokenSelection.shortTokens;
3717
+ const newIndex = currentTokens.length;
3718
+ setTokenSelection(prev => ({
3719
+ ...prev,
3720
+ selectorConfig: { isLong, index: newIndex },
3721
+ openTokenSelector: true,
3722
+ }));
3723
+ }, [tokenSelection.longTokens, tokenSelection.shortTokens, setTokenSelection]);
3724
+ const removeToken = useCallback((isLong, index) => {
3725
+ setTokenSelection(prev => {
3726
+ if (isLong) {
3727
+ const updated = prev.longTokens.filter((_, i) => i !== index);
3728
+ return { ...prev, longTokens: updated };
3729
+ }
3730
+ else {
3731
+ const updated = prev.shortTokens.filter((_, i) => i !== index);
3732
+ return { ...prev, shortTokens: updated };
3733
+ }
3734
+ });
3735
+ }, [setTokenSelection]);
3736
+ const setOpenTokenSelector = useCallback((open) => {
3737
+ setTokenSelection(prev => ({ ...prev, openTokenSelector: open }));
3738
+ }, [setTokenSelection]);
3739
+ const setSelectorConfig = useCallback((config) => {
3740
+ setTokenSelection(prev => ({ ...prev, selectorConfig: config }));
3741
+ }, [setTokenSelection]);
3742
+ const setOpenConflictModal = useCallback((open) => {
3743
+ setTokenSelection(prev => ({ ...prev, openConflictModal: open }));
3744
+ }, [setTokenSelection]);
3745
+ const setConflicts = useCallback((conflicts) => {
3746
+ setTokenSelection(prev => ({ ...prev, conflicts }));
3747
+ }, [setTokenSelection]);
3748
+ const resetToDefaults = useCallback(() => {
3749
+ setTokenSelection(prev => ({
3750
+ ...prev,
3751
+ longTokens: [
3752
+ { symbol: 'HYPE', weight: 25 },
3753
+ { symbol: 'BTC', weight: 25 }
3754
+ ],
3755
+ shortTokens: [
3756
+ { symbol: 'AVAX', weight: 10 },
3757
+ { symbol: 'SEI', weight: 10 },
3758
+ { symbol: 'ADA', weight: 10 },
3759
+ { symbol: 'TRUMP', weight: 10 },
3760
+ { symbol: 'SUI', weight: 10 }
3761
+ ],
3762
+ openTokenSelector: false,
3763
+ selectorConfig: null,
3764
+ openConflictModal: false,
3765
+ }));
3766
+ }, [setTokenSelection]);
3767
+ const handleTokenSelect = useCallback((selectedToken) => {
3768
+ if (!tokenSelection.selectorConfig)
3769
+ return;
3770
+ const { isLong, index } = tokenSelection.selectorConfig;
3771
+ const existingTokens = isLong ? tokenSelection.longTokens : tokenSelection.shortTokens;
3772
+ // Check if token already exists
3773
+ if (existingTokens.some(t => t.symbol === selectedToken))
3774
+ return;
3775
+ // Get metadata for new token
3776
+ const metadata = TokenMetadataExtractor.extractTokenMetadata(selectedToken, webData2, allMids);
3777
+ setTokenSelection(prev => {
3778
+ if (index >= existingTokens.length) {
3779
+ // Adding new token - calculate safe weight
3780
+ const currentLongTotal = prev.longTokens.reduce((sum, token) => sum + token.weight, 0);
3781
+ const currentShortTotal = prev.shortTokens.reduce((sum, token) => sum + token.weight, 0);
3782
+ const currentTotal = currentLongTotal + currentShortTotal;
3783
+ const maxAvailableWeight = Math.max(1, 100 - currentTotal);
3784
+ const safeWeight = Math.min(20, maxAvailableWeight);
3785
+ const newToken = {
3786
+ symbol: selectedToken,
3787
+ weight: safeWeight,
3788
+ metadata: metadata || undefined
3789
+ };
3790
+ if (isLong) {
3791
+ return { ...prev, longTokens: [...prev.longTokens, newToken] };
3792
+ }
3793
+ else {
3794
+ return { ...prev, shortTokens: [...prev.shortTokens, newToken] };
3795
+ }
3796
+ }
3797
+ else {
3798
+ // Replacing existing token
3799
+ if (isLong) {
3800
+ const updated = [...prev.longTokens];
3801
+ updated[index] = {
3802
+ ...updated[index],
3803
+ symbol: selectedToken,
3804
+ metadata: metadata || undefined
3805
+ };
3806
+ return { ...prev, longTokens: updated };
3807
+ }
3808
+ else {
3809
+ const updated = [...prev.shortTokens];
3810
+ updated[index] = {
3811
+ ...updated[index],
3812
+ symbol: selectedToken,
3813
+ metadata: metadata || undefined
3814
+ };
3815
+ return { ...prev, shortTokens: updated };
3816
+ }
3817
+ }
3818
+ });
3819
+ }, [tokenSelection.selectorConfig, tokenSelection.longTokens, tokenSelection.shortTokens, webData2, allMids, setTokenSelection]);
3820
+ // updateTokenMetadata is now handled automatically by the useEffect above
3821
+ return {
3822
+ // State
3823
+ longTokens: tokenSelection.longTokens,
3824
+ shortTokens: tokenSelection.shortTokens,
3825
+ openTokenSelector: tokenSelection.openTokenSelector,
3826
+ selectorConfig: tokenSelection.selectorConfig,
3827
+ openConflictModal: tokenSelection.openConflictModal,
3828
+ conflicts: tokenSelection.conflicts,
3829
+ // Loading states
3830
+ isLoading,
3831
+ isPriceDataReady,
3832
+ // Calculated values
3833
+ weightedRatio,
3834
+ weightedRatio24h,
3835
+ sumNetFunding,
3836
+ maxLeverage,
3837
+ minMargin,
3838
+ // Actions
3839
+ setLongTokens,
3840
+ setShortTokens,
3841
+ updateTokenWeight,
3842
+ addToken,
3843
+ removeToken,
3844
+ handleTokenSelect,
3845
+ setOpenTokenSelector,
3846
+ setSelectorConfig,
3847
+ setOpenConflictModal,
3848
+ setConflicts,
3849
+ resetToDefaults,
3850
+ };
3394
3851
  };
3395
3852
 
3396
- export { AccountSummaryCalculator, PearHyperliquidClient, PearHyperliquidProvider, PearMigrationSDK, PearHyperliquidClient as default, useAccountSummary, useAddress, useCalculatedAccountSummary, useCalculatedOpenPositions, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMigrationSDK, useOpenOrders, useOpenPositions, usePearHyperliquidClient, useTradeHistories };
3853
+ /**
3854
+ * Detects conflicts between selected tokens and existing positions
3855
+ */
3856
+ class ConflictDetector {
3857
+ /**
3858
+ * Detects conflicts between token selections and open positions
3859
+ * @param longTokens - Selected long tokens
3860
+ * @param shortTokens - Selected short tokens
3861
+ * @param openPositions - Current open positions from API
3862
+ * @returns Array of detected conflicts
3863
+ */
3864
+ static detectConflicts(longTokens, shortTokens, openPositions) {
3865
+ const detectedConflicts = [];
3866
+ if (!openPositions) {
3867
+ return detectedConflicts;
3868
+ }
3869
+ // Check long tokens against existing short positions
3870
+ longTokens.forEach((token) => {
3871
+ const existingShortPosition = openPositions.find((pos) => {
3872
+ var _a;
3873
+ // Check multiple possible property names and side values
3874
+ const symbol = pos.coin || pos.symbol || pos.asset;
3875
+ const side = (_a = pos.side) === null || _a === void 0 ? void 0 : _a.toLowerCase();
3876
+ return symbol === token.symbol && (side === 'short' || side === 'sell');
3877
+ });
3878
+ if (existingShortPosition) {
3879
+ detectedConflicts.push({
3880
+ symbol: token.symbol,
3881
+ conflictType: 'short',
3882
+ conflictMessage: `${token.symbol} Long conflicts with existing ${token.symbol} Short position`
3883
+ });
3884
+ }
3885
+ });
3886
+ // Check short tokens against existing long positions
3887
+ shortTokens.forEach((token) => {
3888
+ const existingLongPosition = openPositions.find((pos) => {
3889
+ var _a;
3890
+ // Check multiple possible property names and side values
3891
+ const symbol = pos.coin || pos.symbol || pos.asset;
3892
+ const side = (_a = pos.side) === null || _a === void 0 ? void 0 : _a.toLowerCase();
3893
+ return symbol === token.symbol && (side === 'long' || side === 'buy');
3894
+ });
3895
+ if (existingLongPosition) {
3896
+ detectedConflicts.push({
3897
+ symbol: token.symbol,
3898
+ conflictType: 'long',
3899
+ conflictMessage: `${token.symbol} Short conflicts with existing ${token.symbol} Long position`
3900
+ });
3901
+ }
3902
+ });
3903
+ return detectedConflicts;
3904
+ }
3905
+ }
3906
+
3907
+ export { AccountSummaryCalculator, ConflictDetector, PearHyperliquidClient, PearHyperliquidProvider, PearMigrationSDK, TokenMetadataExtractor, PearHyperliquidClient as default, useAccountSummary, useAddress, useCalculatedAccountSummary, useCalculatedOpenPositions, useHyperliquidNativeWebSocket, useHyperliquidWebSocket, useMigrationSDK, useOpenOrders, useOpenPositions, usePearHyperliquidClient, useTokenSelection, useTradeHistories };
3397
3908
  //# sourceMappingURL=index.esm.js.map