Procházet zdrojové kódy

Enhance Hyperliquid client configuration and balance fetching - Implement CCXT format for API keys, improve logging of sensitive data, and introduce alternative balance fetching methods with multiple approaches for better reliability.

Carles Sentis před 6 dny
rodič
revize
fd0fd8a569
3 změnil soubory, kde provedl 302 přidání a 6 odebrání
  1. 96 6
      src/hyperliquid_client.py
  2. 108 0
      test_balance.py
  3. 98 0
      test_config.py

+ 96 - 6
src/hyperliquid_client.py

@@ -32,17 +32,37 @@ class HyperliquidClient:
             self.config['testnet'] = use_testnet
             self.config['sandbox'] = use_testnet
         
+        # Ensure proper CCXT format
+        if not self.config.get('apiKey') and Config.HYPERLIQUID_PRIVATE_KEY:
+            self.config['apiKey'] = Config.HYPERLIQUID_PRIVATE_KEY
+            
+        if not self.config.get('secret') and Config.HYPERLIQUID_SECRET_KEY:
+            self.config['secret'] = Config.HYPERLIQUID_SECRET_KEY
+        
         # Initialize clients
         self.sync_client = None
         self.async_client = None
         
-        if Config.HYPERLIQUID_PRIVATE_KEY:
+        if self.config.get('apiKey'):
             try:
-                # Initialize with CCXT-style parameters
+                # Log configuration (safely)
                 logger.info(f"🔧 Initializing Hyperliquid client with config: {self._safe_config_log()}")
                 
-                # Try different initialization patterns for CCXT compatibility
-                self.sync_client = HyperliquidSync(self.config)
+                # Initialize with standard CCXT format
+                ccxt_config = {
+                    'apiKey': self.config['apiKey'],
+                    'testnet': self.config.get('testnet', False),
+                    'sandbox': self.config.get('sandbox', False),
+                }
+                
+                # Add secret if available
+                if self.config.get('secret'):
+                    ccxt_config['secret'] = self.config['secret']
+                
+                logger.info(f"📋 Using CCXT config structure: {self._safe_ccxt_config_log(ccxt_config)}")
+                
+                # Initialize with the proper CCXT format
+                self.sync_client = HyperliquidSync(ccxt_config)
                 
                 logger.info(f"✅ Hyperliquid client initialized successfully")
                 logger.info(f"🌐 Network: {'Testnet' if use_testnet else '🚨 MAINNET 🚨'}")
@@ -68,11 +88,26 @@ class HyperliquidClient:
             safe_config['private_key'] = f"{safe_config['private_key'][:8]}..."
         return safe_config
     
+    def _safe_ccxt_config_log(self, config: dict) -> dict:
+        """Return CCXT config with sensitive data masked for logging."""
+        safe_config = config.copy()
+        if 'apiKey' in safe_config and safe_config['apiKey']:
+            safe_config['apiKey'] = f"{safe_config['apiKey'][:10]}..."
+        if 'secret' in safe_config and safe_config['secret']:
+            safe_config['secret'] = f"{safe_config['secret'][:10]}..."
+        return safe_config
+    
     def _test_connection(self):
         """Test the connection to verify credentials."""
         try:
             # Try to fetch balance to test authentication
-            balance = self.sync_client.fetch_balance()
+            # Use the same logic as get_balance for consistency
+            params = {}
+            if Config.HYPERLIQUID_PRIVATE_KEY:
+                wallet_address = Config.HYPERLIQUID_PRIVATE_KEY
+                params['user'] = f"0x{wallet_address}" if not wallet_address.startswith('0x') else wallet_address
+            
+            balance = self.sync_client.fetch_balance(params=params)
             logger.info(f"🔗 Connection test successful")
         except Exception as e:
             logger.warning(f"⚠️ Connection test failed: {e}")
@@ -85,11 +120,66 @@ class HyperliquidClient:
                 logger.error("❌ Client not initialized")
                 return None
             
-            balance = self.sync_client.fetch_balance()
+            # For Hyperliquid, we need to pass the wallet address/user parameter
+            # The user parameter should be the wallet address derived from private key
+            params = {}
+            
+            # If we have a private key, derive the wallet address
+            if Config.HYPERLIQUID_PRIVATE_KEY:
+                # Extract the wallet address from the private key
+                # For CCXT Hyperliquid, the user parameter should be the wallet address
+                wallet_address = Config.HYPERLIQUID_PRIVATE_KEY
+                if wallet_address.startswith('0x'):
+                    # Use the address as the user parameter
+                    params['user'] = wallet_address
+                else:
+                    # If it's just the private key, we might need to derive the address
+                    # For now, try using the private key directly
+                    params['user'] = f"0x{wallet_address}" if not wallet_address.startswith('0x') else wallet_address
+            
+            logger.debug(f"🔍 Fetching balance with params: {params}")
+            balance = self.sync_client.fetch_balance(params=params)
             logger.info("✅ Successfully fetched balance")
             return balance
         except Exception as e:
             logger.error(f"❌ Error fetching balance: {e}")
