Riy777 commited on
Commit
42aed02
·
1 Parent(s): b935d3d

Update whale_news_data.py

Browse files
Files changed (1) hide show
  1. whale_news_data.py +150 -183
whale_news_data.py CHANGED
@@ -124,7 +124,7 @@ class EnhancedWhaleMonitor:
124
  'chain_id': 'mainnet-beta',
125
  'native_coin': 'SOL',
126
  'explorer': 'solscan',
127
- 'rpc_endpoints': self._get_solana_rpc_endpoints(), # ✅ استخدام الدالة المحدثة
128
  'type': 'solana'
129
  }
130
  }
@@ -294,24 +294,18 @@ class EnhancedWhaleMonitor:
294
  'https://rpc.ankr.com/fantom'
295
  ]
296
 
297
- # ✅✅✅ --- الدالة المحدثة لقائمة Solana RPC (بدون مفاتيح API) --- ✅✅✅
298
  def _get_solana_rpc_endpoints(self):
299
  """الحصول على نقاط RPC مجانية لشبكة Solana (بدون مفاتيح API)"""
300
  endpoints = [
301
- 'https://api.mainnet-beta.solana.com', # الرسمي (قد يكون محدود الطلبات بشدة)
302
- 'https://ssc-dao.genesysgo.net/', # GenesysGo (قديم نسبياً، لكن لا يزال يعمل أحياناً)
303
- 'https://solana-mainnet.rpc.extrnode.com', # Extrnode (مزود عام)
304
- 'https://solana-mainnet.phantom.tech/', # Phantom Wallet RPC (قد يكون للاستخدام المحدود)
305
- 'https://mainnet.rpc.solana.pyth.network', # Pyth Network (قد يكون مخصصًا لبيانات Pyth)
306
- # 'https://rpc.ankr.com/solana', # Ankr (يسبب 403 غالباً بدون مفتاح) - نجعله في النهاية كخيار أخير
307
- # --- نقاط عامة أخرى يمكن تجربتها إذا لزم الأمر ---
308
- # 'https://solana.public-rpc.com' # سبب مشاكل SSL سابقاً
309
  ]
310
- # إضافة Ankr في النهاية
311
- endpoints.append('https://rpc.ankr.com/solana')
312
  print(f"ℹ️ قائمة Solana RPC Endpoints (مجانية وبدون مفاتيح) المستخدمة: {endpoints}")
313
  return endpoints
314
- # ✅✅✅ --- نهاية الدالة المحدثة --- ✅✅✅
315
 
316
  def _initialize_comprehensive_exchange_addresses(self):
317
  """تهيئة قاعدة بيانات شاملة لعناوين كل المنصات"""
@@ -336,11 +330,10 @@ class EnhancedWhaleMonitor:
336
  'gate': [ '0x0d0707963952f2fba59dd06f2b425ace40b492fe', '0xc6f9741f5a5a676b91171804cf3c500ab438bb6e' ], # إضافة Gate.io
337
  'uniswap': [ '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad', '0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45' ],
338
  'pancakeswap': [ '0x10ed43c718714eb63d5aa57b78b54704e256024e', '0x13f4ea83d0bd40e75c8222255bc855a974568dd4' ],
339
- # عناوين Solana (تحقق من صحتها وتحديثها إذا لزم الأمر)
340
  'solana_kucoin': ['F3a5ZLPKUCrCj6CGKP2m9wS9Y2L8RcUBoJq9L2D2sUYi', '2AQdpHJ2JpcEgPiATFnAKfV9hPMvouWJAhKvamQ2Krqy' ],
341
  'solana_binance': ['5tzFkiKscXHK5ZXCGbXZJpXaWuBtZ6RrH8eL2a7Yi7Vn', '9WzDXwBbmkg8ZTbNMqUxvQRApF22xwsgMj2SUZrchK2E'],
342
- 'solana_coinbase': ['CeBiLnAE3UAW9mCDSjD1VULKLeQjefjN4d941fVKazSM'], # مثال لعنوان أحدث محتمل
343
- 'solana_wormhole': ['worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth'] # بوابة Wormhole
344
  }
345
 
346
  self.address_categories = {'exchange': set(), 'cex': set(), 'dex': set(), 'bridge': set(), 'whale': set(), 'unknown': set()}
@@ -352,7 +345,6 @@ class EnhancedWhaleMonitor:
352
  self.address_labels[addr_lower] = category
353
  self.address_categories['exchange'].add(addr_lower)
354
 
355
- # تصنيف أدق
356
  if category in ['kucoin', 'binance', 'coinbase', 'kraken', 'okx', 'gate', 'solana_kucoin', 'solana_binance', 'solana_coinbase']:
357
  self.address_categories['cex'].add(addr_lower)
358
  elif category in ['uniswap', 'pancakeswap']:
@@ -581,84 +573,85 @@ class EnhancedWhaleMonitor:
581
  endpoint_name = endpoint.split('//')[1].split('/')[0] if '//' in endpoint else endpoint
582
  print(f" 🔍 محاولة جلب سجلات التحويلات لـ {network} عبر {endpoint_name}...")
583
 
584
- async with self.http_client as client:
585
- payload_block = {"jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": int(time.time())}
586
- response_block = await client.post(endpoint, json=payload_block, timeout=15.0)
587
- response_block.raise_for_status()
588
- json_response_block = response_block.json()
589
- result_block = json_response_block.get('result')
590
- if not result_block:
591
- rpc_error = json_response_block.get('error')
592
- error_msg = rpc_error.get('message') if rpc_error else 'No block number result'
593
- print(f" ❌ لم يتم الحصول على رقم أحدث كتلة من {endpoint_name}: {error_msg}")
594
- continue
595
- latest_block = int(result_block, 16)
596
-
597
- payload_latest_block_time = {"jsonrpc": "2.0", "method": "eth_getBlockByNumber", "params": [hex(latest_block), False], "id": int(time.time())+1}
598
- response_latest_time = await client.post(endpoint, json=payload_latest_block_time, timeout=15.0)
599
- latest_block_timestamp_approx = int(time.time())
600
- if response_latest_time.status_code == 200:
601
- latest_block_data = response_latest_time.json().get('result')
602
- if latest_block_data and latest_block_data.get('timestamp'):
603
- latest_block_timestamp_approx = int(latest_block_data['timestamp'], 16)
604
-
605
- from_block = max(0, latest_block - blocks_to_scan)
606
- print(f" 📦 فحص الكتل من {from_block} إلى {latest_block} (آخر {blocks_to_scan} كتل تقريباً)")
607
-
608
- payload_logs = {
609
- "jsonrpc": "2.0", "method": "eth_getLogs",
610
- "params": [{"fromBlock": hex(from_block), "toBlock": hex(latest_block), "address": contract_address_checksum, "topics": [TRANSFER_EVENT_SIGNATURE]}],
611
- "id": int(time.time())+2
612
- }
613
- response_logs = await client.post(endpoint, json=payload_logs, timeout=45.0)
614
- response_logs.raise_for_status()
615
- json_response_logs = response_logs.json()
616
- logs = json_response_logs.get('result')
617
-
618
- if logs is None:
619
- rpc_error = json_response_logs.get('error')
620
- error_msg = rpc_error.get('message') if rpc_error else 'Invalid logs response'
621
- print(f" ❌ استجابة السجلات غير صالحة من {endpoint_name}: {error_msg}")
622
- continue
623
-
624
- if not logs:
625
- print(f" ✅ لا توجد سجلات تحويل لـ {contract_address} في آخر {blocks_to_scan} كتل عبر {endpoint_name}")
626
- successful_endpoint = endpoint_name
627
- break
628
-
629
- print(f" 📊 تم العثور على {len(logs)} سجل تحويل محتمل.")
630
-
631
- for log in logs:
632
- try:
633
- topics = log.get('topics', [])
634
- data = log.get('data', '0x')
635
- block_num_hex = log.get('blockNumber')
636
- tx_hash = log.get('transactionHash')
637
- log_index_hex = log.get('logIndex', '0x0')
638
-
639
- if len(topics) == 3 and data != '0x' and block_num_hex and tx_hash:
640
- sender = '0x' + topics[1][26:]
641
- receiver = '0x' + topics[2][26:]
642
- value = str(int(data, 16))
643
- block_num = str(int(block_num_hex, 16))
644
- log_index = str(int(log_index_hex, 16))
645
-
646
- transfers.append({
647
- 'hash': tx_hash, 'from': sender.lower(), 'to': receiver.lower(),
648
- 'value': value, 'timeStamp': str(latest_block_timestamp_approx),
649
- 'blockNumber': block_num, 'network': network, 'logIndex': log_index
650
- })
651
- else:
652
- print(f" ⚠️ سجل غير متوافق مع Transfer Event: Topics={len(topics)}, Data={data[:10]}...")
653
-
654
- except (ValueError, TypeError, KeyError, IndexError) as log_parse_error:
655
- print(f" ⚠️ خطأ في تحليل سجل فردي: {log_parse_error} - Log: {log}")
656
- continue
657
 
