@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.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,
@@ -2863,6 +2881,23 @@ const useHyperliquidNativeWebSocket = ({ address }) => {
2863
2881
  };
2864
2882
  };
2865
2883
 
2884
+ const DEFAULT_TOKEN_SELECTION_STATE = {
2885
+ longTokens: [
2886
+ { symbol: 'HYPE', weight: 25 },
2887
+ { symbol: 'BTC', weight: 25 }
2888
+ ],
2889
+ shortTokens: [
2890
+ { symbol: 'AVAX', weight: 10 },
2891
+ { symbol: 'SEI', weight: 10 },
2892
+ { symbol: 'ADA', weight: 10 },
2893
+ { symbol: 'TRUMP', weight: 10 },
2894
+ { symbol: 'SUI', weight: 10 }
2895
+ ],
2896
+ openTokenSelector: false,
2897
+ selectorConfig: null,
2898
+ openConflictModal: false,
2899
+ conflicts: [],
2900
+ };
2866
2901
  const PearHyperliquidContext = require$$0.createContext(undefined);
2867
2902
  /**
2868
2903
  * React Provider for PearHyperliquidClient
@@ -2872,6 +2907,8 @@ const PearHyperliquidProvider = ({ config, wsUrl = 'wss://hl-v2.pearprotocol.io/
2872
2907
  const migrationSDK = require$$0.useMemo(() => new PearMigrationSDK(client), [client]);
2873
2908
  // Address state
2874
2909
  const [address, setAddress] = require$$0.useState(null);
2910
+ // Token selection state
2911
+ const [tokenSelection, setTokenSelection] = require$$0.useState(DEFAULT_TOKEN_SELECTION_STATE);
2875
2912
  // WebSocket connection and data (Pear API)
2876
2913
  const { data, connectionStatus, isConnected, lastError } = useHyperliquidWebSocket({
2877
2914
  wsUrl,
@@ -2901,7 +2938,10 @@ const PearHyperliquidProvider = ({ config, wsUrl = 'wss://hl-v2.pearprotocol.io/
2901
2938
  // HyperLiquid native WebSocket data
2902
2939
  webData2,
2903
2940
  allMids,
2904
- }), [client, migrationSDK, address, connectionStatus, isConnected, data, lastError, nativeConnectionStatus, nativeIsConnected, nativeLastError, webData2, allMids]);
2941
+ // Token selection state
2942
+ tokenSelection,
2943
+ setTokenSelection,
2944
+ }), [client, migrationSDK, address, connectionStatus, isConnected, data, lastError, nativeConnectionStatus, nativeIsConnected, nativeLastError, webData2, allMids, tokenSelection, setTokenSelection]);
2905
2945
  return (jsxRuntimeExports.jsx(PearHyperliquidContext.Provider, { value: contextValue, children: children }));
2906
2946
  };
2907
2947
  /**
@@ -3184,32 +3224,24 @@ class PositionProcessor {
3184
3224
  };
3185
3225
  }
3186
3226
  calculateEntryRatio(syncResults) {
3227
+ var _a, _b;
3187
3228
  const longResults = syncResults.filter(result => result.asset.side === PositionSide.LONG);
3188
3229
  const shortResults = syncResults.filter(result => result.asset.side === PositionSide.SHORT);
3189
3230
  if (longResults.length === 0 || shortResults.length === 0)
3190
3231
  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;
3232
+ const longEntryPrice = ((_a = longResults[0]) === null || _a === void 0 ? void 0 : _a.asset.entryPrice) ? Number(longResults[0].asset.entryPrice) : 0;
3233
+ const shortEntryPrice = ((_b = shortResults[0]) === null || _b === void 0 ? void 0 : _b.asset.entryPrice) ? Number(shortResults[0].asset.entryPrice) : 0;
3234
+ return shortEntryPrice > 0 ? longEntryPrice / shortEntryPrice : 0;
3198
3235
  }
3199
3236
  calculateMarkRatio(syncResults) {
3237
+ var _a, _b;
3200
3238
  const longResults = syncResults.filter(result => result.asset.side === PositionSide.LONG);
3201
3239
  const shortResults = syncResults.filter(result => result.asset.side === PositionSide.SHORT);
3202
3240
  if (longResults.length === 0 || shortResults.length === 0)
3203
3241
  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;
3242
+ const longMarkPrice = ((_a = longResults[0]) === null || _a === void 0 ? void 0 : _a.asset.coin) ? this.getMarketPrice(longResults[0].asset.coin) : 0;
3243
+ const shortMarkPrice = ((_b = shortResults[0]) === null || _b === void 0 ? void 0 : _b.asset.coin) ? this.getMarketPrice(shortResults[0].asset.coin) : 0;
3244
+ return shortMarkPrice > 0 ? longMarkPrice / shortMarkPrice : 0;
3213
3245
  }
3214
3246
  calculateNetFundingFromSyncResults(syncResults) {
3215
3247
  const netFunding = syncResults.reduce((sum, result) => {
@@ -3352,17 +3384,24 @@ const useCalculatedAccountSummary = (platformAccountSummary, platformOpenOrders,
3352
3384
  };
3353
3385
 
3354
3386
  /**
3355
- * Hook to access trade histories
3387
+ * Hook to access trade histories with loading state
3356
3388
  */
