Riy777 commited on
Commit
781de6c
·
1 Parent(s): 75280ce

Update whale_news_data.py

Browse files
Files changed (1) hide show
  1. whale_news_data.py +207 -183
whale_news_data.py CHANGED
@@ -52,7 +52,11 @@ class EnhancedWhaleMonitor:
52
  self._initialize_contracts_db(contracts_db or {})
53
 
54
  self.data_manager = None
55
- self.coingecko_semaphore = asyncio.Semaphore(1)
 
 
 
 
56
  self.r2_service = r2_service
57
 
58
  self.address_labels = {}
@@ -202,9 +206,8 @@ class EnhancedWhaleMonitor:
202
  print(f"⚠️ مفتاح {env_var} غير متوفر أو فارغ.")
203
  setattr(self, attr_name, None)
204
  else:
205
- # إذا لم تكن الخاصية موجودة أصلاً (مثل bscscan_key إذا لم يتم تعيينها كمتغير بيئة)
206
  print(f"ℹ️ خاصية المفتاح {attr_name} (لـ {env_var}) غير معرفة في الكلاس.")
207
- # يمكنك تعيينها إلى None هنا إذا أردت التأكد
208
  setattr(self, attr_name, None)
209
 
210
  def _get_ethereum_rpc_endpoints(self):
@@ -295,14 +298,15 @@ class EnhancedWhaleMonitor:
295
  ]
296
 
297
  def _get_solana_rpc_endpoints(self):
298
- """الحصول على نقاط RPC مجانية لشبكة Solana (بدون مفاتيح API)"""
299
  endpoints = [
300
- 'https://api.mainnet-beta.solana.com',
301
- 'https://ssc-dao.genesysgo.net/',
302
- 'https://solana-mainnet.rpc.extrnode.com',
303
- 'https://solana-mainnet.phantom.tech/',
304
- 'https://mainnet.rpc.solana.pyth.network',
305
- 'https://rpc.ankr.com/solana',
 
306
  ]
307
  print(f"ℹ️ قائمة Solana RPC Endpoints (مجانية وبدون مفاتيح) المستخدمة: {endpoints}")
308
  return endpoints
@@ -387,7 +391,6 @@ class EnhancedWhaleMonitor:
387
  print(f" ℹ️ تم تحديث صيغة {updated_count} عقد إلى الصيغة الجديدة.")
388
  await self._save_contracts_to_r2()
389
 
390
-
391
  except ClientError as e:
392
  if e.response['Error']['Code'] == 'NoSuchKey':
393
  print("⚠️ لم يتم العثور على قاعدة بيانات العقود في R2. ستبدأ فارغة.")
@@ -413,7 +416,6 @@ class EnhancedWhaleMonitor:
413
  contract_info = None
414
  network = None
415
 
416
- # 1. البحث عن عقد العملة والشبكة
417
  if contract_address and isinstance(contract_address, str):
418
  network = self._detect_network_from_address(contract_address)
419
  contract_info_from_db = await self._find_contract_address_enhanced(symbol)
@@ -436,14 +438,12 @@ class EnhancedWhaleMonitor:
436
 
437
  print(f"🌐 البحث في الشبكة المحددة: {network_final} للعقد: {contract_address_final}")
438
 
439
- # 3. جلب بيانات التحويلات
440
  transfers = await self._get_targeted_transfer_data(contract_address_final, network_final)
441
 
442
  if not transfers:
443
  print(f"⚠️ لم يتم العثور على تحويلات للعملة {symbol} على شبكة {network_final}")
444
  return self._create_no_transfers_response(symbol)
445
 
446
- # 4. ترشيح التحويلات الحديثة
447
  recent_transfers = self._filter_recent_transfers(transfers, max_minutes=120)
448
 
449
  if not recent_transfers:
@@ -452,7 +452,6 @@ class EnhancedWhaleMonitor:
452
 
453
  print(f"📊 تم جلب {len(recent_transfers)} تحويلة حديثة فريدة للعملة {symbol}")
454
 
455
- # 5. تحليل تأثير الحيتان
456
  analysis = await self._analyze_enhanced_whale_impact(recent_transfers, symbol, contract_address_final, network_final)
457
 
458
  return analysis
@@ -573,9 +572,10 @@ class EnhancedWhaleMonitor:
573
  endpoint_name = endpoint.split('//')[1].split('/')[0] if '//' in endpoint else endpoint
574
  print(f" 🔍 محاولة جلب سجلات التحويلات لـ {network} عبر {endpoint_name}...")
575
 
576
- # ✅ الإصلاح: استخدام "self.http_client" مباشرة
577
- payload_block = {"jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": int(time.time())}
578
- response_block = await self.http_client.post(endpoint, json=payload_block, timeout=15.0)
 
579
  response_block.raise_for_status()
580
  json_response_block = response_block.json()
581
  result_block = json_response_block.get('result')
@@ -586,8 +586,9 @@ class EnhancedWhaleMonitor:
586
  continue
587
  latest_block = int(result_block, 16)
588
 
589
- payload_latest_block_time = {"jsonrpc": "2.0", "method": "eth_getBlockByNumber", "params": [hex(latest_block), False], "id": int(time.time())+1}
590
- response_latest_time = await self.http_client.post(endpoint, json=payload_latest_block_time, timeout=15.0)
 