658
- print(f" ✅ تم تحليل {len(transfers)} تحويلة بنجاح من {endpoint_name}")
 
659
  successful_endpoint = endpoint_name
660
  break
661
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  except ssl.SSLCertVerificationError as ssl_err:
663
  print(f" ❌ خطأ SSL مع {endpoint_name}: {ssl_err}")
664
  continue
@@ -672,6 +665,11 @@ class EnhancedWhaleMonitor:
672
  print(f" ❌ خطأ في تحليل استجابة JSON من {endpoint_name}: {json_err}")
673
  continue
674
  except Exception as e:
 
 
 
 
 
675
  print(f" ❌ فشل غير متوقع مع {endpoint_name}: {e}")
676
  traceback.print_exc()
677
  continue
@@ -711,8 +709,8 @@ class EnhancedWhaleMonitor:
711
  "params": [ token_address, {"limit": limit, "commitment": "confirmed"} ]
712
  }
713
 
714
- async with self.http_client as client:
715
- response_signatures = await client.post(endpoint, json=payload_signatures, timeout=20.0)
716
 
717
  if response_signatures.status_code == 403:
718
  print(f" ⚠️ Solana endpoint {endpoint_name} failed: 403 Forbidden")
@@ -746,12 +744,12 @@ class EnhancedWhaleMonitor:
746
 
747
  processed_count = 0
748
  transaction_tasks = []
749
- async with self.http_client as client_details:
750
- for signature in signatures[:20]:
751
- transaction_tasks.append(
752
- self._get_solana_transaction_detail_with_retry(signature, endpoint, client_details)
753
- )
754
- transaction_details = await asyncio.gather(*transaction_tasks)
755
 
756
  for detail_result in transaction_details:
757
  if detail_result and self._is_solana_token_transfer(detail_result, token_address):
@@ -777,6 +775,10 @@ class EnhancedWhaleMonitor:
777
  print(f" ❌ خطأ في تحليل استجابة JSON من Solana ({endpoint_name}): {json_err}")
778
  continue
779
  except Exception as e:
 
 
 
 
780
  print(f" ❌ فشل غير متوقع مع endpoint Solana ({endpoint_name}): {e}")
781
  traceback.print_exc()
782
  continue
@@ -860,7 +862,7 @@ class EnhancedWhaleMonitor:
860
  print(f" ❌ خطأ SSL في getTransaction لـ {signature[:10]}... : {ssl_err}")
861
  raise ssl_err
862
  except json.JSONDecodeError as json_err:
863
- print(f" ❌ خطأ JSON في getTransaction لـ {signature[:10]}... : {json_err} - Response: {response.text[:200]}")
864
  return None
865
  except Exception as e:
866
  print(f" ❌ استثناء غير متوقع في getTransaction لـ {signature[:10]}... : {e}")
@@ -890,18 +892,16 @@ class EnhancedWhaleMonitor:
890
  all_balances = (post_balances or []) + (pre_balances or [])
891
  for balance in all_balances:
892
  if isinstance(balance, dict) and balance.get('mint') == token_address:
893
- # التحقق من وجود تغيير فعلي في الرصيد الخام
894
  pre_amount = next((pb.get('uiTokenAmount',{}).get('amount','0') for pb in (pre_balances or []) if isinstance(pb, dict) and pb.get('owner') == balance.get('owner') and pb.get('mint') == token_address), '0')
895
  post_amount = balance.get('uiTokenAmount',{}).get('amount','0')
896
  try:
897
  if int(post_amount) != int(pre_amount):
898
  return True
899
  except (ValueError, TypeError):
900
- pass # تجاهل إذا لم تكن الأرقام صحيحة
901
 
902
  return False
903
  except Exception as e:
904
- # print(f" ⚠️ خطأ في التحقق من نوع معاملة Solana: {e}")
905
  return False
906
 
907
  async def _parse_solana_transfer(self, transaction, token_address):
@@ -943,11 +943,9 @@ class EnhancedWhaleMonitor:
943
  amount_raw = diff_raw
944
  elif diff_raw < 0:
945
  sender = owner
946
- # لا نعيد التعيين، يجب أن يتطابق الفرق
947
 
948
  except (ValueError, TypeError):
949
  print(f" ⚠️ تحذير: فشل تحليل القيمة الخام لـ Solana ({owner}).")
950
- # محاولة استخدام uiAmount كبديل (أقل دقة)
951
  pre_ui = pre_bal_data.get('uiAmount', 0.0) or 0.0
952
  post_ui = post_bal_data.get('uiAmount', 0.0) or 0.0
953
  diff_ui = post_ui - pre_ui
@@ -959,7 +957,6 @@ class EnhancedWhaleMonitor:
959
  elif diff_ui < -1e-9:
960
  sender = owner
961
 
962
- # إذا لم نجد من الأرصدة، تحقق من التعليمات الداخلية
963
  if not sender or not receiver or amount_raw == 0:
964
  inner_instructions = meta.get('innerInstructions', [])
965
  for inner_inst_set in inner_instructions:
@@ -967,23 +964,20 @@ class EnhancedWhaleMonitor:
967
  parsed = inst.get('parsed')
968
  if isinstance(parsed, dict) and parsed.get('type') in ['transfer', 'transferChecked'] and parsed.get('info', {}).get('mint') == token_address:
969
  info = parsed.get('info', {})
970
- sender_inner = info.get('source') or info.get('authority') # Authority قد يكون هو المرسل الفعلي
971
  receiver_inner = info.get('destination')
972
- amount_inner_str = info.get('tokenAmount', {}).get('amount') or info.get('amount') # اسم الحقل قد يختلف
973
  try:
974
  amount_inner_raw = int(amount_inner_str)
975
  if sender_inner and receiver_inner and amount_inner_raw > 0:
976
  sender = sender_inner
977
  receiver = receiver_inner
978
  amount_raw = amount_inner_raw
979
- # الخروج من الحلقات بمجرد العثور على تحويل داخلي صالح
980
  break
981
  except (ValueError, TypeError): continue
982
- if sender and receiver and amount_raw > 0: break # الخروج من الحلقة الخارجية
983
- if sender and receiver and amount_raw > 0: break # الخروج من الحلقة الخارجية
984
-
985
 
986
- # التحقق النهائي
987
  if sender and receiver and amount_raw > 0:
988
  return {
989
  'hash': signature, 'from': sender, 'to': receiver,
@@ -992,7 +986,6 @@ class EnhancedWhaleMonitor:
992
  'type': 'solana_transfer', 'logIndex': '0'
993
  }
994
  else:
995
- # print(f" ℹ️ معاملة Solana {signature[:10]}... لم يتم التعرف عليها كتحويل صالح.")
996
  return None
997
 
998
  except Exception as e:
@@ -1012,30 +1005,22 @@ class EnhancedWhaleMonitor:
1012
  'ethereum': {'url': 'https://api.etherscan.io/api', 'key': self.etherscan_key},
1013
  'bsc': {'url': 'https://api.bscscan.com/api', 'key': self.bscscan_key},
1014
  'polygon': {'url': 'https://api.polygonscan.com/api', 'key': self.polygonscan_key}
1015
- # يمكن إضافة المزيد هنا للمستكشفات الأخرى إذا كان لديك مفاتيح API لها
1016
  }
1017
 
1018
  if network not in explorer_config or not explorer_config[network].get('key'):
1019
- # print(f"⚠️ لا يوجد مستكشف مدعوم أو مفتاح API لشبكة {network}")
1020
  return []
1021
 
1022
  config = explorer_config[network]
1023
- # تحديد المعامل الصحيح لعنوان العقد
1024
- address_param = "contractaddress" if network == 'ethereum' else "address" # BscScan و PolygonScan يستخدمان address
1025
 
1026
  params = {
1027
- "module": "account",
1028
- "action": "tokentx",
1029
- address_param: contract_address, # استخدام المعامل الصحيح
1030
- "page": 1,
1031
- "offset": 100,
1032
- "startblock": 0,
1033
- "endblock": 999999999,
1034
- "sort": "desc",
1035
- "apikey": config['key']
1036
  }
1037
 
1038
- async with httpx.AsyncClient(timeout=20.0) as client:
1039
  response = await client.get(config['url'], params=params)
1040
 
1041
  if response.status_code == 200:
@@ -1047,13 +1032,11 @@ class EnhancedWhaleMonitor:
1047
  transfers = data.get('result', [])
1048
  processed_transfers = []
1049
  for tf in transfers:
1050
- # فلترة إضافية للتأكد من أنه توكن ERC20 وليس Native Coin Transfer
1051
- # وأنه للعقد الصحيح (بعض APIs قد تعيد نتائج أوسع)
1052
  if tf.get('tokenSymbol') and tf.get('contractAddress','').lower() == contract_address.lower():
1053
  tf['network'] = network
1054
  if 'from' in tf: tf['from'] = tf['from'].lower()
1055
  if 'to' in tf: tf['to'] = tf['to'].lower()
1056
- if 'logIndex' not in tf: tf['logIndex'] = 'N/A' # إضافة للتوحيد
1057
  processed_transfers.append(tf)
1058
 
1059
  print(f" ✅ Explorer {network}: تم جلب {len(processed_transfers)} تحويلة توكن للعقد المحدد.")
@@ -1231,6 +1214,7 @@ class EnhancedWhaleMonitor:
1231
  'llm_friendly_summary': llm_summary
1232
  }
1233
 
 
1234
  async def _get_accurate_token_value_optimized(self, raw_value, decimals, symbol):
1235
  """حساب القيمة بالدولار باستخدام decimals مُمررة والسعر الحالي"""
1236
  try:
@@ -1276,8 +1260,8 @@ class EnhancedWhaleMonitor:
1276
  "params": [{"to": contract_address, "data": "0x313ce567"}, "latest"],
1277
  "id": int(time.time())
1278
  }
1279
- async with self.http_client as client:
1280
- response = await client.post(endpoint, json=payload, timeout=10.0)
1281
  if response.status_code == 200:
1282
  result = response.json().get('result')
1283
  if result and result != '0x' and result.startswith('0x'):
@@ -1291,18 +1275,20 @@ class EnhancedWhaleMonitor:
1291
  print(f" ⚠️ فشل تحويل نتيجة decimals من RPC ({result}) لـ {contract_address} على {network}")
1292
  continue
1293
  except Exception as rpc_decimal_error:
 
 
 
 
1294
  print(f" ⚠️ خطأ RPC أثناء جلب decimals من {endpoint.split('//')[-1]}: {rpc_decimal_error}")
1295
  continue
1296
 
1297
  elif network == 'solana':
1298
  print(f"ℹ️ جلب decimals لـ Solana ({contract_address}) غير مدعوم حاليًا عبر RPC.")
1299
- # تقدير شائع لـ SPL tokens، لكنه قد يكون خاطئًا
1300
- estimated_decimals = 9 # أو 6 هي قيم شائعة أخرى
1301
  self.token_decimals_cache[cache_key] = estimated_decimals
1302
  print(f" ⚠️ استخدام قيمة decimals تقديرية لـ Solana: {estimated_decimals}")
1303
  return estimated_decimals
1304
 
1305
-
1306
  print(f"❌ فشل جلب الكسور العشرية للعقد {contract_address} على شبكة {network} من جميع المصادر.")
1307
  return None
1308
 
@@ -1319,13 +1305,11 @@ class EnhancedWhaleMonitor:
1319
  if cache_entry and time.time() - cache_entry.get('timestamp', 0) < 300:
1320
  return cache_entry['price']
1321
 
1322
- # المحاولة 1: CoinGecko API
1323
  price = await self._get_token_price_from_coingecko(symbol)
1324
  if price is not None and price > 0:
1325
  self.token_price_cache[base_symbol] = {'price': price, 'timestamp': time.time()}
1326
  return price
1327
 
1328
- # المحاولة 2: KuCoin API (ccxt.pro)
1329
  price = await self._get_token_price_from_kucoin(symbol)
1330
  if price is not None and price > 0:
1331
  self.token_price_cache[base_symbol] = {'price': price, 'timestamp': time.time()}
@@ -1349,7 +1333,6 @@ class EnhancedWhaleMonitor:
1349
  'LTC': 'litecoin', 'XLM': 'stellar', 'TRX': 'tron', 'EOS': 'eos', 'XMR': 'monero',
1350
  'SOL': 'solana', 'MATIC': 'matic-network', 'AVAX': 'avalanche-2', 'LINK': 'chainlink',
1351
  'ATOM': 'cosmos', 'UNI': 'uniswap', 'AAVE': 'aave', 'KCS': 'kucoin-shares',
1352
- # يجب إضافة المزيد من العملات هنا لزيادة التغطية
1353
  'MAVIA': 'heroes-of-mavia', 'COMMON': 'commonwealth', 'WLFI': 'wolfi',
1354
  'PINGPONG': 'pingpong', 'YB': 'yourbusd', 'REACT': 'react', 'XMN': 'xmine',
1355
  'ANOME': 'anome', 'ZEN': 'zencash', 'AKT': 'akash-network', 'UB': 'unibit'
@@ -1358,7 +1341,7 @@ class EnhancedWhaleMonitor:
1358
  base_symbol = symbol.split('/')[0].upper()
1359
  coingecko_id = symbol_mapping.get(base_symbol)
1360
  if not coingecko_id:
1361
- coingecko_id = base_symbol.lower() # محاولة استخدام الرمز الصغير
1362
  print(f"ℹ️ رمز {base_symbol} غير موجود في symbol_mapping، استخدام {coingecko_id} لـ CoinGecko.")
1363
 
1364
 
@@ -1368,6 +1351,7 @@ class EnhancedWhaleMonitor:
1368
  max_retries = 2
1369
  for attempt in range(max_retries):
1370
  async with self.coingecko_semaphore:
 
1371
  async with httpx.AsyncClient(timeout=10.0, headers=headers, verify=self.ssl_context) as client:
1372
  try:
1373
  response = await client.get(url)
@@ -1388,25 +1372,22 @@ class EnhancedWhaleMonitor:
1388
  print(f"⚠️ استجابة CoinGecko تحتوي على سعر غير صالح لـ {symbol}: {price}")
1389
  return 0
1390
  else:
1391
- # محاولة البحث عن المعرف الصحيح إذا فشلت المحاولة الأولى
1392
  if attempt == 0:
1393
  print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في CoinGecko، محاولة البحث عن المعرف...")
1394
- await asyncio.sleep(1.1) # انتظار قبل البحث
1395
  found_id = await self._find_coingecko_id_via_search(base_symbol, client)