3357
3389
  const useTradeHistories = () => {
3358
3390
  const context = require$$0.useContext(PearHyperliquidContext);
3359
3391
  if (!context) {
3360
3392
  throw new Error('useTradeHistories must be used within a PearHyperliquidProvider');
3361
3393
  }
3362
- return context.data.tradeHistories;
3394
+ const isLoading = require$$0.useMemo(() => {
3395
+ // Loading is true initially and becomes false once we get the first data
3396
+ return context.data.tradeHistories === null && context.isConnected;
3397
+ }, [context.data.tradeHistories, context.isConnected]);
3398
+ return {
3399
+ data: context.data.tradeHistories,
3400
+ isLoading
3401
+ };
3363
3402
  };
3364
3403
  /**
3365
- * Hook to access open positions with real-time calculations
3404
+ * Hook to access open positions with real-time calculations and loading state
3366
3405
  */
3367
3406
  const useOpenPositions = () => {
3368
3407
  const context = require$$0.useContext(PearHyperliquidContext);
@@ -3371,20 +3410,38 @@ const useOpenPositions = () => {
3371
3410
  }
3372
3411
  // Use calculated positions that sync platform data with HyperLiquid real-time data
3373
3412
  const calculatedPositions = useCalculatedOpenPositions(context.data.openPositions, context.webData2, context.allMids);
3374
- return calculatedPositions;
3413
+ const isLoading = require$$0.useMemo(() => {
3414
+ // Loading is true initially and becomes false only when:
3415
+ // 1. WebSocket has open-positions data
3416
+ // 2. webData2 and allMids are ready (required for calculations)
3417
+ // 3. Connection is established
3418
+ return (context.isConnected &&
3419
+ (context.data.openPositions === null || !context.webData2 || !context.allMids));
3420
+ }, [context.data.openPositions, context.webData2, context.allMids, context.isConnected]);
3421
+ return {
3422
+ data: calculatedPositions,
3423
+ isLoading
3424
+ };
3375
3425
  };
3376
3426
  /**
3377
- * Hook to access open orders
3427
+ * Hook to access open orders with loading state
3378
3428
  */
3379
3429
  const useOpenOrders = () => {
3380
3430
  const context = require$$0.useContext(PearHyperliquidContext);
3381
3431
  if (!context) {
3382
3432
  throw new Error('useOpenOrders must be used within a PearHyperliquidProvider');
3383
3433
  }
3384
- return context.data.openOrders;
3434
+ const isLoading = require$$0.useMemo(() => {
3435
+ // Loading is true initially and becomes false once we get the first data
3436
+ return context.data.openOrders === null && context.isConnected;
3437
+ }, [context.data.openOrders, context.isConnected]);
3438
+ return {
3439
+ data: context.data.openOrders,
3440
+ isLoading
3441
+ };
3385
3442
  };
3386
3443
  /**
3387
- * Hook to access account summary with real-time calculations
3444
+ * Hook to access account summary with real-time calculations and loading state
3388
3445
  */