591
  latest_block_timestamp_approx = int(time.time())
592
  if response_latest_time.status_code == 200:
593
  latest_block_data = response_latest_time.json().get('result')
@@ -602,7 +603,8 @@ class EnhancedWhaleMonitor:
602
  "params": [{"fromBlock": hex(from_block), "toBlock": hex(latest_block), "address": contract_address_checksum, "topics": [TRANSFER_EVENT_SIGNATURE]}],
603
  "id": int(time.time())+2
604
  }
605
- response_logs = await self.http_client.post(endpoint, json=payload_logs, timeout=45.0)
 
606
  response_logs.raise_for_status()
607
  json_response_logs = response_logs.json()
608
  logs = json_response_logs.get('result')
@@ -706,8 +708,9 @@ class EnhancedWhaleMonitor:
706
  "params": [ token_address, {"limit": limit, "commitment": "confirmed"} ]
707
  }
708
 
709
- # ✅ الإصلاح: استخدام "self.http_client" مباشرة
710
- response_signatures = await self.http_client.post(endpoint, json=payload_signatures, timeout=20.0)
 
711
 
712
  if response_signatures.status_code == 403:
713
  print(f" ⚠️ Solana endpoint {endpoint_name} failed: 403 Forbidden")
@@ -741,10 +744,11 @@ class EnhancedWhaleMonitor:
741
 
742
  processed_count = 0
743
  transaction_tasks = []
744
- # ✅ الإصلاح: تمرير "self.http_client" مباشرة
 
745
  for signature in signatures[:20]:
746
  transaction_tasks.append(
747
- self._get_solana_transaction_detail_with_retry(signature, endpoint, self.http_client)
748
  )
749
  transaction_details = await asyncio.gather(*transaction_tasks)
750
 
@@ -794,12 +798,16 @@ class EnhancedWhaleMonitor:
794
  return []
795
 
796
 
 
797
  async def _get_solana_transaction_detail_with_retry(self, signature, endpoint, client: httpx.AsyncClient, retries=2, delay=0.5):
798
  """جلب تفاصيل معاملة Solana مع محاولات إعادة بسيطة باستخدام client مشترك"""
799
  last_exception = None
800
  for attempt in range(retries):
801
  try:
802
- detail = await self._get_solana_transaction_detail(signature, endpoint, client)
 
 
 
803
  if detail:
804
  return detail
805
  elif detail is None:
@@ -835,6 +843,7 @@ class EnhancedWhaleMonitor:
835
  "method": "getTransaction",
836
  "params": [ signature, {"encoding": "jsonParsed", "maxSupportedTransactionVersion": 0, "commitment": "confirmed"} ]
837
  }
 
838
  response = await client.post(endpoint, json=payload, timeout=20.0)
839
  response.raise_for_status()
840
 
@@ -974,7 +983,6 @@ class EnhancedWhaleMonitor:
974
  if sender and receiver and amount_raw > 0: break
975
  if sender and receiver and amount_raw > 0: break
976
 
977
-
978
  if sender and receiver and amount_raw > 0:
979
  return {
980
  'hash': signature, 'from': sender, 'to': receiver,
@@ -1017,41 +1025,47 @@ class EnhancedWhaleMonitor:
1017
  "endblock": 999999999, "sort": "desc", "apikey": config['key']
1018
  }
1019
 
1020
- async with httpx.AsyncClient(timeout=20.0, verify=self.ssl_context) as client:
1021
- response = await client.get(config['url'], params=params)
1022
-
1023
- if response.status_code == 200:
1024
- data = response.json()
1025
- status = str(data.get('status', '0'))
1026
- message = data.get('message', '').upper()
1027
-
1028
- if status == '1' and message == 'OK':
1029
- transfers = data.get('result', [])
1030
- processed_transfers = []
1031
- for tf in transfers:
1032
- if tf.get('tokenSymbol') and tf.get('contractAddress','').lower() == contract_address.lower():
1033
- tf['network'] = network
1034
- if 'from' in tf: tf['from'] = tf['from'].lower()
1035
- if 'to' in tf: tf['to'] = tf['to'].lower()
1036
- if 'logIndex' not in tf: tf['logIndex'] = 'N/A'
1037
- processed_transfers.append(tf)
1038
-
1039
- print(f" ✅ Explorer {network}: تم جلب {len(processed_transfers)} تحويلة توكن للعقد المحدد.")
1040
- return processed_transfers
1041
- elif status == '0' and "NO TRANSACTIONS FOUND" in message:
1042
- print(f" ✅ Explorer {network}: لا توجد تحويلات.")
1043
- return []
1044
- else:
1045
- error_message = data.get('result', message)
1046
- print(f"⚠️ Explorer {network} returned error: Status={status}, Message={message}, Result={error_message}")
1047
- if "INVALID API KEY" in str(error_message).upper():
1048
- print(f"🚨 خطأ فادح: مفتاح API الخاص بـ {network.upper()} غير صالح!")
1049
- return []
1050
  else:
1051
- print(f"⚠️ Explorer {network} request failed: {response.status_code}")
 
 
 
1052
  return []
 
 
 
1053
 
1054
  except Exception as e:
 
 
 
1055
  print(f"❌ فشل جلب بيانات Explorer لـ {network}: {e}")