+            logger.debug(f"💡 Attempted with params: {params}")
+            return None
+    
+    def get_balance_alternative(self) -> Optional[Dict[str, Any]]:
+        """Alternative balance fetching method trying different approaches."""
+        try:
+            if not self.sync_client:
+                logger.error("❌ Client not initialized")
+                return None
+            
+            # Try different approaches for balance fetching
+            approaches = [
+                # Approach 1: No params (original)
+                {},
+                # Approach 2: Private key as user
+                {'user': Config.HYPERLIQUID_PRIVATE_KEY},
+                # Approach 3: Private key with 0x prefix
+                {'user': f"0x{Config.HYPERLIQUID_PRIVATE_KEY}" if not Config.HYPERLIQUID_PRIVATE_KEY.startswith('0x') else Config.HYPERLIQUID_PRIVATE_KEY},
+                # Approach 4: Empty user
+                {'user': ''},
+            ]
+            
+            for i, params in enumerate(approaches, 1):
+                try:
+                    logger.info(f"🔍 Trying approach {i}: {params}")
+                    balance = self.sync_client.fetch_balance(params=params)
+                    logger.info(f"✅ Approach {i} successful!")
+                    return balance
+                except Exception as e:
+                    logger.warning(f"⚠️ Approach {i} failed: {e}")
+                    continue
+            
+            logger.error("❌ All approaches failed")
+            return None
+            
+        except Exception as e:
+            logger.error(f"❌ Error in alternative balance fetch: {e}")
             return None
     
     def get_positions(self, symbol: Optional[str] = None) -> Optional[List[Dict[str, Any]]]:

+ 108 - 0
test_balance.py

@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+"""
+Test script to verify Hyperliquid balance fetching with CCXT
+"""
+
+import sys
+import os
+
+# Add the src directory to the path
+sys.path.insert(0, 'src')
+
+from hyperliquid_client import HyperliquidClient
+from config import Config
+
+def test_balance():
+    """Test balance fetching functionality."""
+    print("🧪 Testing Hyperliquid Balance Fetching")
+    print("=" * 50)
+    
+    try:
+        # Validate configuration first
+        print("🔍 Validating configuration...")
+        if not Config.validate():
+            print("❌ Configuration validation failed!")
+            return False
+        
+        print(f"✅ Configuration valid")
+        print(f"🌐 Network: {'Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet'}")
+        print(f"🔑 Private Key: {Config.HYPERLIQUID_PRIVATE_KEY[:10]}..." if Config.HYPERLIQUID_PRIVATE_KEY else "❌ No private key")
+        print()
+        
+        # Initialize client
+        print("🔧 Initializing Hyperliquid client...")
+        client = HyperliquidClient(use_testnet=Config.HYPERLIQUID_TESTNET)
+        
+        if not client.sync_client:
+            print("❌ Failed to initialize client!")
+            return False
+        
+        print("✅ Client initialized successfully")
+        print()
+        
+        # Test balance fetching
+        print("💰 Fetching account balance...")
+        balance = client.get_balance()
+        
+        if balance:
+            print("✅ Balance fetch successful!")
+            print("📊 Balance data:")
+            
+            # Pretty print the balance
+            if isinstance(balance, dict):
+                for key, value in balance.items():
+                    if isinstance(value, dict):
+                        print(f"  {key}:")
+                        for sub_key, sub_value in value.items():
+                            print(f"    {sub_key}: {sub_value}")
+                    else:
+                        print(f"  {key}: {value}")
+            else:
+                print(f"  Raw balance: {balance}")
+                
+            return True
+        else:
+            print("❌ Balance fetch failed! Trying alternative method...")
+            print()
+            
+            # Try alternative method
+            print("🔄 Testing alternative balance fetching approaches...")
+            balance_alt = client.get_balance_alternative()
+            
+            if balance_alt:
+                print("✅ Alternative balance fetch successful!")
+                print("📊 Balance data:")
+                
+                # Pretty print the balance
+                if isinstance(balance_alt, dict):
+                    for key, value in balance_alt.items():
+                        if isinstance(value, dict):
+                            print(f"  {key}:")
+                            for sub_key, sub_value in value.items():
+                                print(f"    {sub_key}: {sub_value}")
+                        else:
+                            print(f"  {key}: {value}")
+                else:
+                    print(f"  Raw balance: {balance_alt}")
+                    
+                return True
+            else:
+                print("❌ Alternative balance fetch also failed!")
+                return False
+            
+    except Exception as e:
+        print(f"💥 Test failed with error: {e}")
+        import traceback
+        print("📜 Full traceback:")
+        traceback.print_exc()
+        return False
+
+if __name__ == "__main__":
+    success = test_balance()
+    
+    if success:
+        print("\n🎉 Balance test PASSED!")
+        sys.exit(0)
+    else:
+        print("\n💥 Balance test FAILED!")
+        sys.exit(1) 