3389
3446
  const useAccountSummary = () => {
3390
3447
  var _a, _b, _c, _d;
@@ -3394,13 +3451,469 @@ const useAccountSummary = () => {
3394
3451
  }
3395
3452
  // Use calculated account summary that syncs platform data with HyperLiquid real-time data
3396
3453
  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;
3454
+ const isLoading = require$$0.useMemo(() => {
3455
+ // Loading is true initially and becomes false once we get the first data
3456
+ return context.data.accountSummary === null && context.isConnected;
3457
+ }, [context.data.accountSummary, context.isConnected]);
3458
+ return {
3459
+ data: calculatedAccountSummary,
3460
+ isLoading
3461
+ };
3462
+ };
3463
+
3464
+ /**
3465
+ * Extracts token metadata from WebData2 and AllMids data
3466
+ */
3467
+ class TokenMetadataExtractor {
3468
+ /**
3469
+ * Extracts comprehensive token metadata
3470
+ * @param symbol - Token symbol
3471
+ * @param webData2 - WebData2 response containing asset context and universe data
3472
+ * @param allMids - AllMids data containing current prices
3473
+ * @returns TokenMetadata or null if token not found
3474
+ */
3475
+ static extractTokenMetadata(symbol, webData2, allMids) {
3476
+ if (!webData2 || !allMids) {
3477
+ return null;
3478
+ }
3479
+ // Find token index in universe
3480
+ const universeIndex = webData2.meta.universe.findIndex(asset => asset.name === symbol);
3481
+ if (universeIndex === -1) {
3482
+ return null;
3483
+ }
3484
+ const universeAsset = webData2.meta.universe[universeIndex];
3485
+ const assetCtx = webData2.assetCtxs[universeIndex];
3486
+ if (!assetCtx) {
3487
+ return null;
3488
+ }
3489
+ // Get current price from allMids
3490
+ const currentPriceStr = allMids.mids[symbol];
3491
+ const currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
3492
+ // Get previous day price
3493
+ const prevDayPrice = parseFloat(assetCtx.prevDayPx);
3494
+ // Calculate 24h price change
3495
+ const priceChange24h = currentPrice - prevDayPrice;
3496
+ const priceChange24hPercent = prevDayPrice !== 0 ? (priceChange24h / prevDayPrice) * 100 : 0;
3497
+ // Parse other metadata
3498
+ const netFunding = parseFloat(assetCtx.funding);
3499
+ const markPrice = parseFloat(assetCtx.markPx);
3500
+ const oraclePrice = parseFloat(assetCtx.oraclePx);
3501
+ return {
3502
+ currentPrice,
3503
+ prevDayPrice,
3504
+ priceChange24h,
3505
+ priceChange24hPercent,
3506
+ netFunding,
3507
+ maxLeverage: universeAsset.maxLeverage,
3508
+ markPrice,
3509
+ oraclePrice,
3510
+ openInterest: assetCtx.openInterest,
3511
+ dayVolume: assetCtx.dayNtlVlm,
3512
+ };
3513
+ }
3514
+ /**
3515
+ * Extracts metadata for multiple tokens
3516
+ * @param symbols - Array of token symbols
3517
+ * @param webData2 - WebData2 response
3518
+ * @param allMids - AllMids data
3519
+ * @returns Record of symbol to TokenMetadata
3520
+ */
3521
+ static extractMultipleTokensMetadata(symbols, webData2, allMids) {
3522
+ const result = {};
3523
+ for (const symbol of symbols) {
3524
+ result[symbol] = this.extractTokenMetadata(symbol, webData2, allMids);
3525
+ }
3526
+ return result;
3527
+ }
3528
+ /**
3529
+ * Checks if token data is available in WebData2
3530
+ * @param symbol - Token symbol
3531
+ * @param webData2 - WebData2 response
3532
+ * @returns boolean indicating if token exists in universe
3533
+ */
3534
+ static isTokenAvailable(symbol, webData2) {
3535
+ if (!webData2)
3536
+ return false;
3537
+ return webData2.meta.universe.some(asset => asset.name === symbol);
3538
+ }
3539
+ }
3540
+
3541
+ /**
3542
+ * Hook to access token selection state using provider's WebSocket data
3543
+ */
3544
+ const useTokenSelection = () => {
3545
+ var _a;
3546
+ const context = require$$0.useContext(PearHyperliquidContext);
3547
+ if (!context) {
3548
+ throw new Error('useTokenSelection must be used within PearHyperliquidProvider');
3549
+ }
3550
+ const { webData2, allMids, tokenSelection, setTokenSelection } = context;
3551
+ // Loading states
3552
+ const isPriceDataReady = require$$0.useMemo(() => {
3553
+ return !!(webData2 && allMids);
3554
+ }, [webData2, allMids]);
3555
+ const isLoading = require$$0.useMemo(() => {
3556
+ // Check if any tokens have missing metadata when price data is available
3557
+ if (!isPriceDataReady)
3558
+ return true;
3559
+ const allTokens = [...tokenSelection.longTokens, ...tokenSelection.shortTokens];
3560
+ if (allTokens.length === 0)
3561
+ return false;
3562
+ // Loading if any token is missing metadata
3563
+ return allTokens.some(token => !token.metadata);
3564
+ }, [isPriceDataReady, tokenSelection.longTokens, tokenSelection.shortTokens]);
3565
+ // Auto-update token metadata when WebSocket data changes
3566
+ require$$0.useEffect(() => {
3567
+ if (!webData2 || !allMids)
3568
+ return;
3569
+ const allTokenSymbols = [...tokenSelection.longTokens, ...tokenSelection.shortTokens].map(t => t.symbol);
3570
+ if (allTokenSymbols.length === 0)
3571
+ return;
3572
+ const metadataMap = TokenMetadataExtractor.extractMultipleTokensMetadata(allTokenSymbols, webData2, allMids);
3573
+ setTokenSelection(prev => ({
3574
+ ...prev,
3575
+ longTokens: prev.longTokens.map(token => ({
3576
+ ...token,
3577
+ metadata: metadataMap[token.symbol] || undefined
3578
+ })),
3579
+ shortTokens: prev.shortTokens.map(token => ({
3580
+ ...token,
3581
+ metadata: metadataMap[token.symbol] || undefined
3582
+ }))
3583
+ }));
3584
+ }, [webData2, allMids, setTokenSelection]);
3585
+ // Calculate weighted ratio: LONG^WEIGHT * SHORT^-WEIGHT
3586
+ const weightedRatio = require$$0.useMemo(() => {
3587
+ let longProduct = 1;
3588
+ let shortProduct = 1;
3589
+ // Calculate long side product: PRICE^(WEIGHT/100)
3590
+ tokenSelection.longTokens.forEach(token => {
3591
+ var _a;
3592
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.currentPrice) && token.weight > 0) {
3593
+ const weightFactor = token.weight / 100;
3594
+ longProduct *= Math.pow(token.metadata.currentPrice, weightFactor);
3595
+ }
3596
+ });
3597
+ // Calculate short side product: PRICE^-(WEIGHT/100)
3598
+ tokenSelection.shortTokens.forEach(token => {
3599
+ var _a;
3600
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.currentPrice) && token.weight > 0) {
3601
+ const weightFactor = token.weight / 100;
3602
+ shortProduct *= Math.pow(token.metadata.currentPrice, -weightFactor);
3603
+ }
3604
+ });
3605
+ return longProduct * shortProduct;
3606
+ }, [tokenSelection.longTokens, tokenSelection.shortTokens]);
3607
+ // Calculate 24h weighted ratio using previous day prices
3608
+ const weightedRatio24h = require$$0.useMemo(() => {
3609
+ let longProduct = 1;
3610
+ let shortProduct = 1;
3611
+ // Calculate long side product: PREV_PRICE^(WEIGHT/100)
3612
+ tokenSelection.longTokens.forEach(token => {
3613
+ var _a;
3614
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.prevDayPrice) && token.weight > 0) {
3615
+ const weightFactor = token.weight / 100;
3616
+ longProduct *= Math.pow(token.metadata.prevDayPrice, weightFactor);
3617
+ }
3618
+ });
3619
+ // Calculate short side product: PREV_PRICE^-(WEIGHT/100)
3620
+ tokenSelection.shortTokens.forEach(token => {
3621
+ var _a;
3622
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.prevDayPrice) && token.weight > 0) {
3623
+ const weightFactor = token.weight / 100;
3624
+ shortProduct *= Math.pow(token.metadata.prevDayPrice, -weightFactor);
3625
+ }
3626
+ });
3627
+ return longProduct * shortProduct;
3628
+ }, [tokenSelection.longTokens, tokenSelection.shortTokens]);
3629
+ // Calculate sum of weighted net funding
3630
+ const sumNetFunding = require$$0.useMemo(() => {
3631
+ let totalFunding = 0;
3632
+ // Add long funding weighted by allocation
3633
+ tokenSelection.longTokens.forEach(token => {
3634
+ var _a;
3635
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.netFunding) && token.weight > 0) {
3636
+ const weightFactor = token.weight / 100;
3637
+ totalFunding += token.metadata.netFunding * weightFactor;
3638
+ }
3639
+ });
3640
+ // Subtract short funding weighted by allocation (since we're short)
3641
+ tokenSelection.shortTokens.forEach(token => {
3642
+ var _a;
3643
+ if (((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.netFunding) && token.weight > 0) {
3644
+ const weightFactor = token.weight / 100;
3645
+ totalFunding -= token.metadata.netFunding * weightFactor;
3646
+ }
3647
+ });
3648
+ return totalFunding;
3649
+ }, [tokenSelection.longTokens, tokenSelection.shortTokens]);
3650
+ // Calculate max leverage (minimum of all token maxLeverages)
3651
+ const maxLeverage = require$$0.useMemo(() => {
3652
+ var _a;
3653
+ if (!((_a = webData2 === null || webData2 === void 0 ? void 0 : webData2.meta) === null || _a === void 0 ? void 0 : _a.universe))
3654
+ return 0;
3655
+ const allTokenSymbols = [...tokenSelection.longTokens, ...tokenSelection.shortTokens].map(t => t.symbol);
3656
+ if (allTokenSymbols.length === 0)
3657
+ return 0;
3658
+ let minLeverage = Infinity;
3659
+ allTokenSymbols.forEach(symbol => {
3660
+ const tokenUniverse = webData2.meta.universe.find(u => u.name === symbol);
3661
+ if (tokenUniverse === null || tokenUniverse === void 0 ? void 0 : tokenUniverse.maxLeverage) {
3662
+ minLeverage = Math.min(minLeverage, tokenUniverse.maxLeverage);
3663
+ }
3664
+ });
3665
+ return minLeverage === Infinity ? 0 : minLeverage;
3666
+ }, [(_a = webData2 === null || webData2 === void 0 ? void 0 : webData2.meta) === null || _a === void 0 ? void 0 : _a.universe, tokenSelection.longTokens, tokenSelection.shortTokens]);
3667
+ // Calculate minimum margin (10 * total number of tokens)
3668
+ const minMargin = require$$0.useMemo(() => {
3669
+ const totalTokenCount = tokenSelection.longTokens.length + tokenSelection.shortTokens.length;
3670
+ return 10 * totalTokenCount;
3671
+ }, [tokenSelection.longTokens.length, tokenSelection.shortTokens.length]);
3672
+ // Actions
3673
+ const setLongTokens = require$$0.useCallback((tokens) => {
3674
+ setTokenSelection(prev => ({ ...prev, longTokens: tokens }));
3675
+ }, [setTokenSelection]);
3676
+ const setShortTokens = require$$0.useCallback((tokens) => {
3677
+ setTokenSelection(prev => ({ ...prev, shortTokens: tokens }));
3678
+ }, [setTokenSelection]);
3679
+ const updateTokenWeight = require$$0.useCallback((isLong, index, newWeight) => {
3680
+ const clampedWeight = Math.max(1, Math.min(100, newWeight));
3681
+ setTokenSelection(prev => {
3682
+ var _a, _b;
3683
+ const currentLongTotal = prev.longTokens.reduce((sum, token) => sum + token.weight, 0);
3684
+ const currentShortTotal = prev.shortTokens.reduce((sum, token) => sum + token.weight, 0);
3685
+ if (isLong) {
3686
+ const oldWeight = ((_a = prev.longTokens[index]) === null || _a === void 0 ? void 0 : _a.weight) || 0;
3687
+ const weightDiff = clampedWeight - oldWeight;
3688
+ const newLongTotal = currentLongTotal + weightDiff;
3689
+ if (newLongTotal + currentShortTotal > 100) {
3690
+ const maxAllowedWeight = Math.max(1, 100 - currentShortTotal - (currentLongTotal - oldWeight));
3691
+ const updated = [...prev.longTokens];
3692
+ updated[index] = { ...updated[index], weight: maxAllowedWeight };
3693
+ return { ...prev, longTokens: updated };
3694
+ }
3695
+ else {
3696
+ const updated = [...prev.longTokens];
3697
+ updated[index] = { ...updated[index], weight: clampedWeight };
3698
+ return { ...prev, longTokens: updated };
3699
+ }
3700
+ }
3701
+ else {
3702
+ const oldWeight = ((_b = prev.shortTokens[index]) === null || _b === void 0 ? void 0 : _b.weight) || 0;
3703
+ const weightDiff = clampedWeight - oldWeight;
3704
+ const newShortTotal = currentShortTotal + weightDiff;
3705
+ if (currentLongTotal + newShortTotal > 100) {
3706
+ const maxAllowedWeight = Math.max(1, 100 - currentLongTotal - (currentShortTotal - oldWeight));
3707
+ const updated = [...prev.shortTokens];
3708
+ updated[index] = { ...updated[index], weight: maxAllowedWeight };
3709
+ return { ...prev, shortTokens: updated };
3710
+ }
3711
+ else {
3712
+ const updated = [...prev.shortTokens];
3713
+ updated[index] = { ...updated[index], weight: clampedWeight };
3714
+ return { ...prev, shortTokens: updated };
3715
+ }
3716
+ }
3717
+ });
3718
+ }, [setTokenSelection]);
3719
+ const addToken = require$$0.useCallback((isLong) => {
3720
+ const currentTokens = isLong ? tokenSelection.longTokens : tokenSelection.shortTokens;
3721
+ const newIndex = currentTokens.length;
3722
+ setTokenSelection(prev => ({
3723
+ ...prev,
3724
+ selectorConfig: { isLong, index: newIndex },
3725
+ openTokenSelector: true,
3726
+ }));
3727
+ }, [tokenSelection.longTokens, tokenSelection.shortTokens, setTokenSelection]);
3728
+ const removeToken = require$$0.useCallback((isLong, index) => {
3729
+ setTokenSelection(prev => {
3730
+ if (isLong) {
3731
+ const updated = prev.longTokens.filter((_, i) => i !== index);
3732
+ return { ...prev, longTokens: updated };
3733
+ }
3734
+ else {
3735
+ const updated = prev.shortTokens.filter((_, i) => i !== index);
3736
+ return { ...prev, shortTokens: updated };
3737
+ }
3738
+ });
3739
+ }, [setTokenSelection]);
3740
+ const setOpenTokenSelector = require$$0.useCallback((open) => {
3741
+ setTokenSelection(prev => ({ ...prev, openTokenSelector: open }));
3742
+ }, [setTokenSelection]);
3743
+ const setSelectorConfig = require$$0.useCallback((config) => {
3744
+ setTokenSelection(prev => ({ ...prev, selectorConfig: config }));
3745
+ }, [setTokenSelection]);
3746
+ const setOpenConflictModal = require$$0.useCallback((open) => {
3747
+ setTokenSelection(prev => ({ ...prev, openConflictModal: open }));
3748
+ }, [setTokenSelection]);
3749
+ const setConflicts = require$$0.useCallback((conflicts) => {
3750
+ setTokenSelection(prev => ({ ...prev, conflicts }));
3751
+ }, [setTokenSelection]);
3752
+ const resetToDefaults = require$$0.useCallback(() => {
3753
+ setTokenSelection(prev => ({
3754
+ ...prev,
3755
+ longTokens: [
3756
+ { symbol: 'HYPE', weight: 25 },
3757
+ { symbol: 'BTC', weight: 25 }
3758
+ ],
3759
+ shortTokens: [
3760
+ { symbol: 'AVAX', weight: 10 },
3761
+ { symbol: 'SEI', weight: 10 },
3762
+ { symbol: 'ADA', weight: 10 },
3763
+ { symbol: 'TRUMP', weight: 10 },
3764
+ { symbol: 'SUI', weight: 10 }
3765
+ ],
3766
+ openTokenSelector: false,
3767
+ selectorConfig: null,
3768
+ openConflictModal: false,
3769
+ }));
3770
+ }, [setTokenSelection]);
3771
+ const handleTokenSelect = require$$0.useCallback((selectedToken) => {
3772
+ if (!tokenSelection.selectorConfig)
3773
+ return;
3774
+ const { isLong, index } = tokenSelection.selectorConfig;
3775
+ const existingTokens = isLong ? tokenSelection.longTokens : tokenSelection.shortTokens;
3776
+ // Check if token already exists
3777
+ if (existingTokens.some(t => t.symbol === selectedToken))
3778
+ return;
3779
+ // Get metadata for new token
3780
+ const metadata = TokenMetadataExtractor.extractTokenMetadata(selectedToken, webData2, allMids);
3781
+ setTokenSelection(prev => {
3782
+ if (index >= existingTokens.length) {
3783
+ // Adding new token - calculate safe weight
3784
+ const currentLongTotal = prev.longTokens.reduce((sum, token) => sum + token.weight, 0);
3785
+ const currentShortTotal = prev.shortTokens.reduce((sum, token) => sum + token.weight, 0);
3786
+ const currentTotal = currentLongTotal + currentShortTotal;
3787
+ const maxAvailableWeight = Math.max(1, 100 - currentTotal);
3788
+ const safeWeight = Math.min(20, maxAvailableWeight);
3789
+ const newToken = {
3790
+ symbol: selectedToken,
3791
+ weight: safeWeight,
3792
+ metadata: metadata || undefined
3793
+ };
3794
+ if (isLong) {
3795
+ return { ...prev, longTokens: [...prev.longTokens, newToken] };
3796
+ }
3797
+ else {
3798
+ return { ...prev, shortTokens: [...prev.shortTokens, newToken] };
3799
+ }
3800
+ }
3801
+ else {
3802
+ // Replacing existing token
3803
+ if (isLong) {
3804
+ const updated = [...prev.longTokens];
3805
+ updated[index] = {
3806
+ ...updated[index],
3807
+ symbol: selectedToken,
3808
+ metadata: metadata || undefined
3809
+ };
3810
+ return { ...prev, longTokens: updated };
3811
+ }
3812
+ else {
3813
+ const updated = [...prev.shortTokens];
3814
+ updated[index] = {
3815
+ ...updated[index],
3816
+ symbol: selectedToken,
3817
+ metadata: metadata || undefined
3818
+ };
3819
+ return { ...prev, shortTokens: updated };
3820
+ }
3821
+ }
3822
+ });
3823
+ }, [tokenSelection.selectorConfig, tokenSelection.longTokens, tokenSelection.shortTokens, webData2, allMids, setTokenSelection]);
3824
+ // updateTokenMetadata is now handled automatically by the useEffect above
3825
+ return {
3826
+ // State
3827
+ longTokens: tokenSelection.longTokens,
3828
+ shortTokens: tokenSelection.shortTokens,
3829
+ openTokenSelector: tokenSelection.openTokenSelector,
3830
+ selectorConfig: tokenSelection.selectorConfig,
3831
+ openConflictModal: tokenSelection.openConflictModal,
3832
+ conflicts: tokenSelection.conflicts,
3833
+ // Loading states
3834
+ isLoading,
3835
+ isPriceDataReady,
3836
+ // Calculated values
3837
+ weightedRatio,
3838
+ weightedRatio24h,
3839
+ sumNetFunding,
3840
+ maxLeverage,
3841
+ minMargin,
3842
+ // Actions
3843
+ setLongTokens,
3844
+ setShortTokens,
3845
+ updateTokenWeight,
3846
+ addToken,
3847
+ removeToken,
3848
+ handleTokenSelect,
3849
+ setOpenTokenSelector,
3850
+ setSelectorConfig,
3851
+ setOpenConflictModal,
3852
+ setConflicts,
3853
+ resetToDefaults,
3854
+ };
3398
3855
  };