1396
  if found_id and found_id != coingecko_id:
1397
  print(f" 🔄 تم العثور على معرف بديل: {found_id}. إعادة المحاولة...")
1398
- coingecko_id = found_id # تحديث المعرف وإعادة المحاولة في الحلقة
1399
- # تعديل URL
1400
  url = f"https://api.coingecko.com/api/v3/simple/price?ids={coingecko_id}&vs_currencies=usd"
1401
- continue # العودة لبداية الحلقة مع المعرف الجديد
1402
  else:
1403
  print(f" ❌ لم يتم العثور على معرف بديل لـ {base_symbol}.")
1404
- return 0 # فشل البحث عن المعرف
1405
- else: # فشل بعد المحاولة الثانية
1406
  print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في استجابة CoinGecko بعد البحث.")
1407
  return 0
1408
 
1409
- # --- معالجة الأخطاء تبقى كما هي ---
1410
  except httpx.HTTPStatusError as http_err:
1411
  print(f"❌ خطأ HTTP من CoinGecko لـ {symbol}: {http_err.response.status_code}")
1412
  return 0
@@ -1424,8 +1405,8 @@ class EnhancedWhaleMonitor:
1424
  print(f"❌ فشل عام في _get_token_price_from_coingecko لـ {symbol}: {e}")
1425
  return 0
1426
 
1427
- # دالة مساعدة للبحث عن معرف CoinGecko
1428
  async def _find_coingecko_id_via_search(self, symbol, client: httpx.AsyncClient):
 
1429
  try:
1430
  search_url = f"https://api.coingecko.com/api/v3/search?query={symbol}"
1431
  response = await client.get(search_url)
@@ -1433,12 +1414,10 @@ class EnhancedWhaleMonitor:
1433
  data = response.json()
1434
  coins = data.get('coins', [])
1435
  if coins:
1436
- # البحث عن تطابق الرمز أولاً
1437
  search_symbol_lower = symbol.lower()
1438
  for coin in coins:
1439
  if coin.get('symbol', '').lower() == search_symbol_lower:
1440
  return coin.get('id')
1441
- # إذا لم نجد، نأخذ معرف أول نتيجة
1442
  return coins[0].get('id')
1443
  return None
1444
  except Exception as e:
@@ -1450,12 +1429,11 @@ class EnhancedWhaleMonitor:
1450
  """جلب سعر العملة من KuCoin كبديل (بشكل غير متزامن)"""
1451
  exchange = None
1452
  try:
1453
- # استخدام enableRateLimit مهم لـ ccxt.pro
1454
  exchange = ccxt.kucoin({'enableRateLimit': True})
1455
- # التحقق من أن السوق موجود قبل محاولة جلبه
1456
  markets = await exchange.load_markets()
1457
  if symbol not in markets:
1458
  print(f"⚠️ الرمز {symbol} غير موجود في أسواق KuCoin.")
 
1459
  return 0
1460
 
1461
  ticker = await exchange.fetch_ticker(symbol)
@@ -1480,7 +1458,6 @@ class EnhancedWhaleMonitor:
1480
  print(f"❌ خطأ شبكة أثناء جلب السعر من KuCoin لـ {symbol}: {e}")
1481
  return 0
1482
  except ccxt.ExchangeError as e:
1483
- # تحسين طباعة الخطأ
1484
  print(f"❌ خطأ منصة KuCoin ({type(e).__name__}) لـ {symbol}: {e}")
1485
  return 0
1486
  except Exception as e:
@@ -1500,50 +1477,42 @@ class EnhancedWhaleMonitor:
1500
  abs_net_flow = abs(net_flow_usd)
1501
  total_flow_volume = total_to_exchanges_usd + total_from_exchanges_usd
1502
 
1503
- # مضاعف الثقة يعتمد بشكل أكبر على الحجم المطلق للتدفق وعدد التحويلات
1504
  confidence_multiplier = min( (abs_net_flow / 500000.0) + (whale_transfers_count / 5.0) , 1.5)
1505
-
1506
  base_confidence = 0.5 + (network_strength * 0.1)
1507
 
1508
  action = 'HOLD'
1509
  reason = f'نشاط حيتان عبر {len(network_stats)} شبكة: {whale_transfers_count} تحويلة كبيرة بقيمة إجمالية ${total_flow_volume:,.0f}. صافي التدفق ${net_flow_usd:,.0f}'
1510
  critical_alert = False
1511
 
1512
- # شروط البيع
1513
- if net_flow_usd > 500000 and deposit_count >= 2: # قوي
1514
  action = 'STRONG_SELL'
1515
  confidence = min(0.7 + (base_confidence * confidence_multiplier * 0.3), 0.95)
1516
  reason = f'ضغط بيعي قوي عبر {len(network_stats)} شبكة: ${net_flow_usd:,.0f} إيداع للمنصات ({deposit_count} تحويلة).'
1517
  critical_alert = abs_net_flow > 1000000
1518
- elif net_flow_usd > 150000 and (deposit_count >= 1 or whale_transfers_count > 0): # عادي (حد أدنى أقل)
1519
  action = 'SELL'
1520
  confidence = min(0.6 + (base_confidence * confidence_multiplier * 0.2), 0.85)
1521
  reason = f'ضغط بيعي محتمل عبر {len(network_stats)} شبكة: ${net_flow_usd:,.0f} إيداع للمنصات.'
1522
  critical_alert = abs_net_flow > 750000
1523
-
1524
- # شروط الشراء
1525
- elif net_flow_usd < -500000 and withdrawal_count >= 2: # قوي
1526
  action = 'STRONG_BUY'
1527
  confidence = min(0.7 + (base_confidence * confidence_multiplier * 0.3), 0.95)
1528
  reason = f'تراكم شرائي قوي عبر {len(network_stats)} شبكة: ${abs_net_flow:,.0f} سحب من المنصات ({withdrawal_count} تحويلة).'
1529
  critical_alert = abs_net_flow > 1000000
1530
- elif net_flow_usd < -150000 and (withdrawal_count >= 1 or whale_transfers_count > 0): # عادي (حد أدنى أقل)
1531
  action = 'BUY'
1532
  confidence = min(0.6 + (base_confidence * confidence_multiplier * 0.2), 0.85)
1533
  reason = f'تراكم شرائي محتمل عبر {len(network_stats)} شبكة: ${abs_net_flow:,.0f} سحب من المنصات.'
1534
  critical_alert = abs_net_flow > 750000
1535
-
1536
- # حالة HOLD
1537
  else:
1538
  if whale_transfers_count > 0 and abs_net_flow < 100000:
1539
  confidence = min(base_confidence * 1.1, 0.6)
1540
  reason += " (صافي التدفق منخفض نسبياً)"
1541
- # تعديل: إذا كان هناك تحويلات حيتان لكن التدفق متعادل، قد تكون الثقة أقل من الأساسية
1542
  elif whale_transfers_count > 0 and abs_net_flow < 50000:
1543
- confidence = max(0.4, base_confidence * 0.9) # تقليل الثقة قليلاً للنشاط غير الواضح
1544
  reason += " (صافي التدفق شبه متعادل)"
1545
  elif whale_transfers_count == 0:
1546
- confidence = 0.3 # ثقة منخفضة جدًا لعدم وجود حيتان
1547
  reason = 'لا توجد تحركات حيتان كبيرة في الساعات الأخيرة'
1548
  else:
1549
  confidence = base_confidence
@@ -1581,33 +1550,29 @@ class EnhancedWhaleMonitor:
1581
 
1582
  print(f"🔍 البحث عن عقد للعملة: {symbol}")
1583
 
1584
- # 1. البحث في قاعدة البيانات المحلية
1585
  if symbol_lower in self.contracts_db:
1586
  contract_info = self.contracts_db[symbol_lower]