1056
  return []
1057
 
@@ -1257,7 +1271,10 @@ class EnhancedWhaleMonitor:
1257
  "params": [{"to": contract_address, "data": "0x313ce567"}, "latest"],
1258
  "id": int(time.time())
1259
  }
1260
- response = await self.http_client.post(endpoint, json=payload, timeout=10.0)
 
 
 
1261
  if response.status_code == 200:
1262
  result = response.json().get('result')
1263
  if result and result != '0x' and result.startswith('0x'):
@@ -1284,7 +1301,6 @@ class EnhancedWhaleMonitor:
1284
  print(f" ⚠️ استخدام قيمة decimals تقديرية لـ Solana: {estimated_decimals}")
1285
  return estimated_decimals
1286
 
1287
-
1288
  print(f"❌ فشل جلب الكسور العشرية للعقد {contract_address} على شبكة {network} من جميع المصادر.")
1289
  return None
1290
 
@@ -1331,7 +1347,7 @@ class EnhancedWhaleMonitor:
1331
  'ATOM': 'cosmos', 'UNI': 'uniswap', 'AAVE': 'aave', 'KCS': 'kucoin-shares',
1332
  'MAVIA': 'heroes-of-mavia', 'COMMON': 'commonwealth', 'WLFI': 'wolfi',
1333
  'PINGPONG': 'pingpong', 'YB': 'yourbusd', 'REACT': 'react', 'XMN': 'xmine',
1334
- 'ANOME': 'anome', 'ZEN': 'zencash', 'AKT': 'akash-network', 'UB': 'unibit', 'WLD': 'worldcoin' # إضافة WLD
1335
  }
1336
 
1337
  base_symbol = symbol.split('/')[0].upper()
@@ -1347,52 +1363,55 @@ class EnhancedWhaleMonitor:
1347
  max_retries = 2
1348
  for attempt in range(max_retries):
1349
  async with self.coingecko_semaphore:
1350
- # ✅ الإصلاح: إنشاء client جديد ومؤقت هنا
1351
- async with httpx.AsyncClient(timeout=10.0, headers=headers, verify=self.ssl_context) as client:
1352
- try:
1353
- response = await client.get(url)
1354
- if response.status_code == 429:
1355
- wait_time = 3 * (attempt + 1)
1356
- print(f"⏰ Rate limit من CoinGecko لـ {symbol} (محاولة {attempt + 1}) - الانتظار {wait_time}s")
1357
- await asyncio.sleep(wait_time)
1358
- continue
1359
-
1360
- response.raise_for_status()
1361
- data = response.json()
1362
- price_data = data.get(coingecko_id)
1363
- if price_data and 'usd' in price_data:
1364
- price = price_data['usd']
1365
- if isinstance(price, (int, float)) and price > 0:
1366
- return price
1367
- else:
1368
- print(f"⚠️ استجابة CoinGecko تحتوي على سعر غير صالح لـ {symbol}: {price}")
1369
- return 0
1370
  else:
1371
- if attempt == 0:
1372
- print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في CoinGecko، محاولة البحث عن المعرف...")
1373
- await asyncio.sleep(1.1)
1374
- found_id = await self._find_coingecko_id_via_search(base_symbol, client)
1375
- if found_id and found_id != coingecko_id:
1376
- print(f" 🔄 تم العثور على معرف بديل: {found_id}. إعادة المحاولة...")
1377
- coingecko_id = found_id
1378
- url = f"https://api.coingecko.com/api/v3/simple/price?ids={coingecko_id}&vs_currencies=usd"
1379
- continue
1380
- else:
1381
- print(f" ❌ لم يتم العثور على معرف بديل لـ {base_symbol}.")
1382
- return 0
1383
- else:
1384
- print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في استجابة CoinGecko بعد البحث.")
1385
- return 0
1386
-
1387
- except httpx.HTTPStatusError as http_err:
1388
- print(f" خطأ HTTP من CoinGecko لـ {symbol}: {http_err.response.status_code}")
1389
- return 0
1390
- except httpx.RequestError as req_err:
1391
- print(f"❌ خطأ اتصال بـ CoinGecko لـ {symbol}: {req_err}")
1392
- return 0
1393
- except Exception as e:
1394
- print(f"❌ خطأ غير متوقع أثناء جلب السعر من CoinGecko لـ {symbol}: {e}")
1395
- return 0
 
 
 
 
 
 
 
1396
 
1397
  print(f"❌ فشل جلب السعر من CoinGecko لـ {symbol} بعد {max_retries} محاولات.")
1398
  return 0
@@ -1405,7 +1424,9 @@ class EnhancedWhaleMonitor:
1405
  """دالة مساعدة للبحث عن معرف CoinGecko"""
1406
  try:
1407
  search_url = f"https://api.coingecko.com/api/v3/search?query={symbol}"
1408
- response = await client.get(search_url)
 
 
1409
  response.raise_for_status()
1410
  data = response.json()
1411
  coins = data.get('coins', [])
@@ -1587,82 +1608,85 @@ class EnhancedWhaleMonitor:
1587
  max_retries = 2
1588
  for attempt in range(max_retries):
1589
  async with self.coingecko_semaphore:
1590
- async with httpx.AsyncClient(timeout=15, headers=headers, verify=self.ssl_context) as client:
1591
- try:
1592
- print(f" 🔍 CoinGecko Search API Call (Attempt {attempt + 1}) for {symbol}")
1593
- response = await client.get(search_url)
1594
-
1595
- if response.status_code == 429:
1596
- wait_time = 3 * (attempt + 1)
1597
- print(f" ⏰ Rate limit (Search) for {symbol} - Waiting {wait_time}s")
1598
- await asyncio.sleep(wait_time)
1599
- continue
1600
-
1601
- response.raise_for_status()
1602
- data = response.json()
1603
- coins = data.get('coins', [])
1604
-
1605
- if not coins:
1606
- print(f" ❌ No matching coins found for {symbol} on CoinGecko")
1607
- return None
1608
-
1609
- print(f" 📊 Found {len(coins)} potential matches for {symbol}")
1610
-
1611
- best_coin = None
1612
- search_symbol_lower = symbol.lower()
1613
- for coin in coins:
1614
- coin_symbol = coin.get('symbol', '').lower()
1615
- coin_name = coin.get('name', '').lower()
1616
- if coin_symbol == search_symbol_lower:
1617
- best_coin = coin; print(f" ✅ Exact symbol match: {coin.get('name')}"); break
1618
- if not best_coin and coin_name == search_symbol_lower:
1619
- best_coin = coin; print(f" ✅ Exact name match: {coin.get('name')}")
1620
- if not best_coin:
1621
- best_coin = coins[0]; print(f" ⚠️ Using first result: {best_coin.get('name')}")
1622
-
1623
- coin_id = best_coin.get('id')
1624
- if not coin_id: print(f" ❌ No ID found"); return None
1625
-
1626
- print(f" 🔍 Fetching details for CoinGecko ID: {coin_id}")
1627
- detail_url = f"https://api.coingecko.com/api/v3/coins/{coin_id}"
1628
- await asyncio.sleep(1.1)
1629
- detail_response = await client.get(detail_url)
1630
-
1631
- if detail_response.status_code == 429:
1632
- wait_time = 5 * (attempt + 1)
1633
- print(f" ⏰ Rate limit (Details) for {coin_id} - Waiting {wait_time}s")
1634
- await asyncio.sleep(wait_time)
1635
- detail_response = await client.get(detail_url)
1636
-
1637
- detail_response.raise_for_status()
1638
- detail_data = detail_response.json()
1639
- platforms = detail_data.get('platforms', {})
1640
- if not platforms: print(f" ❌ No platform data found"); return None
1641
-
1642
- network_priority = ['ethereum', 'binance-smart-chain', 'polygon-pos', 'arbitrum-one', 'optimistic-ethereum', 'avalanche', 'fantom', 'solana']
1643
- network_map = {'ethereum': 'ethereum', 'binance-smart-chain': 'bsc', 'polygon-pos': 'polygon', 'arbitrum-one': 'arbitrum', 'optimistic-ethereum': 'optimism', 'avalanche': 'avalanche', 'fantom': 'fantom', 'solana': 'solana'}
1644
-
1645
- for platform_cg in network_priority:
1646
- address = platforms.get(platform_cg)
1647
- if address and isinstance(address, str) and address.strip():
1648
- network = network_map.get(platform_cg)
1649
- if network:
1650
- print(f" ✅ Found contract on {network}: {address}")
1651
- return address, network
1652
-
1653
- print(f" ❌ No contract found on supported platforms for {coin_id}")
1654
- return None
1655
 
1656
- except httpx.HTTPStatusError as http_err:
1657
- print(f" ❌ HTTP Error from CoinGecko ({http_err.request.url}): {http_err.response.status_code}")
1658
- return None
1659
- except httpx.RequestError as req_err:
1660
- print(f" ❌ Connection Error with CoinGecko ({req_err.request.url}): {req_err}")
 
 
 
 
 
 
 
1661
  return None
1662
- except Exception as inner_e:
1663
- print(f" Unexpected error during CoinGecko API call (Attempt {attempt + 1}): {inner_e}")
1664
- if attempt < max_retries - 1: await asyncio.sleep(1); continue
1665
- else: return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1666
 
1667
  print(f" ❌ Failed to get contract from CoinGecko for {symbol} after {max_retries} attempts.")
1668
  return None
@@ -1756,7 +1780,7 @@ class EnhancedWhaleMonitor:
1756
  }
1757
 
1758
  whale_signal = whale_data.get('trading_signal', {})
1759
- analysis_details = whale_data # إرجاع كافة التفاصيل
1760
 
