|
@@ -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]]]:
|