1587
- if isinstance(contract_info, str): # التحويل للصيغة الجديدة إذا لزم الأمر
1588
  network = self._detect_network_from_address(contract_info)
1589
  contract_info = {'address': contract_info, 'network': network}
1590
  self.contracts_db[symbol_lower] = contract_info
1591
- # التأكد من وجود المفاتيح المطلوبة
1592
  if 'address' in contract_info and 'network' in contract_info:
1593
  print(f" ✅ وجد في قاعدة البيانات المحلية: {contract_info}")
1594
  return contract_info
1595
  else:
1596
  print(f" ⚠️ بيانات العقد غير مكتملة في القاعدة المحلية لـ {symbol}: {contract_info}")
1597
- # سنحاول البحث عبر CoinGecko كبديل
1598
 
1599
- # 2. البحث في CoinGecko
1600
  print(f" 🔍 البحث في CoinGecko عن {base_symbol}...")
1601
  coingecko_result = await self._find_contract_via_coingecko(base_symbol)
1602
 
1603
  if coingecko_result:
1604
  address, network = coingecko_result
1605
  contract_info = {'address': address, 'network': network}
1606
- self.contracts_db[symbol_lower] = contract_info # حفظ النتيجة في الذاكرة
1607
  print(f" ✅ تم العثور على عقد {symbol} عبر CoinGecko على شبكة {network}: {address}")
1608
 
1609
  if self.r2_service:
1610
- await self._save_contracts_to_r2() # حفظ التحديث في R2
1611
 
1612
  return contract_info
1613
 
@@ -1624,6 +1589,7 @@ class EnhancedWhaleMonitor:
1624
  max_retries = 2
1625
  for attempt in range(max_retries):
1626
  async with self.coingecko_semaphore:
 
1627
  async with httpx.AsyncClient(timeout=15, headers=headers, verify=self.ssl_context) as client:
1628
  try:
1629
  print(f" 🔍 CoinGecko Search API Call (Attempt {attempt + 1}) for {symbol}")
@@ -1690,9 +1656,12 @@ class EnhancedWhaleMonitor:
1690
  print(f" ❌ No contract found on supported platforms for {coin_id}")
1691
  return None
1692
 
1693
- # --- معالجة الأخطاء تبقى كما هي ---
1694
- except httpx.HTTPStatusError as http_err: print(f" ❌ HTTP Error from CoinGecko ({http_err.request.url}): {http_err.response.status_code}"); return None
1695
- except httpx.RequestError as req_err: print(f" ❌ Connection Error with CoinGecko ({req_err.request.url}): {req_err}"); return None
 
 
 
1696
  except Exception as inner_e:
1697
  print(f" ❌ Unexpected error during CoinGecko API call (Attempt {attempt + 1}): {inner_e}")
1698
  if attempt < max_retries - 1: await asyncio.sleep(1); continue
@@ -1790,9 +1759,7 @@ class EnhancedWhaleMonitor:
1790
  }
1791
 
1792
  whale_signal = whale_data.get('trading_signal', {})
1793
-
1794
- # إضافة تفاصيل التحليل المتاحة
1795
- analysis_details = whale_data # استخدام القاموس الكامل كـ تفاصيل
1796
 
1797
  return {
1798
  'action': whale_signal.get('action', 'HOLD'),
@@ -1801,7 +1768,7 @@ class EnhancedWhaleMonitor:
1801
  'source': 'whale_analysis',
1802
  'critical_alert': whale_signal.get('critical_alert', False),
1803
  'data_available': True,
1804
- 'whale_analysis_details': analysis_details # إرجاع كافة التفاصيل
1805
  }
1806
 
1807
  except Exception as e:
@@ -1823,4 +1790,4 @@ class EnhancedWhaleMonitor:
1823
  print(f"⚠️ خطأ أثناء إغلاق HTTP client لمراقب الحيتان: {e}")
1824
 
1825
 
1826
- print("✅ EnhancedWhaleMonitor loaded - Correct RPC (eth_getLogs), Robust Solana RPC Handling (Free Endpoints)")
 
124
  'chain_id': 'mainnet-beta',
125
  'native_coin': 'SOL',
126
  'explorer': 'solscan',
127
+ 'rpc_endpoints': self._get_solana_rpc_endpoints(),
128
  'type': 'solana'
129
  }
130
  }
 
294
  'https://rpc.ankr.com/fantom'
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
 
309
 
310
  def _initialize_comprehensive_exchange_addresses(self):
311
  """تهيئة قاعدة بيانات شاملة لعناوين كل المنصات"""
 
330
  'gate': [ '0x0d0707963952f2fba59dd06f2b425ace40b492fe', '0xc6f9741f5a5a676b91171804cf3c500ab438bb6e' ], # إضافة Gate.io
331
  'uniswap': [ '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad', '0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45' ],
332
  'pancakeswap': [ '0x10ed43c718714eb63d5aa57b78b54704e256024e', '0x13f4ea83d0bd40e75c8222255bc855a974568dd4' ],
 
333
  'solana_kucoin': ['F3a5ZLPKUCrCj6CGKP2m9wS9Y2L8RcUBoJq9L2D2sUYi', '2AQdpHJ2JpcEgPiATFnAKfV9hPMvouWJAhKvamQ2Krqy' ],
334
  'solana_binance': ['5tzFkiKscXHK5ZXCGbXZJpXaWuBtZ6RrH8eL2a7Yi7Vn', '9WzDXwBbmkg8ZTbNMqUxvQRApF22xwsgMj2SUZrchK2E'],
335
+ 'solana_coinbase': ['CeBiLnAE3UAW9mCDSjD1VULKLeQjefjN4d941fVKazSM'],
336
+ 'solana_wormhole': ['worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth']
337
  }
338
 
339
  self.address_categories = {'exchange': set(), 'cex': set(), 'dex': set(), 'bridge': set(), 'whale': set(), 'unknown': set()}
 
345
  self.address_labels[addr_lower] = category
346
  self.address_categories['exchange'].add(addr_lower)
347
 
 
348
  if category in ['kucoin', 'binance', 'coinbase', 'kraken', 'okx', 'gate', 'solana_kucoin', 'solana_binance', 'solana_coinbase']:
349
  self.address_categories['cex'].add(addr_lower)
350
  elif category in ['uniswap', 'pancakeswap']:
 
573
  endpoint_name = endpoint.split('//')[1].split('/')[0] if '//' in endpoint else endpoint
574
  print(f" 🔍 محاولة جلب سجلات التحويلات لـ {network} عبر {endpoint_name}...")
575
 