1761
  return {
1762
  'action': whale_signal.get('action', 'HOLD'),
@@ -1787,4 +1811,4 @@ class EnhancedWhaleMonitor:
1787
  print(f"⚠️ خطأ أثناء إغلاق HTTP client لمراقب الحيتان: {e}")
1788
 
1789
 
1790
- print("✅ EnhancedWhaleMonitor loaded - Correct RPC (eth_getLogs), Robust Solana RPC Handling (Free Endpoints), Fixed httpx client usage")
 
52
  self._initialize_contracts_db(contracts_db or {})
53
 
54
  self.data_manager = None
55
+ # الإصلاح: إضافة منظم طلبات لـ CoinGecko
56
+ self.coingecko_semaphore = asyncio.Semaphore(1) # 1 طلب متزامن فقط لـ CoinGecko
57
+ # ✅ الإصلاح: إضافة منظم طلبات لـ RPC
58
+ self.rpc_semaphore = asyncio.Semaphore(5) # 5 طلبات RPC متزامنة كحد أقصى (لجميع الشبكات)
59
+
60
  self.r2_service = r2_service
61
 
62
  self.address_labels = {}
 
206
  print(f"⚠️ مفتاح {env_var} غير متوفر أو فارغ.")
207
  setattr(self, attr_name, None)
208
  else:
209
+ # إذا لم تكن الخاصية موجودة أصلاً
210
  print(f"ℹ️ خاصية المفتاح {attr_name} (لـ {env_var}) غير معرفة في الكلاس.")
 
211
  setattr(self, attr_name, None)
212
 
213
  def _get_ethereum_rpc_endpoints(self):
 
298
  ]
299
 
300
  def _get_solana_rpc_endpoints(self):
301
+ """الحصول على نقاط RPC مجانية لشبكة Solana (بدون مفاتيح API) - محدثة"""
302
  endpoints = [
303
+ 'https://api.mainnet-beta.solana.com', # الرسمي (قد يكون محدود الطلبات بشدة)
304
+ 'https://solana-mainnet.publicnode.com', # PublicNode (جديد وموثوق)
305
+ 'https://ssc-dao.genesysgo.net/', # GenesysGo (قديم نسبياً)
306
+ 'https://solana-mainnet.rpc.extrnode.com', # Extrnode (مزود عام)
307
+ 'https://solana-mainnet.phantom.tech/', # Phantom Wallet RPC (قد يكون للاستخدام المحدود)
308
+ 'https://rpc.ankr.com/solana', # Ankr (يسبب 403 غالباً بدون مفتاح)
309
+ # 'https://mainnet.rpc.solana.pyth.network', # تمت إزالته (خطأ DNS)
310
  ]
311
  print(f"ℹ️ قائمة Solana RPC Endpoints (مجانية وبدون مفاتيح) المستخدمة: {endpoints}")
312
  return endpoints
 
391
  print(f" ℹ️ تم تحديث صيغة {updated_count} عقد إلى الصيغة الجديدة.")
392
  await self._save_contracts_to_r2()
393
 
 
394
  except ClientError as e:
395
  if e.response['Error']['Code'] == 'NoSuchKey':
396
  print("⚠️ لم يتم العثور على قاعدة بيانات العقود في R2. ستبدأ فارغة.")
 
416
  contract_info = None
417
  network = None
418
 
 
419
  if contract_address and isinstance(contract_address, str):
420
  network = self._detect_network_from_address(contract_address)
421
  contract_info_from_db = await self._find_contract_address_enhanced(symbol)
 
438
 
439
  print(f"🌐 البحث في الشبكة المحددة: {network_final} للعقد: {contract_address_final}")
440
 
 
441
  transfers = await self._get_targeted_transfer_data(contract_address_final, network_final)
442
 
443
  if not transfers:
444
  print(f"⚠️ لم يتم العثور على تحويلات للعملة {symbol} على شبكة {network_final}")
445
  return self._create_no_transfers_response(symbol)
446
 
 
447
  recent_transfers = self._filter_recent_transfers(transfers, max_minutes=120)
448
 
449
  if not recent_transfers:
 
452
 
453
  print(f"📊 تم جلب {len(recent_transfers)} تحويلة حديثة فريدة للعملة {symbol}")
454
 
 
455
  analysis = await self._analyze_enhanced_whale_impact(recent_transfers, symbol, contract_address_final, network_final)
456
 
457
  return analysis
 
572
  endpoint_name = endpoint.split('//')[1].split('/')[0] if '//' in endpoint else endpoint
573
  print(f" 🔍 محاولة جلب سجلات التحويلات لـ {network} عبر {endpoint_name}...")
574
 
575
+ # ✅ الإصلاح: استخدام "self.rpc_semaphore"
576
+ async with self.rpc_semaphore:
577
+ payload_block = {"jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": int(time.time())}
578
+ response_block = await self.http_client.post(endpoint, json=payload_block, timeout=15.0)
579
  response_block.raise_for_status()
580
  json_response_block = response_block.json()
581
  result_block = json_response_block.get('result')
 
586
  continue
587
  latest_block = int(result_block, 16)
588
 
589
+ async with self.rpc_semaphore:
590
+ payload_latest_block_time = {"jsonrpc": "2.0", "method": "eth_getBlockByNumber", "params": [hex(latest_block), False], "id": int(time.time())+1}
591
+ response_latest_time = await self.http_client.post(endpoint, json=payload_latest_block_time, timeout=15.0)
592
  latest_block_timestamp_approx = int(time.time())
593
  if response_latest_time.status_code == 200:
594
  latest_block_data = response_latest_time.json().get('result')
 
603
  "params": [{"fromBlock": hex(from_block), "toBlock": hex(latest_block), "address": contract_address_checksum, "topics": [TRANSFER_EVENT_SIGNATURE]}],
604
  "id": int(time.time())+2
605
  }
606
+ async with self.rpc_semaphore:
607
+ response_logs = await self.http_client.post(endpoint, json=payload_logs, timeout=45.0)
608
  response_logs.raise_for_status()