3399
3856
 
3857
+ /**
3858
+ * Detects conflicts between selected tokens and existing positions
3859
+ */
3860
+ class ConflictDetector {
3861
+ /**
3862
+ * Detects conflicts between token selections and open positions
3863
+ * @param longTokens - Selected long tokens
3864
+ * @param shortTokens - Selected short tokens
3865
+ * @param openPositions - Current open positions from API
3866
+ * @returns Array of detected conflicts
3867
+ */
3868
+ static detectConflicts(longTokens, shortTokens, openPositions) {
3869
+ const detectedConflicts = [];
3870
+ if (!openPositions) {
3871
+ return detectedConflicts;
3872
+ }
3873
+ // Check long tokens against existing short positions
3874
+ longTokens.forEach((token) => {
3875
+ const existingShortPosition = openPositions.find((pos) => {
3876
+ var _a;
3877
+ // Check multiple possible property names and side values
3878
+ const symbol = pos.coin || pos.symbol || pos.asset;
3879
+ const side = (_a = pos.side) === null || _a === void 0 ? void 0 : _a.toLowerCase();
3880
+ return symbol === token.symbol && (side === 'short' || side === 'sell');
3881
+ });
3882
+ if (existingShortPosition) {
3883
+ detectedConflicts.push({
3884
+ symbol: token.symbol,
3885
+ conflictType: 'short',
3886
+ conflictMessage: `${token.symbol} Long conflicts with existing ${token.symbol} Short position`
3887
+ });
3888
+ }
3889
+ });
3890
+ // Check short tokens against existing long positions
3891
+ shortTokens.forEach((token) => {
3892
+ const existingLongPosition = openPositions.find((pos) => {
3893
+ var _a;
3894
+ // Check multiple possible property names and side values
3895
+ const symbol = pos.coin || pos.symbol || pos.asset;
3896
+ const side = (_a = pos.side) === null || _a === void 0 ? void 0 : _a.toLowerCase();
3897
+ return symbol === token.symbol && (side === 'long' || side === 'buy');
3898
+ });
3899
+ if (existingLongPosition) {
3900
+ detectedConflicts.push({
3901
+ symbol: token.symbol,
3902
+ conflictType: 'long',
3903
+ conflictMessage: `${token.symbol} Short conflicts with existing ${token.symbol} Long position`
3904
+ });
3905
+ }
3906
+ });
3907
+ return detectedConflicts;
3908
+ }
3909
+ }
3910
+
3400
3911
  exports.AccountSummaryCalculator = AccountSummaryCalculator;
3912
+ exports.ConflictDetector = ConflictDetector;
3401
3913
  exports.PearHyperliquidClient = PearHyperliquidClient;
3402
3914
  exports.PearHyperliquidProvider = PearHyperliquidProvider;
3403
3915
  exports.PearMigrationSDK = PearMigrationSDK;
3916
+ exports.TokenMetadataExtractor = TokenMetadataExtractor;
3404
3917
  exports.default = PearHyperliquidClient;
3405
3918
  exports.useAccountSummary = useAccountSummary;
3406
3919
  exports.useAddress = useAddress;
@@ -3412,5 +3925,6 @@ exports.useMigrationSDK = useMigrationSDK;
3412
3925
  exports.useOpenOrders = useOpenOrders;
3413
3926
  exports.useOpenPositions = useOpenPositions;
3414
3927
  exports.usePearHyperliquidClient = usePearHyperliquidClient;
3928
+ exports.useTokenSelection = useTokenSelection;
3415
3929
  exports.useTradeHistories = useTradeHistories;
3416
3930
  //# sourceMappingURL=index.js.map