576
+ # ✅ الإصلاح: عدم استخدام "async with self.http_client"
577
+ # استخدام "self.http_client" مباشرة
578
+ payload_block = {"jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": int(time.time())}
579
+ response_block = await self.http_client.post(endpoint, json=payload_block, timeout=15.0)
580
+ response_block.raise_for_status()
581
+ json_response_block = response_block.json()
582
+ result_block = json_response_block.get('result')
583
+ if not result_block:
584
+ rpc_error = json_response_block.get('error')
585
+ error_msg = rpc_error.get('message') if rpc_error else 'No block number result'
586
+ print(f" ❌ لم يتم الحصول على رقم أحدث كتلة من {endpoint_name}: {error_msg}")
587
+ continue
588
+ latest_block = int(result_block, 16)
589
+
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')
595
+ if latest_block_data and latest_block_data.get('timestamp'):
596
+ latest_block_timestamp_approx = int(latest_block_data['timestamp'], 16)
597
+
598
+ from_block = max(0, latest_block - blocks_to_scan)
599
+ print(f" 📦 فحص الكتل من {from_block} إلى {latest_block} (آخر {blocks_to_scan} كتل تقريباً)")
600
+
601
+ payload_logs = {
602
+ "jsonrpc": "2.0", "method": "eth_getLogs",
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
+ response_logs = await self.http_client.post(endpoint, json=payload_logs, timeout=45.0)
607
+ response_logs.raise_for_status()
608
+ json_response_logs = response_logs.json()
609
+ logs = json_response_logs.get('result')
610
+
611
+ if logs is None:
612
+ rpc_error = json_response_logs.get('error')
613
+ error_msg = rpc_error.get('message') if rpc_error else 'Invalid logs response'
614
+ print(f" ❌ استجابة السجلات غير صالحة من {endpoint_name}: {error_msg}")
615
+ continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616
 
617
+ if not logs:
618
+ print(f" ✅ لا توجد سجلات تحويل لـ {contract_address} في آخر {blocks_to_scan} كتل عبر {endpoint_name}")
619
  successful_endpoint = endpoint_name
620
  break
621
 
622
+ print(f" 📊 تم العثور على {len(logs)} سجل تحويل محتمل.")
623
+
624
+ for log in logs:
625
+ try:
626
+ topics = log.get('topics', [])
627
+ data = log.get('data', '0x')
628
+ block_num_hex = log.get('blockNumber')
629
+ tx_hash = log.get('transactionHash')
630
+ log_index_hex = log.get('logIndex', '0x0')
631
+
632
+ if len(topics) == 3 and data != '0x' and block_num_hex and tx_hash:
633
+ sender = '0x' + topics[1][26:]
634
+ receiver = '0x' + topics[2][26:]
635
+ value = str(int(data, 16))
636
+ block_num = str(int(block_num_hex, 16))
637
+ log_index = str(int(log_index_hex, 16))
638
+
639
+ transfers.append({
640
+ 'hash': tx_hash, 'from': sender.lower(), 'to': receiver.lower(),
641
+ 'value': value, 'timeStamp': str(latest_block_timestamp_approx),
642
+ 'blockNumber': block_num, 'network': network, 'logIndex': log_index
643
+ })
644
+ else:
645
+ print(f" ⚠️ سجل غير متوافق مع Transfer Event: Topics={len(topics)}, Data={data[:10]}...")
646
+
647
+ except (ValueError, TypeError, KeyError, IndexError) as log_parse_error:
648
+ print(f" ⚠️ خطأ في تحليل سجل فردي: {log_parse_error} - Log: {log}")
649
+ continue
650
+
651
+ print(f" ✅ تم تحليل {len(transfers)} تحويلة بنجاح من {endpoint_name}")
652
+ successful_endpoint = endpoint_name
653
+ break
654
+
655
  except ssl.SSLCertVerificationError as ssl_err:
656
  print(f" ❌ خطأ SSL مع {endpoint_name}: {ssl_err}")
657
  continue
 
665
  print(f" ❌ خطأ في تحليل استجابة JSON من {endpoint_name}: {json_err}")
666
  continue
667
  except Exception as e:
668
+ # ✅ التعامل مع الخطأ الذي أبلغت عنه
669
+ if "Cannot reopen a client instance" in str(e):
670
+ print(f" ❌ فشل فادح: محاولة استخدام client مغلق. {e}")
671
+ # هذا لا يجب أن يحدث الآن، ولكن كإجراء وقائي، نكسر الحلقة
672
+ return transfers # إرجاع ما تم تجميعه حتى الآن
673
  print(f" ❌ فشل غير متوقع مع {endpoint_name}: {e}")
674
  traceback.print_exc()
675
  continue
 
709
  "params": [ token_address, {"limit": limit, "commitment": "confirmed"} ]
710
  }
711
 
712
+ # ✅ الإصلاح: عدم استخدام "async with self.http_client"
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
+ # الإصلاح: تمرير "self.http_client" مباشرة
748
+ for signature in signatures[:20]:
749
+ transaction_tasks.append(
750
+ self._get_solana_transaction_detail_with_retry(signature, endpoint, self.http_client)
751
+ )
752
+ transaction_details = await asyncio.gather(*transaction_tasks)
753
 
754
  for detail_result in transaction_details:
755
  if detail_result and self._is_solana_token_transfer(detail_result, token_address):
 
775
  print(f" ❌ خطأ في تحليل استجابة JSON من Solana ({endpoint_name}): {json_err}")
776
  continue
777
  except Exception as e:
778
+ # ✅ التعامل مع الخطأ الذي أبلغت عنه
779
+ if "Cannot reopen a client instance" in str(e):
780
+ print(f" ❌ فشل فادح: محاولة استخدام client مغلق. {e}")
781
+ return transfers
782
  print(f" ❌ فشل غير متوقع مع endpoint Solana ({endpoint_name}): {e}")
783
  traceback.print_exc()
784
  continue
 
862
  print(f" ❌ خطأ SSL في getTransaction لـ {signature[:10]}... : {ssl_err}")
863
  raise ssl_err
864
  except json.JSONDecodeError as json_err:
865
+ print(f" ❌ خطأ JSON في getTransaction لـ {signature[:10]}... : {response.text[:200]}")
866
  return None
867
  except Exception as e:
868
  print(f" ❌ استثناء غير متوقع في getTransaction لـ {signature[:10]}... : {e}")
 
892
  all_balances = (post_balances or []) + (pre_balances or [])
893
  for balance in all_balances:
894
  if isinstance(balance, dict) and balance.get('mint') == token_address:
 
895
  pre_amount = next((pb.get('uiTokenAmount',{}).get('amount','0') for pb in (pre_balances or []) if isinstance(pb, dict) and pb.get('owner') == balance.get('owner') and pb.get('mint') == token_address), '0')
896
  post_amount = balance.get('uiTokenAmount',{}).get('amount','0')
897
  try:
898
  if int(post_amount) != int(pre_amount):
899
  return True
900
  except (ValueError, TypeError):
901
+ pass
902
 
903
  return False
904
  except Exception as e:
 
905
  return False
906
 
907
  async def _parse_solana_transfer(self, transaction, token_address):
 
943
  amount_raw = diff_raw
944
  elif diff_raw < 0:
945
  sender = owner
 
946
 
947
  except (ValueError, TypeError):
948
  print(f" ⚠️ تحذير: فشل تحليل القيمة الخام لـ Solana ({owner}).")
 
949
  pre_ui = pre_bal_data.get('uiAmount', 0.0) or 0.0
950
  post_ui = post_bal_data.get('uiAmount', 0.0) or 0.0
951
  diff_ui = post_ui - pre_ui
 
957
  elif diff_ui < -1e-9:
958
  sender = owner
959
 
 
960
  if not sender or not receiver or amount_raw == 0:
961
  inner_instructions = meta.get('innerInstructions', [])
962
  for inner_inst_set in inner_instructions:
 
964
  parsed = inst.get('parsed')
965
  if isinstance(parsed, dict) and parsed.get('type') in ['transfer', 'transferChecked'] and parsed.get('info', {}).get('mint') == token_address:
966
  info = parsed.get('info', {})
967
+ sender_inner = info.get('source') or info.get('authority')
968
  receiver_inner = info.get('destination')
969
+ amount_inner_str = info.get('tokenAmount', {}).get('amount') or info.get('amount')
970
  try:
971
  amount_inner_raw = int(amount_inner_str)
972
  if sender_inner and receiver_inner and amount_inner_raw > 0:
973
  sender = sender_inner
974
  receiver = receiver_inner
975
  amount_raw = amount_inner_raw
 
976
  break
977
  except (ValueError, TypeError): continue
978
+ if sender and receiver and amount_raw > 0: break
979
+ if sender and receiver and amount_raw > 0: break
 
980
 
 
981
  if sender and receiver and amount_raw > 0:
982
  return {
983
  'hash': signature, 'from': sender, 'to': receiver,
 
986
  'type': 'solana_transfer', 'logIndex': '0'
987
  }
988
  else:
 
989
  return None
990
 
991
  except Exception as e:
 
1005
  'ethereum': {'url': 'https://api.etherscan.io/api', 'key': self.etherscan_key},
1006
  'bsc': {'url': 'https://api.bscscan.com/api', 'key': self.bscscan_key},