609
  json_response_logs = response_logs.json()
610
  logs = json_response_logs.get('result')
 
708
  "params": [ token_address, {"limit": limit, "commitment": "confirmed"} ]
709
  }
710
 
711
+ # ✅ الإصلاح: استخدام "self.rpc_semaphore"
712
+ async with self.rpc_semaphore:
713
+ response_signatures = await self.http_client.post(endpoint, json=payload_signatures, timeout=20.0)
714
 
715
  if response_signatures.status_code == 403:
716
  print(f" ⚠️ Solana endpoint {endpoint_name} failed: 403 Forbidden")
 
744
 
745
  processed_count = 0
746
  transaction_tasks = []
747
+
748
+ # ✅ الإصلاح: تمرير "self" إلى الدالة المساعدة
749
  for signature in signatures[:20]:
750
  transaction_tasks.append(
751
+ self._get_solana_transaction_detail_with_retry(self, signature, endpoint, self.http_client)
752
  )
753
  transaction_details = await asyncio.gather(*transaction_tasks)
754
 
 
798
  return []
799
 
800
 
801
+ # ✅ الإصلاح: إضافة "self" كباراميتر
802
  async def _get_solana_transaction_detail_with_retry(self, signature, endpoint, client: httpx.AsyncClient, retries=2, delay=0.5):
803
  """جلب تفاصيل معاملة Solana مع محاولات إعادة بسيطة باستخدام client مشترك"""
804
  last_exception = None
805
  for attempt in range(retries):
806
  try:
807
+ # الإصلاح: استخدام "self.rpc_semaphore"
808
+ async with self.rpc_semaphore:
809
+ detail = await self._get_solana_transaction_detail(signature, endpoint, client)
810
+
811
  if detail:
812
  return detail
813
  elif detail is None:
 
843
  "method": "getTransaction",
844
  "params": [ signature, {"encoding": "jsonParsed", "maxSupportedTransactionVersion": 0, "commitment": "confirmed"} ]
845
  }
846
+ # ✅ الإصلاح: لا يوجد semaphore هنا، يتم تطبيقه في الدالة التي تستدعيها
847
  response = await client.post(endpoint, json=payload, timeout=20.0)
848
  response.raise_for_status()
849
 
 
983
  if sender and receiver and amount_raw > 0: break
984
  if sender and receiver and amount_raw > 0: break
985
 
 
986
  if sender and receiver and amount_raw > 0:
987
  return {
988
  'hash': signature, 'from': sender, 'to': receiver,
 
1025
  "endblock": 999999999, "sort": "desc", "apikey": config['key']
1026
  }
1027
 
1028
+ # الإصلاح: استخدام "self.http_client" بدلاً من إنشاء client جديد
1029
+ # ويفضل استخدام semaphore خاص بالمستكشفات إذا كان لديهم حد طلبات صارم
1030
+ # حالياً، سنستخدم semaphore الـ RPC العام كإجراء وقائي
1031
+ async with self.rpc_semaphore:
1032
+ response = await self.http_client.get(config['url'], params=params, timeout=20.0)
1033
+
1034
+ if response.status_code == 200:
1035
+ data = response.json()
1036
+ status = str(data.get('status', '0'))
1037
+ message = data.get('message', '').upper()
1038
+
1039
+ if status == '1' and message == 'OK':
1040
+ transfers = data.get('result', [])
1041
+ processed_transfers = []
1042
+ for tf in transfers:
1043
+ if tf.get('tokenSymbol') and tf.get('contractAddress','').lower() == contract_address.lower():
1044
+ tf['network'] = network
1045
+ if 'from' in tf: tf['from'] = tf['from'].lower()
1046
+ if 'to' in tf: tf['to'] = tf['to'].lower()
1047
+ if 'logIndex' not in tf: tf['logIndex'] = 'N/A'
1048
+ processed_transfers.append(tf)
1049
+
1050
+ print(f" ✅ Explorer {network}: تم جلب {len(processed_transfers)} تحويلة توكن للعقد المحدد.")
1051
+ return processed_transfers
1052
+ elif status == '0' and "NO TRANSACTIONS FOUND" in message:
1053
+ print(f" ✅ Explorer {network}: لا توجد تحويلات.")
1054
+ return []
 
 
 
1055
  else:
1056
+ error_message = data.get('result', message)
1057
+ print(f"⚠️ Explorer {network} returned error: Status={status}, Message={message}, Result={error_message}")
1058
+ if "INVALID API KEY" in str(error_message).upper():
1059
+ print(f"🚨 خطأ فادح: مفتاح API الخاص بـ {network.upper()} غير صالح!")
1060
  return []
1061
+ else:
1062
+ print(f"⚠️ Explorer {network} request failed: {response.status_code}")
1063
+ return []
1064
 
1065
  except Exception as e:
1066
+ if "Cannot reopen a client instance" in str(e):
1067
+ print(f" ❌ فشل فادح (Explorer): محاولة استخدام client مغلق. {e}")
1068
+ return []
1069
  print(f"❌ فشل جلب بيانات Explorer لـ {network}: {e}")
1070
  return []
1071
 
 
1271
  "params": [{"to": contract_address, "data": "0x313ce567"}, "latest"],
1272
  "id": int(time.time())
1273
  }