+ 98 - 0
test_config.py

@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+"""
+Test script to verify configuration and CCXT format
+"""
+
+import sys
+import os
+
+# Add the src directory to the path
+sys.path.insert(0, 'src')
+
+from config import Config
+
+def test_config():
+    """Test configuration setup."""
+    print("🧪 Testing Configuration Setup")
+    print("=" * 50)
+    
+    try:
+        # Test basic configuration
+        print("🔍 Checking environment variables...")
+        print(f"📂 Current directory: {os.getcwd()}")
+        print(f"📄 .env file exists: {os.path.exists('.env')}")
+        print()
+        
+        # Show raw environment variables
+        print("🔑 Raw Environment Variables:")
+        print(f"  HYPERLIQUID_PRIVATE_KEY: {'✅ Set' if os.getenv('HYPERLIQUID_PRIVATE_KEY') else '❌ Not Set'}")
+        print(f"  HYPERLIQUID_SECRET_KEY: {'✅ Set' if os.getenv('HYPERLIQUID_SECRET_KEY') else '❌ Not Set'}")
+        print(f"  HYPERLIQUID_TESTNET: {os.getenv('HYPERLIQUID_TESTNET', 'NOT SET')}")
+        print(f"  TELEGRAM_BOT_TOKEN: {'✅ Set' if os.getenv('TELEGRAM_BOT_TOKEN') else '❌ Not Set'}")
+        print(f"  TELEGRAM_CHAT_ID: {'✅ Set' if os.getenv('TELEGRAM_CHAT_ID') else '❌ Not Set'}")
+        print(f"  TELEGRAM_ENABLED: {os.getenv('TELEGRAM_ENABLED', 'NOT SET')}")
+        print()
+        
+        # Test Config class
+        print("⚙️ Config Class Values:")
+        print(f"  HYPERLIQUID_PRIVATE_KEY: {'✅ Set (' + Config.HYPERLIQUID_PRIVATE_KEY[:10] + '...)' if Config.HYPERLIQUID_PRIVATE_KEY else '❌ Not Set'}")
+        print(f"  HYPERLIQUID_SECRET_KEY: {'✅ Set (' + Config.HYPERLIQUID_SECRET_KEY[:10] + '...)' if Config.HYPERLIQUID_SECRET_KEY else '❌ Not Set'}")
+        print(f"  HYPERLIQUID_TESTNET: {Config.HYPERLIQUID_TESTNET}")
+        print(f"  TELEGRAM_ENABLED: {Config.TELEGRAM_ENABLED}")
+        print()
+        
+        # Test CCXT configuration format
+        print("🔧 CCXT Configuration Format:")
+        ccxt_config = Config.get_hyperliquid_config()
+        
+        for key, value in ccxt_config.items():
+            if key in ['apiKey', 'secret', 'private_key'] and value:
+                print(f"  {key}: {value[:10]}...")
+            else:
+                print(f"  {key}: {value}")
+        print()
+        
+        # Test validation
+        print("✅ Configuration Validation:")
+        is_valid = Config.validate()
+        
+        if is_valid:
+            print("🎉 Configuration is VALID!")
+            
+            # Show the exact format that will be sent to HyperliquidSync
+            final_config = {
+                'apiKey': Config.HYPERLIQUID_PRIVATE_KEY,
+                'testnet': Config.HYPERLIQUID_TESTNET,
+                'sandbox': Config.HYPERLIQUID_TESTNET,
+            }
+            
+            if Config.HYPERLIQUID_SECRET_KEY:
+                final_config['secret'] = Config.HYPERLIQUID_SECRET_KEY
+            
+            print("\n📋 Final CCXT Config (masked):")
+            for key, value in final_config.items():
+                if key in ['apiKey', 'secret'] and value:
+                    print(f"  '{key}': '{value[:10]}...'")
+                else:
+                    print(f"  '{key}': {value}")
+            
+            return True
+        else:
+            print("❌ Configuration is INVALID!")
+            return False
+            
+    except Exception as e:
+        print(f"💥 Configuration test failed: {e}")
+        import traceback
+        traceback.print_exc()
+        return False
+
+if __name__ == "__main__":
+    success = test_config()
+    
+    if success:
+        print("\n🎉 Config test PASSED!")
+        sys.exit(0)
+    else:
+        print("\n💥 Config test FAILED!")
+        sys.exit(1)