1007
  'polygon': {'url': 'https://api.polygonscan.com/api', 'key': self.polygonscan_key}
 
1008
  }
1009
 
1010
  if network not in explorer_config or not explorer_config[network].get('key'):
 
1011
  return []
1012
 
1013
  config = explorer_config[network]
1014
+ address_param = "contractaddress" if network == 'ethereum' else "address"
 
1015
 
1016
  params = {
1017
+ "module": "account", "action": "tokentx",
1018
+ address_param: contract_address,
1019
+ "page": 1, "offset": 100, "startblock": 0,
1020
+ "endblock": 999999999, "sort": "desc", "apikey": config['key']
 
 
 
 
 
1021
  }
1022
 
1023
+ async with httpx.AsyncClient(timeout=20.0, verify=self.ssl_context) as client: # استخدام سياق SSL
1024
  response = await client.get(config['url'], params=params)
1025
 
1026
  if response.status_code == 200:
 
1032
  transfers = data.get('result', [])
1033
  processed_transfers = []
1034
  for tf in transfers:
 
 
1035
  if tf.get('tokenSymbol') and tf.get('contractAddress','').lower() == contract_address.lower():
1036
  tf['network'] = network
1037
  if 'from' in tf: tf['from'] = tf['from'].lower()
1038
  if 'to' in tf: tf['to'] = tf['to'].lower()
1039
+ if 'logIndex' not in tf: tf['logIndex'] = 'N/A'
1040
  processed_transfers.append(tf)
1041
 
1042
  print(f" ✅ Explorer {network}: تم جلب {len(processed_transfers)} تحويلة توكن للعقد المحدد.")
 
1214
  'llm_friendly_summary': llm_summary
1215
  }
1216
 
1217
+
1218
  async def _get_accurate_token_value_optimized(self, raw_value, decimals, symbol):
1219
  """حساب القيمة بالدولار باستخدام decimals مُمررة والسعر الحالي"""
1220
  try:
 
1260
  "params": [{"to": contract_address, "data": "0x313ce567"}, "latest"],
1261
  "id": int(time.time())
1262
  }
1263
+ # الإصلاح: استخدام "self.http_client" مباشرة
1264
+ response = await self.http_client.post(endpoint, json=payload, timeout=10.0)
1265
  if response.status_code == 200:
1266
  result = response.json().get('result')
1267
  if result and result != '0x' and result.startswith('0x'):
 
1275
  print(f" ⚠️ فشل تحويل نتيجة decimals من RPC ({result}) لـ {contract_address} على {network}")
1276
  continue
1277
  except Exception as rpc_decimal_error:
1278
+ # ✅ التعامل مع خطأ العميل المغلق
1279
+ if "Cannot reopen a client instance" in str(rpc_decimal_error):
1280
+ print(f" ❌ فشل فادح (Decimals): محاولة استخدام client مغلق. {rpc_decimal_error}")
1281
+ return None # لا يمكن المتابعة
1282
  print(f" ⚠️ خطأ RPC أثناء جلب decimals من {endpoint.split('//')[-1]}: {rpc_decimal_error}")
1283
  continue
1284
 
1285
  elif network == 'solana':
1286
  print(f"ℹ️ جلب decimals لـ Solana ({contract_address}) غير مدعوم حاليًا عبر RPC.")
1287
+ estimated_decimals = 9
 
1288
  self.token_decimals_cache[cache_key] = estimated_decimals
1289
  print(f" ⚠️ استخدام قيمة decimals تقديرية لـ Solana: {estimated_decimals}")
1290
  return estimated_decimals
1291
 
 
1292
  print(f"❌ فشل جلب الكسور العشرية للعقد {contract_address} على شبكة {network} من جميع المصادر.")
1293
  return None
1294
 
 
1305
  if cache_entry and time.time() - cache_entry.get('timestamp', 0) < 300:
1306
  return cache_entry['price']
1307
 
 
1308
  price = await self._get_token_price_from_coingecko(symbol)
1309
  if price is not None and price > 0:
1310
  self.token_price_cache[base_symbol] = {'price': price, 'timestamp': time.time()}
1311
  return price
1312
 
 
1313
  price = await self._get_token_price_from_kucoin(symbol)
1314
  if price is not None and price > 0:
1315
  self.token_price_cache[base_symbol] = {'price': price, 'timestamp': time.time()}
 
1333
  'LTC': 'litecoin', 'XLM': 'stellar', 'TRX': 'tron', 'EOS': 'eos', 'XMR': 'monero',
1334
  'SOL': 'solana', 'MATIC': 'matic-network', 'AVAX': 'avalanche-2', 'LINK': 'chainlink',
1335
  'ATOM': 'cosmos', 'UNI': 'uniswap', 'AAVE': 'aave', 'KCS': 'kucoin-shares',
 
1336
  'MAVIA': 'heroes-of-mavia', 'COMMON': 'commonwealth', 'WLFI': 'wolfi',
1337
  'PINGPONG': 'pingpong', 'YB': 'yourbusd', 'REACT': 'react', 'XMN': 'xmine',
1338
  'ANOME': 'anome', 'ZEN': 'zencash', 'AKT': 'akash-network', 'UB': 'unibit'
 
1341
  base_symbol = symbol.split('/')[0].upper()
1342
  coingecko_id = symbol_mapping.get(base_symbol)
1343
  if not coingecko_id:
1344
+ coingecko_id = base_symbol.lower()
1345
  print(f"ℹ️ رمز {base_symbol} غير موجود في symbol_mapping، استخدام {coingecko_id} لـ CoinGecko.")
1346
 
1347
 
 
1351
  max_retries = 2
1352
  for attempt in range(max_retries):
1353
  async with self.coingecko_semaphore:
1354
+ # ✅ الإصلاح: إنشاء client جديد ومؤقت هنا، وليس استخدام self.http_client
1355
  async with httpx.AsyncClient(timeout=10.0, headers=headers, verify=self.ssl_context) as client:
1356
  try:
1357
  response = await client.get(url)
 
1372
  print(f"⚠️ استجابة CoinGecko تحتوي على سعر غير صالح لـ {symbol}: {price}")
1373
  return 0
1374
  else:
 
1375
  if attempt == 0:
1376
  print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في CoinGecko، محاولة البحث عن المعرف...")
1377
+ await asyncio.sleep(1.1)
1378
  found_id = await self._find_coingecko_id_via_search(base_symbol, client)
1379
  if found_id and found_id != coingecko_id:
1380
  print(f" 🔄 تم العثور على معرف بديل: {found_id}. إعادة المحاولة...")
1381
+ coingecko_id = found_id
 
1382
  url = f"https://api.coingecko.com/api/v3/simple/price?ids={coingecko_id}&vs_currencies=usd"
1383
+ continue
1384
  else:
1385
  print(f" ❌ لم يتم العثور على معرف بديل لـ {base_symbol}.")
1386
+ return 0
1387
+ else:
1388
  print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في استجابة CoinGecko بعد البحث.")
1389
  return 0
1390
 
 
1391
  except httpx.HTTPStatusError as http_err:
1392
  print(f"❌ خطأ HTTP من CoinGecko لـ {symbol}: {http_err.response.status_code}")
1393
  return 0
 
1405
  print(f"❌ فشل عام في _get_token_price_from_coingecko لـ {symbol}: {e}")
1406
  return 0
1407
 
 
1408
  async def _find_coingecko_id_via_search(self, symbol, client: httpx.AsyncClient):
1409
+ """دالة مساعدة للبحث عن معرف CoinGecko"""
1410
  try:
1411
  search_url = f"https://api.coingecko.com/api/v3/search?query={symbol}"
1412
  response = await client.get(search_url)
 
1414
  data = response.json()
1415
  coins = data.get('coins', [])
1416
  if coins:
 
1417
  search_symbol_lower = symbol.lower()
1418
  for coin in coins:
1419
  if coin.get('symbol', '').lower() == search_symbol_lower:
1420
  return coin.get('id')
 
1421
  return coins[0].get('id')
1422
  return None
1423
  except Exception as e:
 
1429
  """جلب سعر العملة من KuCoin كبديل (بشكل غير متزامن)"""
1430
  exchange = None
1431
  try:
 
1432
  exchange = ccxt.kucoin({'enableRateLimit': True})
 
1433
  markets = await exchange.load_markets()
1434
  if symbol not in markets:
1435
  print(f"⚠️ الرمز {symbol} غير موجود في أسواق KuCoin.")
1436
+ await exchange.close() # الإغلاق عند عدم العثور على الرمز
1437
  return 0
1438
 
1439
  ticker = await exchange.fetch_ticker(symbol)
 
1458
  print(f"❌ خطأ شبكة أثناء جلب السعر من KuCoin لـ {symbol}: {e}")
1459
  return 0
1460
  except ccxt.ExchangeError as e:
 
1461
  print(f"❌ خطأ منصة KuCoin ({type(e).__name__}) لـ {symbol}: {e}")
1462
  return 0
1463
  except Exception as e:
 
1477
  abs_net_flow = abs(net_flow_usd)
1478
  total_flow_volume = total_to_exchanges_usd + total_from_exchanges_usd
1479
 
 
1480
  confidence_multiplier = min( (abs_net_flow / 500000.0) + (whale_transfers_count / 5.0) , 1.5)
 
1481
  base_confidence = 0.5 + (network_strength * 0.1)
1482
 
1483
  action = 'HOLD'
1484
  reason = f'نشاط حيتان عبر {len(network_stats)} شبكة: {whale_transfers_count} تحويلة كبيرة بقيمة إجمالية ${total_flow_volume:,.0f}. صافي التدفق ${net_flow_usd:,.0f}'
1485
  critical_alert = False
1486
 
1487
+ if net_flow_usd > 500000 and deposit_count >= 2:
 
1488
  action = 'STRONG_SELL'
1489
  confidence = min(0.7 + (base_confidence * confidence_multiplier * 0.3), 0.95)
1490
  reason = f'ضغط بيعي قوي عبر {len(network_stats)} شبكة: ${net_flow_usd:,.0f} إيداع للمنصات ({deposit_count} تحويلة).'
1491
  critical_alert = abs_net_flow > 1000000
1492
+ elif net_flow_usd > 150000 and (deposit_count >= 1 or whale_transfers_count > 0):
1493
  action = 'SELL'
1494
  confidence = min(0.6 + (base_confidence * confidence_multiplier * 0.2), 0.85)
1495
  reason = f'ضغط بيعي محتمل عبر {len(network_stats)} شبكة: ${net_flow_usd:,.0f} إيداع للمنصات.'
1496
  critical_alert = abs_net_flow > 750000
1497
+ elif net_flow_usd < -500000 and withdrawal_count >= 2:
 
 
1498
  action = 'STRONG_BUY'
1499
  confidence = min(0.7 + (base_confidence * confidence_multiplier * 0.3), 0.95)
1500
  reason = f'تراكم شرائي قوي عبر {len(network_stats)} شبكة: ${abs_net_flow:,.0f} سحب من المنصات ({withdrawal_count} تحويلة).'
1501
  critical_alert = abs_net_flow > 1000000
1502
+ elif net_flow_usd < -150000 and (withdrawal_count >= 1 or whale_transfers_count > 0):
1503
  action = 'BUY'
1504
  confidence = min(0.6 + (base_confidence * confidence_multiplier * 0.2), 0.85)
1505
  reason = f'تراكم شرائي محتمل عبر {len(network_stats)} شبكة: ${abs_net_flow:,.0f} سحب من المنصات.'
1506
  critical_alert = abs_net_flow > 750000
 
 
1507
  else:
1508
  if whale_transfers_count > 0 and abs_net_flow < 100000:
1509
  confidence = min(base_confidence * 1.1, 0.6)
1510
  reason += " (صافي التدفق منخفض نسبياً)"
 
1511
  elif whale_transfers_count > 0 and abs_net_flow < 50000:
1512
+ confidence = max(0.4, base_confidence * 0.9)
1513
  reason += " (صافي التدفق شبه متعادل)"
1514
  elif whale_transfers_count == 0:
1515
+ confidence = 0.3
1516
  reason = 'لا توجد تحركات حيتان كبيرة في الساعات الأخيرة'
1517
  else:
1518
  confidence = base_confidence
 
1550
 
1551
  print(f"🔍 البحث عن عقد للعملة: {symbol}")
1552
 
 
1553
  if symbol_lower in self.contracts_db:
1554
  contract_info = self.contracts_db[symbol_lower]
1555
+ if isinstance(contract_info, str):
1556
  network = self._detect_network_from_address(contract_info)
1557
  contract_info = {'address': contract_info, 'network': network}
1558
  self.contracts_db[symbol_lower] = contract_info
 
1559
  if 'address' in contract_info and 'network' in contract_info:
1560
  print(f" ✅ وجد في قاعدة البيانات المحلية: {contract_info}")
1561
  return contract_info
1562
  else:
1563
  print(f" ⚠️ بيانات العقد غير مكتملة في القاعدة المحلية لـ {symbol}: {contract_info}")
 
1564
 
 
1565
  print(f" 🔍 البحث في CoinGecko عن {base_symbol}...")
1566
  coingecko_result = await self._find_contract_via_coingecko(base_symbol)
1567
 
1568
  if coingecko_result:
1569
  address, network = coingecko_result
1570
  contract_info = {'address': address, 'network': network}
1571
+ self.contracts_db[symbol_lower] = contract_info
1572
  print(f" ✅ تم العثور على عقد {symbol} عبر CoinGecko على شبكة {network}: {address}")
1573
 
1574
  if self.r2_service:
1575
+ await self._save_contracts_to_r2()
1576
 
1577
  return contract_info
1578
 
 
1589
  max_retries = 2
1590
  for attempt in range(max_retries):
1591
  async with self.coingecko_semaphore:
1592
+ # ✅ استخدام client جديد ومؤقت هنا
1593
  async with httpx.AsyncClient(timeout=15, headers=headers, verify=self.ssl_context) as client:
1594
  try:
1595
  print(f" 🔍 CoinGecko Search API Call (Attempt {attempt + 1}) for {symbol}")
 
1656
  print(f" ❌ No contract found on supported platforms for {coin_id}")
1657
  return None
1658
 
1659
+ except httpx.HTTPStatusError as http_err:
1660
+ print(f" ❌ HTTP Error from CoinGecko ({http_err.request.url}): {http_err.response.status_code}")
1661
+ return None
1662
+ except httpx.RequestError as req_err:
1663
+ print(f" ❌ Connection Error with CoinGecko ({req_err.request.url}): {req_err}")
1664
+ return None
1665
  except Exception as inner_e:
1666
  print(f" ❌ Unexpected error during CoinGecko API call (Attempt {attempt + 1}): {inner_e}")
1667
  if attempt < max_retries - 1: await asyncio.sleep(1); continue
 
1759
  }
1760
 
1761
  whale_signal = whale_data.get('trading_signal', {})
1762
+ analysis_details = whale_data # إرجاع كافة التفاصيل
 
 
1763
 
1764
  return {
1765
  'action': whale_signal.get('action', 'HOLD'),
 
1768
  'source': 'whale_analysis',
1769
  'critical_alert': whale_signal.get('critical_alert', False),
1770
  'data_available': True,
1771
+ 'whale_analysis_details': analysis_details
1772
  }
1773
 
1774
  except Exception as e:
 
1790
  print(f"⚠️ خطأ أثناء إغلاق HTTP client لمراقب الحيتان: {e}")
1791
 
1792
 
1793
+ print("✅ EnhancedWhaleMonitor loaded - Correct RPC (eth_getLogs), Robust Solana RPC Handling (Free Endpoints), Fixed httpx client usage")