1274
+ # الإصلاح: استخدام "self.rpc_semaphore"
1275
+ async with self.rpc_semaphore:
1276
+ response = await self.http_client.post(endpoint, json=payload, timeout=10.0)
1277
+
1278
  if response.status_code == 200:
1279
  result = response.json().get('result')
1280
  if result and result != '0x' and result.startswith('0x'):
 
1301
  print(f" ⚠️ استخدام قيمة decimals تقديرية لـ Solana: {estimated_decimals}")
1302
  return estimated_decimals
1303
 
 
1304
  print(f"❌ فشل جلب الكسور العشرية للعقد {contract_address} على شبكة {network} من جميع المصادر.")
1305
  return None
1306
 
 
1347
  'ATOM': 'cosmos', 'UNI': 'uniswap', 'AAVE': 'aave', 'KCS': 'kucoin-shares',
1348
  'MAVIA': 'heroes-of-mavia', 'COMMON': 'commonwealth', 'WLFI': 'wolfi',
1349
  'PINGPONG': 'pingpong', 'YB': 'yourbusd', 'REACT': 'react', 'XMN': 'xmine',
1350
+ 'ANOME': 'anome', 'ZEN': 'zencash', 'AKT': 'akash-network', 'UB': 'unibit', 'WLD': 'worldcoin'
1351
  }
1352
 
1353
  base_symbol = symbol.split('/')[0].upper()
 
1363
  max_retries = 2
1364
  for attempt in range(max_retries):
1365
  async with self.coingecko_semaphore:
1366
+ # ✅ الإصلاح: استخدام "self.http_client"
1367
+ try:
1368
+ response = await self.http_client.get(url, headers=headers, timeout=10.0) # استخدام headers محلي
1369
+ if response.status_code == 429:
1370
+ wait_time = 3 * (attempt + 1)
1371
+ print(f"⏰ Rate limit من CoinGecko لـ {symbol} (محاولة {attempt + 1}) - الانتظار {wait_time}s")
1372
+ await asyncio.sleep(wait_time)
1373
+ continue
1374
+
1375
+ response.raise_for_status()
1376
+ data = response.json()
1377
+ price_data = data.get(coingecko_id)
1378
+ if price_data and 'usd' in price_data:
1379
+ price = price_data['usd']
1380
+ if isinstance(price, (int, float)) and price > 0:
1381
+ return price
 
 
 
 
1382
  else:
1383
+ print(f"⚠️ استجابة CoinGecko تحتوي على سعر غير صالح لـ {symbol}: {price}")
1384
+ return 0
1385
+ else:
1386
+ if attempt == 0:
1387
+ print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في CoinGecko، محاولة البحث عن المعرف...")
1388
+ await asyncio.sleep(1.1)
1389
+ # الإصلاح: استخدام "self.http_client" للبحث
1390
+ found_id = await self._find_coingecko_id_via_search(base_symbol, self.http_client)
1391
+ if found_id and found_id != coingecko_id:
1392
+ print(f" 🔄 تم العثور على معرف بديل: {found_id}. إعادة المحاولة...")
1393
+ coingecko_id = found_id
1394
+ url = f"https://api.coingecko.com/api/v3/simple/price?ids={coingecko_id}&vs_currencies=usd"
1395
+ continue
1396
+ else:
1397
+ print(f" ❌ لم يتم العثور على معرف بديل لـ {base_symbol}.")
1398
+ return 0
1399
+ else:
1400
+ print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في استجابة CoinGecko بعد البحث.")
1401
+ return 0
1402
+
1403
+ except Exception as inner_e:
1404
+ if "Cannot reopen a client instance" in str(inner_e):
1405
+ print(f" ❌ فشل فادح (CoinGecko): محاولة استخدام client مغلق. {inner_e}")
1406
+ return 0 # لا يمكن المتابعة
1407
+ # التعامل مع الأخطاء الأخرى
1408
+ if isinstance(inner_e, httpx.HTTPStatusError):
1409
+ print(f"❌ خطأ HTTP من CoinGecko لـ {symbol}: {inner_e.response.status_code}")
1410
+ elif isinstance(inner_e, httpx.RequestError):
1411
+ print(f"❌ خطأ اتصال بـ CoinGecko لـ {symbol}: {inner_e}")
1412
+ else:
1413
+ print(f"❌ خطأ غير متوقع أثناء جلب السعر من CoinGecko لـ {symbol}: {inner_e}")
1414
+ return 0 # فشل في هذه المحاولة
1415
 
1416
  print(f"❌ فشل جلب السعر من CoinGecko لـ {symbol} بعد {max_retries} محاولات.")
1417
  return 0
 
1424
  """دالة مساعدة للبحث عن معرف CoinGecko"""
1425
  try:
1426
  search_url = f"https://api.coingecko.com/api/v3/search?query={symbol}"
1427
+ # الإصلاح: استخدام "self.coingecko_semaphore"
1428
+ async with self.coingecko_semaphore:
1429
+ response = await client.get(search_url, timeout=10.0)
1430
  response.raise_for_status()
1431
  data = response.json()
1432
  coins = data.get('coins', [])
 
1608
  max_retries = 2
1609
  for attempt in range(max_retries):
1610
  async with self.coingecko_semaphore:
1611
+ # الإصلاح: استخدام "self.http_client"
1612
+ try:
1613
+ print(f" 🔍 CoinGecko Search API Call (Attempt {attempt + 1}) for {symbol}")
1614
+ response = await self.http_client.get(search_url, headers=headers, timeout=15.0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1615
 
1616
+ if response.status_code == 429:
1617
+ wait_time = 3 * (attempt + 1)
1618
+ print(f" ⏰ Rate limit (Search) for {symbol} - Waiting {wait_time}s")
1619
+ await asyncio.sleep(wait_time)
1620
+ continue
1621
+
1622
+ response.raise_for_status()
1623
+ data = response.json()
1624
+ coins = data.get('coins', [])
1625
+
1626
+ if not coins:
1627
+ print(f" ❌ No matching coins found for {symbol} on CoinGecko")
1628
  return None
1629
+
1630
+ print(f" 📊 Found {len(coins)} potential matches for {symbol}")
1631
+
1632
+ best_coin = None
1633
+ search_symbol_lower = symbol.lower()
1634
+ for coin in coins:
1635
+ coin_symbol = coin.get('symbol', '').lower()
1636
+ coin_name = coin.get('name', '').lower()
1637
+ if coin_symbol == search_symbol_lower:
1638
+ best_coin = coin; print(f" ✅ Exact symbol match: {coin.get('name')}"); break
1639
+ if not best_coin and coin_name == search_symbol_lower:
1640
+ best_coin = coin; print(f" ✅ Exact name match: {coin.get('name')}")
1641
+ if not best_coin:
1642
+ best_coin = coins[0]; print(f" ⚠️ Using first result: {best_coin.get('name')}")
1643
+
1644
+ coin_id = best_coin.get('id')
1645
+ if not coin_id: print(f" ❌ No ID found"); return None
1646
+
1647
+ print(f" 🔍 Fetching details for CoinGecko ID: {coin_id}")
1648
+ detail_url = f"https://api.coingecko.com/api/v3/coins/{coin_id}"
1649
+ await asyncio.sleep(1.1)
1650
+ detail_response = await self.http_client.get(detail_url, headers=headers, timeout=15.0)
1651
+
1652
+ if detail_response.status_code == 429:
1653
+ wait_time = 5 * (attempt + 1)
1654
+ print(f" ⏰ Rate limit (Details) for {coin_id} - Waiting {wait_time}s")
1655
+ await asyncio.sleep(wait_time)
1656
+ detail_response = await self.http_client.get(detail_url, headers=headers, timeout=15.0)
1657
+
1658
+ detail_response.raise_for_status()
1659
+ detail_data = detail_response.json()
1660
+ platforms = detail_data.get('platforms', {})
1661
+ if not platforms: print(f" ❌ No platform data found"); return None
1662
+
1663
+ network_priority = ['ethereum', 'binance-smart-chain', 'polygon-pos', 'arbitrum-one', 'optimistic-ethereum', 'avalanche', 'fantom', 'solana']
1664
+ network_map = {'ethereum': 'ethereum', 'binance-smart-chain': 'bsc', 'polygon-pos': 'polygon', 'arbitrum-one': 'arbitrum', 'optimistic-ethereum': 'optimism', 'avalanche': 'avalanche', 'fantom': 'fantom', 'solana': 'solana'}
1665
+
1666
+ for platform_cg in network_priority:
1667
+ address = platforms.get(platform_cg)
1668
+ if address and isinstance(address, str) and address.strip():
1669
+ network = network_map.get(platform_cg)
1670
+ if network:
1671
+ print(f" ✅ Found contract on {network}: {address}")
1672
+ return address, network
1673
+
1674
+ print(f" ❌ No contract found on supported platforms for {coin_id}")
1675
+ return None
1676
+
1677
+ except Exception as inner_e:
1678
+ if "Cannot reopen a client instance" in str(inner_e):
1679
+ print(f" ❌ فشل فادح (CoinGecko): محاولة استخدام client مغلق. {inner_e}")
1680
+ return None
1681
+ if isinstance(inner_e, httpx.HTTPStatusError):
1682
+ print(f" �� HTTP Error from CoinGecko ({inner_e.request.url}): {inner_e.response.status_code}")
1683
+ elif isinstance(inner_e, httpx.RequestError):
1684
+ print(f" ❌ Connection Error with CoinGecko ({inner_e.request.url}): {inner_e}")
1685
+ else:
1686
+ print(f" ❌ Unexpected error during CoinGecko API call (Attempt {attempt + 1}): {inner_e}")
1687
+
1688
+ if attempt < max_retries - 1: await asyncio.sleep(1); continue
1689
+ else: return None
1690
 
1691
  print(f" ❌ Failed to get contract from CoinGecko for {symbol} after {max_retries} attempts.")
1692
  return None
 
1780
  }
1781
 
1782
  whale_signal = whale_data.get('trading_signal', {})
1783
+ analysis_details = whale_data
1784
 
1785
  return {
1786
  'action': whale_signal.get('action', 'HOLD'),
 
1811
  print(f"⚠️ خطأ أثناء إغلاق HTTP client لمراقب الحيتان: {e}")
1812
 
1813
 
1814
+ print("✅ EnhancedWhaleMonitor loaded - RPC Rate Limiting (Semaphore) & Updated Endpoints")