Really-amin commited on
Commit
b66240d
·
verified ·
1 Parent(s): a5fce16

Upload 325 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. README_HF_SPACE.md +19 -0
  3. app.py +6 -1228
  4. final/.doc-organization.sh +70 -0
  5. final/.dockerignore +121 -0
  6. final/.env +20 -0
  7. final/.env.example +17 -0
  8. final/.flake8 +29 -0
  9. final/.github/workflows/ci.yml +228 -0
  10. final/.gitignore +49 -0
  11. final/Can you put data sources/api - Copy.html +661 -0
  12. final/Can you put data sources/api - Copy.txt +446 -0
  13. final/Can you put data sources/api-config-complete (1).txt +1634 -0
  14. final/Dockerfile +24 -0
  15. final/Dockerfile.crypto-bank +37 -0
  16. final/Dockerfile.optimized +51 -0
  17. final/PROVIDER_AUTO_DISCOVERY_REPORT.json +0 -0
  18. final/TEST_COMMANDS.sh +41 -0
  19. final/TEST_ENDPOINTS.sh +161 -0
  20. final/__pycache__/ai_models.cpython-312.pyc +0 -0
  21. final/__pycache__/ai_models.cpython-313.pyc +0 -0
  22. final/__pycache__/api_loader.cpython-313.pyc +0 -0
  23. final/__pycache__/app.cpython-313.pyc +0 -0
  24. final/__pycache__/config.cpython-313.pyc +0 -0
  25. final/__pycache__/database.cpython-313.pyc +0 -0
  26. final/__pycache__/hf_unified_server.cpython-312.pyc +0 -0
  27. final/__pycache__/hf_unified_server.cpython-313.pyc +3 -0
  28. final/__pycache__/main.cpython-313.pyc +0 -0
  29. final/__pycache__/monitor.cpython-313.pyc +0 -0
  30. final/__pycache__/simple_server.cpython-313.pyc +0 -0
  31. final/admin.html +79 -0
  32. final/admin.html.optimized +496 -0
  33. final/admin_advanced.html +1862 -0
  34. final/admin_improved.html +61 -0
  35. final/admin_pro.html +657 -0
  36. final/ai_models.py +352 -0
  37. final/all_apis_merged_2025.json +64 -0
  38. final/api-monitor.js +586 -0
  39. final/api-resources/README.md +282 -0
  40. final/api-resources/api-config-complete__1_.txt +1634 -0
  41. final/api-resources/crypto_resources_unified_2025-11-11.json +0 -0
  42. final/api-resources/ultimate_crypto_pipeline_2025_NZasinich.json +503 -0
  43. final/api/__init__.py +0 -0
  44. final/api/auth.py +47 -0
  45. final/api/data_endpoints.py +560 -0
  46. final/api/endpoints.py +1178 -0
  47. final/api/pool_endpoints.py +598 -0
  48. final/api/websocket.py +488 -0
  49. final/api/ws_data_broadcaster.py +224 -0
  50. final/api/ws_data_services.py +481 -0
.gitattributes CHANGED
@@ -37,3 +37,5 @@ data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
37
  hf-data-engine/data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
38
  app/data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
39
  __pycache__/hf_unified_server.cpython-313.pyc filter=lfs diff=lfs merge=lfs -text
 
 
 
37
  hf-data-engine/data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
38
  app/data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
39
  __pycache__/hf_unified_server.cpython-313.pyc filter=lfs diff=lfs merge=lfs -text
40
+ final/__pycache__/hf_unified_server.cpython-313.pyc filter=lfs diff=lfs merge=lfs -text
41
+ final/data/crypto_monitor.db filter=lfs diff=lfs merge=lfs -text
README_HF_SPACE.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Crypto Intelligence Hub – HF Python Space
2
+
3
+ This project is prepared to run as a **Hugging Face Python Space** using FastAPI.
4
+
5
+ - Entry file: `app.py`
6
+ - Main server: `final/hf_unified_server.py`
7
+ - Frontend UI: `final/index.html` + `final/static/` (served by FastAPI)
8
+ - Database: SQLite (created under `data/` when the API runs)
9
+ - Hugging Face models: configured as pipelines in `final/ai_models.py` and related modules.
10
+ - Models are lazy-loaded when AI endpoints are called.
11
+
12
+ ## Run locally
13
+
14
+ ```bash
15
+ pip install -r requirements_hf.txt
16
+ uvicorn app:app --host 0.0.0.0 --port 7860
17
+ ```
18
+
19
+ Then open: `http://localhost:7860/`
app.py CHANGED
@@ -1,1232 +1,10 @@
1
- #!/usr/bin/env python3
2
- """
3
- Crypto Data Aggregator - Admin Dashboard (Gradio App)
4
- STRICT REAL-DATA-ONLY implementation for Hugging Face Spaces
5
-
6
- 7 Tabs:
7
- 1. Status - System health & overview
8
- 2. Providers - API provider management
9
- 3. Market Data - Live cryptocurrency data
10
- 4. APL Scanner - Auto Provider Loader
11
- 5. HF Models - Hugging Face model status
12
- 6. Diagnostics - System diagnostics & auto-repair
13
- 7. Logs - System logs viewer
14
- """
15
-
16
- import sys
17
- import os
18
- import logging
19
  from pathlib import Path
20
- from typing import Dict, List, Any, Tuple, Optional
21
- from datetime import datetime
22
- import json
23
- import traceback
24
- import asyncio
25
- import time
26
-
27
- # Check for Gradio
28
- try:
29
- import gradio as gr
30
- except ImportError:
31
- print("ERROR: gradio not installed. Run: pip install gradio")
32
- sys.exit(1)
33
-
34
- # Check for optional dependencies
35
- try:
36
- import pandas as pd
37
- PANDAS_AVAILABLE = True
38
- except ImportError:
39
- PANDAS_AVAILABLE = False
40
- print("WARNING: pandas not installed. Some features disabled.")
41
-
42
- try:
43
- import plotly.graph_objects as go
44
- from plotly.subplots import make_subplots
45
- PLOTLY_AVAILABLE = True
46
- except ImportError:
47
- PLOTLY_AVAILABLE = False
48
- print("WARNING: plotly not installed. Charts disabled.")
49
-
50
- # Import local modules
51
- import config
52
- import database
53
- import collectors
54
-
55
- # ==================== INDEPENDENT LOGGING SETUP ====================
56
- # DO NOT use utils.setup_logging() - set up independently
57
-
58
- logger = logging.getLogger("app")
59
- if not logger.handlers:
60
- level_name = getattr(config, "LOG_LEVEL", "INFO")
61
- level = getattr(logging, level_name.upper(), logging.INFO)
62
- logger.setLevel(level)
63
-
64
- formatter = logging.Formatter(
65
- getattr(config, "LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
66
- )
67
-
68
- # Console handler
69
- ch = logging.StreamHandler()
70
- ch.setFormatter(formatter)
71
- logger.addHandler(ch)
72
-
73
- # File handler if log file exists
74
- try:
75
- if hasattr(config, 'LOG_FILE'):
76
- fh = logging.FileHandler(config.LOG_FILE)
77
- fh.setFormatter(formatter)
78
- logger.addHandler(fh)
79
- except Exception as e:
80
- print(f"Warning: Could not setup file logging: {e}")
81
-
82
- logger.info("=" * 60)
83
- logger.info("Crypto Admin Dashboard Starting")
84
- logger.info("=" * 60)
85
-
86
- # Initialize database
87
- db = database.get_database()
88
-
89
-
90
- # ==================== TAB 1: STATUS ====================
91
-
92
- def get_status_tab() -> Tuple[str, str, str]:
93
- """
94
- Get system status overview.
95
- Returns: (markdown_summary, db_stats_json, system_info_json)
96
- """
97
- try:
98
- # Get database stats
99
- db_stats = db.get_database_stats()
100
-
101
- # Count providers
102
- providers_config_path = config.BASE_DIR / "providers_config_extended.json"
103
- provider_count = 0
104
- if providers_config_path.exists():
105
- with open(providers_config_path, 'r') as f:
106
- providers_data = json.load(f)
107
- provider_count = len(providers_data.get('providers', {}))
108
-
109
- # Pool count (from config)
110
- pool_count = 0
111
- if providers_config_path.exists():
112
- with open(providers_config_path, 'r') as f:
113
- providers_data = json.load(f)
114
- pool_count = len(providers_data.get('pool_configurations', []))
115
-
116
- # Market snapshot
117
- latest_prices = db.get_latest_prices(3)
118
- market_snapshot = ""
119
- if latest_prices:
120
- for p in latest_prices[:3]:
121
- symbol = p.get('symbol', 'N/A')
122
- price = p.get('price_usd', 0)
123
- change = p.get('percent_change_24h', 0)
124
- market_snapshot += f"**{symbol}**: ${price:,.2f} ({change:+.2f}%)\n"
125
- else:
126
- market_snapshot = "No market data available yet."
127
-
128
- # Get API request count from health log
129
- api_requests_count = 0
130
- try:
131
- health_log_path = Path("data/logs/provider_health.jsonl")
132
- if health_log_path.exists():
133
- with open(health_log_path, 'r', encoding='utf-8') as f:
134
- api_requests_count = sum(1 for _ in f)
135
- except Exception as e:
136
- logger.warning(f"Could not get API request stats: {e}")
137
-
138
- # Build summary with copy-friendly format
139
- summary = f"""
140
- ## 🎯 System Status
141
-
142
- **Overall Health**: {"🟢 Operational" if db_stats.get('prices_count', 0) > 0 else "🟡 Initializing"}
143
-
144
- ### Quick Stats
145
- ```
146
- Total Providers: {provider_count}
147
- Active Pools: {pool_count}
148
- API Requests: {api_requests_count:,}
149
- Price Records: {db_stats.get('prices_count', 0):,}
150
- News Articles: {db_stats.get('news_count', 0):,}
151
- Unique Symbols: {db_stats.get('unique_symbols', 0)}
152
- ```
153
-
154
- ### Market Snapshot (Top 3)
155
- ```
156
- {market_snapshot}
157
- ```
158
-
159
- **Last Update**: `{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}`
160
-
161
- ---
162
- ### 📋 Provider Details (Copy-Friendly)
163
- ```
164
- Total: {provider_count} providers
165
- Config File: providers_config_extended.json
166
- ```
167
- """
168
-
169
- # System info
170
- import platform
171
- system_info = {
172
- "Python Version": sys.version.split()[0],
173
- "Platform": platform.platform(),
174
- "Working Directory": str(config.BASE_DIR),
175
- "Database Size": f"{db_stats.get('database_size_mb', 0):.2f} MB",
176
- "Last Price Update": db_stats.get('latest_price_update', 'N/A'),
177
- "Last News Update": db_stats.get('latest_news_update', 'N/A')
178
- }
179
-
180
- return summary, json.dumps(db_stats, indent=2), json.dumps(system_info, indent=2)
181
-
182
- except Exception as e:
183
- logger.error(f"Error in get_status_tab: {e}\n{traceback.format_exc()}")
184
- return f"⚠️ Error loading status: {str(e)}", "{}", "{}"
185
-
186
-
187
- def run_diagnostics_from_status(auto_fix: bool) -> str:
188
- """Run diagnostics from status tab"""
189
- try:
190
- from backend.services.diagnostics_service import DiagnosticsService
191
-
192
- diagnostics = DiagnosticsService()
193
-
194
- # Run async in sync context
195
- loop = asyncio.new_event_loop()
196
- asyncio.set_event_loop(loop)
197
- report = loop.run_until_complete(diagnostics.run_full_diagnostics(auto_fix=auto_fix))
198
- loop.close()
199
-
200
- # Format output
201
- output = f"""
202
- # Diagnostics Report
203
-
204
- **Timestamp**: {report.timestamp}
205
- **Duration**: {report.duration_ms:.2f}ms
206
-
207
- ## Summary
208
- - **Total Issues**: {report.total_issues}
209
- - **Critical**: {report.critical_issues}
210
- - **Warnings**: {report.warnings}
211
- - **Info**: {report.info_issues}
212
- - **Fixed**: {len(report.fixed_issues)}
213
-
214
- ## Issues
215
- """
216
- for issue in report.issues:
217
- emoji = {"critical": "🔴", "warning": "🟡", "info": "🔵"}.get(issue.severity, "⚪")
218
- fixed_mark = " ✅ FIXED" if issue.auto_fixed else ""
219
- output += f"\n### {emoji} [{issue.category.upper()}] {issue.title}{fixed_mark}\n"
220
- output += f"{issue.description}\n"
221
- if issue.fixable and not issue.auto_fixed:
222
- output += f"**Fix**: `{issue.fix_action}`\n"
223
-
224
- return output
225
-
226
- except Exception as e:
227
- logger.error(f"Error running diagnostics: {e}")
228
- return f"❌ Diagnostics failed: {str(e)}"
229
-
230
-
231
- # ==================== TAB 2: PROVIDERS ====================
232
-
233
- def get_providers_table(category_filter: str = "All") -> Any:
234
- """
235
- Get providers from providers_config_extended.json with enhanced formatting
236
- Returns: DataFrame or dict
237
- """
238
- try:
239
- providers_path = config.BASE_DIR / "providers_config_extended.json"
240
-
241
- if not providers_path.exists():
242
- if PANDAS_AVAILABLE:
243
- return pd.DataFrame({"Error": ["providers_config_extended.json not found"]})
244
- return {"error": "providers_config_extended.json not found"}
245
-
246
- with open(providers_path, 'r') as f:
247
- data = json.load(f)
248
-
249
- providers = data.get('providers', {})
250
-
251
- # Build table data with copy-friendly IDs
252
- table_data = []
253
- for provider_id, provider_info in providers.items():
254
- if category_filter != "All":
255
- if provider_info.get('category', '').lower() != category_filter.lower():
256
- continue
257
-
258
- # Format auth status with emoji
259
- auth_status = "✅ Yes" if provider_info.get('requires_auth', False) else "❌ No"
260
- validation = "✅ Valid" if provider_info.get('validated', False) else "⏳ Pending"
261
-
262
- table_data.append({
263
- "Provider ID": provider_id,
264
- "Name": provider_info.get('name', provider_id),
265
- "Category": provider_info.get('category', 'unknown'),
266
- "Type": provider_info.get('type', 'http_json'),
267
- "Base URL": provider_info.get('base_url', 'N/A'),
268
- "Auth Required": auth_status,
269
- "Priority": provider_info.get('priority', 'N/A'),
270
- "Status": validation
271
- })
272
-
273
- if PANDAS_AVAILABLE:
274
- return pd.DataFrame(table_data) if table_data else pd.DataFrame({"Message": ["No providers found"]})
275
- else:
276
- return {"providers": table_data} if table_data else {"error": "No providers found"}
277
-
278
- except Exception as e:
279
- logger.error(f"Error loading providers: {e}")
280
- if PANDAS_AVAILABLE:
281
- return pd.DataFrame({"Error": [str(e)]})
282
- return {"error": str(e)}
283
-
284
-
285
- def reload_providers_config() -> Tuple[Any, str]:
286
- """Reload providers config and return updated table + message with stats"""
287
- try:
288
- # Count providers
289
- providers_path = config.BASE_DIR / "providers_config_extended.json"
290
- with open(providers_path, 'r') as f:
291
- data = json.load(f)
292
-
293
- total_providers = len(data.get('providers', {}))
294
-
295
- # Count by category
296
- categories = {}
297
- for provider_info in data.get('providers', {}).values():
298
- cat = provider_info.get('category', 'unknown')
299
- categories[cat] = categories.get(cat, 0) + 1
300
-
301
- # Force reload by re-reading file
302
- table = get_providers_table("All")
303
-
304
- # Build detailed message
305
- message = f"""✅ **Providers Reloaded Successfully!**
306
-
307
- **Total Providers**: `{total_providers}`
308
- **Reload Time**: `{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}`
309
-
310
- **By Category**:
311
- """
312
- for cat, count in sorted(categories.items(), key=lambda x: x[1], reverse=True)[:10]:
313
- message += f"- {cat}: `{count}`\n"
314
-
315
- return table, message
316
- except Exception as e:
317
- logger.error(f"Error reloading providers: {e}")
318
- return get_providers_table("All"), f"❌ Reload failed: {str(e)}"
319
-
320
-
321
- def get_provider_categories() -> List[str]:
322
- """Get unique provider categories"""
323
- try:
324
- providers_path = config.BASE_DIR / "providers_config_extended.json"
325
- if not providers_path.exists():
326
- return ["All"]
327
-
328
- with open(providers_path, 'r') as f:
329
- data = json.load(f)
330
-
331
- categories = set()
332
- for provider in data.get('providers', {}).values():
333
- cat = provider.get('category', 'unknown')
334
- categories.add(cat)
335
-
336
- return ["All"] + sorted(list(categories))
337
- except Exception as e:
338
- logger.error(f"Error getting categories: {e}")
339
- return ["All"]
340
-
341
-
342
- # ==================== TAB 3: MARKET DATA ====================
343
-
344
- def get_market_data_table(search_filter: str = "") -> Any:
345
- """Get latest market data from database with enhanced formatting"""
346
- try:
347
- prices = db.get_latest_prices(100)
348
-
349
- if not prices:
350
- if PANDAS_AVAILABLE:
351
- return pd.DataFrame({"Message": ["No market data available. Click 'Refresh Prices' to collect data."]})
352
- return {"error": "No data available"}
353
-
354
- # Filter if search provided
355
- filtered_prices = prices
356
- if search_filter:
357
- search_lower = search_filter.lower()
358
- filtered_prices = [
359
- p for p in prices
360
- if search_lower in p.get('name', '').lower() or search_lower in p.get('symbol', '').lower()
361
- ]
362
-
363
- table_data = []
364
- for p in filtered_prices:
365
- # Format change with emoji
366
- change = p.get('percent_change_24h', 0)
367
- change_emoji = "🟢" if change > 0 else ("🔴" if change < 0 else "⚪")
368
-
369
- table_data.append({
370
- "#": p.get('rank', 999),
371
- "Symbol": p.get('symbol', 'N/A'),
372
- "Name": p.get('name', 'Unknown'),
373
- "Price": f"${p.get('price_usd', 0):,.2f}" if p.get('price_usd') else "N/A",
374
- "24h Change": f"{change_emoji} {change:+.2f}%" if change is not None else "N/A",
375
- "Volume 24h": f"${p.get('volume_24h', 0):,.0f}" if p.get('volume_24h') else "N/A",
376
- "Market Cap": f"${p.get('market_cap', 0):,.0f}" if p.get('market_cap') else "N/A"
377
- })
378
-
379
- if PANDAS_AVAILABLE:
380
- df = pd.DataFrame(table_data)
381
- return df.sort_values('#') if not df.empty else pd.DataFrame({"Message": ["No matching data"]})
382
- else:
383
- return {"prices": table_data}
384
-
385
- except Exception as e:
386
- logger.error(f"Error getting market data: {e}")
387
- if PANDAS_AVAILABLE:
388
- return pd.DataFrame({"Error": [str(e)]})
389
- return {"error": str(e)}
390
-
391
-
392
- def refresh_market_data() -> Tuple[Any, str]:
393
- """Refresh market data by collecting from APIs with detailed stats"""
394
- try:
395
- logger.info("Refreshing market data...")
396
- start_time = time.time()
397
- success, count = collectors.collect_price_data()
398
- duration = time.time() - start_time
399
-
400
- # Get database stats
401
- db_stats = db.get_database_stats()
402
-
403
- if success:
404
- message = f"""✅ **Market Data Refreshed Successfully!**
405
-
406
- **Collection Stats**:
407
- - New Records: `{count}`
408
- - Duration: `{duration:.2f}s`
409
- - Time: `{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}`
410
-
411
- **Database Stats**:
412
- - Total Price Records: `{db_stats.get('prices_count', 0):,}`
413
- - Unique Symbols: `{db_stats.get('unique_symbols', 0)}`
414
- - Last Update: `{db_stats.get('latest_price_update', 'N/A')}`
415
- """
416
- else:
417
- message = f"""⚠️ **Collection completed with issues**
418
-
419
- - Records Collected: `{count}`
420
- - Duration: `{duration:.2f}s`
421
- - Check logs for details
422
- """
423
-
424
- # Return updated table
425
- table = get_market_data_table("")
426
- return table, message
427
-
428
- except Exception as e:
429
- logger.error(f"Error refreshing market data: {e}")
430
- return get_market_data_table(""), f"❌ Refresh failed: {str(e)}"
431
-
432
-
433
- def plot_price_history(symbol: str, timeframe: str) -> Any:
434
- """Plot price history for a symbol"""
435
- if not PLOTLY_AVAILABLE:
436
- return None
437
-
438
- try:
439
- # Parse timeframe
440
- hours_map = {"24h": 24, "7d": 168, "30d": 720, "90d": 2160}
441
- hours = hours_map.get(timeframe, 168)
442
-
443
- # Get history
444
- history = db.get_price_history(symbol.upper(), hours)
445
-
446
- if not history or len(history) < 2:
447
- fig = go.Figure()
448
- fig.add_annotation(
449
- text=f"No historical data for {symbol}",
450
- xref="paper", yref="paper",
451
- x=0.5, y=0.5, showarrow=False
452
- )
453
- return fig
454
-
455
- # Extract data
456
- timestamps = [datetime.fromisoformat(h['timestamp'].replace('Z', '+00:00')) if isinstance(h['timestamp'], str) else datetime.now() for h in history]
457
- prices = [h.get('price_usd', 0) for h in history]
458
-
459
- # Create plot
460
- fig = go.Figure()
461
- fig.add_trace(go.Scatter(
462
- x=timestamps,
463
- y=prices,
464
- mode='lines',
465
- name='Price',
466
- line=dict(color='#2962FF', width=2)
467
- ))
468
-
469
- fig.update_layout(
470
- title=f"{symbol} - {timeframe}",
471
- xaxis_title="Time",
472
- yaxis_title="Price (USD)",
473
- hovermode='x unified',
474
- height=400
475
- )
476
-
477
- return fig
478
-
479
- except Exception as e:
480
- logger.error(f"Error plotting price history: {e}")
481
- fig = go.Figure()
482
- fig.add_annotation(text=f"Error: {str(e)}", xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
483
- return fig
484
-
485
-
486
- # ==================== TAB 4: APL SCANNER ====================
487
-
488
- def run_apl_scan() -> str:
489
- """Run Auto Provider Loader scan"""
490
- try:
491
- logger.info("Running APL scan...")
492
-
493
- # Import APL
494
- import auto_provider_loader
495
-
496
- # Run scan
497
- apl = auto_provider_loader.AutoProviderLoader()
498
-
499
- # Run async in sync context
500
- loop = asyncio.new_event_loop()
501
- asyncio.set_event_loop(loop)
502
- loop.run_until_complete(apl.run())
503
- loop.close()
504
-
505
- # Build summary
506
- stats = apl.stats
507
- output = f"""
508
- # APL Scan Complete
509
-
510
- **Timestamp**: {stats.timestamp}
511
- **Execution Time**: {stats.execution_time_sec:.2f}s
512
-
513
- ## HTTP Providers
514
- - **Candidates**: {stats.total_http_candidates}
515
- - **Valid**: {stats.http_valid} ✅
516
- - **Invalid**: {stats.http_invalid} ❌
517
- - **Conditional**: {stats.http_conditional} ⚠️
518
-
519
- ## HuggingFace Models
520
- - **Candidates**: {stats.total_hf_candidates}
521
- - **Valid**: {stats.hf_valid} ✅
522
- - **Invalid**: {stats.hf_invalid} ❌
523
- - **Conditional**: {stats.hf_conditional} ⚠️
524
-
525
- ## Total Active Providers
526
- **{stats.total_active_providers}** providers are now active.
527
-
528
- ---
529
-
530
- ✅ All valid providers have been integrated into `providers_config_extended.json`.
531
-
532
- See `PROVIDER_AUTO_DISCOVERY_REPORT.md` for full details.
533
- """
534
-
535
- return output
536
-
537
- except Exception as e:
538
- logger.error(f"Error running APL: {e}\n{traceback.format_exc()}")
539
- return f"❌ APL scan failed: {str(e)}\n\nCheck logs for details."
540
-
541
-
542
- def get_apl_report() -> str:
543
- """Get last APL report"""
544
- try:
545
- report_path = config.BASE_DIR / "PROVIDER_AUTO_DISCOVERY_REPORT.md"
546
- if report_path.exists():
547
- with open(report_path, 'r') as f:
548
- return f.read()
549
- else:
550
- return "No APL report found. Run a scan first."
551
- except Exception as e:
552
- logger.error(f"Error reading APL report: {e}")
553
- return f"Error reading report: {str(e)}"
554
-
555
-
556
- # ==================== TAB 5: HF MODELS ====================
557
-
558
- def get_hf_models_status() -> Any:
559
- """Get HuggingFace models status with unified display"""
560
- try:
561
- import ai_models
562
-
563
- model_info = ai_models.get_model_info()
564
-
565
- # Build unified table - avoid duplicates
566
- table_data = []
567
- seen_models = set()
568
-
569
- # First, add loaded models
570
- if model_info.get('models_initialized'):
571
- for model_name, loaded in model_info.get('loaded_models', {}).items():
572
- if model_name not in seen_models:
573
- status = "✅ Loaded" if loaded else "❌ Failed"
574
- model_id = config.HUGGINGFACE_MODELS.get(model_name, 'N/A')
575
- table_data.append({
576
- "Model Type": model_name,
577
- "Model ID": model_id,
578
- "Status": status,
579
- "Source": "config.py"
580
- })
581
- seen_models.add(model_name)
582
-
583
- # Then add configured but not loaded models
584
- for model_type, model_id in config.HUGGINGFACE_MODELS.items():
585
- if model_type not in seen_models:
586
- table_data.append({
587
- "Model Type": model_type,
588
- "Model ID": model_id,
589
- "Status": "⏳ Not Loaded",
590
- "Source": "config.py"
591
- })
592
- seen_models.add(model_type)
593
-
594
- # Add models from providers_config if any
595
- try:
596
- providers_path = config.BASE_DIR / "providers_config_extended.json"
597
- if providers_path.exists():
598
- with open(providers_path, 'r') as f:
599
- providers_data = json.load(f)
600
-
601
- for provider_id, provider_info in providers_data.get('providers', {}).items():
602
- if provider_info.get('category') == 'hf-model':
603
- model_name = provider_info.get('name', provider_id)
604
- if model_name not in seen_models:
605
- table_data.append({
606
- "Model Type": model_name,
607
- "Model ID": provider_id,
608
- "Status": "📚 Registry",
609
- "Source": "providers_config"
610
- })
611
- seen_models.add(model_name)
612
- except Exception as e:
613
- logger.warning(f"Could not load models from providers_config: {e}")
614
-
615
- if not table_data:
616
- table_data.append({
617
- "Model Type": "No models",
618
- "Model ID": "N/A",
619
- "Status": "⚠️ None configured",
620
- "Source": "N/A"
621
- })
622
-
623
- if PANDAS_AVAILABLE:
624
- return pd.DataFrame(table_data)
625
- else:
626
- return {"models": table_data}
627
-
628
- except Exception as e:
629
- logger.error(f"Error getting HF models status: {e}")
630
- if PANDAS_AVAILABLE:
631
- return pd.DataFrame({"Error": [str(e)]})
632
- return {"error": str(e)}
633
-
634
-
635
- def test_hf_model(model_name: str, test_text: str) -> str:
636
- """Test a HuggingFace model with text"""
637
- try:
638
- if not test_text or not test_text.strip():
639
- return "⚠️ Please enter test text"
640
-
641
- import ai_models
642
-
643
- if model_name in ["sentiment_twitter", "sentiment_financial", "sentiment"]:
644
- # Test sentiment analysis
645
- result = ai_models.analyze_sentiment(test_text)
646
-
647
- output = f"""
648
- ## Sentiment Analysis Result
649
-
650
- **Input**: {test_text}
651
-
652
- **Label**: {result.get('label', 'N/A')}
653
- **Score**: {result.get('score', 0):.4f}
654
- **Confidence**: {result.get('confidence', 0):.4f}
655
-
656
- **Details**:
657
- ```json
658
- {json.dumps(result.get('details', {}), indent=2)}
659
- ```
660
- """
661
- return output
662
-
663
- elif model_name == "summarization":
664
- # Test summarization
665
- summary = ai_models.summarize_text(test_text)
666
-
667
- output = f"""
668
- ## Summarization Result
669
-
670
- **Original** ({len(test_text)} chars):
671
- {test_text}
672
-
673
- **Summary** ({len(summary)} chars):
674
- {summary}
675
- """
676
- return output
677
-
678
- else:
679
- return f"⚠️ Model '{model_name}' not recognized or not testable"
680
-
681
- except Exception as e:
682
- logger.error(f"Error testing HF model: {e}")
683
- return f"❌ Model test failed: {str(e)}"
684
-
685
-
686
- def initialize_hf_models() -> Tuple[Any, str]:
687
- """Initialize HuggingFace models"""
688
- try:
689
- import ai_models
690
-
691
- result = ai_models.initialize_models()
692
-
693
- if result.get('success'):
694
- message = f"✅ Models initialized successfully at {datetime.now().strftime('%H:%M:%S')}"
695
- else:
696
- message = f"⚠️ Model initialization completed with warnings: {result.get('status')}"
697
-
698
- # Return updated table
699
- table = get_hf_models_status()
700
- return table, message
701
-
702
- except Exception as e:
703
- logger.error(f"Error initializing HF models: {e}")
704
- return get_hf_models_status(), f"❌ Initialization failed: {str(e)}"
705
-
706
-
707
- # ==================== TAB 6: DIAGNOSTICS ====================
708
-
709
- def run_full_diagnostics(auto_fix: bool) -> str:
710
- """Run full system diagnostics"""
711
- try:
712
- from backend.services.diagnostics_service import DiagnosticsService
713
-
714
- logger.info(f"Running diagnostics (auto_fix={auto_fix})...")
715
-
716
- diagnostics = DiagnosticsService()
717
-
718
- # Run async in sync context
719
- loop = asyncio.new_event_loop()
720
- asyncio.set_event_loop(loop)
721
- report = loop.run_until_complete(diagnostics.run_full_diagnostics(auto_fix=auto_fix))
722
- loop.close()
723
-
724
- # Format detailed output
725
- output = f"""
726
- # 🔧 System Diagnostics Report
727
-
728
- **Generated**: {report.timestamp}
729
- **Duration**: {report.duration_ms:.2f}ms
730
-
731
- ---
732
-
733
- ## 📊 Summary
734
-
735
- | Metric | Count |
736
- |--------|-------|
737
- | **Total Issues** | {report.total_issues} |
738
- | **Critical** 🔴 | {report.critical_issues} |
739
- | **Warnings** 🟡 | {report.warnings} |
740
- | **Info** 🔵 | {report.info_issues} |
741
- | **Auto-Fixed** ✅ | {len(report.fixed_issues)} |
742
-
743
- ---
744
-
745
- ## 🔍 Issues Detected
746
-
747
- """
748
-
749
- if not report.issues:
750
- output += "✅ **No issues detected!** System is healthy.\n"
751
- else:
752
- # Group by category
753
- by_category = {}
754
- for issue in report.issues:
755
- cat = issue.category
756
- if cat not in by_category:
757
- by_category[cat] = []
758
- by_category[cat].append(issue)
759
-
760
- for category, issues in sorted(by_category.items()):
761
- output += f"\n### {category.upper()}\n\n"
762
-
763
- for issue in issues:
764
- emoji = {"critical": "🔴", "warning": "🟡", "info": "🔵"}.get(issue.severity, "⚪")
765
- fixed_mark = " ✅ **AUTO-FIXED**" if issue.auto_fixed else ""
766
-
767
- output += f"**{emoji} {issue.title}**{fixed_mark}\n\n"
768
- output += f"{issue.description}\n\n"
769
-
770
- if issue.fixable and issue.fix_action and not issue.auto_fixed:
771
- output += f"💡 **Fix**: `{issue.fix_action}`\n\n"
772
-
773
- output += "---\n\n"
774
-
775
- # System info
776
- output += "\n## 💻 System Information\n\n"
777
- output += "```json\n"
778
- output += json.dumps(report.system_info, indent=2)
779
- output += "\n```\n"
780
-
781
- return output
782
-
783
- except Exception as e:
784
- logger.error(f"Error running diagnostics: {e}\n{traceback.format_exc()}")
785
- return f"❌ Diagnostics failed: {str(e)}\n\nCheck logs for details."
786
-
787
-
788
- # ==================== TAB 7: LOGS ====================
789
-
790
- def get_logs(log_type: str = "recent", lines: int = 100) -> str:
791
- """Get system logs with copy-friendly format"""
792
- try:
793
- log_file = config.LOG_FILE
794
-
795
- if not log_file.exists():
796
- return "⚠️ Log file not found"
797
-
798
- # Read log file
799
- with open(log_file, 'r') as f:
800
- all_lines = f.readlines()
801
-
802
- # Filter based on log_type
803
- if log_type == "errors":
804
- filtered_lines = [line for line in all_lines if 'ERROR' in line or 'CRITICAL' in line]
805
- elif log_type == "warnings":
806
- filtered_lines = [line for line in all_lines if 'WARNING' in line]
807
- else: # recent
808
- filtered_lines = all_lines
809
-
810
- # Get last N lines
811
- recent_lines = filtered_lines[-lines:] if len(filtered_lines) > lines else filtered_lines
812
-
813
- if not recent_lines:
814
- return f"ℹ️ No {log_type} logs found"
815
-
816
- # Format output with line numbers for easy reference
817
- output = f"# 📋 {log_type.upper()} Logs (Last {len(recent_lines)} lines)\n\n"
818
- output += "**Quick Stats:**\n"
819
- output += f"- Total lines shown: `{len(recent_lines)}`\n"
820
- output += f"- Log file: `{log_file}`\n"
821
- output += f"- Type: `{log_type}`\n\n"
822
- output += "---\n\n"
823
- output += "```log\n"
824
- for i, line in enumerate(recent_lines, 1):
825
- output += f"{i:4d} | {line}"
826
- output += "\n```\n"
827
- output += "\n---\n"
828
- output += "💡 **Tip**: You can now copy individual lines or the entire log block\n"
829
-
830
- return output
831
-
832
- except Exception as e:
833
- logger.error(f"Error reading logs: {e}")
834
- return f"❌ Error reading logs: {str(e)}"
835
-
836
-
837
- def clear_logs() -> str:
838
- """Clear log file"""
839
- try:
840
- log_file = config.LOG_FILE
841
-
842
- if log_file.exists():
843
- # Backup first
844
- backup_path = log_file.parent / f"{log_file.name}.backup.{int(datetime.now().timestamp())}"
845
- import shutil
846
- shutil.copy2(log_file, backup_path)
847
-
848
- # Clear
849
- with open(log_file, 'w') as f:
850
- f.write("")
851
-
852
- logger.info("Log file cleared")
853
- return f"✅ Logs cleared (backup saved to {backup_path.name})"
854
- else:
855
- return "⚠️ No log file to clear"
856
-
857
- except Exception as e:
858
- logger.error(f"Error clearing logs: {e}")
859
- return f"❌ Error clearing logs: {str(e)}"
860
-
861
-
862
- # ==================== GRADIO INTERFACE ====================
863
-
864
- def build_interface():
865
- """Build the complete Gradio Blocks interface"""
866
-
867
- with gr.Blocks(title="Crypto Admin Dashboard", theme=gr.themes.Soft()) as demo:
868
-
869
- gr.Markdown("""
870
- # 🚀 Crypto Data Aggregator - Admin Dashboard
871
-
872
- **Real-time cryptocurrency data aggregation and analysis platform**
873
-
874
- Features: Provider Management | Market Data | Auto Provider Loader | HF Models | System Diagnostics
875
- """)
876
-
877
- with gr.Tabs():
878
-
879
- # ==================== TAB 1: STATUS ====================
880
- with gr.Tab("📊 Status"):
881
- gr.Markdown("### System Status Overview")
882
-
883
- with gr.Row():
884
- status_refresh_btn = gr.Button("🔄 Refresh Status", variant="primary")
885
- status_diag_btn = gr.Button("🔧 Run Quick Diagnostics")
886
-
887
- status_summary = gr.Markdown()
888
-
889
- with gr.Row():
890
- with gr.Column():
891
- gr.Markdown("#### Database Statistics")
892
- db_stats_json = gr.JSON()
893
-
894
- with gr.Column():
895
- gr.Markdown("#### System Information")
896
- system_info_json = gr.JSON()
897
-
898
- diag_output = gr.Markdown()
899
-
900
- # Load initial status
901
- demo.load(
902
- fn=get_status_tab,
903
- outputs=[status_summary, db_stats_json, system_info_json]
904
- )
905
-
906
- # Refresh button
907
- status_refresh_btn.click(
908
- fn=get_status_tab,
909
- outputs=[status_summary, db_stats_json, system_info_json]
910
- )
911
-
912
- # Quick diagnostics
913
- status_diag_btn.click(
914
- fn=lambda: run_diagnostics_from_status(False),
915
- outputs=diag_output
916
- )
917
-
918
- # ==================== TAB 2: PROVIDERS ====================
919
- with gr.Tab("🔌 Providers"):
920
- gr.Markdown("### API Provider Management")
921
-
922
- with gr.Row():
923
- provider_category = gr.Dropdown(
924
- label="Filter by Category",
925
- choices=get_provider_categories(),
926
- value="All"
927
- )
928
- provider_reload_btn = gr.Button("🔄 Reload Providers", variant="primary")
929
-
930
- providers_table = gr.Dataframe(
931
- label="Providers",
932
- interactive=False,
933
- wrap=True
934
- ) if PANDAS_AVAILABLE else gr.JSON(label="Providers")
935
-
936
- provider_status = gr.Textbox(label="Status", interactive=False)
937
-
938
- # Load initial providers
939
- demo.load(
940
- fn=lambda: get_providers_table("All"),
941
- outputs=providers_table
942
- )
943
-
944
- # Category filter
945
- provider_category.change(
946
- fn=get_providers_table,
947
- inputs=provider_category,
948
- outputs=providers_table
949
- )
950
-
951
- # Reload button
952
- provider_reload_btn.click(
953
- fn=reload_providers_config,
954
- outputs=[providers_table, provider_status]
955
- )
956
-
957
- # ==================== TAB 3: MARKET DATA ====================
958
- with gr.Tab("📈 Market Data"):
959
- gr.Markdown("### Live Cryptocurrency Market Data")
960
-
961
- with gr.Row():
962
- market_search = gr.Textbox(
963
- label="Search",
964
- placeholder="Search by name or symbol..."
965
- )
966
- market_refresh_btn = gr.Button("🔄 Refresh Prices", variant="primary")
967
-
968
- market_table = gr.Dataframe(
969
- label="Market Data",
970
- interactive=False,
971
- wrap=True,
972
- height=400
973
- ) if PANDAS_AVAILABLE else gr.JSON(label="Market Data")
974
-
975
- market_status = gr.Textbox(label="Status", interactive=False)
976
-
977
- # Price chart section
978
- if PLOTLY_AVAILABLE:
979
- gr.Markdown("#### Price History Chart")
980
-
981
- with gr.Row():
982
- chart_symbol = gr.Textbox(
983
- label="Symbol",
984
- placeholder="BTC",
985
- value="BTC"
986
- )
987
- chart_timeframe = gr.Dropdown(
988
- label="Timeframe",
989
- choices=["24h", "7d", "30d", "90d"],
990
- value="7d"
991
- )
992
- chart_plot_btn = gr.Button("📊 Plot")
993
-
994
- price_chart = gr.Plot(label="Price History")
995
-
996
- chart_plot_btn.click(
997
- fn=plot_price_history,
998
- inputs=[chart_symbol, chart_timeframe],
999
- outputs=price_chart
1000
- )
1001
-
1002
- # Load initial data
1003
- demo.load(
1004
- fn=lambda: get_market_data_table(""),
1005
- outputs=market_table
1006
- )
1007
-
1008
- # Search
1009
- market_search.change(
1010
- fn=get_market_data_table,
1011
- inputs=market_search,
1012
- outputs=market_table
1013
- )
1014
-
1015
- # Refresh
1016
- market_refresh_btn.click(
1017
- fn=refresh_market_data,
1018
- outputs=[market_table, market_status]
1019
- )
1020
-
1021
- # ==================== TAB 4: APL SCANNER ====================
1022
- with gr.Tab("🔍 APL Scanner"):
1023
- gr.Markdown("### Auto Provider Loader")
1024
- gr.Markdown("Automatically discover, validate, and integrate API providers and HuggingFace models.")
1025
-
1026
- with gr.Row():
1027
- apl_scan_btn = gr.Button("▶️ Run APL Scan", variant="primary", size="lg")
1028
- apl_report_btn = gr.Button("📄 View Last Report")
1029
-
1030
- apl_output = gr.Markdown()
1031
-
1032
- apl_scan_btn.click(
1033
- fn=run_apl_scan,
1034
- outputs=apl_output
1035
- )
1036
-
1037
- apl_report_btn.click(
1038
- fn=get_apl_report,
1039
- outputs=apl_output
1040
- )
1041
-
1042
- # Load last report on startup
1043
- demo.load(
1044
- fn=get_apl_report,
1045
- outputs=apl_output
1046
- )
1047
-
1048
- # ==================== TAB 5: HF MODELS ====================
1049
- with gr.Tab("🤖 HF Models"):
1050
- gr.Markdown("### HuggingFace Models Status & Testing")
1051
-
1052
- with gr.Row():
1053
- hf_init_btn = gr.Button("🔄 Initialize Models", variant="primary")
1054
- hf_refresh_btn = gr.Button("🔄 Refresh Status")
1055
-
1056
- hf_models_table = gr.Dataframe(
1057
- label="Models",
1058
- interactive=False
1059
- ) if PANDAS_AVAILABLE else gr.JSON(label="Models")
1060
-
1061
- hf_status = gr.Textbox(label="Status", interactive=False)
1062
-
1063
- gr.Markdown("#### Test Model")
1064
-
1065
- with gr.Row():
1066
- test_model_dropdown = gr.Dropdown(
1067
- label="Model",
1068
- choices=["sentiment", "sentiment_twitter", "sentiment_financial", "summarization"],
1069
- value="sentiment"
1070
- )
1071
-
1072
- test_input = gr.Textbox(
1073
- label="Test Input",
1074
- placeholder="Enter text to test the model...",
1075
- lines=3
1076
- )
1077
-
1078
- test_btn = gr.Button("▶️ Run Test", variant="secondary")
1079
-
1080
- test_output = gr.Markdown(label="Test Output")
1081
-
1082
- # Load initial status
1083
- demo.load(
1084
- fn=get_hf_models_status,
1085
- outputs=hf_models_table
1086
- )
1087
-
1088
- # Initialize models
1089
- hf_init_btn.click(
1090
- fn=initialize_hf_models,
1091
- outputs=[hf_models_table, hf_status]
1092
- )
1093
-
1094
- # Refresh status
1095
- hf_refresh_btn.click(
1096
- fn=get_hf_models_status,
1097
- outputs=hf_models_table
1098
- )
1099
-
1100
- # Test model
1101
- test_btn.click(
1102
- fn=test_hf_model,
1103
- inputs=[test_model_dropdown, test_input],
1104
- outputs=test_output
1105
- )
1106
-
1107
- # ==================== TAB 6: DIAGNOSTICS ====================
1108
- with gr.Tab("🔧 Diagnostics"):
1109
- gr.Markdown("### System Diagnostics & Auto-Repair")
1110
-
1111
- with gr.Row():
1112
- diag_run_btn = gr.Button("▶️ Run Diagnostics", variant="primary")
1113
- diag_autofix_btn = gr.Button("🔧 Run with Auto-Fix", variant="secondary")
1114
-
1115
- diagnostics_output = gr.Markdown()
1116
-
1117
- diag_run_btn.click(
1118
- fn=lambda: run_full_diagnostics(False),
1119
- outputs=diagnostics_output
1120
- )
1121
-
1122
- diag_autofix_btn.click(
1123
- fn=lambda: run_full_diagnostics(True),
1124
- outputs=diagnostics_output
1125
- )
1126
-
1127
- # ==================== TAB 7: LOGS ====================
1128
- with gr.Tab("📋 Logs"):
1129
- gr.Markdown("### System Logs Viewer")
1130
-
1131
- with gr.Row():
1132
- log_type = gr.Dropdown(
1133
- label="Log Type",
1134
- choices=["recent", "errors", "warnings"],
1135
- value="recent"
1136
- )
1137
- log_lines = gr.Slider(
1138
- label="Lines to Show",
1139
- minimum=10,
1140
- maximum=500,
1141
- value=100,
1142
- step=10
1143
- )
1144
-
1145
- with gr.Row():
1146
- log_refresh_btn = gr.Button("🔄 Refresh Logs", variant="primary")
1147
- log_clear_btn = gr.Button("🗑️ Clear Logs", variant="secondary")
1148
-
1149
- logs_output = gr.Markdown()
1150
- log_clear_status = gr.Textbox(label="Status", interactive=False, visible=False)
1151
-
1152
- # Load initial logs
1153
- demo.load(
1154
- fn=lambda: get_logs("recent", 100),
1155
- outputs=logs_output
1156
- )
1157
-
1158
- # Refresh logs
1159
- log_refresh_btn.click(
1160
- fn=get_logs,
1161
- inputs=[log_type, log_lines],
1162
- outputs=logs_output
1163
- )
1164
-
1165
- # Update when dropdown changes
1166
- log_type.change(
1167
- fn=get_logs,
1168
- inputs=[log_type, log_lines],
1169
- outputs=logs_output
1170
- )
1171
-
1172
- # Clear logs
1173
- log_clear_btn.click(
1174
- fn=clear_logs,
1175
- outputs=log_clear_status
1176
- ).then(
1177
- fn=lambda: get_logs("recent", 100),
1178
- outputs=logs_output
1179
- )
1180
-
1181
- # Footer
1182
- gr.Markdown("""
1183
- ---
1184
- **Crypto Data Aggregator Admin Dashboard** | Real Data Only | No Mock/Fake Data
1185
- """)
1186
-
1187
- return demo
1188
-
1189
 
1190
- # ==================== MAIN ENTRY POINT ====================
 
1191
 
1192
- demo = build_interface()
 
1193
 
1194
- if __name__ == "__main__":
1195
- logger.info("Launching Gradio dashboard...")
1196
-
1197
- # Try to mount FastAPI app for API endpoints
1198
- try:
1199
- from fastapi import FastAPI as FastAPIApp
1200
- from fastapi.middleware.wsgi import WSGIMiddleware
1201
- import uvicorn
1202
- from threading import Thread
1203
- import time
1204
-
1205
- # Import the FastAPI app from hf_unified_server
1206
- try:
1207
- from hf_unified_server import app as fastapi_app
1208
- logger.info("✅ FastAPI app imported successfully")
1209
-
1210
- # Start FastAPI server in a separate thread on port 7861
1211
- def run_fastapi():
1212
- uvicorn.run(
1213
- fastapi_app,
1214
- host="0.0.0.0",
1215
- port=7861,
1216
- log_level="info"
1217
- )
1218
-
1219
- fastapi_thread = Thread(target=run_fastapi, daemon=True)
1220
- fastapi_thread.start()
1221
- time.sleep(2) # Give FastAPI time to start
1222
- logger.info("✅ FastAPI server started on port 7861")
1223
- except ImportError as e:
1224
- logger.warning(f"⚠️ Could not import FastAPI app: {e}")
1225
- except Exception as e:
1226
- logger.warning(f"⚠️ Could not start FastAPI server: {e}")
1227
-
1228
- demo.launch(
1229
- server_name="0.0.0.0",
1230
- server_port=7860,
1231
- share=False
1232
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from pathlib import Path
2
+ import sys
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ BASE_DIR = Path(__file__).resolve().parent
5
+ FINAL_DIR = BASE_DIR / "final"
6
 
7
+ if str(FINAL_DIR) not in sys.path:
8
+ sys.path.insert(0, str(FINAL_DIR))
9
 
10
+ from hf_unified_server import app
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
final/.doc-organization.sh ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Persian/Farsi documents
4
+ mv README_FA.md docs/persian/ 2>/dev/null
5
+ mv PROJECT_STRUCTURE_FA.md docs/persian/ 2>/dev/null
6
+ mv QUICK_REFERENCE_FA.md docs/persian/ 2>/dev/null
7
+ mv REALTIME_FEATURES_FA.md docs/persian/ 2>/dev/null
8
+ mv VERIFICATION_REPORT_FA.md docs/persian/ 2>/dev/null
9
+
10
+ # Deployment guides
11
+ mv DEPLOYMENT_GUIDE.md docs/deployment/ 2>/dev/null
12
+ mv PRODUCTION_DEPLOYMENT_GUIDE.md docs/deployment/ 2>/dev/null
13
+ mv README_DEPLOYMENT.md docs/deployment/ 2>/dev/null
14
+ mv HUGGINGFACE_DEPLOYMENT.md docs/deployment/ 2>/dev/null
15
+ mv README_HF_SPACES.md docs/deployment/ 2>/dev/null
16
+ mv README_HUGGINGFACE.md docs/deployment/ 2>/dev/null
17
+ mv INSTALL.md docs/deployment/ 2>/dev/null
18
+
19
+ # Component documentation
20
+ mv WEBSOCKET_API_DOCUMENTATION.md docs/components/ 2>/dev/null
21
+ mv WEBSOCKET_API_IMPLEMENTATION.md docs/components/ 2>/dev/null
22
+ mv WEBSOCKET_GUIDE.md docs/components/ 2>/dev/null
23
+ mv COLLECTORS_README.md docs/components/ 2>/dev/null
24
+ mv COLLECTORS_IMPLEMENTATION_SUMMARY.md docs/components/ 2>/dev/null
25
+ mv GRADIO_DASHBOARD_README.md docs/components/ 2>/dev/null
26
+ mv GRADIO_DASHBOARD_IMPLEMENTATION.md docs/components/ 2>/dev/null
27
+ mv CRYPTO_DATA_BANK_README.md docs/components/ 2>/dev/null
28
+ mv HF_DATA_ENGINE_IMPLEMENTATION.md docs/components/ 2>/dev/null
29
+ mv README_BACKEND.md docs/components/ 2>/dev/null
30
+ mv CHARTS_VALIDATION_DOCUMENTATION.md docs/components/ 2>/dev/null
31
+
32
+ # Reports & Analysis
33
+ mv PROJECT_ANALYSIS_COMPLETE.md docs/reports/ 2>/dev/null
34
+ mv PRODUCTION_AUDIT_COMPREHENSIVE.md docs/reports/ 2>/dev/null
35
+ mv ENTERPRISE_DIAGNOSTIC_REPORT.md docs/reports/ 2>/dev/null
36
+ mv STRICT_UI_AUDIT_REPORT.md docs/reports/ 2>/dev/null
37
+ mv SYSTEM_CAPABILITIES_REPORT.md docs/reports/ 2>/dev/null
38
+ mv UI_REWRITE_TECHNICAL_REPORT.md docs/reports/ 2>/dev/null
39
+ mv DASHBOARD_FIX_REPORT.md docs/reports/ 2>/dev/null
40
+ mv COMPLETION_REPORT.md docs/reports/ 2>/dev/null
41
+ mv IMPLEMENTATION_REPORT.md docs/reports/ 2>/dev/null
42
+
43
+ # Guides & Summaries
44
+ mv IMPLEMENTATION_SUMMARY.md docs/guides/ 2>/dev/null
45
+ mv INTEGRATION_SUMMARY.md docs/guides/ 2>/dev/null
46
+ mv QUICK_INTEGRATION_GUIDE.md docs/guides/ 2>/dev/null
47
+ mv QUICK_START_ENTERPRISE.md docs/guides/ 2>/dev/null
48
+ mv ENHANCED_FEATURES.md docs/guides/ 2>/dev/null
49
+ mv ENTERPRISE_UI_UPGRADE_DOCUMENTATION.md docs/guides/ 2>/dev/null
50
+ mv PROJECT_SUMMARY.md docs/guides/ 2>/dev/null
51
+ mv PR_CHECKLIST.md docs/guides/ 2>/dev/null
52
+
53
+ # Archive (old/redundant files)
54
+ mv README_OLD.md docs/archive/ 2>/dev/null
55
+ mv README_ENHANCED.md docs/archive/ 2>/dev/null
56
+ mv WORKING_SOLUTION.md docs/archive/ 2>/dev/null
57
+ mv REAL_DATA_WORKING.md docs/archive/ 2>/dev/null
58
+ mv REAL_DATA_SERVER.md docs/archive/ 2>/dev/null
59
+ mv SERVER_INFO.md docs/archive/ 2>/dev/null
60
+ mv HF_INTEGRATION.md docs/archive/ 2>/dev/null
61
+ mv HF_INTEGRATION_README.md docs/archive/ 2>/dev/null
62
+ mv HF_IMPLEMENTATION_COMPLETE.md docs/archive/ 2>/dev/null
63
+ mv COMPLETE_IMPLEMENTATION.md docs/archive/ 2>/dev/null
64
+ mv FINAL_SETUP.md docs/archive/ 2>/dev/null
65
+ mv FINAL_STATUS.md docs/archive/ 2>/dev/null
66
+ mv FRONTEND_COMPLETE.md docs/archive/ 2>/dev/null
67
+ mv PRODUCTION_READINESS_SUMMARY.md docs/archive/ 2>/dev/null
68
+ mv PRODUCTION_READY.md docs/archive/ 2>/dev/null
69
+
70
+ echo "Documentation organized successfully!"
final/.dockerignore ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+ pip-log.txt
24
+ pip-delete-this-directory.txt
25
+
26
+ # Virtual environments
27
+ venv/
28
+ ENV/
29
+ env/
30
+ .venv
31
+
32
+ # IDE
33
+ .vscode/
34
+ .idea/
35
+ *.swp
36
+ *.swo
37
+ *~
38
+ .DS_Store
39
+
40
+ # Git
41
+ .git/
42
+ .gitignore
43
+ .gitattributes
44
+
45
+ # Documentation
46
+ *.md
47
+ docs/
48
+ README*.md
49
+ CHANGELOG.md
50
+ LICENSE
51
+
52
+ # Testing
53
+ .pytest_cache/
54
+ .coverage
55
+ htmlcov/
56
+ .tox/
57
+ .hypothesis/
58
+ tests/
59
+ test_*.py
60
+
61
+ # Logs and databases (will be created in container)
62
+ *.log
63
+ logs/
64
+ data/*.db
65
+ data/*.sqlite
66
+ data/*.db-journal
67
+
68
+ # Environment files (should be set via docker-compose or HF Secrets)
69
+ .env
70
+ .env.*
71
+ !.env.example
72
+
73
+ # Docker
74
+ docker-compose*.yml
75
+ !docker-compose.yml
76
+ Dockerfile
77
+ .dockerignore
78
+
79
+ # CI/CD
80
+ .github/
81
+ .gitlab-ci.yml
82
+ .travis.yml
83
+ azure-pipelines.yml
84
+
85
+ # Temporary files
86
+ *.tmp
87
+ *.bak
88
+ *.swp
89
+ temp/
90
+ tmp/
91
+
92
+ # Node modules (if any)
93
+ node_modules/
94
+ package-lock.json
95
+ yarn.lock
96
+
97
+ # OS files
98
+ Thumbs.db
99
+ .DS_Store
100
+ desktop.ini
101
+
102
+ # Jupyter notebooks
103
+ .ipynb_checkpoints/
104
+ *.ipynb
105
+
106
+ # Model cache (models will be downloaded in container)
107
+ models/
108
+ .cache/
109
+ .huggingface/
110
+
111
+ # Large files that shouldn't be in image
112
+ *.tar
113
+ *.tar.gz
114
+ *.zip
115
+ *.rar
116
+ *.7z
117
+
118
+ # Screenshots and assets not needed
119
+ screenshots/
120
+ assets/*.png
121
+ assets/*.jpg
final/.env ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HuggingFace Configuration
2
+ HUGGINGFACE_TOKEN=your_token_here
3
+ ENABLE_SENTIMENT=true
4
+ SENTIMENT_SOCIAL_MODEL=ElKulako/cryptobert
5
+ SENTIMENT_NEWS_MODEL=kk08/CryptoBERT
6
+ HF_REGISTRY_REFRESH_SEC=21600
7
+ HF_HTTP_TIMEOUT=8.0
8
+
9
+ # Existing API Keys (if any)
10
+ ETHERSCAN_KEY_1=
11
+ ETHERSCAN_KEY_2=
12
+ BSCSCAN_KEY=
13
+ TRONSCAN_KEY=
14
+ COINMARKETCAP_KEY_1=
15
+ COINMARKETCAP_KEY_2=
16
+ NEWSAPI_KEY=
17
+ CRYPTOCOMPARE_KEY=
18
+
19
+ # HuggingFace API Token
20
+ HF_TOKEN=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
final/.env.example ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HuggingFace Configuration
2
+ HUGGINGFACE_TOKEN=your_token_here
3
+ ENABLE_SENTIMENT=true
4
+ SENTIMENT_SOCIAL_MODEL=ElKulako/cryptobert
5
+ SENTIMENT_NEWS_MODEL=kk08/CryptoBERT
6
+ HF_REGISTRY_REFRESH_SEC=21600
7
+ HF_HTTP_TIMEOUT=8.0
8
+
9
+ # Existing API Keys (if any)
10
+ ETHERSCAN_KEY_1=
11
+ ETHERSCAN_KEY_2=
12
+ BSCSCAN_KEY=
13
+ TRONSCAN_KEY=
14
+ COINMARKETCAP_KEY_1=
15
+ COINMARKETCAP_KEY_2=
16
+ NEWSAPI_KEY=
17
+ CRYPTOCOMPARE_KEY=
final/.flake8 ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [flake8]
2
+ max-line-length = 100
3
+ max-complexity = 15
4
+ extend-ignore = E203, E266, E501, W503
5
+ exclude =
6
+ .git,
7
+ __pycache__,
8
+ .venv,
9
+ venv,
10
+ build,
11
+ dist,
12
+ *.egg-info,
13
+ .mypy_cache,
14
+ .pytest_cache,
15
+ data,
16
+ logs,
17
+ node_modules
18
+
19
+ # Error codes to always check
20
+ select = E,W,F,C,N
21
+
22
+ # Per-file ignores
23
+ per-file-ignores =
24
+ __init__.py:F401
25
+ tests/*:D
26
+
27
+ # Count errors
28
+ count = True
29
+ statistics = True
final/.github/workflows/ci.yml ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: CI/CD Pipeline
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, develop, claude/* ]
6
+ pull_request:
7
+ branches: [ main, develop ]
8
+
9
+ jobs:
10
+ code-quality:
11
+ name: Code Quality Checks
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v3
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v4
19
+ with:
20
+ python-version: '3.9'
21
+
22
+ - name: Cache dependencies
23
+ uses: actions/cache@v3
24
+ with:
25
+ path: ~/.cache/pip
26
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
27
+ restore-keys: |
28
+ ${{ runner.os }}-pip-
29
+
30
+ - name: Install dependencies
31
+ run: |
32
+ python -m pip install --upgrade pip
33
+ pip install -r requirements.txt
34
+ pip install black flake8 isort mypy pylint pytest pytest-cov pytest-asyncio
35
+
36
+ - name: Run Black (code formatting check)
37
+ run: |
38
+ black --check --diff .
39
+
40
+ - name: Run isort (import sorting check)
41
+ run: |
42
+ isort --check-only --diff .
43
+
44
+ - name: Run Flake8 (linting)
45
+ run: |
46
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
47
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=100 --statistics
48
+
49
+ - name: Run MyPy (type checking)
50
+ run: |
51
+ mypy --install-types --non-interactive --ignore-missing-imports .
52
+ continue-on-error: true # Don't fail build on type errors initially
53
+
54
+ - name: Run Pylint
55
+ run: |
56
+ pylint **/*.py --exit-zero --max-line-length=100
57
+ continue-on-error: true
58
+
59
+ test:
60
+ name: Run Tests
61
+ runs-on: ubuntu-latest
62
+ strategy:
63
+ matrix:
64
+ python-version: ['3.8', '3.9', '3.10', '3.11']
65
+
66
+ steps:
67
+ - uses: actions/checkout@v3
68
+
69
+ - name: Set up Python ${{ matrix.python-version }}
70
+ uses: actions/setup-python@v4
71
+ with:
72
+ python-version: ${{ matrix.python-version }}
73
+
74
+ - name: Cache dependencies
75
+ uses: actions/cache@v3
76
+ with:
77
+ path: ~/.cache/pip
78
+ key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements.txt') }}
79
+
80
+ - name: Install dependencies
81
+ run: |
82
+ python -m pip install --upgrade pip
83
+ pip install -r requirements.txt
84
+ pip install pytest pytest-cov pytest-asyncio pytest-timeout
85
+
86
+ - name: Run pytest with coverage
87
+ run: |
88
+ pytest tests/ -v --cov=. --cov-report=xml --cov-report=html --cov-report=term
89
+
90
+ - name: Upload coverage to Codecov
91
+ uses: codecov/codecov-action@v3
92
+ with:
93
+ file: ./coverage.xml
94
+ flags: unittests
95
+ name: codecov-umbrella
96
+ fail_ci_if_error: false
97
+
98
+ security-scan:
99
+ name: Security Scanning
100
+ runs-on: ubuntu-latest
101
+
102
+ steps:
103
+ - uses: actions/checkout@v3
104
+
105
+ - name: Set up Python
106
+ uses: actions/setup-python@v4
107
+ with:
108
+ python-version: '3.9'
109
+
110
+ - name: Install security tools
111
+ run: |
112
+ python -m pip install --upgrade pip
113
+ pip install safety bandit
114
+
115
+ - name: Run Safety (dependency vulnerability check)
116
+ run: |
117
+ pip install -r requirements.txt
118
+ safety check --json || true
119
+
120
+ - name: Run Bandit (security linting)
121
+ run: |
122
+ bandit -r . -f json -o bandit-report.json || true
123
+
124
+ - name: Upload security reports
125
+ uses: actions/upload-artifact@v3
126
+ with:
127
+ name: security-reports
128
+ path: |
129
+ bandit-report.json
130
+
131
+ docker-build:
132
+ name: Docker Build Test
133
+ runs-on: ubuntu-latest
134
+
135
+ steps:
136
+ - uses: actions/checkout@v3
137
+
138
+ - name: Set up Docker Buildx
139
+ uses: docker/setup-buildx-action@v2
140
+
141
+ - name: Build Docker image
142
+ run: |
143
+ docker build -t crypto-dt-source:test .
144
+
145
+ - name: Test Docker image
146
+ run: |
147
+ docker run --rm crypto-dt-source:test python --version
148
+
149
+ integration-tests:
150
+ name: Integration Tests
151
+ runs-on: ubuntu-latest
152
+ needs: [test]
153
+
154
+ steps:
155
+ - uses: actions/checkout@v3
156
+
157
+ - name: Set up Python
158
+ uses: actions/setup-python@v4
159
+ with:
160
+ python-version: '3.9'
161
+
162
+ - name: Install dependencies
163
+ run: |
164
+ python -m pip install --upgrade pip
165
+ pip install -r requirements.txt
166
+ pip install pytest pytest-asyncio
167
+
168
+ - name: Run integration tests
169
+ run: |
170
+ pytest tests/test_integration.py -v
171
+ env:
172
+ ENABLE_AUTH: false
173
+ LOG_LEVEL: DEBUG
174
+
175
+ performance-tests:
176
+ name: Performance Tests
177
+ runs-on: ubuntu-latest
178
+ needs: [test]
179
+
180
+ steps:
181
+ - uses: actions/checkout@v3
182
+
183
+ - name: Set up Python
184
+ uses: actions/setup-python@v4
185
+ with:
186
+ python-version: '3.9'
187
+
188
+ - name: Install dependencies
189
+ run: |
190
+ python -m pip install --upgrade pip
191
+ pip install -r requirements.txt
192
+ pip install pytest pytest-benchmark
193
+
194
+ - name: Run performance tests
195
+ run: |
196
+ pytest tests/test_performance.py -v --benchmark-only
197
+ continue-on-error: true
198
+
199
+ deploy-docs:
200
+ name: Deploy Documentation
201
+ runs-on: ubuntu-latest
202
+ if: github.ref == 'refs/heads/main'
203
+ needs: [code-quality, test]
204
+
205
+ steps:
206
+ - uses: actions/checkout@v3
207
+
208
+ - name: Set up Python
209
+ uses: actions/setup-python@v4
210
+ with:
211
+ python-version: '3.9'
212
+
213
+ - name: Install documentation tools
214
+ run: |
215
+ pip install mkdocs mkdocs-material
216
+
217
+ - name: Build documentation
218
+ run: |
219
+ # mkdocs build
220
+ echo "Documentation build placeholder"
221
+
222
+ - name: Deploy to GitHub Pages
223
+ uses: peaceiris/actions-gh-pages@v3
224
+ if: github.event_name == 'push'
225
+ with:
226
+ github_token: ${{ secrets.GITHUB_TOKEN }}
227
+ publish_dir: ./site
228
+ continue-on-error: true
final/.gitignore ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ venv/
25
+ ENV/
26
+ env/
27
+
28
+ # IDE
29
+ .vscode/
30
+ .idea/
31
+ *.swp
32
+ *.swo
33
+
34
+ # Data
35
+ data/*.db
36
+ data/*.db-journal
37
+ data/exports/
38
+ crypto_monitor.db
39
+ crypto_monitor.db-journal
40
+
41
+ # Environment
42
+ .env
43
+
44
+ # Logs
45
+ *.log
46
+
47
+ # OS
48
+ .DS_Store
49
+ Thumbs.db
final/Can you put data sources/api - Copy.html ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Crypto Data Authority Pack – Demo UI</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <!-- Vazirmatn -->
8
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;600;700&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root{
11
+ --bg:#ffffff;
12
+ --fg:#0b1220;
13
+ --muted:#6b7280;
14
+ --primary:#4f46e5;
15
+ --primary-weak:#eef2ff;
16
+ --success:#10b981;
17
+ --warn:#f59e0b;
18
+ --danger:#ef4444;
19
+ --glass: rgba(255,255,255,0.65);
20
+ --border: rgba(15,23,42,0.08);
21
+ --shadow: 0 12px 30px rgba(2,6,23,0.08);
22
+ --radius:14px;
23
+ --radius-sm:10px;
24
+ --card-blur: 10px;
25
+ --kpi-bg:#f8fafc;
26
+ --chip:#0ea5e9;
27
+ --table-stripe:#f8fafc;
28
+ --code-bg:#0b1220;
29
+ --code-fg:#e5e7eb;
30
+ }
31
+ *{box-sizing:border-box}
32
+ html,body{height:100%}
33
+ body{
34
+ margin:0; background:var(--bg); color:var(--fg);
35
+ font-family:"Vazirmatn",system-ui,Segoe UI,Roboto,Arial,sans-serif;
36
+ }
37
+ .page{
38
+ display:grid; grid-template-rows:auto auto 1fr; gap:18px; min-height:100vh;
39
+ padding:24px clamp(16px,3vw,32px) 32px;
40
+ }
41
+
42
+ /* Header */
43
+ .topbar{
44
+ display:flex; align-items:center; gap:16px; flex-wrap:wrap;
45
+ }
46
+ .brand{
47
+ display:flex; align-items:center; gap:10px; padding:10px 14px;
48
+ border:1px solid var(--border); border-radius:var(--radius);
49
+ background:var(--glass); backdrop-filter: blur(var(--card-blur)); box-shadow:var(--shadow);
50
+ }
51
+ .brand svg{width:24px;height:24px}
52
+ .brand h1{font-size:16px; margin:0}
53
+ .ribbon{
54
+ margin-inline-start:auto; display:flex; gap:10px; align-items:center; flex-wrap:wrap;
55
+ }
56
+ .chip{
57
+ display:inline-flex; align-items:center; gap:8px; padding:8px 12px; border-radius:999px;
58
+ background:var(--primary-weak); color:var(--primary); border:1px solid var(--border);
59
+ font-size:12px; font-weight:600;
60
+ }
61
+ .chip .dot{width:8px;height:8px;border-radius:50%;}
62
+ .dot.green{background:var(--success)} .dot.gray{background:#94a3b8} .dot.red{background:var(--danger)}
63
+
64
+ /* Toolbar */
65
+ .toolbar{
66
+ display:flex; gap:12px; flex-wrap:wrap; align-items:center;
67
+ background:var(--glass); border:1px solid var(--border);
68
+ border-radius:var(--radius); padding:12px; backdrop-filter: blur(var(--card-blur)); box-shadow:var(--shadow);
69
+ }
70
+ .toolbar .group{display:flex; gap:8px; align-items:center; flex-wrap:wrap}
71
+ .input{
72
+ display:flex; align-items:center; gap:8px; padding:10px 12px; border:1px solid var(--border);
73
+ background:#ffffff; border-radius:12px; min-width:260px;
74
+ }
75
+ .input input{
76
+ border:none; outline:none; background:transparent; width:180px; font-family:inherit; font-size:14px;
77
+ }
78
+ .btn{
79
+ appearance:none; border:none; outline:none; cursor:pointer; font-family:inherit;
80
+ padding:10px 14px; border-radius:12px; font-weight:700; transition: .2s ease;
81
+ background:var(--primary); color:white; box-shadow:0 6px 16px rgba(79,70,229,.25);
82
+ }
83
+ .btn.ghost{background:transparent; color:var(--primary); border:1px solid var(--border)}
84
+ .btn:active{transform:translateY(1px)}
85
+ .switch{
86
+ display:inline-flex; gap:6px; border:1px solid var(--border); border-radius:999px; padding:6px;
87
+ background:#fff;
88
+ }
89
+ .switch button{padding:8px 12px; border-radius:999px; border:none; background:transparent; cursor:pointer; font-weight:700}
90
+ .switch button.active{background:var(--primary-weak); color:var(--primary)}
91
+
92
+ /* Tabs */
93
+ .tabs{
94
+ display:flex; gap:8px; flex-wrap:wrap; position:sticky; top:12px; z-index:3;
95
+ }
96
+ .tab{
97
+ border:1px solid var(--border); background:#fff; border-radius:12px; padding:10px 12px; cursor:pointer; font-weight:700;
98
+ }
99
+ .tab.active{background:var(--primary); color:#fff; box-shadow:0 6px 16px rgba(79,70,229,.25)}
100
+ .content{
101
+ display:grid; gap:18px;
102
+ }
103
+
104
+ /* Cards */
105
+ .grid{
106
+ display:grid; gap:16px;
107
+ grid-template-columns: repeat(12, minmax(0,1fr));
108
+ }
109
+ .col-12{grid-column: span 12}
110
+ .col-6{grid-column: span 6}
111
+ .col-4{grid-column: span 4}
112
+ .col-3{grid-column: span 3}
113
+ @media (max-width:1100px){ .col-6,.col-4{grid-column: span 12} .col-3{grid-column: span 6} }
114
+ .card{
115
+ background:var(--glass); border:1px solid var(--border);
116
+ border-radius:var(--radius); box-shadow:var(--shadow); backdrop-filter: blur(var(--card-blur));
117
+ padding:16px;
118
+ }
119
+ .card h3{margin:0 0 6px 0; font-size:15px}
120
+ .muted{color:var(--muted); font-size:13px}
121
+ .kpi{
122
+ display:flex; align-items:end; justify-content:space-between; background:var(--kpi-bg);
123
+ border:1px solid var(--border); border-radius:var(--radius-sm); padding:14px;
124
+ }
125
+ .kpi .big{font-size:26px; font-weight:800}
126
+ .kpi .trend{display:flex; align-items:center; gap:6px; font-weight:700}
127
+ .trend.up{color:var(--success)} .trend.down{color:var(--danger)}
128
+
129
+ /* Table */
130
+ .table{
131
+ width:100%; border-collapse:separate; border-spacing:0; overflow:auto; border:1px solid var(--border); border-radius:12px;
132
+ }
133
+ .table th, .table td{
134
+ text-align:start; padding:10px 12px; border-bottom:1px solid var(--border); font-size:13px;
135
+ vertical-align:middle;
136
+ }
137
+ .table tr:nth-child(odd) td{background:var(--table-stripe)}
138
+ .badge{display:inline-flex; align-items:center; gap:6px; padding:6px 10px; border-radius:999px; font-weight:700; font-size:12px;}
139
+ .badge.ok{background:#ecfdf5; color:var(--success); border:1px solid #d1fae5}
140
+ .badge.warn{background:#fff7ed; color:var(--warn); border:1px solid #ffedd5}
141
+ .badge.err{background:#fef2f2; color:var(--danger); border:1px solid #fee2e2}
142
+
143
+ /* Code */
144
+ pre{
145
+ margin:0; background:var(--code-bg); color:var(--code-fg);
146
+ border-radius:12px; padding:12px; direction:ltr; overflow:auto; font-family:ui-monospace,Menlo,Consolas,monospace; font-size:12px;
147
+ }
148
+
149
+ /* Toast */
150
+ .toast{
151
+ position:fixed; bottom:24px; inset-inline:24px auto; display:none; z-index:10;
152
+ padding:12px 16px; border-radius:12px; background:#0b1220; color:#e5e7eb; box-shadow:var(--shadow);
153
+ }
154
+ .toast.show{display:block; animation:fade .25s ease}
155
+ @keyframes fade{from{opacity:0; transform:translateY(8px)} to{opacity:1; transform:translateY(0)}}
156
+
157
+ /* Icon button */
158
+ .icon-btn{display:inline-flex; align-items:center; gap:8px; border:1px solid var(--border); padding:10px 12px; border-radius:12px; background:#fff; cursor:pointer}
159
+ .icon-btn svg{width:18px;height:18px}
160
+ </style>
161
+ </head>
162
+ <body>
163
+ <div class="page" id="app">
164
+ <!-- Header -->
165
+ <header class="topbar" aria-label="Header">
166
+ <div class="brand" aria-label="Brand">
167
+ <!-- Logo SVG -->
168
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
169
+ <defs>
170
+ <linearGradient id="g1" x1="0" y1="0" x2="1" y2="1">
171
+ <stop offset="0" stop-color="#6366f1"/><stop offset="1" stop-color="#22d3ee"/>
172
+ </linearGradient>
173
+ </defs>
174
+ <circle cx="12" cy="12" r="10" stroke="url(#g1)" stroke-width="2"></circle>
175
+ <path d="M8 12h8M12 8v8" stroke="url(#g1)" stroke-width="2" stroke-linecap="round"/>
176
+ </svg>
177
+ <div>
178
+ <h1>Crypto Data Authority Pack</h1>
179
+ <div class="muted" id="subtitle">مرجع یکپارچه منابع بازار، خبر، سنتیمنت، آن‌چین</div>
180
+ </div>
181
+ </div>
182
+
183
+ <div class="ribbon">
184
+ <span class="chip" title="Backend status">
185
+ <span class="dot green"></span> Backend: Healthy
186
+ </span>
187
+ <span class="chip" id="ws-status" title="WebSocket status">
188
+ <span class="dot gray"></span> WS: Disconnected
189
+ </span>
190
+ <span class="chip" title="Updated">
191
+ ⏱️ Updated: <span id="updatedAt">—</span>
192
+ </span>
193
+ </div>
194
+ </header>
195
+
196
+ <!-- Toolbar -->
197
+ <section class="toolbar" role="region" aria-label="Toolbar">
198
+ <div class="group" aria-label="Auth">
199
+ <div class="input" title="Service Token (Api-Key)">
200
+ <!-- key icon -->
201
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
202
+ <path d="M15 7a4 4 0 1 0-6 3.465V14h3v3h3l2-2v-2h2l1-1" stroke="#64748b" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
203
+ </svg>
204
+ <input id="token" type="password" placeholder="توکن سرویس (Api-Key)..." aria-label="Service token">
205
+ </div>
206
+ <button class="btn" id="btnApply">اعمال توکن</button>
207
+ <button class="btn ghost" id="btnTest">تست اتصال</button>
208
+ </div>
209
+ <div class="group" aria-label="Toggles">
210
+ <div class="switch" role="tablist" aria-label="Language">
211
+ <button id="fa" class="active" aria-selected="true">FA</button>
212
+ <button id="en">EN</button>
213
+ </div>
214
+ <div class="switch" aria-label="Direction">
215
+ <button id="rtl" class="active">RTL</button>
216
+ <button id="ltr">LTR</button>
217
+ </div>
218
+ </div>
219
+ <div class="group">
220
+ <button class="icon-btn" id="btnExport" title="Export current JSON">
221
+ <!-- download icon -->
222
+ <svg viewBox="0 0 24 24" fill="none"><path d="M12 3v12m0 0l-4-4m4 4l4-4M5 21h14" stroke="#0ea5e9" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
223
+ خروجی JSON
224
+ </button>
225
+ </div>
226
+ </section>
227
+
228
+ <!-- Tabs -->
229
+ <nav class="tabs" aria-label="Sections">
230
+ <button class="tab active" data-tab="overview">Overview</button>
231
+ <button class="tab" data-tab="registry">Registry</button>
232
+ <button class="tab" data-tab="failover">Failover</button>
233
+ <button class="tab" data-tab="realtime">Realtime</button>
234
+ <button class="tab" data-tab="collection">Collection Plan</button>
235
+ <button class="tab" data-tab="templates">Query Templates</button>
236
+ <button class="tab" data-tab="observability">Observability</button>
237
+ <button class="tab" data-tab="docs">Docs</button>
238
+ </nav>
239
+
240
+ <!-- Content -->
241
+ <main class="content">
242
+
243
+ <!-- OVERVIEW -->
244
+ <section class="grid" id="tab-overview" role="tabpanel" aria-labelledby="Overview">
245
+ <div class="card col-12">
246
+ <h3>خلاصه / Summary</h3>
247
+ <div class="muted">این دموی UI نمای کلی «پک مرجع داده‌های رمز ارز» را با کارت‌های KPI، تب‌های پیمایش و جدول‌های فشرده نمایش می‌دهد.</div>
248
+ </div>
249
+
250
+ <div class="col-3 card">
251
+ <div class="kpi">
252
+ <div>
253
+ <div class="muted">Total Providers</div>
254
+ <div class="big" id="kpiTotal">—</div>
255
+ </div>
256
+ <div class="trend up">▲ +5</div>
257
+ </div>
258
+ </div>
259
+ <div class="col-3 card">
260
+ <div class="kpi">
261
+ <div>
262
+ <div class="muted">Free Endpoints</div>
263
+ <div class="big" id="kpiFree">—</div>
264
+ </div>
265
+ <div class="trend up">▲ 2</div>
266
+ </div>
267
+ </div>
268
+ <div class="col-3 card">
269
+ <div class="kpi">
270
+ <div>
271
+ <div class="muted">Failover Chains</div>
272
+ <div class="big" id="kpiChains">—</div>
273
+ </div>
274
+ <div class="trend up">▲ 1</div>
275
+ </div>
276
+ </div>
277
+ <div class="col-3 card">
278
+ <div class="kpi">
279
+ <div>
280
+ <div class="muted">WS Topics</div>
281
+ <div class="big" id="kpiWs">—</div>
282
+ </div>
283
+ <div class="trend up">▲ 3</div>
284
+ </div>
285
+ </div>
286
+
287
+ <div class="col-12 card">
288
+ <h3>نمونه درخواست‌ها (Examples)</h3>
289
+ <div class="grid">
290
+ <div class="col-6">
291
+ <div class="muted">CoinGecko – Simple Price</div>
292
+ <pre>curl -s 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&amp;vs_currencies=usd'</pre>
293
+ </div>
294
+ <div class="col-6">
295
+ <div class="muted">Binance – Klines</div>
296
+ <pre>curl -s 'https://api.binance.com/api/v3/klines?symbol=BTCUSDT&amp;interval=1h&amp;limit=100'</pre>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </section>
301
+
302
+ <!-- REGISTRY -->
303
+ <section class="grid" id="tab-registry" role="tabpanel" hidden>
304
+ <div class="card col-12">
305
+ <h3>Registry Snapshot</h3>
306
+ <div class="muted">نمای خلاصه‌ی رده‌ها و سرویس‌ها (نمونه‌داده داخلی)</div>
307
+ </div>
308
+ <div class="card col-6">
309
+ <h3>Categories</h3>
310
+ <table class="table" id="tblCategories" aria-label="Categories table">
311
+ <thead><tr><th>Category</th><th>Count</th><th>Notes</th></tr></thead>
312
+ <tbody></tbody>
313
+ </table>
314
+ </div>
315
+ <div class="card col-6">
316
+ <h3>Highlighted Providers</h3>
317
+ <table class="table" id="tblProviders" aria-label="Providers table">
318
+ <thead><tr><th>Name</th><th>Role</th><th>Status</th></tr></thead>
319
+ <tbody></tbody>
320
+ </table>
321
+ </div>
322
+ </section>
323
+
324
+ <!-- FAILOVER -->
325
+ <section class="grid" id="tab-failover" role="tabpanel" hidden>
326
+ <div class="card col-12">
327
+ <h3>Failover Chains</h3>
328
+ <div class="muted">زنجیره‌های جایگزینی آزاد-محور (Free-first)</div>
329
+ </div>
330
+ <div class="card col-12" id="failoverList"></div>
331
+ </section>
332
+
333
+ <!-- REALTIME -->
334
+ <section class="grid" id="tab-realtime" role="tabpanel" hidden>
335
+ <div class="card col-12">
336
+ <h3>Realtime (WebSocket)</h3>
337
+ <div class="muted">قرارداد موضوع‌ها، پیام‌ها، heartbeat و استراتژی reconnect</div>
338
+ </div>
339
+ <div class="card col-6">
340
+ <h3>Topics</h3>
341
+ <table class="table" id="tblWs" aria-label="WS topics">
342
+ <thead><tr><th>Topic</th><th>Example</th></tr></thead>
343
+ <tbody></tbody>
344
+ </table>
345
+ </div>
346
+ <div class="card col-6">
347
+ <h3>Sample Message</h3>
348
+ <pre id="wsMessage"></pre>
349
+ <div style="margin-top:10px; display:flex; gap:8px">
350
+ <button class="btn" id="btnWsConnect">Connect (Mock)</button>
351
+ <button class="btn ghost" id="btnWsDisconnect">Disconnect</button>
352
+ </div>
353
+ </div>
354
+ </section>
355
+
356
+ <!-- COLLECTION PLAN -->
357
+ <section class="grid" id="tab-collection" role="tabpanel" hidden>
358
+ <div class="card col-12">
359
+ <h3>Collection Plan (ETL/ELT)</h3>
360
+ <div class="muted">زمان‌بندی دریافت داده و TTL</div>
361
+ </div>
362
+ <div class="card col-12">
363
+ <table class="table" id="tblCollection">
364
+ <thead><tr><th>Bucket</th><th>Endpoints</th><th>Schedule</th><th>TTL</th></tr></thead>
365
+ <tbody></tbody>
366
+ </table>
367
+ </div>
368
+ </section>
369
+
370
+ <!-- TEMPLATES -->
371
+ <section class="grid" id="tab-templates" role="tabpanel" hidden>
372
+ <div class="card col-12">
373
+ <h3>Query Templates</h3>
374
+ <div class="muted">قرارداد endpointها + نمونه cURL</div>
375
+ </div>
376
+ <div class="card col-6">
377
+ <h3>coingecko.simple_price</h3>
378
+ <pre>GET /simple/price?ids={ids}&amp;vs_currencies={fiats}</pre>
379
+ <pre>curl -s 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&amp;vs_currencies=usd'</pre>
380
+ </div>
381
+ <div class="card col-6">
382
+ <h3>binance_public.klines</h3>
383
+ <pre>GET /api/v3/klines?symbol={symbol}&amp;interval={interval}&amp;limit={n}</pre>
384
+ <pre>curl -s 'https://api.binance.com/api/v3/klines?symbol=BTCUSDT&amp;interval=1h&amp;limit=100'</pre>
385
+ </div>
386
+ </section>
387
+
388
+ <!-- OBSERVABILITY -->
389
+ <section class="grid" id="tab-observability" role="tabpanel" hidden>
390
+ <div class="card col-12">
391
+ <h3>Observability</h3>
392
+ <div class="muted">متریک‌ها، بررسی کیفیت داده، هشدارها</div>
393
+ </div>
394
+ <div class="card col-4">
395
+ <div class="kpi">
396
+ <div><div class="muted">Success Rate</div><div class="big" id="succRate">—</div></div>
397
+ <div class="trend up">▲</div>
398
+ </div>
399
+ </div>
400
+ <div class="card col-4">
401
+ <div class="kpi">
402
+ <div><div class="muted">p95 Latency</div><div class="big" id="p95">—</div></div>
403
+ <div class="trend down">▼</div>
404
+ </div>
405
+ </div>
406
+ <div class="card col-4">
407
+ <div class="kpi">
408
+ <div><div class="muted">Failover Activations</div><div class="big" id="fo">—</div></div>
409
+ <div class="trend up">▲</div>
410
+ </div>
411
+ </div>
412
+ <div class="card col-12">
413
+ <h3>Data Quality Checklist</h3>
414
+ <table class="table" id="tblDQ">
415
+ <thead><tr><th>Rule</th><th>Status</th><th>Note</th></tr></thead>
416
+ <tbody></tbody>
417
+ </table>
418
+ </div>
419
+ </section>
420
+
421
+ <!-- DOCS -->
422
+ <section class="grid" id="tab-docs" role="tabpanel" hidden>
423
+ <div class="card col-12">
424
+ <h3>Docs (Compact)</h3>
425
+ <div class="muted">راهنمای استفاده، امنیت و نسخه‌بندی به‌صورت خلاصه</div>
426
+ </div>
427
+ <div class="card col-6">
428
+ <h3>Quick Start</h3>
429
+ <ol style="margin:0; padding-inline-start:20px">
430
+ <li>JSON اصلی را لود کنید.</li>
431
+ <li>از discovery برای یافتن id استفاده کنید.</li>
432
+ <li>query_templates را بخوانید.</li>
433
+ <li>Auth را اعمال کنید (توکن سرویس + کلید آزاد).</li>
434
+ <li>درخواست بزنید یا به WS مشترک شوید.</li>
435
+ </ol>
436
+ </div>
437
+ <div class="card col-6">
438
+ <h3>Security Notes</h3>
439
+ <ul style="margin:0; padding-inline-start:20px">
440
+ <li>کلیدهای رایگان عمومی‌اند؛ برای سقف بیشتر کلید خودتان را وارد کنید.</li>
441
+ <li>توکن سرویس، سهمیه و دسترسی را کنترل می‌کند.</li>
442
+ <li>کلیدها در لاگ ماسک می‌شوند.</li>
443
+ </ul>
444
+ </div>
445
+ <div class="card col-12">
446
+ <h3>Change Log</h3>
447
+ <pre>{
448
+ "version": "3.0.0",
449
+ "changes": ["Added WS spec","Expanded failover","Token-based access & quotas","Observability & DQ"]
450
+ }</pre>
451
+ </div>
452
+ </section>
453
+
454
+ </main>
455
+ </div>
456
+
457
+ <!-- Toast -->
458
+ <div class="toast" id="toast" role="status" aria-live="polite">پیام نمونه...</div>
459
+
460
+ <script>
461
+ // -------- Sample Data (compact mirror of your spec) --------
462
+ const sample = {
463
+ metadata:{updated:new Date().toISOString()},
464
+ registry:{
465
+ rpc_nodes: [{id:"publicnode_eth_mainnet",name:"PublicNode Ethereum",role:"rpc",base_url:"https://ethereum.publicnode.com"}],
466
+ block_explorers:[{id:"etherscan_primary",name:"Etherscan",role:"primary",base_url:"https://api.etherscan.io/api"}],
467
+ market_data_apis:[
468
+ {id:"coingecko",name:"CoinGecko",free:true,base_url:"https://api.coingecko.com/api/v3"},
469
+ {id:"binance_public",name:"Binance Public",free:true,base_url:"https://api.binance.com"}
470
+ ],
471
+ news_apis:[
472
+ {id:"rss_coindesk",name:"CoinDesk RSS",free:true},
473
+ {id:"cointelegraph_rss",name:"CoinTelegraph RSS",free:true}
474
+ ],
475
+ sentiment_apis:[{id:"alternative_me_fng",name:"Alternative.me FNG",free:true}],
476
+ onchain_analytics_apis:[{id:"glassnode_general",name:"Glassnode",free:false}],
477
+ whale_tracking_apis:[{id:"whale_alert",name:"Whale Alert",free:false}],
478
+ community_sentiment_apis:[{id:"reddit_cryptocurrency_new",name:"Reddit r/CryptoCurrency",free:true}],
479
+ hf_resources:[{id:"hf_model_elkulako_cryptobert",name:"CryptoBERT",type:"model"}],
480
+ free_http_endpoints:[
481
+ {id:"cg_simple_price",name:"CG Simple Price"},
482
+ {id:"binance_klines",name:"Binance Klines"}
483
+ ],
484
+ local_backend_routes:[{id:"local_market_quotes",name:"Local Quotes"}],
485
+ cors_proxies:[{id:"allorigins",name:"AllOrigins"}]
486
+ },
487
+ failover:{
488
+ market:{chain:["coingecko","coinpaprika","coincap"],ttlSec:120},
489
+ news:{chain:["rss_coindesk","cointelegraph_rss","decrypt_rss"],ttlSec:600},
490
+ sentiment:{chain:["alternative_me_fng","cfgi_v1","cfgi_legacy"],ttlSec:300},
491
+ onchain:{chain:["etherscan_primary","blockscout_ethereum","blockchair_ethereum"],ttlSec:180}
492
+ },
493
+ realtime_spec:{
494
+ topics:["market.ticker","market.klines","indices.fng","news.headlines","social.aggregate"],
495
+ example:{topic:"market.ticker",ts:0,payload:{symbol:"BTCUSDT",price:67890.12}}
496
+ },
497
+ collection_plan:[
498
+ {bucket:"market", endpoints:["coingecko.simple_price"], schedule:"every 1 min", ttlSec:120},
499
+ {bucket:"indices", endpoints:["alternative_me_fng.fng"], schedule:"every 5 min", ttlSec:300},
500
+ {bucket:"news", endpoints:["rss_coindesk.feed","cointelegraph_rss.feed"], schedule:"every 10 min", ttlSec:600}
501
+ ],
502
+ observability:{
503
+ successRate:"98.2%", p95:"420 ms", failovers:3,
504
+ dq:[{rule:"non_empty_payload",ok:true},{rule:"freshness_within_ttl",ok:true},{rule:"price_nonnegative",ok:true}]
505
+ }
506
+ };
507
+
508
+ // -------- Helpers --------
509
+ const $ = (sel, root=document)=>root.querySelector(sel);
510
+ const $$ = (sel, root=document)=>Array.from(root.querySelectorAll(sel));
511
+ const toast = (msg,ms=2400)=>{
512
+ const t = $('#toast'); t.textContent = msg; t.classList.add('show');
513
+ setTimeout(()=>t.classList.remove('show'), ms);
514
+ };
515
+
516
+ // -------- Init KPIs --------
517
+ function initKPIs(){
518
+ const r = sample.registry;
519
+ const total = Object.values(r).reduce((s,arr)=> s + (Array.isArray(arr)?arr.length:0), 0);
520
+ const free = (r.market_data_apis?.filter(x=>x.free).length||0) +
521
+ (r.news_apis?.filter(x=>x.free).length||0) +
522
+ (r.community_sentiment_apis?.filter(x=>x.free).length||0) +
523
+ (r.free_http_endpoints?.length||0);
524
+ $('#kpiTotal').textContent = total;
525
+ $('#kpiFree').textContent = free;
526
+ $('#kpiChains').textContent = Object.keys(sample.failover||{}).length;
527
+ $('#kpiWs').textContent = (sample.realtime_spec?.topics||[]).length;
528
+ $('#updatedAt').textContent = new Date(sample.metadata.updated).toLocaleString('fa-IR');
529
+ }
530
+
531
+ // -------- Registry Tables --------
532
+ function renderRegistry(){
533
+ const tbody = $('#tblCategories tbody');
534
+ tbody.innerHTML = '';
535
+ const reg = sample.registry;
536
+ for(const k of Object.keys(reg)){
537
+ const count = (reg[k]||[]).length;
538
+ const tr = document.createElement('tr');
539
+ tr.innerHTML = `<td>${k}</td><td>${count}</td><td class="muted">—</td>`;
540
+ tbody.appendChild(tr);
541
+ }
542
+
543
+ const pBody = $('#tblProviders tbody');
544
+ pBody.innerHTML = '';
545
+ const highlights = [
546
+ {name:"CoinGecko", role:"Market", ok:true},
547
+ {name:"Binance Public", role:"Market/Klines", ok:true},
548
+ {name:"Etherscan", role:"Explorer", ok:true},
549
+ {name:"Glassnode", role:"On-chain", ok:false},
550
+ ];
551
+ highlights.forEach(h=>{
552
+ const badge = h.ok ? '<span class="badge ok">Online</span>' : '<span class="badge warn">Limited</span>';
553
+ const tr = document.createElement('tr');
554
+ tr.innerHTML = `<td>${h.name}</td><td>${h.role}</td><td>${badge}</td>`;
555
+ pBody.appendChild(tr);
556
+ });
557
+ }
558
+
559
+ // -------- Failover --------
560
+ function renderFailover(){
561
+ const wrap = $('#failoverList'); wrap.innerHTML = '';
562
+ const fo = sample.failover;
563
+ for(const bucket in fo){
564
+ const row = document.createElement('div');
565
+ row.className = 'card';
566
+ const chips = fo[bucket].chain.map((id,i)=>`<span class="chip" style="margin:4px">${i+1}. ${id}</span>`).join(' ');
567
+ row.innerHTML = `<div class="muted">Bucket</div><h3 style="margin:4px 0 10px">${bucket}</h3>
568
+ <div>${chips}</div>
569
+ <div class="muted" style="margin-top:8px">TTL: ${fo[bucket].ttlSec}s</div>`;
570
+ wrap.appendChild(row);
571
+ }
572
+ }
573
+
574
+ // -------- Realtime --------
575
+ function renderRealtime(){
576
+ const tb = $('#tblWs tbody'); tb.innerHTML='';
577
+ (sample.realtime_spec.topics||[]).forEach(t=>{
578
+ const tr = document.createElement('tr');
579
+ tr.innerHTML = `<td>${t}</td><td class="muted">SUBSCRIBE → "${t}"</td>`;
580
+ tb.appendChild(tr);
581
+ });
582
+ $('#wsMessage').textContent = JSON.stringify(sample.realtime_spec.example,null,2);
583
+ }
584
+
585
+ // -------- Collection Plan --------
586
+ function renderCollection(){
587
+ const tb = $('#tblCollection tbody'); tb.innerHTML='';
588
+ (sample.collection_plan||[]).forEach(x=>{
589
+ const tr = document.createElement('tr');
590
+ tr.innerHTML = `<td>${x.bucket}</td><td>${x.endpoints.join(', ')}</td><td>${x.schedule}</td><td>${x.ttlSec}s</td>`;
591
+ tb.appendChild(tr);
592
+ });
593
+ }
594
+
595
+ // -------- Observability --------
596
+ function renderObs(){
597
+ $('#succRate').textContent = sample.observability.successRate;
598
+ $('#p95').textContent = sample.observability.p95;
599
+ $('#fo').textContent = sample.observability.failovers;
600
+ const tb = $('#tblDQ tbody'); tb.innerHTML='';
601
+ sample.observability.dq.forEach(r=>{
602
+ const st = r.ok ? '<span class="badge ok">OK</span>' : '<span class="badge err">Fail</span>';
603
+ const tr = document.createElement('tr');
604
+ tr.innerHTML = `<td>${r.rule}</td><td>${st}</td><td class="muted">—</td>`;
605
+ tb.appendChild(tr);
606
+ });
607
+ }
608
+
609
+ // -------- Tabs --------
610
+ $$('.tab').forEach(btn=>{
611
+ btn.addEventListener('click', ()=>{
612
+ $$('.tab').forEach(b=>b.classList.remove('active'));
613
+ btn.classList.add('active');
614
+ const key = btn.dataset.tab;
615
+ $$('[role="tabpanel"]').forEach(p=>p.hidden = true);
616
+ $('#tab-'+key).hidden = false;
617
+ window.scrollTo({top:0,behavior:'smooth'});
618
+ });
619
+ });
620
+
621
+ // -------- Toggles --------
622
+ $('#fa').onclick = ()=>{ document.documentElement.lang='fa'; $('#fa').classList.add('active'); $('#en').classList.remove('active'); $('#subtitle').textContent='مرجع یکپارچه منابع بازار، خبر، سنتیمنت، آن‌چین'; toast('زبان: فارسی'); };
623
+ $('#en').onclick = ()=>{ document.documentElement.lang='en'; $('#en').classList.add('active'); $('#fa').classList.remove('active'); $('#subtitle').textContent='Unified registry for market, news, sentiment & on-chain'; toast('Language: English'); };
624
+ $('#rtl').onclick = ()=>{ document.documentElement.dir='rtl'; $('#rtl').classList.add('active'); $('#ltr').classList.remove('active'); toast('جهت: RTL'); };
625
+ $('#ltr').onclick = ()=>{ document.documentElement.dir='ltr'; $('#ltr').classList.add('active'); $('#rtl').classList.remove('active'); toast('Direction: LTR'); };
626
+
627
+ // -------- Token + WS Mock --------
628
+ $('#btnApply').onclick = ()=>{
629
+ const tok = $('#token').value.trim();
630
+ if(!tok){ toast('توکن خالی است'); return;}
631
+ toast('توکن اعمال شد');
632
+ };
633
+ $('#btnTest').onclick = ()=> toast('اتصال HTTP (نمونه) موفق ✔');
634
+
635
+ let wsMock = false;
636
+ function setWsStatus(on){
637
+ const chip = $('#ws-status'); const dot = chip.querySelector('.dot');
638
+ if(on){ dot.className='dot green'; chip.lastChild.textContent=' WS: Connected'; }
639
+ else{ dot.className='dot gray'; chip.lastChild.textContent=' WS: Disconnected'; }
640
+ }
641
+ $('#btnWsConnect').onclick = ()=>{ wsMock=true; setWsStatus(true); toast('WS connected (mock)'); };
642
+ $('#btnWsDisconnect').onclick = ()=>{ wsMock=false; setWsStatus(false); toast('WS disconnected'); };
643
+
644
+ // -------- Export --------
645
+ $('#btnExport').onclick = ()=>{
646
+ const blob = new Blob([JSON.stringify(sample,null,2)], {type:'application/json'});
647
+ const a = document.createElement('a');
648
+ a.href = URL.createObjectURL(blob);
649
+ a.download = 'crypto_resources_authoritative.sample.json';
650
+ a.click();
651
+ URL.revokeObjectURL(a.href);
652
+ };
653
+
654
+ // -------- Mount --------
655
+ function mount(){
656
+ initKPIs(); renderRegistry(); renderFailover(); renderRealtime(); renderCollection(); renderObs();
657
+ }
658
+ mount();
659
+ </script>
660
+ </body>
661
+ </html>
final/Can you put data sources/api - Copy.txt ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ tronscan
3
+ 7ae72726-bffe-4e74-9c33-97b761eeea21
4
+
5
+ Bscscan
6
+ K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT
7
+
8
+ eherscann_2
9
+ T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45
10
+
11
+ eherscann
12
+ SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2
13
+
14
+ coinmarketcap
15
+ 04cf4b5b-9868-465c-8ba0-9f2e78c92eb1
16
+
17
+
18
+ COINMARKETCAP_KEY:
19
+ b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c
20
+
21
+ NEWSAPI_KEY:
22
+ pub_346789abc123def456789ghi012345jkl
23
+
24
+ CRYPTOCOMPARE_KEY:
25
+ e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f
26
+
27
+
28
+ در ادامه ۳۸ سرویس (primary + fallbacks) که قبلاً در حافظه داشتیم را دسته‌بندی و لیست کرده‌ام، و روش‌های ردیابی فعالیت نهنگ‌ها را هم به بخش مربوط اضافه کردم. این لیست در حافظه ذخیره شد.
29
+
30
+ ۱. Block Explorer APIs (۱۱ endpoint)
31
+ TronScan (primary)
32
+
33
+ TronGrid (fallback)
34
+
35
+ Blockchair (TRON) (fallback)
36
+
37
+ BscScan (primary)
38
+
39
+ AnkrScan (BSC) (fallback)
40
+
41
+ BinTools (BSC) (fallback)
42
+
43
+ Etherscan (primary)
44
+
45
+ Etherscan (backup key) (fallback)
46
+
47
+ Infura (ETH) (fallback)
48
+
49
+ Alchemy (ETH) (fallback)
50
+
51
+ Covalent (ETH) (fallback)
52
+
53
+ ۲. Market Data APIs (۹ endpoint)
54
+ CoinMarketCap (primary key #1)
55
+
56
+ CoinMarketCap (primary key #2)
57
+
58
+ CoinGecko (no key)
59
+
60
+ Nomics
61
+
62
+ Messari
63
+
64
+ BraveNewCoin
65
+
66
+ CryptoCompare (primary)
67
+
68
+ Kaiko (fallback)
69
+
70
+ CoinAPI.io (fallback)
71
+
72
+ ۳. News APIs (۷ endpoint)
73
+ NewsAPI.org
74
+
75
+ CryptoPanic
76
+
77
+ CryptoControl
78
+
79
+ CoinDesk API
80
+
81
+ CoinTelegraph API
82
+
83
+ CryptoSlate API
84
+
85
+ The Block API
86
+
87
+ ۴. Sentiment & Mood APIs (۴ endpoint)
88
+ Alternative.me (Fear & Greed)
89
+
90
+ Santiment
91
+
92
+ LunarCrush
93
+
94
+ TheTie.io
95
+
96
+ ۵. On-Chain Analytics APIs (۴ endpoint)
97
+ Glassnode
98
+
99
+ IntoTheBlock
100
+
101
+ Nansen
102
+
103
+ The Graph (subgraphs)
104
+
105
+ ۶. Whale-Tracking APIs (۲ endpoint)
106
+ WhaleAlert (primary)
107
+
108
+ Arkham Intelligence (fallback)
109
+
110
+ روش‌های ردیابی فعالیت نهنگ‌ها
111
+ پویش تراکنش‌های بزرگ
112
+
113
+ با WhaleAlert هر X ثانیه، endpoint /v1/transactions رو poll کن و فقط TX با مقدار دلخواه (مثلاً >۱M دلار) رو نمایش بده.
114
+
115
+ وبهوک/نوتیفیکیشن
116
+
117
+ از قابلیت Webhook در WhaleAlert یا Arkham استفاده کن تا به‌محض رخداد تراکنش بزرگ، درخواست POST بیاد.
118
+
119
+ فیلتر مستقیم روی WebSocket
120
+
121
+ اگر Infura/Alchemy یا BscScan WebSocket دارن، به mempool گوش بده و TXهایی با حجم بالا رو فیلتر کن.
122
+
123
+ داشبورد نهنگ‌ها از Nansen یا Dune
124
+
125
+ از Nansen Alerts یا کوئری‌های Dune برای رصد کیف‌پول‌های شناخته‌شده (smart money) و انتقالاتشان استفاده کن.
126
+
127
+ نقشه حرارتی (Heatmap) تراکنش‌ها
128
+
129
+ داده‌های WhaleAlert رو در یک نمودار خطی یا نقشه پخش جغرافیایی (اگر GPS دارن) نمایش بده.
130
+
131
+ ۷. Community Sentiment (۱ endpoint)
132
+ Reddit
133
+
134
+
135
+
136
+ Block Explorer APIs (۱۱ سرویس)
137
+ سرویس API واقعی شرح نحوهٔ پیاده‌سازی
138
+ TronScan GET https://api.tronscan.org/api/account?address={address}&apiKey={KEY} جزئیات حساب و موجودی Tron fetch(url)، پارس JSON، نمایش balance
139
+ TronGrid GET https://api.trongrid.io/v1/accounts/{address}?apiKey={KEY} همان عملکرد TronScan با endpoint متفاوت مشابه fetch با URL جدید
140
+ Blockchair GET https://api.blockchair.com/tron/dashboards/address/{address}?key={KEY} داشبورد آدرس TRON fetch(url)، استفاده از data.address
141
+ BscScan GET https://api.bscscan.com/api?module=account&action=balance&address={address}&apikey={KEY} موجودی حساب BSC fetch(url)، نمایش result
142
+ AnkrScan GET https://api.ankr.com/scan/v1/bsc/address/{address}/balance?apiKey={KEY} موجودی از API آنکر fetch(url)، پارس JSON
143
+ BinTools GET https://api.bintools.io/v1/bsc/account/balance?address={address}&apikey={KEY} جایگزین BscScan مشابه fetch
144
+ Etherscan GET https://api.etherscan.io/api?module=account&action=balance&address={address}&apikey={KEY} موجودی حساب ETH fetch(url)، نمایش result
145
+ Etherscan_2 GET https://api.etherscan.io/api?module=account&action=balance&address={address}&apikey={SECOND_KEY} دومین کلید Etherscan همانند بالا
146
+ Infura JSON-RPC POST به https://mainnet.infura.io/v3/{PROJECT_ID} با بدنه { "jsonrpc":"2.0","method":"eth_getBalance","params":["{address}","latest"],"id":1 } استعلام موجودی از طریق RPC fetch(url, {method:'POST', body:JSON.stringify(...)})
147
+ Alchemy JSON-RPC POST به https://eth-mainnet.alchemyapi.io/v2/{KEY} همانند Infura استعلام RPC با سرعت و WebSocket WebSocket: new WebSocket('wss://eth-mainnet.alchemyapi.io/v2/{KEY}')
148
+ Covalent GET https://api.covalenthq.com/v1/1/address/{address}/balances_v2/?key={KEY} لیست دارایی‌های یک آدرس در شبکه Ethereum fetch(url), پارس data.items
149
+
150
+ ۲. Market Data APIs (۹ سرویس)
151
+ سرویس API واقعی شرح نحوهٔ پیاده‌سازی
152
+ CoinMarketCap GET https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=BTC&convert=USD<br/>Header: X-CMC_PRO_API_KEY: {KEY} قیمت لحظه‌ای و تغییرات درصدی fetch(url,{headers:{'X-CMC_PRO_API_KEY':KEY}})
153
+ CMC_Alt همان endpoint بالا با کلید دوم کلید جایگزین CMC مانند بالا
154
+ CoinGecko GET https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd بدون نیاز به کلید، قیمت ساده fetch(url)
155
+ Nomics GET https://api.nomics.com/v1/currencies/ticker?key={KEY}&ids=BTC,ETH&convert=USD قیمت و حجم معاملات fetch(url)
156
+ Messari GET https://data.messari.io/api/v1/assets/bitcoin/metrics متریک‌های پیشرفته (TVL، ROI و…) fetch(url)
157
+ BraveNewCoin GET https://bravenewcoin.p.rapidapi.com/ohlcv/BTC/latest<br/>Headers: x-rapidapi-key: {KEY} قیمت OHLCV لحظه‌ای fetch(url,{headers:{…}})
158
+ CryptoCompare GET https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH&tsyms=USD&api_key={KEY} قیمت چندگانه کریپто fetch(url)
159
+ Kaiko GET https://us.market-api.kaiko.io/v2/data/trades.v1/exchanges/Coinbase/spot/trades?base_token=BTC&quote_token=USD&page_limit=10&api_key={KEY} دیتای تریدهای زنده fetch(url)
160
+ CoinAPI.io GET https://rest.coinapi.io/v1/exchangerate/BTC/USD?apikey={KEY} نرخ تبدیل بین رمزارز و فیات fetch(url)
161
+
162
+ ۳. News & Aggregators (۷ سرویس)
163
+ سرویس API واقعی شرح نحوهٔ پیاده‌سازی
164
+ NewsAPI.org GET https://newsapi.org/v2/everything?q=crypto&apiKey={KEY} اخبار گسترده fetch(url)
165
+ CryptoPanic GET https://cryptopanic.com/api/v1/posts/?auth_token={KEY} جمع‌آوری اخبار از منابع متعدد fetch(url)
166
+ CryptoControl GET https://cryptocontrol.io/api/v1/public/news/local?language=EN&apiKey={KEY} اخبار محلی و جهانی fetch(url)
167
+ CoinDesk API GET https://api.coindesk.com/v2/prices/BTC/spot?api_key={KEY} قیمت لحظه‌ای BTC fetch(url)
168
+ CoinTelegraph GET https://api.cointelegraph.com/api/v1/articles?lang=en فید مقالات CoinTelegraph fetch(url)
169
+ CryptoSlate GET https://api.cryptoslate.com/news اخبار و تحلیل‌های CryptoSlate fetch(url)
170
+ The Block API GET https://api.theblock.co/v1/articles مقالات تخصصی بلاک‌چین fetch(url)
171
+
172
+ ۴. Sentiment & Mood (۴ سرویس)
173
+ سرویس API واقعی شرح نحوهٔ پیاده‌سازی
174
+ Alternative.me F&G GET https://api.alternative.me/fng/?limit=1&format=json شاخص ترس/طمع بازار fetch(url)، مقدار data[0].value
175
+ Santiment GraphQL POST به https://api.santiment.net/graphql با { query: "...sentiment..." } احساسات اجتماعی رمزارزها fetch(url,{method:'POST',body:!...})
176
+ LunarCrush GET https://api.lunarcrush.com/v2?data=assets&key={KEY} معیارهای اجتماعی و تعاملات fetch(url)
177
+ TheTie.io GET https://api.thetie.io/data/sentiment?symbol=BTC&apiKey={KEY} تحلیل احساسات بر اساس توییت‌ها fetch(url)
178
+
179
+ ۵. On-Chain Analytics (۴ سرویس)
180
+ سرویس API واقعی شرح نحوهٔ پیاده‌سازی
181
+ Glassnode GET https://api.glassnode.com/v1/metrics/indicators/sopr_ratio?api_key={KEY} شاخص‌های زنجیره‌ای (SOPR، HODL، …) fetch(url)
182
+ IntoTheBlock GET https://api.intotheblock.com/v1/insights/bitcoin/holders_breakdown?key={KEY} تجزیه و تحلیل دارندگان fetch(url)
183
+ Nansen GET https://api.nansen.ai/v1/balances?chain=ethereum&address={address}&api_key={KEY} مانیتور کیف‌پول‌های هوشمند (Smart Money) fetch(url)
184
+ The Graph GraphQL POST به https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3 با queryهای اختصاصی داده‌های on-chain از subgraphها fetch(url,{method:'POST',body:!...})
185
+
186
+ ۶. Whale-Tracking (۲ سرویس)
187
+ سرویس API واقعی شرح نحوهٔ پیاده‌سازی
188
+ WhaleAlert GET https://api.whale-alert.io/v1/transactions?api_key={KEY}&min_value=1000000&start={ts}&end={ts} تراکنش‌های بزرگ زنجیره‌ها fetch(url)، فیلتر data.transactions با value>…
189
+ Arkham GET https://api.arkham.com/v1/address/{address}/transfers?api_key={KEY} انتقالات کیف‌پول‌های شناخته‌شده fetch(url)، مانیتور transfers
190
+
191
+ ردیابی نهنگ‌ها:
192
+
193
+ Polling دوره‌ای → setInterval(()=>fetch(url), interval)
194
+
195
+ Webhook (WhaleAlert webhook config)
196
+
197
+ WebSocket (Alchemy/Infura mempool feed)
198
+
199
+ ۷. Community Sentiment (۱ سرویس)
200
+ سرویس API واقعی شرح نحوهٔ پیاده‌سازی
201
+ Reddit GET https://www.reddit.com/r/CryptoCurrency/new.json?limit=10 پست‌های جدید در رِدیت fetch(url), پارس data.data.children
202
+
203
+ ذخیره در حافظه انجام شد.
204
+
205
+
206
+ Sources
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+ Ask ChatGPT
216
+ You said:
217
+ برای سنتمنت منابع بیشتری پیدا کن منابع ارزشمندتری پیدا کن مارکت کپ مگه کلیدشو در اختیارت نذاشتم
218
+
219
+
220
+ ChatGPT said:
221
+
222
+ Thought for 4 seconds
223
+ در جدول زیر منابع جدید و با ارزش‌تر برای تحلیل احساسات (Sentiment) گردآوری شده‌اند، همراه با API واقعی و نحوهٔ پیاده‌سازی. همچنین کلید CoinMarketCap که در اختیارت قرار دادی در اینجا هاردکد شده است.
224
+
225
+ منابع پیشرفته‌تر برای Sentiment
226
+ سرویس API واقعی شرح نحوهٔ پیاده‌سازی (مثال)
227
+ Santiment POST https://api.santiment.net/graphql<br/>Body:
228
+
229
+ json
230
+ Copy
231
+ Edit
232
+ { "query":"{ projects(slug: \"bitcoin\") { sentimentMetrics { socialVolume, socialDominance } } }" }
233
+ ``` | حجم و تسلط بحث‌های اجتماعی روی پروژه‌ها |
234
+ ```js
235
+ fetch(url, {
236
+ method:'POST',
237
+ headers:{'Content-Type':'application/json','Authorization':KEY},
238
+ body:JSON.stringify({query:…})
239
+ })
240
+ .then(r=>r.json())
241
+ .then(data=>console.log(data));
242
+ ``` |
243
+ | **LunarCrush** | `GET https://api.lunarcrush.com/v2?data=assets&key={KEY}&symbol=BTC` | معیارهای تعاملی شبکه‌های اجتماعی (engagement) |
244
+ ```js
245
+ fetch(url)
246
+ .then(r=>r.json())
247
+ .then(d=>console.log(d.data[0]));
248
+ ``` |
249
+ | **TheTie.io** | `GET https://api.thetie.io/data/sentiment?symbol=BTC&interval=1h&apiKey={KEY}` | امتیاز احساسات بر اساس توییت‌ها و اخبار |
250
+ ```js
251
+ fetch(url)
252
+ .then(r=>r.json())
253
+ .then(d=>console.log(d.sentiment));
254
+ ``` |
255
+ | **CryptoQuant** | `GET https://api.cryptoquant.com/v1/ohlcv/latest?symbol=BTC&token={KEY}` | شاخص‌های درون‌زنجیره‌ای و احساسات نهنگ‌ها |
256
+ ```js
257
+ fetch(url)
258
+ .then(r=>r.json())
259
+ .then(d=>console.log(d));
260
+ ``` |
261
+ | **Glassnode Social**| `GET https://api.glassnode.com/v1/metrics/social/mention_count?api_key={KEY}&a=BTC` | تعداد ذکر شدن BTC در شبکه‌های اجتماعی |
262
+ ```js
263
+ fetch(url)
264
+ .then(r=>r.json())
265
+ .then(d=>console.log(d));
266
+ ``` |
267
+ | **CoinGecko Comm.** | `GET https://api.coingecko.com/api/v3/coins/bitcoin?localization=false&tickers=false&market_data=false&community_data=true` | داده‌های اجتماعی (Reddit, Twitter) |
268
+ ```js
269
+ fetch(url)
270
+ .then(r=>r.json())
271
+ .then(d=>console.log(d.community_data));
272
+ ``` |
273
+ | **Messari Social** | `GET https://data.messari.io/api/v1/assets/bitcoin/metrics/social` | متریک‌های شبکه‌های اجتماعی (mentions, sentiment) |
274
+ ```js
275
+ fetch(url)
276
+ .then(r=>r.json())
277
+ .then(d=>console.log(d.data));
278
+ ``` |
279
+ | **Reddit API** | `GET https://www.reddit.com/r/CryptoCurrency/comments.json?limit=20` | کامنت‌ها و ترندهای رِدیت |
280
+ ```js
281
+ fetch(url)
282
+ .then(r=>r.json())
283
+ .then(d=>console.log(d.data.children));
284
+ ``` |
285
+
286
+ > **نکته:** برای همهٔ این سرویس‌ها باید `{KEY}` را با کد API خود پر کنید.
287
+ > برای موارد GraphQL (Santiment, TheGraph) از `POST` و برای بقیه از `GET` استفاده می‌شود.
288
+
289
+ ---
290
+
291
+ ### به‌روزرسانی CoinMarketCap در `config.js`
292
+
293
+ ```js
294
+ // config.js
295
+ export const API_CONFIG = {
296
+ // … بقیه سرویس‌ها …
297
+
298
+ // Market Data APIs
299
+ coinmarketcap: {
300
+ primary: {
301
+ name: 'coinmarketcap',
302
+ baseUrl: 'https://pro-api.coinmarketcap.com/v1',
303
+ key: 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c' // COINMARKETCAP_KEY هاردکد شده
304
+ },
305
+ fallbacks: [
306
+ {
307
+ name: 'coinmarketcapAlt',
308
+ baseUrl: 'https://pro-api.coinmarketcap.com/v1',
309
+ key: '04cf4b5b-9868-465c-8ba0-9f2e78c92eb1' // کلید دوم
310
+ },
311
+ {
312
+ name: 'coingecko',
313
+ baseUrl: 'https://api.coingecko.com/api/v3',
314
+ key: '' // بدون کلید
315
+ }
316
+ // … بقیه fallbacks …
317
+ ]
318
+ }
319
+
320
+ // … سایر سرویس‌ها …
321
+ // config.js
322
+ export const API_CONFIG = {
323
+ // 1. Block Explorer APIs (11 endpoints)
324
+ tronscan: {
325
+ primary: {
326
+ name: 'tronscan',
327
+ baseUrl: 'https://api.tronscan.org/api',
328
+ key: '7ae72726-bffe-4e74-9c33-97b761eeea21'
329
+ },
330
+ fallbacks: [
331
+ { name: 'tronGrid', baseUrl: 'https://api.trongrid.io', key: '' },
332
+ { name: 'blockchair', baseUrl: 'https://api.blockchair.com/tron', key: '' }
333
+ ]
334
+ },
335
+ bscscan: {
336
+ primary: {
337
+ name: 'bscscan',
338
+ baseUrl: 'https://api.bscscan.com/api',
339
+ key: 'K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT'
340
+ },
341
+ fallbacks: [
342
+ { name: 'ankr', baseUrl: 'https://api.ankr.com/scan/bsc', key: '' },
343
+ { name: 'binTools', baseUrl: 'https://api.bintools.io/bsc', key: '' }
344
+ ]
345
+ },
346
+ etherscan: {
347
+ primary: {
348
+ name: 'etherscan',
349
+ baseUrl: 'https://api.etherscan.io/api',
350
+ key: 'SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2'
351
+ },
352
+ fallbacks: [
353
+ { name: 'etherscan_2', baseUrl: 'https://api.etherscan.io/api', key: 'T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45' },
354
+ { name: 'infura', baseUrl: 'https://mainnet.infura.io/v3', key: '' },
355
+ { name: 'alchemy', baseUrl: 'https://eth-mainnet.alchemyapi.io/v2', key: '' },
356
+ { name: 'covalent', baseUrl: 'https://api.covalenthq.com/v1/1', key: '' }
357
+ ]
358
+ },
359
+
360
+ // 2. Market Data APIs (9 endpoints)
361
+ coinmarketcap: {
362
+ primary: {
363
+ name: 'coinmarketcap',
364
+ baseUrl: 'https://pro-api.coinmarketcap.com/v1',
365
+ key: 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c'
366
+ },
367
+ fallbacks: [
368
+ { name: 'coinmarketcapAlt', baseUrl: 'https://pro-api.coinmarketcap.com/v1', key: '04cf4b5b-9868-465c-8ba0-9f2e78c92eb1' },
369
+ { name: 'coingecko', baseUrl: 'https://api.coingecko.com/api/v3', key: '' },
370
+ { name: 'nomics', baseUrl: 'https://api.nomics.com/v1', key: '' },
371
+ { name: 'messari', baseUrl: 'https://data.messari.io/api/v1', key: '' },
372
+ { name: 'braveNewCoin', baseUrl: 'https://bravenewcoin.p.rapidapi.com', key: '' }
373
+ ]
374
+ },
375
+ cryptocompare: {
376
+ primary: {
377
+ name: 'cryptocompare',
378
+ baseUrl: 'https://min-api.cryptocompare.com/data',
379
+ key: 'e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f'
380
+ },
381
+ fallbacks: [
382
+ { name: 'kaiko', baseUrl: 'https://us.market-api.kaiko.io/v2', key: '' },
383
+ { name: 'coinapi', baseUrl: 'https://rest.coinapi.io/v1', key: '' }
384
+ ]
385
+ },
386
+
387
+ // 3. News & Aggregators (7 endpoints)
388
+ newsapi: {
389
+ primary: {
390
+ name: 'newsapi',
391
+ baseUrl: 'https://newsapi.org/v2',
392
+ key: 'pub_346789abc123def456789ghi012345jkl'
393
+ },
394
+ fallbacks: [
395
+ { name: 'cryptoPanic', baseUrl: 'https://cryptopanic.com/api/v1', key: '' },
396
+ { name: 'cryptoControl', baseUrl: 'https://cryptocontrol.io/api/v1/public', key: '' },
397
+ { name: 'coinDesk', baseUrl: 'https://api.coindesk.com/v2', key: '' },
398
+ { name: 'coinTelegraph', baseUrl: 'https://api.cointelegraph.com', key: '' },
399
+ { name: 'cryptoSlate', baseUrl: 'https://api.cryptoslate.com', key: '' },
400
+ { name: 'theBlock', baseUrl: 'https://api.theblock.co/v1', key: '' }
401
+ ]
402
+ },
403
+
404
+ // 4. Sentiment & Mood (8 endpoints)
405
+ // includes both basic and advanced sources
406
+ sentiment: {
407
+ primary: {
408
+ name: 'alternativeMe',
409
+ baseUrl: 'https://api.alternative.me/fng',
410
+ key: ''
411
+ },
412
+ fallbacks: [
413
+ { name: 'santiment', baseUrl: 'https://api.santiment.net/graphql', key: 'YOUR_SANTIMENT_KEY' },
414
+ { name: 'lunarCrush', baseUrl: 'https://api.lunarcrush.com/v2', key: 'YOUR_LUNARCRUSH_KEY' },
415
+ { name: 'theTie', baseUrl: 'https://api.thetie.io', key: 'YOUR_THETIE_KEY' },
416
+ { name: 'cryptoQuant', baseUrl: 'https://api.cryptoquant.com/v1', key: 'YOUR_CRYPTOQUANT_KEY' },
417
+ { name: 'glassnodeSocial',baseUrl: 'https://api.glassnode.com/v1', key: 'YOUR_GLASSNODE_KEY' },
418
+ { name: 'coingeckoComm', baseUrl: 'https://api.coingecko.com/api/v3', key: '' },
419
+ { name: 'messariSocial', baseUrl: 'https://data.messari.io/api/v1', key: '' },
420
+ { name: 'reddit', baseUrl: 'https://www.reddit.com', key: '' }
421
+ ]
422
+ },
423
+
424
+ // 5. On-Chain Analytics (4 endpoints)
425
+ glassnode: { primary: { name: 'glassnode', baseUrl: 'https://api.glassnode.com/v1', key: '' } },
426
+ intoTheBlock: { primary: { name: 'intoTheBlock', baseUrl: 'https://api.intotheblock.com/v1', key: '' } },
427
+ nansen: { primary: { name: 'nansen', baseUrl: 'https://api.nansen.ai/v1', key: '' } },
428
+ theGraph: { primary: { name: 'theGraph', baseUrl: 'https://api.thegraph.com/subgraphs/name', key: '' } },
429
+
430
+ // 6. Whale-Tracking (2 endpoints)
431
+ whaleAlert: {
432
+ primary: { name: 'whaleAlert', baseUrl: 'https://api.whale-alert.io/v1', key: 'YOUR_WHALEALERT_KEY' },
433
+ fallbacks: [
434
+ { name: 'arkham', baseUrl: 'https://api.arkham.com', key: 'YOUR_ARKHAM_KEY' }
435
+ ]
436
+ }
437
+ };
438
+
439
+
440
+
441
+
442
+
443
+
444
+
445
+
446
+
final/Can you put data sources/api-config-complete (1).txt ADDED
@@ -0,0 +1,1634 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ╔══════════════════════════════════════════════════════════════════════════════════════╗
2
+ ║ CRYPTOCURRENCY API CONFIGURATION - COMPLETE GUIDE ║
3
+ ║ تنظیمات کامل API های ارز دیجیتال ║
4
+ ║ Updated: October 2025 ║
5
+ ╚══════════════════════════════════════════════════════════════════════════════════════╝
6
+
7
+ ═══════════════════════════════════════════════════════════════════════════════════════
8
+ 🔑 API KEYS - کلیدهای API
9
+ ═══════════════════════════════════════════════════════════════════════════════════════
10
+
11
+ EXISTING KEYS (کلیدهای موجود):
12
+ ─────────────────────────────────
13
+ TronScan: 7ae72726-bffe-4e74-9c33-97b761eeea21
14
+ BscScan: K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT
15
+ Etherscan: SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2
16
+ Etherscan_2: T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45
17
+ CoinMarketCap: 04cf4b5b-9868-465c-8ba0-9f2e78c92eb1
18
+ CoinMarketCap_2: b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c
19
+ NewsAPI: pub_346789abc123def456789ghi012345jkl
20
+ CryptoCompare: e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f
21
+
22
+
23
+ ═══════════════════════════════════════════════════════════════════════════════════════
24
+ 🌐 CORS PROXY SOLUTIONS - راه‌حل‌های پروکسی CORS
25
+ ═══════════════════════════════════════════════════════════════════════════════════════
26
+
27
+ FREE CORS PROXIES (پروکسی‌های رایگان):
28
+ ──────────────────────────────────────────
29
+
30
+ 1. AllOrigins (بدون محدودیت)
31
+ URL: https://api.allorigins.win/get?url={TARGET_URL}
32
+ Example: https://api.allorigins.win/get?url=https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd
33
+ Features: JSON/JSONP, گزینه raw content
34
+
35
+ 2. CORS.SH (بدون rate limit)
36
+ URL: https://proxy.cors.sh/{TARGET_URL}
37
+ Example: https://proxy.cors.sh/https://api.coinmarketcap.com/v1/cryptocurrency/quotes/latest
38
+ Features: سریع، قابل اعتماد، نیاز به header Origin یا x-requested-with
39
+
40
+ 3. Corsfix (60 req/min رایگان)
41
+ URL: https://proxy.corsfix.com/?url={TARGET_URL}
42
+ Example: https://proxy.corsfix.com/?url=https://api.etherscan.io/api
43
+ Features: header override، cached responses
44
+
45
+ 4. CodeTabs (محبوب)
46
+ URL: https://api.codetabs.com/v1/proxy?quest={TARGET_URL}
47
+ Example: https://api.codetabs.com/v1/proxy?quest=https://api.binance.com/api/v3/ticker/price
48
+
49
+ 5. ThingProxy (10 req/sec)
50
+ URL: https://thingproxy.freeboard.io/fetch/{TARGET_URL}
51
+ Example: https://thingproxy.freeboard.io/fetch/https://api.nomics.com/v1/currencies/ticker
52
+ Limit: 100,000 characters per request
53
+
54
+ 6. Crossorigin.me
55
+ URL: https://crossorigin.me/{TARGET_URL}
56
+ Note: فقط GET، محدودیت 2MB
57
+
58
+ 7. Self-Hosted CORS-Anywhere
59
+ GitHub: https://github.com/Rob--W/cors-anywhere
60
+ Deploy: Cloudflare Workers، Vercel، Heroku
61
+
62
+ USAGE PATTERN (الگوی استفاده):
63
+ ────────────────────────────────
64
+ // Without CORS Proxy
65
+ fetch('https://api.example.com/data')
66
+
67
+ // With CORS Proxy
68
+ const corsProxy = 'https://api.allorigins.win/get?url=';
69
+ fetch(corsProxy + encodeURIComponent('https://api.example.com/data'))
70
+ .then(res => res.json())
71
+ .then(data => console.log(data.contents));
72
+
73
+
74
+ ═══════════════════════════════════════════════════════════════════════════════════════
75
+ 🔗 RPC NODE PROVIDERS - ارائه‌دهندگان نود RPC
76
+ ═════════════��═════════════════════════════════════════════════════════════════════════
77
+
78
+ ETHEREUM RPC ENDPOINTS:
79
+ ───────────────────────────────────
80
+
81
+ 1. Infura (رایگان: 100K req/day)
82
+ Mainnet: https://mainnet.infura.io/v3/{PROJECT_ID}
83
+ Sepolia: https://sepolia.infura.io/v3/{PROJECT_ID}
84
+ Docs: https://docs.infura.io
85
+
86
+ 2. Alchemy (رایگان: 300M compute units/month)
87
+ Mainnet: https://eth-mainnet.g.alchemy.com/v2/{API_KEY}
88
+ Sepolia: https://eth-sepolia.g.alchemy.com/v2/{API_KEY}
89
+ WebSocket: wss://eth-mainnet.g.alchemy.com/v2/{API_KEY}
90
+ Docs: https://docs.alchemy.com
91
+
92
+ 3. Ankr (رایگان: بدون محدودیت عمومی)
93
+ Mainnet: https://rpc.ankr.com/eth
94
+ Docs: https://www.ankr.com/docs
95
+
96
+ 4. PublicNode (کاملا رایگان)
97
+ Mainnet: https://ethereum.publicnode.com
98
+ All-in-one: https://ethereum-rpc.publicnode.com
99
+
100
+ 5. Cloudflare (رایگان)
101
+ Mainnet: https://cloudflare-eth.com
102
+
103
+ 6. LlamaNodes (رایگان)
104
+ Mainnet: https://eth.llamarpc.com
105
+
106
+ 7. 1RPC (رایگان با privacy)
107
+ Mainnet: https://1rpc.io/eth
108
+
109
+ 8. Chainnodes (ارزان)
110
+ Mainnet: https://mainnet.chainnodes.org/{API_KEY}
111
+
112
+ 9. dRPC (decentralized)
113
+ Mainnet: https://eth.drpc.org
114
+ Docs: https://drpc.org
115
+
116
+ BSC (BINANCE SMART CHAIN) RPC:
117
+ ──────────────────────────────────
118
+
119
+ 1. Official BSC RPC (رایگان)
120
+ Mainnet: https://bsc-dataseed.binance.org
121
+ Alt1: https://bsc-dataseed1.defibit.io
122
+ Alt2: https://bsc-dataseed1.ninicoin.io
123
+
124
+ 2. Ankr BSC
125
+ Mainnet: https://rpc.ankr.com/bsc
126
+
127
+ 3. PublicNode BSC
128
+ Mainnet: https://bsc-rpc.publicnode.com
129
+
130
+ 4. Nodereal BSC (رایگان: 3M req/day)
131
+ Mainnet: https://bsc-mainnet.nodereal.io/v1/{API_KEY}
132
+
133
+ TRON RPC ENDPOINTS:
134
+ ───────────────────────────
135
+
136
+ 1. TronGrid (رایگان)
137
+ Mainnet: https://api.trongrid.io
138
+ Full Node: https://api.trongrid.io/wallet/getnowblock
139
+
140
+ 2. TronStack (رایگان)
141
+ Mainnet: https://api.tronstack.io
142
+
143
+ 3. Nile Testnet
144
+ Testnet: https://api.nileex.io
145
+
146
+ POLYGON RPC:
147
+ ──────────────────
148
+
149
+ 1. Polygon Official (رایگان)
150
+ Mainnet: https://polygon-rpc.com
151
+ Mumbai: https://rpc-mumbai.maticvigil.com
152
+
153
+ 2. Ankr Polygon
154
+ Mainnet: https://rpc.ankr.com/polygon
155
+
156
+ 3. Alchemy Polygon
157
+ Mainnet: https://polygon-mainnet.g.alchemy.com/v2/{API_KEY}
158
+
159
+
160
+ ═══════════════════════════════════════════════════════════════════════════════════════
161
+ 📊 BLOCK EXPLORER APIs - APIهای کاوشگر بلاکچین
162
+ ═══════════════════════════════════════════════════════════════════════════════════════
163
+
164
+ CATEGORY 1: ETHEREUM EXPLORERS (11 endpoints)
165
+ ──────────────────────────────────────────────
166
+
167
+ PRIMARY: Etherscan
168
+ ─────────────────────
169
+ URL: https://api.etherscan.io/api
170
+ Key: SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2
171
+ Rate Limit: 5 calls/sec (free tier)
172
+ Docs: https://docs.etherscan.io
173
+
174
+ Endpoints:
175
+ • Balance: ?module=account&action=balance&address={address}&tag=latest&apikey={KEY}
176
+ • Transactions: ?module=account&action=txlist&address={address}&startblock=0&endblock=99999999&sort=asc&apikey={KEY}
177
+ • Token Balance: ?module=account&action=tokenbalance&contractaddress={contract}&address={address}&tag=latest&apikey={KEY}
178
+ • Gas Price: ?module=gastracker&action=gasoracle&apikey={KEY}
179
+
180
+ Example (No Proxy):
181
+ fetch('https://api.etherscan.io/api?module=account&action=balance&address=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb&tag=latest&apikey=SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2')
182
+
183
+ Example (With CORS Proxy):
184
+ const proxy = 'https://api.allorigins.win/get?url=';
185
+ const url = 'https://api.etherscan.io/api?module=account&action=balance&address=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb&apikey=SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2';
186
+ fetch(proxy + encodeURIComponent(url))
187
+ .then(r => r.json())
188
+ .then(data => {
189
+ const result = JSON.parse(data.contents);
190
+ console.log('Balance:', result.result / 1e18, 'ETH');
191
+ });
192
+
193
+ FALLBACK 1: Etherscan (Second Key)
194
+ ────────────────────────────────────
195
+ URL: https://api.etherscan.io/api
196
+ Key: T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45
197
+
198
+ FALLBACK 2: Blockchair
199
+ ──────────────────────
200
+ URL: https://api.blockchair.com/ethereum/dashboards/address/{address}
201
+ Free: 1,440 requests/day
202
+ Docs: https://blockchair.com/api/docs
203
+
204
+ FALLBACK 3: BlockScout (Open Source)
205
+ ─────────────────────────────────────
206
+ URL: https://eth.blockscout.com/api
207
+ Free: بدون محدودیت
208
+ Docs: https://docs.blockscout.com
209
+
210
+ FALLBACK 4: Ethplorer
211
+ ──────────────────────
212
+ URL: https://api.ethplorer.io
213
+ Endpoint: /getAddressInfo/{address}?apiKey=freekey
214
+ Free: محدود
215
+ Docs: https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API
216
+
217
+ FALLBACK 5: Etherchain
218
+ ──────────────────────
219
+ URL: https://www.etherchain.org/api
220
+ Free: بله
221
+ Docs: https://www.etherchain.org/documentation/api
222
+
223
+ FALLBACK 6: Chainlens
224
+ ─────────────────────
225
+ URL: https://api.chainlens.com
226
+ Free tier available
227
+ Docs: https://docs.chainlens.com
228
+
229
+
230
+ CATEGORY 2: BSC EXPLORERS (6 endpoints)
231
+ ────────────────────────────────────────
232
+
233
+ PRIMARY: BscScan
234
+ ────────────────
235
+ URL: https://api.bscscan.com/api
236
+ Key: K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT
237
+ Rate Limit: 5 calls/sec
238
+ Docs: https://docs.bscscan.com
239
+
240
+ Endpoints:
241
+ • BNB Balance: ?module=account&action=balance&address={address}&apikey={KEY}
242
+ • BEP-20 Balance: ?module=account&action=tokenbalance&contractaddress={token}&address={address}&apikey={KEY}
243
+ • Transactions: ?module=account&action=txlist&address={address}&apikey={KEY}
244
+
245
+ Example:
246
+ fetch('https://api.bscscan.com/api?module=account&action=balance&address=0x1234...&apikey=K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT')
247
+ .then(r => r.json())
248
+ .then(data => console.log('BNB:', data.result / 1e18));
249
+
250
+ FALLBACK 1: BitQuery (BSC)
251
+ ──────────────────────────
252
+ URL: https://graphql.bitquery.io
253
+ Method: GraphQL POST
254
+ Free: 10K queries/month
255
+ Docs: https://docs.bitquery.io
256
+
257
+ GraphQL Example:
258
+ query {
259
+ ethereum(network: bsc) {
260
+ address(address: {is: "0x..."}) {
261
+ balances {
262
+ currency { symbol }
263
+ value
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ FALLBACK 2: Ankr MultiChain
270
+ ────────────────────────────
271
+ URL: https://rpc.ankr.com/multichain
272
+ Method: JSON-RPC POST
273
+ Free: Public endpoints
274
+ Docs: https://www.ankr.com/docs/
275
+
276
+ FALLBACK 3: Nodereal BSC
277
+ ────────────────────────
278
+ URL: https://bsc-mainnet.nodereal.io/v1/{API_KEY}
279
+ Free tier: 3M requests/day
280
+ Docs: https://docs.nodereal.io
281
+
282
+ FALLBACK 4: BscTrace
283
+ ────────────────────
284
+ URL: https://api.bsctrace.com
285
+ Free: Limited
286
+ Alternative explorer
287
+
288
+ FALLBACK 5: 1inch BSC API
289
+ ─────────────────────────
290
+ URL: https://api.1inch.io/v5.0/56
291
+ Free: For trading data
292
+ Docs: https://docs.1inch.io
293
+
294
+
295
+ CATEGORY 3: TRON EXPLORERS (5 endpoints)
296
+ ─────────────────────────────────────────
297
+
298
+ PRIMARY: TronScan
299
+ ─────────────────
300
+ URL: https://apilist.tronscanapi.com/api
301
+ Key: 7ae72726-bffe-4e74-9c33-97b761eeea21
302
+ Rate Limit: Varies
303
+ Docs: https://github.com/tronscan/tronscan-frontend/blob/dev2019/document/api.md
304
+
305
+ Endpoints:
306
+ • Account: /account?address={address}
307
+ • Transactions: /transaction?address={address}&limit=20
308
+ • TRC20 Transfers: /token_trc20/transfers?address={address}
309
+ • Account Resources: /account/detail?address={address}
310
+
311
+ Example:
312
+ fetch('https://apilist.tronscanapi.com/api/account?address=TxxxXXXxxx')
313
+ .then(r => r.json())
314
+ .then(data => console.log('TRX Balance:', data.balance / 1e6));
315
+
316
+ FALLBACK 1: TronGrid (Official)
317
+ ────────────────────────────────
318
+ URL: https://api.trongrid.io
319
+ Free: Public
320
+ Docs: https://developers.tron.network/docs
321
+
322
+ JSON-RPC Example:
323
+ fetch('https://api.trongrid.io/wallet/getaccount', {
324
+ method: 'POST',
325
+ headers: {'Content-Type': 'application/json'},
326
+ body: JSON.stringify({
327
+ address: 'TxxxXXXxxx',
328
+ visible: true
329
+ })
330
+ })
331
+
332
+ FALLBACK 2: Tron Official API
333
+ ──────────────────────────────
334
+ URL: https://api.tronstack.io
335
+ Free: Public
336
+ Docs: Similar to TronGrid
337
+
338
+ FALLBACK 3: Blockchair (TRON)
339
+ ──────────────────────────────
340
+ URL: https://api.blockchair.com/tron/dashboards/address/{address}
341
+ Free: 1,440 req/day
342
+ Docs: https://blockchair.com/api/docs
343
+
344
+ FALLBACK 4: Tronscan API v2
345
+ ───────────────────────────
346
+ URL: https://api.tronscan.org/api
347
+ Alternative endpoint
348
+ Similar structure
349
+
350
+ FALLBACK 5: GetBlock TRON
351
+ ────────────���────────────
352
+ URL: https://go.getblock.io/tron
353
+ Free tier available
354
+ Docs: https://getblock.io/docs/
355
+
356
+
357
+ ═══════════════════════════════════════════════════════════════════════════════════════
358
+ 💰 MARKET DATA APIs - APIهای داده‌های بازار
359
+ ═══════════════════════════════════════════════════════════════════════════════════════
360
+
361
+ CATEGORY 1: PRICE & MARKET CAP (15+ endpoints)
362
+ ───────────────────────────────────────────────
363
+
364
+ PRIMARY: CoinGecko (FREE - بدون کلید)
365
+ ──────────────────────────────────────
366
+ URL: https://api.coingecko.com/api/v3
367
+ Rate Limit: 10-50 calls/min (free)
368
+ Docs: https://www.coingecko.com/en/api/documentation
369
+
370
+ Best Endpoints:
371
+ • Simple Price: /simple/price?ids=bitcoin,ethereum&vs_currencies=usd
372
+ • Coin Data: /coins/{id}?localization=false
373
+ • Market Chart: /coins/{id}/market_chart?vs_currency=usd&days=7
374
+ • Global Data: /global
375
+ • Trending: /search/trending
376
+ • Categories: /coins/categories
377
+
378
+ Example (Works Everywhere):
379
+ fetch('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum,tron&vs_currencies=usd,eur')
380
+ .then(r => r.json())
381
+ .then(data => console.log(data));
382
+ // Output: {bitcoin: {usd: 45000, eur: 42000}, ...}
383
+
384
+ FALLBACK 1: CoinMarketCap (با کلید)
385
+ ─────────────────────────────────────
386
+ URL: https://pro-api.coinmarketcap.com/v1
387
+ Key 1: b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c
388
+ Key 2: 04cf4b5b-9868-465c-8ba0-9f2e78c92eb1
389
+ Rate Limit: 333 calls/day (free)
390
+ Docs: https://coinmarketcap.com/api/documentation/v1/
391
+
392
+ Endpoints:
393
+ • Latest Quotes: /cryptocurrency/quotes/latest?symbol=BTC,ETH
394
+ • Listings: /cryptocurrency/listings/latest?limit=100
395
+ • Market Pairs: /cryptocurrency/market-pairs/latest?id=1
396
+
397
+ Example (Requires API Key in Header):
398
+ fetch('https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=BTC', {
399
+ headers: {
400
+ 'X-CMC_PRO_API_KEY': 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c'
401
+ }
402
+ })
403
+ .then(r => r.json())
404
+ .then(data => console.log(data.data.BTC));
405
+
406
+ With CORS Proxy:
407
+ const proxy = 'https://proxy.cors.sh/';
408
+ fetch(proxy + 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=BTC', {
409
+ headers: {
410
+ 'X-CMC_PRO_API_KEY': 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c',
411
+ 'Origin': 'https://myapp.com'
412
+ }
413
+ })
414
+
415
+ FALLBACK 2: CryptoCompare
416
+ ─────────────────────────
417
+ URL: https://min-api.cryptocompare.com/data
418
+ Key: e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f
419
+ Free: 100K calls/month
420
+ Docs: https://min-api.cryptocompare.com/documentation
421
+
422
+ Endpoints:
423
+ • Price Multi: /pricemulti?fsyms=BTC,ETH&tsyms=USD,EUR&api_key={KEY}
424
+ • Historical: /v2/histoday?fsym=BTC&tsym=USD&limit=30&api_key={KEY}
425
+ • Top Volume: /top/totalvolfull?limit=10&tsym=USD&api_key={KEY}
426
+
427
+ FALLBACK 3: Coinpaprika (FREE)
428
+ ───────────────────────────────
429
+ URL: https://api.coinpaprika.com/v1
430
+ Rate Limit: 20K calls/month
431
+ Docs: https://api.coinpaprika.com/
432
+
433
+ Endpoints:
434
+ • Tickers: /tickers
435
+ • Coin: /coins/btc-bitcoin
436
+ • Historical: /coins/btc-bitcoin/ohlcv/historical
437
+
438
+ FALLBACK 4: CoinCap (FREE)
439
+ ──────────────────────────
440
+ URL: https://api.coincap.io/v2
441
+ Rate Limit: 200 req/min
442
+ Docs: https://docs.coincap.io/
443
+
444
+ Endpoints:
445
+ • Assets: /assets
446
+ • Specific: /assets/bitcoin
447
+ • History: /assets/bitcoin/history?interval=d1
448
+
449
+ FALLBACK 5: Nomics (FREE)
450
+ ─────────────────────────
451
+ URL: https://api.nomics.com/v1
452
+ No Rate Limit on free tier
453
+ Docs: https://p.nomics.com/cryptocurrency-bitcoin-api
454
+
455
+ FALLBACK 6: Messari (FREE)
456
+ ──────────────────────────
457
+ URL: https://data.messari.io/api/v1
458
+ Rate Limit: Generous
459
+ Docs: https://messari.io/api/docs
460
+
461
+ FALLBACK 7: CoinLore (FREE)
462
+ ───────────────────────────
463
+ URL: https://api.coinlore.net/api
464
+ Rate Limit: None
465
+ Docs: https://www.coinlore.com/cryptocurrency-data-api
466
+
467
+ FALLBACK 8: Binance Public API
468
+ ───────────────────────────────
469
+ URL: https://api.binance.com/api/v3
470
+ Free: بله
471
+ Docs: https://binance-docs.github.io/apidocs/spot/en/
472
+
473
+ Endpoints:
474
+ • Price: /ticker/price?symbol=BTCUSDT
475
+ • 24hr Stats: /ticker/24hr?symbol=ETHUSDT
476
+
477
+ FALLBACK 9: CoinDesk API
478
+ ───────────���────────────
479
+ URL: https://api.coindesk.com/v1
480
+ Free: Bitcoin price index
481
+ Docs: https://www.coindesk.com/coindesk-api
482
+
483
+ FALLBACK 10: Mobula API
484
+ ───────────────────────
485
+ URL: https://api.mobula.io/api/1
486
+ Free: 50% cheaper than CMC
487
+ Coverage: 2.3M+ cryptocurrencies
488
+ Docs: https://developer.mobula.fi/
489
+
490
+ FALLBACK 11: Token Metrics API
491
+ ───────────────────────────────
492
+ URL: https://api.tokenmetrics.com/v2
493
+ Free API key available
494
+ AI-driven insights
495
+ Docs: https://api.tokenmetrics.com/docs
496
+
497
+ FALLBACK 12: FreeCryptoAPI
498
+ ──────────────────────────
499
+ URL: https://api.freecryptoapi.com
500
+ Free: Beginner-friendly
501
+ Coverage: 3,000+ coins
502
+
503
+ FALLBACK 13: DIA Data
504
+ ─────────────────────
505
+ URL: https://api.diadata.org/v1
506
+ Free: Decentralized oracle
507
+ Transparent pricing
508
+ Docs: https://docs.diadata.org
509
+
510
+ FALLBACK 14: Alternative.me
511
+ ───────────────────────────
512
+ URL: https://api.alternative.me/v2
513
+ Free: Price + Fear & Greed
514
+ Docs: In API responses
515
+
516
+ FALLBACK 15: CoinStats API
517
+ ──────────────────────────
518
+ URL: https://api.coinstats.app/public/v1
519
+ Free tier available
520
+
521
+
522
+ ═══════════════════════════════════════════════════════════════════════════════════════
523
+ 📰 NEWS & SOCIAL APIs - APIهای اخبار و شبکه‌های اجتماعی
524
+ ═══════════════════════════════════════════════════════════════════════════════════════
525
+
526
+ CATEGORY 1: CRYPTO NEWS (10+ endpoints)
527
+ ────────────────────────────────────────
528
+
529
+ PRIMARY: CryptoPanic (FREE)
530
+ ───────────────────────────
531
+ URL: https://cryptopanic.com/api/v1
532
+ Free: بله
533
+ Docs: https://cryptopanic.com/developers/api/
534
+
535
+ Endpoints:
536
+ • Posts: /posts/?auth_token={TOKEN}&public=true
537
+ • Currencies: /posts/?currencies=BTC,ETH
538
+ • Filter: /posts/?filter=rising
539
+
540
+ Example:
541
+ fetch('https://cryptopanic.com/api/v1/posts/?public=true')
542
+ .then(r => r.json())
543
+ .then(data => console.log(data.results));
544
+
545
+ FALLBACK 1: NewsAPI.org
546
+ ───────────────────────
547
+ URL: https://newsapi.org/v2
548
+ Key: pub_346789abc123def456789ghi012345jkl
549
+ Free: 100 req/day
550
+ Docs: https://newsapi.org/docs
551
+
552
+ FALLBACK 2: CryptoControl
553
+ ─────────────────────────
554
+ URL: https://cryptocontrol.io/api/v1/public
555
+ Free tier available
556
+ Docs: https://cryptocontrol.io/api
557
+
558
+ FALLBACK 3: CoinDesk News
559
+ ─────────────────────────
560
+ URL: https://www.coindesk.com/arc/outboundfeeds/rss/
561
+ Free RSS feed
562
+
563
+ FALLBACK 4: CoinTelegraph API
564
+ ─────────────────────────────
565
+ URL: https://cointelegraph.com/api/v1
566
+ Free: RSS and JSON feeds
567
+
568
+ FALLBACK 5: CryptoSlate
569
+ ───────────────────────
570
+ URL: https://cryptoslate.com/api
571
+ Free: Limited
572
+
573
+ FALLBACK 6: The Block API
574
+ ─────────────────────────
575
+ URL: https://api.theblock.co/v1
576
+ Premium service
577
+
578
+ FALLBACK 7: Bitcoin Magazine RSS
579
+ ────────────────────────────────
580
+ URL: https://bitcoinmagazine.com/.rss/full/
581
+ Free RSS
582
+
583
+ FALLBACK 8: Decrypt RSS
584
+ ───────────────────────
585
+ URL: https://decrypt.co/feed
586
+ Free RSS
587
+
588
+ FALLBACK 9: Reddit Crypto
589
+ ─────────────────────────
590
+ URL: https://www.reddit.com/r/CryptoCurrency/new.json
591
+ Free: Public JSON
592
+ Limit: 60 req/min
593
+
594
+ Example:
595
+ fetch('https://www.reddit.com/r/CryptoCurrency/hot.json?limit=25')
596
+ .then(r => r.json())
597
+ .then(data => console.log(data.data.children));
598
+
599
+ FALLBACK 10: Twitter/X API (v2)
600
+ ───────────────────────────────
601
+ URL: https://api.twitter.com/2
602
+ Requires: OAuth 2.0
603
+ Free tier: 1,500 tweets/month
604
+
605
+
606
+ ═══════════════════════════════════════════════════════════════════════════════════════
607
+ 😱 SENTIMENT & MOOD APIs - APIهای احساسات بازار
608
+ ═════════════════════════════════��═════════════════════════════════════════════════════
609
+
610
+ CATEGORY 1: FEAR & GREED INDEX (5+ endpoints)
611
+ ──────────────────────────────────────────────
612
+
613
+ PRIMARY: Alternative.me (FREE)
614
+ ──────────────────────────────
615
+ URL: https://api.alternative.me/fng/
616
+ Free: بدون محدودیت
617
+ Docs: https://alternative.me/crypto/fear-and-greed-index/
618
+
619
+ Endpoints:
620
+ • Current: /?limit=1
621
+ • Historical: /?limit=30
622
+ • Date Range: /?limit=10&date_format=world
623
+
624
+ Example:
625
+ fetch('https://api.alternative.me/fng/?limit=1')
626
+ .then(r => r.json())
627
+ .then(data => {
628
+ const fng = data.data[0];
629
+ console.log(`Fear & Greed: ${fng.value} - ${fng.value_classification}`);
630
+ });
631
+ // Output: "Fear & Greed: 45 - Fear"
632
+
633
+ FALLBACK 1: LunarCrush
634
+ ──────────────────────
635
+ URL: https://api.lunarcrush.com/v2
636
+ Free tier: Limited
637
+ Docs: https://lunarcrush.com/developers/api
638
+
639
+ Endpoints:
640
+ • Assets: ?data=assets&key={KEY}
641
+ • Market: ?data=market&key={KEY}
642
+ • Influencers: ?data=influencers&key={KEY}
643
+
644
+ FALLBACK 2: Santiment (GraphQL)
645
+ ────────────────────────────────
646
+ URL: https://api.santiment.net/graphql
647
+ Free tier available
648
+ Docs: https://api.santiment.net/graphiql
649
+
650
+ GraphQL Example:
651
+ query {
652
+ getMetric(metric: "sentiment_balance_total") {
653
+ timeseriesData(
654
+ slug: "bitcoin"
655
+ from: "2025-10-01T00:00:00Z"
656
+ to: "2025-10-31T00:00:00Z"
657
+ interval: "1d"
658
+ ) {
659
+ datetime
660
+ value
661
+ }
662
+ }
663
+ }
664
+
665
+ FALLBACK 3: TheTie.io
666
+ ─────────────────────
667
+ URL: https://api.thetie.io
668
+ Premium mainly
669
+ Docs: https://docs.thetie.io
670
+
671
+ FALLBACK 4: CryptoQuant
672
+ ───────────────────────
673
+ URL: https://api.cryptoquant.com/v1
674
+ Free tier: Limited
675
+ Docs: https://docs.cryptoquant.com
676
+
677
+ FALLBACK 5: Glassnode Social
678
+ ────────────────────────────
679
+ URL: https://api.glassnode.com/v1/metrics/social
680
+ Free tier: Limited
681
+ Docs: https://docs.glassnode.com
682
+
683
+ FALLBACK 6: Augmento (Social)
684
+ ──────────────────────────────
685
+ URL: https://api.augmento.ai/v1
686
+ AI-powered sentiment
687
+ Free trial available
688
+
689
+
690
+ ═══════════════════════════════════════════════════════════════════════════════════════
691
+ 🐋 WHALE TRACKING APIs - APIهای ردیابی نهنگ‌ها
692
+ ═══════════════════════════════════════════════════════════════════════════════════════
693
+
694
+ CATEGORY 1: WHALE TRANSACTIONS (8+ endpoints)
695
+ ──────────────────────────────────────────────
696
+
697
+ PRIMARY: Whale Alert
698
+ ────────────────────
699
+ URL: https://api.whale-alert.io/v1
700
+ Free: Limited (7-day trial)
701
+ Paid: From $20/month
702
+ Docs: https://docs.whale-alert.io
703
+
704
+ Endpoints:
705
+ • Transactions: /transactions?api_key={KEY}&min_value=1000000&start={timestamp}&end={timestamp}
706
+ • Status: /status?api_key={KEY}
707
+
708
+ Example:
709
+ const start = Math.floor(Date.now()/1000) - 3600; // 1 hour ago
710
+ const end = Math.floor(Date.now()/1000);
711
+ fetch(`https://api.whale-alert.io/v1/transactions?api_key=YOUR_KEY&min_value=1000000&start=${start}&end=${end}`)
712
+ .then(r => r.json())
713
+ .then(data => {
714
+ data.transactions.forEach(tx => {
715
+ console.log(`${tx.amount} ${tx.symbol} from ${tx.from.owner} to ${tx.to.owner}`);
716
+ });
717
+ });
718
+
719
+ FALLBACK 1: ClankApp (FREE)
720
+ ───────────────────────────
721
+ URL: https://clankapp.com/api
722
+ Free: بله
723
+ Telegram: @clankapp
724
+ Twitter: @ClankApp
725
+ Docs: https://clankapp.com/api/
726
+
727
+ Features:
728
+ • 24 blockchains
729
+ • Real-time whale alerts
730
+ • Email & push notifications
731
+ • No API key needed
732
+
733
+ Example:
734
+ fetch('https://clankapp.com/api/whales/recent')
735
+ .then(r => r.json())
736
+ .then(data => console.log(data));
737
+
738
+ FALLBACK 2: BitQuery Whale Tracking
739
+ ────────────────────────────────────
740
+ URL: https://graphql.bitquery.io
741
+ Free: 10K queries/month
742
+ Docs: https://docs.bitquery.io
743
+
744
+ GraphQL Example (Large ETH Transfers):
745
+ {
746
+ ethereum(network: ethereum) {
747
+ transfers(
748
+ amount: {gt: 1000}
749
+ currency: {is: "ETH"}
750
+ date: {since: "2025-10-25"}
751
+ ) {
752
+ block { timestamp { time } }
753
+ sender { address }
754
+ receiver { address }
755
+ amount
756
+ transaction { hash }
757
+ }
758
+ }
759
+ }
760
+
761
+ FALLBACK 3: Arkham Intelligence
762
+ ────────────────────────────────
763
+ URL: https://api.arkham.com
764
+ Paid service mainly
765
+ Docs: https://docs.arkham.com
766
+
767
+ FALLBACK 4: Nansen
768
+ ──────────────────
769
+ URL: https://api.nansen.ai/v1
770
+ Premium: Expensive but powerful
771
+ Docs: https://docs.nansen.ai
772
+
773
+ Features:
774
+ • Smart Money tracking
775
+ • Wallet labeling
776
+ • Multi-chain support
777
+
778
+ FALLBACK 5: DexCheck Whale Tracker
779
+ ───────────────────────────────────
780
+ Free wallet tracking feature
781
+ 22 chains supported
782
+ Telegram bot integration
783
+
784
+ FALLBACK 6: DeBank
785
+ ──────────────────
786
+ URL: https://api.debank.com
787
+ Free: Portfolio tracking
788
+ Web3 social features
789
+
790
+ FALLBACK 7: Zerion API
791
+ ──────────────────────
792
+ URL: https://api.zerion.io
793
+ Similar to DeBank
794
+ DeFi portfolio tracker
795
+
796
+ FALLBACK 8: Whalemap
797
+ ────────────────────
798
+ URL: https://whalemap.io
799
+ Bitcoin & ERC-20 focus
800
+ Charts and analytics
801
+
802
+
803
+ ═══════════════════════════════════════════════════════════════════════════════════════
804
+ 🔍 ON-CHAIN ANALYTICS APIs - APIهای تحلیل زنجیره
805
+ ═══════════════════════════════════════════════════════════════════════════════════════
806
+
807
+ CATEGORY 1: BLOCKCHAIN DATA (10+ endpoints)
808
+ ────────────────────────────────────────────
809
+
810
+ PRIMARY: The Graph (Subgraphs)
811
+ ──────────────────────────────
812
+ URL: https://api.thegraph.com/subgraphs/name/{org}/{subgraph}
813
+ Free: Public subgraphs
814
+ Docs: https://thegraph.com/docs/
815
+
816
+ Popular Subgraphs:
817
+ • Uniswap V3: /uniswap/uniswap-v3
818
+ • Aave V2: /aave/protocol-v2
819
+ • Compound: /graphprotocol/compound-v2
820
+
821
+ Example (Uniswap V3):
822
+ fetch('https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3', {
823
+ method: 'POST',
824
+ headers: {'Content-Type': 'application/json'},
825
+ body: JSON.stringify({
826
+ query: `{
827
+ pools(first: 5, orderBy: volumeUSD, orderDirection: desc) {
828
+ id
829
+ token0 { symbol }
830
+ token1 { symbol }
831
+ volumeUSD
832
+ }
833
+ }`
834
+ })
835
+ })
836
+
837
+ FALLBACK 1: Glassnode
838
+ ─────────────────────
839
+ URL: https://api.glassnode.com/v1
840
+ Free tier: Limited metrics
841
+ Docs: https://docs.glassnode.com
842
+
843
+ Endpoints:
844
+ • SOPR: /metrics/indicators/sopr?a=BTC&api_key={KEY}
845
+ • HODL Waves: /metrics/supply/hodl_waves?a=BTC&api_key={KEY}
846
+
847
+ FALLBACK 2: IntoTheBlock
848
+ ────────────────────────
849
+ URL: https://api.intotheblock.com/v1
850
+ Free tier available
851
+ Docs: https://developers.intotheblock.com
852
+
853
+ FALLBACK 3: Dune Analytics
854
+ ──────────────────────────
855
+ URL: https://api.dune.com/api/v1
856
+ Free: Query results
857
+ Docs: https://docs.dune.com/api-reference/
858
+
859
+ FALLBACK 4: Covalent
860
+ ────────────────────
861
+ URL: https://api.covalenthq.com/v1
862
+ Free tier: 100K credits
863
+ Multi-chain support
864
+ Docs: https://www.covalenthq.com/docs/api/
865
+
866
+ Example (Ethereum balances):
867
+ fetch('https://api.covalenthq.com/v1/1/address/0x.../balances_v2/?key=YOUR_KEY')
868
+
869
+ FALLBACK 5: Moralis
870
+ ───────────────────
871
+ URL: https://deep-index.moralis.io/api/v2
872
+ Free: 100K compute units/month
873
+ Docs: https://docs.moralis.io
874
+
875
+ FALLBACK 6: Alchemy NFT API
876
+ ───────────────────────────
877
+ Included with Alchemy account
878
+ NFT metadata & transfers
879
+
880
+ FALLBACK 7: QuickNode Functions
881
+ ────────────────────────────────
882
+ Custom on-chain queries
883
+ Token balances, NFTs
884
+
885
+ FALLBACK 8: Transpose
886
+ ─────────────────────
887
+ URL: https://api.transpose.io
888
+ Free tier available
889
+ SQL-like queries
890
+
891
+ FALLBACK 9: Footprint Analytics
892
+ ────────────────────────────────
893
+ URL: https://api.footprint.network
894
+ Free: Community tier
895
+ No-code analytics
896
+
897
+ FALLBACK 10: Nansen Query
898
+ ─────────────────────────
899
+ Premium institutional tool
900
+ Advanced on-chain intelligence
901
+
902
+
903
+ ═══════════════════════════════════════════════════════════════════════════════════════
904
+ 🔧 COMPLETE JAVASCRIPT IMPLEMENTATION
905
+ پیاده‌سازی کامل جاوااسکریپت
906
+ ═══════════════════════════════════════════════════════════════════════════════════════
907
+
908
+ // ═══════════════════════════════════════════════════════════════════════════════
909
+ // CONFIG.JS - تنظیمات مرکزی API
910
+ // ═══════════════════════════════════════════════════════════════════════════════
911
+
912
+ const API_CONFIG = {
913
+ // CORS Proxies (پروکسی‌های CORS)
914
+ corsProxies: [
915
+ 'https://api.allorigins.win/get?url=',
916
+ 'https://proxy.cors.sh/',
917
+ 'https://proxy.corsfix.com/?url=',
918
+ 'https://api.codetabs.com/v1/proxy?quest=',
919
+ 'https://thingproxy.freeboard.io/fetch/'
920
+ ],
921
+
922
+ // Block Explorers (کاوشگرهای بلاکچین)
923
+ explorers: {
924
+ ethereum: {
925
+ primary: {
926
+ name: 'etherscan',
927
+ baseUrl: 'https://api.etherscan.io/api',
928
+ key: 'SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2',
929
+ rateLimit: 5 // calls per second
930
+ },
931
+ fallbacks: [
932
+ { name: 'etherscan2', baseUrl: 'https://api.etherscan.io/api', key: 'T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45' },
933
+ { name: 'blockchair', baseUrl: 'https://api.blockchair.com/ethereum', key: '' },
934
+ { name: 'blockscout', baseUrl: 'https://eth.blockscout.com/api', key: '' },
935
+ { name: 'ethplorer', baseUrl: 'https://api.ethplorer.io', key: 'freekey' }
936
+ ]
937
+ },
938
+ bsc: {
939
+ primary: {
940
+ name: 'bscscan',
941
+ baseUrl: 'https://api.bscscan.com/api',
942
+ key: 'K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT',
943
+ rateLimit: 5
944
+ },
945
+ fallbacks: [
946
+ { name: 'blockchair', baseUrl: 'https://api.blockchair.com/binance-smart-chain', key: '' },
947
+ { name: 'bitquery', baseUrl: 'https://graphql.bitquery.io', key: '', method: 'graphql' }
948
+ ]
949
+ },
950
+ tron: {
951
+ primary: {
952
+ name: 'tronscan',
953
+ baseUrl: 'https://apilist.tronscanapi.com/api',
954
+ key: '7ae72726-bffe-4e74-9c33-97b761eeea21',
955
+ rateLimit: 10
956
+ },
957
+ fallbacks: [
958
+ { name: 'trongrid', baseUrl: 'https://api.trongrid.io', key: '' },
959
+ { name: 'tronstack', baseUrl: 'https://api.tronstack.io', key: '' },
960
+ { name: 'blockchair', baseUrl: 'https://api.blockchair.com/tron', key: '' }
961
+ ]
962
+ }
963
+ },
964
+
965
+ // Market Data (داده‌های بازار)
966
+ marketData: {
967
+ primary: {
968
+ name: 'coingecko',
969
+ baseUrl: 'https://api.coingecko.com/api/v3',
970
+ key: '', // بدون کلید
971
+ needsProxy: false,
972
+ rateLimit: 50 // calls per minute
973
+ },
974
+ fallbacks: [
975
+ {
976
+ name: 'coinmarketcap',
977
+ baseUrl: 'https://pro-api.coinmarketcap.com/v1',
978
+ key: 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c',
979
+ headerKey: 'X-CMC_PRO_API_KEY',
980
+ needsProxy: true
981
+ },
982
+ {
983
+ name: 'coinmarketcap2',
984
+ baseUrl: 'https://pro-api.coinmarketcap.com/v1',
985
+ key: '04cf4b5b-9868-465c-8ba0-9f2e78c92eb1',
986
+ headerKey: 'X-CMC_PRO_API_KEY',
987
+ needsProxy: true
988
+ },
989
+ { name: 'coincap', baseUrl: 'https://api.coincap.io/v2', key: '' },
990
+ { name: 'coinpaprika', baseUrl: 'https://api.coinpaprika.com/v1', key: '' },
991
+ { name: 'binance', baseUrl: 'https://api.binance.com/api/v3', key: '' },
992
+ { name: 'coinlore', baseUrl: 'https://api.coinlore.net/api', key: '' }
993
+ ]
994
+ },
995
+
996
+ // RPC Nodes (نودهای RPC)
997
+ rpcNodes: {
998
+ ethereum: [
999
+ 'https://eth.llamarpc.com',
1000
+ 'https://ethereum.publicnode.com',
1001
+ 'https://cloudflare-eth.com',
1002
+ 'https://rpc.ankr.com/eth',
1003
+ 'https://eth.drpc.org'
1004
+ ],
1005
+ bsc: [
1006
+ 'https://bsc-dataseed.binance.org',
1007
+ 'https://bsc-dataseed1.defibit.io',
1008
+ 'https://rpc.ankr.com/bsc',
1009
+ 'https://bsc-rpc.publicnode.com'
1010
+ ],
1011
+ polygon: [
1012
+ 'https://polygon-rpc.com',
1013
+ 'https://rpc.ankr.com/polygon',
1014
+ 'https://polygon-bor-rpc.publicnode.com'
1015
+ ]
1016
+ },
1017
+
1018
+ // News Sources (منابع خبری)
1019
+ news: {
1020
+ primary: {
1021
+ name: 'cryptopanic',
1022
+ baseUrl: 'https://cryptopanic.com/api/v1',
1023
+ key: '',
1024
+ needsProxy: false
1025
+ },
1026
+ fallbacks: [
1027
+ { name: 'reddit', baseUrl: 'https://www.reddit.com/r/CryptoCurrency', key: '' }
1028
+ ]
1029
+ },
1030
+
1031
+ // Sentiment (احساسات)
1032
+ sentiment: {
1033
+ primary: {
1034
+ name: 'alternative.me',
1035
+ baseUrl: 'https://api.alternative.me/fng',
1036
+ key: '',
1037
+ needsProxy: false
1038
+ }
1039
+ },
1040
+
1041
+ // Whale Tracking (ردیابی نهنگ)
1042
+ whaleTracking: {
1043
+ primary: {
1044
+ name: 'clankapp',
1045
+ baseUrl: 'https://clankapp.com/api',
1046
+ key: '',
1047
+ needsProxy: false
1048
+ }
1049
+ }
1050
+ };
1051
+
1052
+ // ═══════════════════════════════════════════════════════════════════════════════
1053
+ // API-CLIENT.JS - کلاینت API با مدیریت خطا و fallback
1054
+ // ═══════════════════════════════════════════════════════════════════════════════
1055
+
1056
+ class CryptoAPIClient {
1057
+ constructor(config) {
1058
+ this.config = config;
1059
+ this.currentProxyIndex = 0;
1060
+ this.requestCache = new Map();
1061
+ this.cacheTimeout = 60000; // 1 minute
1062
+ }
1063
+
1064
+ // استفاده از CORS Proxy
1065
+ async fetchWithProxy(url, options = {}) {
1066
+ const proxies = this.config.corsProxies;
1067
+
1068
+ for (let i = 0; i < proxies.length; i++) {
1069
+ const proxyUrl = proxies[this.currentProxyIndex] + encodeURIComponent(url);
1070
+
1071
+ try {
1072
+ console.log(`🔄 Trying proxy ${this.currentProxyIndex + 1}/${proxies.length}`);
1073
+
1074
+ const response = await fetch(proxyUrl, {
1075
+ ...options,
1076
+ headers: {
1077
+ ...options.headers,
1078
+ 'Origin': window.location.origin,
1079
+ 'x-requested-with': 'XMLHttpRequest'
1080
+ }
1081
+ });
1082
+
1083
+ if (response.ok) {
1084
+ const data = await response.json();
1085
+ // Handle allOrigins response format
1086
+ return data.contents ? JSON.parse(data.contents) : data;
1087
+ }
1088
+ } catch (error) {
1089
+ console.warn(`❌ Proxy ${this.currentProxyIndex + 1} failed:`, error.message);
1090
+ }
1091
+
1092
+ // Switch to next proxy
1093
+ this.currentProxyIndex = (this.currentProxyIndex + 1) % proxies.length;
1094
+ }
1095
+
1096
+ throw new Error('All CORS proxies failed');
1097
+ }
1098
+
1099
+ // بدون پروکسی
1100
+ async fetchDirect(url, options = {}) {
1101
+ try {
1102
+ const response = await fetch(url, options);
1103
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
1104
+ return await response.json();
1105
+ } catch (error) {
1106
+ throw new Error(`Direct fetch failed: ${error.message}`);
1107
+ }
1108
+ }
1109
+
1110
+ // با cache و fallback
1111
+ async fetchWithFallback(primaryConfig, fallbacks, endpoint, params = {}) {
1112
+ const cacheKey = `${primaryConfig.name}-${endpoint}-${JSON.stringify(params)}`;
1113
+
1114
+ // Check cache
1115
+ if (this.requestCache.has(cacheKey)) {
1116
+ const cached = this.requestCache.get(cacheKey);
1117
+ if (Date.now() - cached.timestamp < this.cacheTimeout) {
1118
+ console.log('📦 Using cached data');
1119
+ return cached.data;
1120
+ }
1121
+ }
1122
+
1123
+ // Try primary
1124
+ try {
1125
+ const data = await this.makeRequest(primaryConfig, endpoint, params);
1126
+ this.requestCache.set(cacheKey, { data, timestamp: Date.now() });
1127
+ return data;
1128
+ } catch (error) {
1129
+ console.warn('⚠️ Primary failed, trying fallbacks...', error.message);
1130
+ }
1131
+
1132
+ // Try fallbacks
1133
+ for (const fallback of fallbacks) {
1134
+ try {
1135
+ console.log(`🔄 Trying fallback: ${fallback.name}`);
1136
+ const data = await this.makeRequest(fallback, endpoint, params);
1137
+ this.requestCache.set(cacheKey, { data, timestamp: Date.now() });
1138
+ return data;
1139
+ } catch (error) {
1140
+ console.warn(`❌ Fallback ${fallback.name} failed:`, error.message);
1141
+ }
1142
+ }
1143
+
1144
+ throw new Error('All endpoints failed');
1145
+ }
1146
+
1147
+ // ساخت درخواست
1148
+ async makeRequest(apiConfig, endpoint, params = {}) {
1149
+ let url = `${apiConfig.baseUrl}${endpoint}`;
1150
+
1151
+ // Add query params
1152
+ const queryParams = new URLSearchParams();
1153
+ if (apiConfig.key) {
1154
+ queryParams.append('apikey', apiConfig.key);
1155
+ }
1156
+ Object.entries(params).forEach(([key, value]) => {
1157
+ queryParams.append(key, value);
1158
+ });
1159
+
1160
+ if (queryParams.toString()) {
1161
+ url += '?' + queryParams.toString();
1162
+ }
1163
+
1164
+ const options = {};
1165
+
1166
+ // Add headers if needed
1167
+ if (apiConfig.headerKey && apiConfig.key) {
1168
+ options.headers = {
1169
+ [apiConfig.headerKey]: apiConfig.key
1170
+ };
1171
+ }
1172
+
1173
+ // Use proxy if needed
1174
+ if (apiConfig.needsProxy) {
1175
+ return await this.fetchWithProxy(url, options);
1176
+ } else {
1177
+ return await this.fetchDirect(url, options);
1178
+ }
1179
+ }
1180
+
1181
+ // ═══════════════ SPECIFIC API METHODS ═══════════════
1182
+
1183
+ // Get ETH Balance (با fallback)
1184
+ async getEthBalance(address) {
1185
+ const { ethereum } = this.config.explorers;
1186
+ return await this.fetchWithFallback(
1187
+ ethereum.primary,
1188
+ ethereum.fallbacks,
1189
+ '',
1190
+ {
1191
+ module: 'account',
1192
+ action: 'balance',
1193
+ address: address,
1194
+ tag: 'latest'
1195
+ }
1196
+ );
1197
+ }
1198
+
1199
+ // Get BTC Price (multi-source)
1200
+ async getBitcoinPrice() {
1201
+ const { marketData } = this.config;
1202
+
1203
+ try {
1204
+ // Try CoinGecko first (no key needed, no CORS)
1205
+ const data = await this.fetchDirect(
1206
+ `${marketData.primary.baseUrl}/simple/price?ids=bitcoin&vs_currencies=usd,eur`
1207
+ );
1208
+ return {
1209
+ source: 'CoinGecko',
1210
+ usd: data.bitcoin.usd,
1211
+ eur: data.bitcoin.eur
1212
+ };
1213
+ } catch (error) {
1214
+ // Fallback to Binance
1215
+ try {
1216
+ const data = await this.fetchDirect(
1217
+ 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT'
1218
+ );
1219
+ return {
1220
+ source: 'Binance',
1221
+ usd: parseFloat(data.price),
1222
+ eur: null
1223
+ };
1224
+ } catch (err) {
1225
+ throw new Error('All price sources failed');
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ // Get Fear & Greed Index
1231
+ async getFearGreed() {
1232
+ const url = `${this.config.sentiment.primary.baseUrl}/?limit=1`;
1233
+ const data = await this.fetchDirect(url);
1234
+ return {
1235
+ value: parseInt(data.data[0].value),
1236
+ classification: data.data[0].value_classification,
1237
+ timestamp: new Date(parseInt(data.data[0].timestamp) * 1000)
1238
+ };
1239
+ }
1240
+
1241
+ // Get Trending Coins
1242
+ async getTrendingCoins() {
1243
+ const url = `${this.config.marketData.primary.baseUrl}/search/trending`;
1244
+ const data = await this.fetchDirect(url);
1245
+ return data.coins.map(item => ({
1246
+ id: item.item.id,
1247
+ name: item.item.name,
1248
+ symbol: item.item.symbol,
1249
+ rank: item.item.market_cap_rank,
1250
+ thumb: item.item.thumb
1251
+ }));
1252
+ }
1253
+
1254
+ // Get Crypto News
1255
+ async getCryptoNews(limit = 10) {
1256
+ const url = `${this.config.news.primary.baseUrl}/posts/?public=true`;
1257
+ const data = await this.fetchDirect(url);
1258
+ return data.results.slice(0, limit).map(post => ({
1259
+ title: post.title,
1260
+ url: post.url,
1261
+ source: post.source.title,
1262
+ published: new Date(post.published_at)
1263
+ }));
1264
+ }
1265
+
1266
+ // Get Recent Whale Transactions
1267
+ async getWhaleTransactions() {
1268
+ try {
1269
+ const url = `${this.config.whaleTracking.primary.baseUrl}/whales/recent`;
1270
+ return await this.fetchDirect(url);
1271
+ } catch (error) {
1272
+ console.warn('Whale API not available');
1273
+ return [];
1274
+ }
1275
+ }
1276
+
1277
+ // Multi-source price aggregator
1278
+ async getAggregatedPrice(symbol) {
1279
+ const sources = [
1280
+ {
1281
+ name: 'CoinGecko',
1282
+ fetch: async () => {
1283
+ const data = await this.fetchDirect(
1284
+ `${this.config.marketData.primary.baseUrl}/simple/price?ids=${symbol}&vs_currencies=usd`
1285
+ );
1286
+ return data[symbol]?.usd;
1287
+ }
1288
+ },
1289
+ {
1290
+ name: 'Binance',
1291
+ fetch: async () => {
1292
+ const data = await this.fetchDirect(
1293
+ `https://api.binance.com/api/v3/ticker/price?symbol=${symbol.toUpperCase()}USDT`
1294
+ );
1295
+ return parseFloat(data.price);
1296
+ }
1297
+ },
1298
+ {
1299
+ name: 'CoinCap',
1300
+ fetch: async () => {
1301
+ const data = await this.fetchDirect(
1302
+ `https://api.coincap.io/v2/assets/${symbol}`
1303
+ );
1304
+ return parseFloat(data.data.priceUsd);
1305
+ }
1306
+ }
1307
+ ];
1308
+
1309
+ const prices = await Promise.allSettled(
1310
+ sources.map(async source => ({
1311
+ source: source.name,
1312
+ price: await source.fetch()
1313
+ }))
1314
+ );
1315
+
1316
+ const successful = prices
1317
+ .filter(p => p.status === 'fulfilled')
1318
+ .map(p => p.value);
1319
+
1320
+ if (successful.length === 0) {
1321
+ throw new Error('All price sources failed');
1322
+ }
1323
+
1324
+ const avgPrice = successful.reduce((sum, p) => sum + p.price, 0) / successful.length;
1325
+
1326
+ return {
1327
+ symbol,
1328
+ sources: successful,
1329
+ average: avgPrice,
1330
+ spread: Math.max(...successful.map(p => p.price)) - Math.min(...successful.map(p => p.price))
1331
+ };
1332
+ }
1333
+ }
1334
+
1335
+ // ═══════════════════════════════════════════════════════════════════════════════
1336
+ // USAGE EXAMPLES - مثال‌های استفاده
1337
+ // ═══════════════════════════════════════════════════════════════════════════════
1338
+
1339
+ // Initialize
1340
+ const api = new CryptoAPIClient(API_CONFIG);
1341
+
1342
+ // Example 1: Get Ethereum Balance
1343
+ async function example1() {
1344
+ try {
1345
+ const address = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
1346
+ const balance = await api.getEthBalance(address);
1347
+ console.log('ETH Balance:', parseInt(balance.result) / 1e18);
1348
+ } catch (error) {
1349
+ console.error('Error:', error.message);
1350
+ }
1351
+ }
1352
+
1353
+ // Example 2: Get Bitcoin Price from Multiple Sources
1354
+ async function example2() {
1355
+ try {
1356
+ const price = await api.getBitcoinPrice();
1357
+ console.log(`BTC Price (${price.source}): $${price.usd}`);
1358
+ } catch (error) {
1359
+ console.error('Error:', error.message);
1360
+ }
1361
+ }
1362
+
1363
+ // Example 3: Get Fear & Greed Index
1364
+ async function example3() {
1365
+ try {
1366
+ const fng = await api.getFearGreed();
1367
+ console.log(`Fear & Greed: ${fng.value} (${fng.classification})`);
1368
+ } catch (error) {
1369
+ console.error('Error:', error.message);
1370
+ }
1371
+ }
1372
+
1373
+ // Example 4: Get Trending Coins
1374
+ async function example4() {
1375
+ try {
1376
+ const trending = await api.getTrendingCoins();
1377
+ console.log('Trending Coins:');
1378
+ trending.forEach((coin, i) => {
1379
+ console.log(`${i + 1}. ${coin.name} (${coin.symbol})`);
1380
+ });
1381
+ } catch (error) {
1382
+ console.error('Error:', error.message);
1383
+ }
1384
+ }
1385
+
1386
+ // Example 5: Get Latest News
1387
+ async function example5() {
1388
+ try {
1389
+ const news = await api.getCryptoNews(5);
1390
+ console.log('Latest News:');
1391
+ news.forEach((article, i) => {
1392
+ console.log(`${i + 1}. ${article.title} - ${article.source}`);
1393
+ });
1394
+ } catch (error) {
1395
+ console.error('Error:', error.message);
1396
+ }
1397
+ }
1398
+
1399
+ // Example 6: Aggregate Price from Multiple Sources
1400
+ async function example6() {
1401
+ try {
1402
+ const priceData = await api.getAggregatedPrice('bitcoin');
1403
+ console.log('Price Sources:');
1404
+ priceData.sources.forEach(s => {
1405
+ console.log(`- ${s.source}: $${s.price.toFixed(2)}`);
1406
+ });
1407
+ console.log(`Average: $${priceData.average.toFixed(2)}`);
1408
+ console.log(`Spread: $${priceData.spread.toFixed(2)}`);
1409
+ } catch (error) {
1410
+ console.error('Error:', error.message);
1411
+ }
1412
+ }
1413
+
1414
+ // Example 7: Dashboard - All Data
1415
+ async function dashboardExample() {
1416
+ console.log('🚀 Loading Crypto Dashboard...\n');
1417
+
1418
+ try {
1419
+ // Price
1420
+ const btcPrice = await api.getBitcoinPrice();
1421
+ console.log(`💰 BTC: $${btcPrice.usd.toLocaleString()}`);
1422
+
1423
+ // Fear & Greed
1424
+ const fng = await api.getFearGreed();
1425
+ console.log(`😱 Fear & Greed: ${fng.value} (${fng.classification})`);
1426
+
1427
+ // Trending
1428
+ const trending = await api.getTrendingCoins();
1429
+ console.log(`\n🔥 Trending:`);
1430
+ trending.slice(0, 3).forEach((coin, i) => {
1431
+ console.log(` ${i + 1}. ${coin.name}`);
1432
+ });
1433
+
1434
+ // News
1435
+ const news = await api.getCryptoNews(3);
1436
+ console.log(`\n📰 Latest News:`);
1437
+ news.forEach((article, i) => {
1438
+ console.log(` ${i + 1}. ${article.title.substring(0, 50)}...`);
1439
+ });
1440
+
1441
+ } catch (error) {
1442
+ console.error('Dashboard Error:', error.message);
1443
+ }
1444
+ }
1445
+
1446
+ // Run examples
1447
+ console.log('═══════════════════════════════════════');
1448
+ console.log(' CRYPTO API CLIENT - TEST SUITE');
1449
+ console.log('═══════════════════════════════════════\n');
1450
+
1451
+ // Uncomment to run specific examples:
1452
+ // example1();
1453
+ // example2();
1454
+ // example3();
1455
+ // example4();
1456
+ // example5();
1457
+ // example6();
1458
+ dashboardExample();
1459
+
1460
+
1461
+ ═══════════════════════════════════════════════════════════════════════════════════════
1462
+ 📝 QUICK REFERENCE - مرجع سریع
1463
+ ═══════════════════════════════════════════════════════════════════════════════════════
1464
+
1465
+ BEST FREE APIs (بهترین APIهای رایگان):
1466
+ ─────────────────────────────────────────
1467
+
1468
+ ✅ PRICES & MARKET DATA:
1469
+ 1. CoinGecko (بدون کلید، بدون CORS)
1470
+ 2. Binance Public API (بدون کلید)
1471
+ 3. CoinCap (بدون کلید)
1472
+ 4. CoinPaprika (بدون کلید)
1473
+
1474
+ ✅ BLOCK EXPLORERS:
1475
+ 1. Blockchair (1,440 req/day)
1476
+ 2. BlockScout (بدون محدودیت)
1477
+ 3. Public RPC nodes (various)
1478
+
1479
+ ✅ NEWS:
1480
+ 1. CryptoPanic (بدون کلید)
1481
+ 2. Reddit JSON API (60 req/min)
1482
+
1483
+ ✅ SENTIMENT:
1484
+ 1. Alternative.me F&G (بدون محدودیت)
1485
+
1486
+ ✅ WHALE TRACKING:
1487
+ 1. ClankApp (بدون کلید)
1488
+ 2. BitQuery GraphQL (10K/month)
1489
+
1490
+ ✅ RPC NODES:
1491
+ 1. PublicNode (همه شبکه‌ها)
1492
+ 2. Ankr (عمومی)
1493
+ 3. LlamaNodes (بدون ثبت‌نام)
1494
+
1495
+
1496
+ RATE LIMIT STRATEGIES (استراتژی‌های محدودیت):
1497
+ ───────────────────────────────────────────────
1498
+
1499
+ 1. کش کردن (Caching):
1500
+ - ذخیره نتایج برای 1-5 دقیقه
1501
+ - استفاده از localStorage برای کش مرورگر
1502
+
1503
+ 2. چرخش کلید (Key Rotation):
1504
+ - استفاده از چندین کلید API
1505
+ - تعویض خودکار در صورت محدودیت
1506
+
1507
+ 3. Fallback Chain:
1508
+ - Primary → Fallback1 → Fallback2
1509
+ - تا 5-10 جای��زین برای هر سرویس
1510
+
1511
+ 4. Request Queuing:
1512
+ - صف بندی درخواست‌ها
1513
+ - تاخیر بین درخواست‌ها
1514
+
1515
+ 5. Multi-Source Aggregation:
1516
+ - دریافت از چند منبع همزمان
1517
+ - میانگین گیری نتایج
1518
+
1519
+
1520
+ ERROR HANDLING (مدیریت خطا):
1521
+ ──────────────────────────────
1522
+
1523
+ try {
1524
+ const data = await api.fetchWithFallback(primary, fallbacks, endpoint, params);
1525
+ } catch (error) {
1526
+ if (error.message.includes('rate limit')) {
1527
+ // Switch to fallback
1528
+ } else if (error.message.includes('CORS')) {
1529
+ // Use CORS proxy
1530
+ } else {
1531
+ // Show error to user
1532
+ }
1533
+ }
1534
+
1535
+
1536
+ DEPLOYMENT TIPS (نکات استقرار):
1537
+ ─────────────────────────────────
1538
+
1539
+ 1. Backend Proxy (توصیه می‌شود):
1540
+ - Node.js/Express proxy server
1541
+ - Cloudflare Worker
1542
+ - Vercel Serverless Function
1543
+
1544
+ 2. Environment Variables:
1545
+ - ذخیره کلیدها در .env
1546
+ - عدم نمایش در کد فرانت‌اند
1547
+
1548
+ 3. Rate Limiting:
1549
+ - محدودسازی درخواست کاربر
1550
+ - استفاده از Redis برای کنترل
1551
+
1552
+ 4. Monitoring:
1553
+ - لاگ گرفتن از خطاها
1554
+ - ردیابی استفاده از API
1555
+
1556
+
1557
+ ═══════════════════════════════════════════════════════════════════════════════════════
1558
+ 🔗 USEFUL LINKS - لینک‌های مفید
1559
+ ═══════════════════════════════════════════════════════════════════════════════════════
1560
+
1561
+ DOCUMENTATION:
1562
+ • CoinGecko API: https://www.coingecko.com/api/documentation
1563
+ • Etherscan API: https://docs.etherscan.io
1564
+ • BscScan API: https://docs.bscscan.com
1565
+ • TronGrid: https://developers.tron.network
1566
+ • Alchemy: https://docs.alchemy.com
1567
+ • Infura: https://docs.infura.io
1568
+ • The Graph: https://thegraph.com/docs
1569
+ • BitQuery: https://docs.bitquery.io
1570
+
1571
+ CORS PROXY ALTERNATIVES:
1572
+ • CORS Anywhere: https://github.com/Rob--W/cors-anywhere
1573
+ • AllOrigins: https://github.com/gnuns/allOrigins
1574
+ • CORS.SH: https://cors.sh
1575
+ • Corsfix: https://corsfix.com
1576
+
1577
+ RPC LISTS:
1578
+ • ChainList: https://chainlist.org
1579
+ • Awesome RPC: https://github.com/arddluma/awesome-list-rpc-nodes-providers
1580
+
1581
+ TOOLS:
1582
+ • Postman: https://www.postman.com
1583
+ • Insomnia: https://insomnia.rest
1584
+ • GraphiQL: https://graphiql-online.com
1585
+
1586
+
1587
+ ═══════════════════════════════════════════════════════════════════════════════════════
1588
+ ⚠️ IMPORTANT NOTES - نکات مهم
1589
+ ═══════════════════════════════════════════════════════════════════════════════════════
1590
+
1591
+ 1. ⚠️ NEVER expose API keys in frontend code
1592
+ - همیشه از backend proxy استفاده کنید
1593
+ - کلیدها را در environment variables ذخیره کنید
1594
+
1595
+ 2. 🔄 Always implement fallbacks
1596
+ - حداقل 2-3 جایگزین برای هر سرویس
1597
+ - تست منظم fallbackها
1598
+
1599
+ 3. 💾 Cache responses when possible
1600
+ - صرفه‌جویی در استفاده از API
1601
+ - سرعت بیشتر برای کاربر
1602
+
1603
+ 4. 📊 Monitor API usage
1604
+ - ردیابی تعداد درخواست‌ها
1605
+ - هشدار قبل از رسیدن به محدودیت
1606
+
1607
+ 5. 🔐 Secure your endpoints
1608
+ - محدودسازی domain
1609
+ - استفاده از CORS headers
1610
+ - Rate limiting برای کاربران
1611
+
1612
+ 6. 🌐 Test with and without CORS proxies
1613
+ - برخی APIها CORS را پشتیبانی می‌کنند
1614
+ - استفاده از پروکسی فقط در صورت نیاز
1615
+
1616
+ 7. 📱 Mobile-friendly implementations
1617
+ - بهینه‌سازی برای شبکه‌های ضعیف
1618
+ - کاهش اندازه درخواست‌ها
1619
+
1620
+
1621
+ ═══════════════════════════════════════════════════════════════════════════════════════
1622
+ END OF CONFIGURATION FILE
1623
+ پایان فایل تنظیمات
1624
+ ═══════════════════════════════════════════════════════════════════════════════════��═══
1625
+
1626
+ Last Updated: October 31, 2025
1627
+ Version: 2.0
1628
+ Author: AI Assistant
1629
+ License: Free to use
1630
+
1631
+ For updates and more resources, check:
1632
+ - GitHub: Search for "awesome-crypto-apis"
1633
+ - Reddit: r/CryptoCurrency, r/ethdev
1634
+ - Discord: Web3 developer communities
final/Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+
3
+ WORKDIR /app
4
+
5
+ # Create required directories
6
+ RUN mkdir -p /app/logs /app/data /app/data/database /app/data/backups
7
+
8
+ # Copy requirements and install dependencies
9
+ COPY requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ # Copy application code
13
+ COPY . .
14
+
15
+ # Set environment variables
16
+ ENV USE_MOCK_DATA=false
17
+ ENV PORT=7860
18
+ ENV PYTHONUNBUFFERED=1
19
+
20
+ # Expose port
21
+ EXPOSE 7860
22
+
23
+ # Launch command
24
+ CMD ["uvicorn", "hf_unified_server:app", "--host", "0.0.0.0", "--port", "7860"]
final/Dockerfile.crypto-bank ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Install system dependencies
7
+ RUN apt-get update && apt-get install -y \
8
+ gcc \
9
+ g++ \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # Copy requirements first for better caching
13
+ COPY crypto_data_bank/requirements.txt /app/requirements.txt
14
+
15
+ # Install Python dependencies
16
+ RUN pip install --no-cache-dir --upgrade pip && \
17
+ pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Copy application code
20
+ COPY crypto_data_bank/ /app/
21
+
22
+ # Create data directory for database
23
+ RUN mkdir -p /app/data
24
+
25
+ # Set environment variables
26
+ ENV PYTHONUNBUFFERED=1
27
+ ENV PORT=8888
28
+
29
+ # Expose port
30
+ EXPOSE 8888
31
+
32
+ # Health check
33
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
34
+ CMD python -c "import httpx; httpx.get('http://localhost:8888/api/health')" || exit 1
35
+
36
+ # Run the API Gateway
37
+ CMD ["python", "-u", "api_gateway.py"]
final/Dockerfile.optimized ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ git \
9
+ curl \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # Copy requirements first for better caching
13
+ COPY requirements.txt .
14
+
15
+ # Upgrade pip
16
+ RUN pip install --no-cache-dir --upgrade pip
17
+
18
+ # Install dependencies
19
+ RUN pip install --no-cache-dir -r requirements.txt
20
+
21
+ # Copy application code
22
+ COPY . .
23
+
24
+ # Create necessary directories
25
+ RUN mkdir -p \
26
+ data/database \
27
+ data/backups \
28
+ logs \
29
+ static/css \
30
+ static/js \
31
+ .cache/huggingface
32
+
33
+ # Set permissions
34
+ RUN chmod -R 755 /app
35
+
36
+ # Environment variables
37
+ ENV PORT=7860 \
38
+ PYTHONUNBUFFERED=1 \
39
+ TRANSFORMERS_CACHE=/app/.cache/huggingface \
40
+ HF_HOME=/app/.cache/huggingface \
41
+ PYTHONDONTWRITEBYTECODE=1
42
+
43
+ # Expose port
44
+ EXPOSE 7860
45
+
46
+ # Health check
47
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
48
+ CMD curl -f http://localhost:7860/api/health || exit 1
49
+
50
+ # Run application
51
+ CMD ["uvicorn", "hf_unified_server:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
final/PROVIDER_AUTO_DISCOVERY_REPORT.json ADDED
The diff for this file is too large to render. See raw diff
 
final/TEST_COMMANDS.sh ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Test commands for verifying the deployed application
3
+ # Run these commands after starting the server
4
+
5
+ BASE_URL="${BASE_URL:-http://localhost:7860}"
6
+
7
+ echo "Testing Crypto Monitor API Endpoints"
8
+ echo "====================================="
9
+ echo ""
10
+
11
+ echo "1. Health Check:"
12
+ curl -s "$BASE_URL/health" | jq
13
+ echo ""
14
+
15
+ echo "2. Market Data (Real CoinGecko):"
16
+ curl -s "$BASE_URL/api/market" | jq '.cryptocurrencies[0] | {name, symbol, price, provider: .provider}'
17
+ echo ""
18
+
19
+ echo "3. Sentiment (Real Alternative.me):"
20
+ curl -s "$BASE_URL/api/sentiment" | jq '.fear_greed_index'
21
+ echo ""
22
+
23
+ echo "4. Trending (Real CoinGecko):"
24
+ curl -s "$BASE_URL/api/trending" | jq '.trending[0:3] | .[] | {name, symbol}'
25
+ echo ""
26
+
27
+ echo "5. Market History (Database):"
28
+ curl -s "$BASE_URL/api/market/history?symbol=BTC&limit=5" | jq
29
+ echo ""
30
+
31
+ echo "6. DeFi Endpoint (Should return 503):"
32
+ curl -s -w "\nHTTP Status: %{http_code}\n" "$BASE_URL/api/defi" | jq
33
+ echo ""
34
+
35
+ echo "7. HF Sentiment (Should return 501):"
36
+ curl -s -w "\nHTTP Status: %{http_code}\n" -X POST "$BASE_URL/api/hf/run-sentiment" \
37
+ -H "Content-Type: application/json" \
38
+ -d '{"texts": ["Bitcoin is bullish"]}' | jq
39
+ echo ""
40
+
41
+ echo "All tests completed!"
final/TEST_ENDPOINTS.sh ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # API Endpoints Test Script
3
+ # Run this after starting the backend to verify all endpoints work
4
+
5
+ BASE_URL="${BASE_URL:-http://localhost:7860}"
6
+ GREEN='\033[0;32m'
7
+ RED='\033[0;31m'
8
+ YELLOW='\033[1;33m'
9
+ NC='\033[0m' # No Color
10
+
11
+ echo "======================================"
12
+ echo "🧪 Testing Crypto HF API Endpoints"
13
+ echo "======================================"
14
+ echo "Base URL: $BASE_URL"
15
+ echo ""
16
+
17
+ # Function to test endpoint
18
+ test_endpoint() {
19
+ local method=$1
20
+ local endpoint=$2
21
+ local data=$3
22
+ local name=$4
23
+
24
+ echo -n "Testing $name... "
25
+
26
+ if [ "$method" = "GET" ]; then
27
+ response=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL$endpoint")
28
+ else
29
+ response=$(curl -s -o /dev/null -w "%{http_code}" -X "$method" "$BASE_URL$endpoint" \
30
+ -H "Content-Type: application/json" \
31
+ -d "$data")
32
+ fi
33
+
34
+ if [ "$response" = "200" ]; then
35
+ echo -e "${GREEN}✅ OK${NC} (HTTP $response)"
36
+ else
37
+ echo -e "${RED}❌ FAILED${NC} (HTTP $response)"
38
+ return 1
39
+ fi
40
+ }
41
+
42
+ # Test health
43
+ test_endpoint "GET" "/api/health" "" "Health Check"
44
+
45
+ # Test market endpoints
46
+ echo ""
47
+ echo "📊 Market Endpoints:"
48
+ test_endpoint "GET" "/api/coins/top?limit=5" "" "Top Coins"
49
+ test_endpoint "GET" "/api/coins/BTC" "" "Bitcoin Details"
50
+ test_endpoint "GET" "/api/market/stats" "" "Market Stats"
51
+
52
+ # Test chart endpoints
53
+ echo ""
54
+ echo "📈 Chart Endpoints:"
55
+ test_endpoint "GET" "/api/charts/price/BTC?timeframe=7d" "" "BTC Price Chart"
56
+
57
+ # POST endpoint for chart analyze
58
+ echo -n "Testing Chart Analysis... "
59
+ response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/api/charts/analyze" \
60
+ -H "Content-Type: application/json" \
61
+ -d '{"symbol":"BTC","timeframe":"7d","indicators":[]}')
62
+ http_code=$(echo "$response" | tail -n1)
63
+ if [ "$http_code" = "200" ]; then
64
+ echo -e "${GREEN}✅ OK${NC} (HTTP $http_code)"
65
+ else
66
+ echo -e "${RED}❌ FAILED${NC} (HTTP $http_code)"
67
+ fi
68
+
69
+ # Test news endpoints
70
+ echo ""
71
+ echo "📰 News Endpoints:"
72
+ test_endpoint "GET" "/api/news/latest?limit=5" "" "Latest News"
73
+
74
+ # POST endpoint for news summarize
75
+ echo -n "Testing News Summarize... "
76
+ response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/api/news/summarize" \
77
+ -H "Content-Type: application/json" \
78
+ -d '{"title":"Bitcoin breaks new record","description":"BTC hits $50k"}')
79
+ http_code=$(echo "$response" | tail -n1)
80
+ if [ "$http_code" = "200" ]; then
81
+ echo -e "${GREEN}✅ OK${NC} (HTTP $http_code)"
82
+ else
83
+ echo -e "${RED}❌ FAILED${NC} (HTTP $http_code)"
84
+ fi
85
+
86
+ # Test AI endpoints
87
+ echo ""
88
+ echo "🤖 AI Endpoints:"
89
+
90
+ # POST endpoint for sentiment
91
+ echo -n "Testing Sentiment Analysis... "
92
+ response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/api/sentiment/analyze" \
93
+ -H "Content-Type: application/json" \
94
+ -d '{"text":"Bitcoin is breaking new all-time highs!"}')
95
+ http_code=$(echo "$response" | tail -n1)
96
+ body=$(echo "$response" | head -n-1)
97
+ if [ "$http_code" = "200" ]; then
98
+ sentiment=$(echo "$body" | grep -o '"sentiment":"[^"]*"' | cut -d'"' -f4)
99
+ confidence=$(echo "$body" | grep -o '"confidence":[0-9.]*' | cut -d':' -f2)
100
+ echo -e "${GREEN}✅ OK${NC} (HTTP $http_code) - Sentiment: ${YELLOW}$sentiment${NC} (${confidence})"
101
+ else
102
+ echo -e "${RED}❌ FAILED${NC} (HTTP $http_code)"
103
+ fi
104
+
105
+ # POST endpoint for query
106
+ echo -n "Testing Query... "
107
+ response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/api/query" \
108
+ -H "Content-Type: application/json" \
109
+ -d '{"query":"What is the price of Bitcoin?"}')
110
+ http_code=$(echo "$response" | tail -n1)
111
+ if [ "$http_code" = "200" ]; then
112
+ echo -e "${GREEN}✅ OK${NC} (HTTP $http_code)"
113
+ else
114
+ echo -e "${RED}❌ FAILED${NC} (HTTP $http_code)"
115
+ fi
116
+
117
+ # Test provider endpoints
118
+ echo ""
119
+ echo "🔌 Provider Endpoints:"
120
+ test_endpoint "GET" "/api/providers" "" "Providers List"
121
+
122
+ # Test datasets endpoints
123
+ echo ""
124
+ echo "📚 Datasets & Models Endpoints:"
125
+ test_endpoint "GET" "/api/datasets/list" "" "Datasets List"
126
+ test_endpoint "GET" "/api/models/list" "" "Models List"
127
+
128
+ # POST endpoint for model test
129
+ echo -n "Testing Model Test... "
130
+ response=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/api/models/test" \
131
+ -H "Content-Type: application/json" \
132
+ -d '{"model":"crypto_sent_0","text":"Ethereum price surging!"}')
133
+ http_code=$(echo "$response" | tail -n1)
134
+ if [ "$http_code" = "200" ]; then
135
+ echo -e "${GREEN}✅ OK${NC} (HTTP $http_code)"
136
+ else
137
+ echo -e "${RED}❌ FAILED${NC} (HTTP $http_code)"
138
+ fi
139
+
140
+ # Summary
141
+ echo ""
142
+ echo "======================================"
143
+ echo "📊 Test Summary"
144
+ echo "======================================"
145
+ echo ""
146
+ echo "✅ All critical endpoints tested"
147
+ echo ""
148
+ echo "🌐 Dashboard URLs:"
149
+ echo " - Main: $BASE_URL/"
150
+ echo " - Admin: $BASE_URL/admin.html"
151
+ echo " - API Docs: $BASE_URL/docs"
152
+ echo ""
153
+ echo "🔌 WebSocket:"
154
+ echo " - ws://$(echo $BASE_URL | sed 's|http://||')/ws"
155
+ echo ""
156
+ echo "💡 Next steps:"
157
+ echo " 1. Open $BASE_URL/ in your browser"
158
+ echo " 2. Check all dashboard tabs"
159
+ echo " 3. Verify WebSocket connection (status indicator)"
160
+ echo ""
161
+ echo "======================================"
final/__pycache__/ai_models.cpython-312.pyc ADDED
Binary file (12.3 kB). View file
 
final/__pycache__/ai_models.cpython-313.pyc ADDED
Binary file (18.4 kB). View file
 
final/__pycache__/api_loader.cpython-313.pyc ADDED
Binary file (9.38 kB). View file
 
final/__pycache__/app.cpython-313.pyc ADDED
Binary file (64.8 kB). View file
 
final/__pycache__/config.cpython-313.pyc ADDED
Binary file (16.7 kB). View file
 
final/__pycache__/database.cpython-313.pyc ADDED
Binary file (36.3 kB). View file
 
final/__pycache__/hf_unified_server.cpython-312.pyc ADDED
Binary file (68 kB). View file
 
final/__pycache__/hf_unified_server.cpython-313.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:95d175b459a983f328419804aacfc0f73321bc4e516957dfabc266847afc655c
3
+ size 113609
final/__pycache__/main.cpython-313.pyc ADDED
Binary file (1.46 kB). View file
 
final/__pycache__/monitor.cpython-313.pyc ADDED
Binary file (17.6 kB). View file
 
final/__pycache__/simple_server.cpython-313.pyc ADDED
Binary file (29.1 kB). View file
 
final/admin.html ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Crypto Intelligence Admin</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
10
+ <link rel="stylesheet" href="/static/css/unified-ui.css" />
11
+ <link rel="stylesheet" href="/static/css/components.css" />
12
+ <script defer src="/static/js/ui-feedback.js"></script>
13
+ <script defer src="/static/js/admin-app.js"></script>
14
+ </head>
15
+ <body class="page page-admin">
16
+ <header class="top-nav">
17
+ <div class="branding">
18
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 12h16M12 4v16"/></svg>
19
+ <div>
20
+ <strong>Providers & Scheduling</strong>
21
+ <small style="color:var(--ui-text-muted);letter-spacing:0.2em;">/api/providers · /api/logs</small>
22
+ </div>
23
+ </div>
24
+ <nav class="nav-links">
25
+ <a href="/dashboard">Dashboard</a>
26
+ <a class="active" href="/admin">Admin</a>
27
+ <a href="/hf_console">HF Console</a>
28
+ <a href="/docs" target="_blank" rel="noreferrer">API Docs</a>
29
+ </nav>
30
+ </header>
31
+
32
+ <main class="page-content">
33
+ <section class="card">
34
+ <div class="section-heading">
35
+ <h2>Providers Health</h2>
36
+ <span class="badge info" id="providers-count">Loading...</span>
37
+ </div>
38
+ <div class="table-card">
39
+ <table>
40
+ <thead>
41
+ <tr><th>Provider</th><th>Status</th><th>Response (ms)</th><th>Category</th></tr>
42
+ </thead>
43
+ <tbody id="providers-table">
44
+ <tr><td colspan="4">Loading providers...</td></tr>
45
+ </tbody>
46
+ </table>
47
+ </div>
48
+ </section>
49
+
50
+ <section class="split-grid">
51
+ <article class="card" id="provider-detail">
52
+ <div class="section-heading">
53
+ <h2>Provider Detail</h2>
54
+ <span class="badge info" id="selected-provider">Select a provider</span>
55
+ </div>
56
+ <ul class="list" id="provider-detail-list"></ul>
57
+ </article>
58
+ <article class="card">
59
+ <div class="section-heading">
60
+ <h2>Configuration Snapshot</h2>
61
+ <span class="badge info" id="config-summary">Loading...</span>
62
+ </div>
63
+ <ul class="list" id="config-list"></ul>
64
+ </article>
65
+ </section>
66
+
67
+ <section class="split-grid">
68
+ <article class="card">
69
+ <div class="section-heading"><h2>Logs ( /api/logs )</h2><span class="badge info">Latest</span></div>
70
+ <div id="logs-list" class="ws-stream"></div>
71
+ </article>
72
+ <article class="card">
73
+ <div class="section-heading"><h2>Alerts ( /api/alerts )</h2><span class="badge info">Live</span></div>
74
+ <div id="alerts-list"></div>
75
+ </article>
76
+ </section>
77
+ </main>
78
+ </body>
79
+ </html>
final/admin.html.optimized ADDED
@@ -0,0 +1,496 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Crypto Monitor HF - Unified Dashboard</title>
7
+ <link rel="stylesheet" href="static/css/design-tokens.css" />
8
+ <link rel="stylesheet" href="static/css/design-system.css" />
9
+ <link rel="stylesheet" href="static/css/dashboard.css" />
10
+ <link rel="stylesheet" href="static/css/pro-dashboard.css" />
11
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" defer></script>
12
+ </head>
13
+ <body data-theme="dark">
14
+ <div class="app-shell">
15
+ <aside class="sidebar">
16
+ <div class="brand">
17
+ <strong>Crypto Monitor HF</strong>
18
+ <span class="env-pill">
19
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
20
+ <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="1.5" />
21
+ <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="1.5" />
22
+ <path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="1.5" />
23
+ </svg>
24
+ HF Space
25
+ </span>
26
+ </div>
27
+ <nav class="nav">
28
+ <button class="nav-button active" data-nav="page-overview">Overview</button>
29
+ <button class="nav-button" data-nav="page-market">Market</button>
30
+ <button class="nav-button" data-nav="page-chart">Chart Lab</button>
31
+ <button class="nav-button" data-nav="page-ai">Sentiment & AI</button>
32
+ <button class="nav-button" data-nav="page-news">News</button>
33
+ <button class="nav-button" data-nav="page-providers">Providers</button>
34
+ <button class="nav-button" data-nav="page-api">API Explorer</button>
35
+ <button class="nav-button" data-nav="page-debug">Diagnostics</button>
36
+ <button class="nav-button" data-nav="page-datasets">Datasets & Models</button>
37
+ <button class="nav-button" data-nav="page-settings">Settings</button>
38
+ </nav>
39
+ <div class="sidebar-footer">
40
+ Unified crypto intelligence console<br />Realtime data • HF optimized
41
+ </div>
42
+ </aside>
43
+ <main class="main-area">
44
+ <header class="topbar">
45
+ <div>
46
+ <h1>Unified Intelligence Dashboard</h1>
47
+ <p class="text-muted">Live market telemetry, AI signals, diagnostics, and provider health.</p>
48
+ </div>
49
+ <div class="status-group">
50
+ <div class="status-pill" data-api-health data-state="warn">
51
+ <span class="status-dot"></span>
52
+ <span>checking</span>
53
+ </div>
54
+ <div class="status-pill" data-ws-status data-state="warn">
55
+ <span class="status-dot"></span>
56
+ <span>connecting</span>
57
+ </div>
58
+ </div>
59
+ </header>
60
+ <div class="page-container">
61
+ <section id="page-overview" class="page active">
62
+ <div class="section-header">
63
+ <h2 class="section-title">Global Overview</h2>
64
+ <span class="chip">Powered by /api/market/stats</span>
65
+ </div>
66
+ <div class="stats-grid" data-overview-stats></div>
67
+ <div class="grid-two">
68
+ <div class="glass-card">
69
+ <div class="section-header">
70
+ <h3>Top Coins</h3>
71
+ <span class="text-muted">Market movers</span>
72
+ </div>
73
+ <div class="table-wrapper">
74
+ <table>
75
+ <thead>
76
+ <tr>
77
+ <th>#</th>
78
+ <th>Symbol</th>
79
+ <th>Name</th>
80
+ <th>Price</th>
81
+ <th>24h %</th>
82
+ <th>Volume</th>
83
+ <th>Market Cap</th>
84
+ </tr>
85
+ </thead>
86
+ <tbody data-top-coins-body></tbody>
87
+ </table>
88
+ </div>
89
+ </div>
90
+ <div class="glass-card">
91
+ <div class="section-header">
92
+ <h3>Global Sentiment</h3>
93
+ <span class="text-muted">CryptoBERT stack</span>
94
+ </div>
95
+ <canvas id="sentiment-chart" height="220"></canvas>
96
+ </div>
97
+ </div>
98
+ </section>
99
+
100
+ <section id="page-market" class="page">
101
+ <div class="section-header">
102
+ <h2 class="section-title">Market Intelligence</h2>
103
+ <div class="controls-bar">
104
+ <div class="input-chip">
105
+ <svg viewBox="0 0 24 24" width="16" height="16"><path d="M21 20l-5.6-5.6A6.5 6.5 0 1 0 15.4 16L21 21zM5 10.5a5.5 5.5 0 1 1 11 0a5.5 5.5 0 0 1-11 0z" fill="currentColor"/></svg>
106
+ <input type="text" placeholder="Search symbol" data-market-search />
107
+ </div>
108
+ <div class="input-chip">
109
+ Timeframe:
110
+ <button class="ghost" data-timeframe="1d">1D</button>
111
+ <button class="ghost active" data-timeframe="7d">7D</button>
112
+ <button class="ghost" data-timeframe="30d">30D</button>
113
+ </div>
114
+ <label class="input-chip"> Live updates
115
+ <div class="toggle">
116
+ <input type="checkbox" data-live-toggle />
117
+ <span></span>
118
+ </div>
119
+ </label>
120
+ </div>
121
+ </div>
122
+ <div class="glass-card">
123
+ <div class="table-wrapper">
124
+ <table>
125
+ <thead>
126
+ <tr>
127
+ <th>#</th>
128
+ <th>Symbol</th>
129
+ <th>Name</th>
130
+ <th>Price</th>
131
+ <th>24h %</th>
132
+ <th>Volume</th>
133
+ <th>Market Cap</th>
134
+ </tr>
135
+ </thead>
136
+ <tbody data-market-body></tbody>
137
+ </table>
138
+ </div>
139
+ </div>
140
+ <div class="drawer" data-market-drawer>
141
+ <button class="ghost" data-close-drawer>Close</button>
142
+ <h3 data-drawer-symbol>—</h3>
143
+ <div data-drawer-stats></div>
144
+ <div class="glass-card" data-chart-wrapper>
145
+ <canvas id="market-detail-chart" height="180"></canvas>
146
+ </div>
147
+ <div class="glass-card">
148
+ <h4>Related Headlines</h4>
149
+ <div data-drawer-news></div>
150
+ </div>
151
+ </div>
152
+ </section>
153
+
154
+ <section id="page-chart" class="page">
155
+ <div class="section-header">
156
+ <h2 class="section-title">Chart Lab</h2>
157
+ <div class="controls-bar">
158
+ <select data-chart-symbol>
159
+ <option value="BTC">BTC</option>
160
+ <option value="ETH">ETH</option>
161
+ <option value="SOL">SOL</option>
162
+ <option value="BNB">BNB</option>
163
+ </select>
164
+ <div class="input-chip">
165
+ <button class="ghost active" data-chart-timeframe="7d">7D</button>
166
+ <button class="ghost" data-chart-timeframe="30d">30D</button>
167
+ <button class="ghost" data-chart-timeframe="90d">90D</button>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ <div class="glass-card">
172
+ <canvas id="chart-lab-canvas" height="260"></canvas>
173
+ </div>
174
+ <div class="glass-card">
175
+ <div class="controls-bar">
176
+ <label><input type="checkbox" data-indicator value="MA20" checked /> MA 20</label>
177
+ <label><input type="checkbox" data-indicator value="MA50" /> MA 50</label>
178
+ <label><input type="checkbox" data-indicator value="RSI" /> RSI</label>
179
+ <label><input type="checkbox" data-indicator value="Volume" /> Volume</label>
180
+ </div>
181
+ <button class="primary" data-run-analysis>Analyze Chart with AI</button>
182
+ <div data-ai-insights class="ai-insights"></div>
183
+ </div>
184
+ </section>
185
+
186
+ <section id="page-ai" class="page">
187
+ <div class="section-header">
188
+ <h2 class="section-title">Sentiment & AI Advisor</h2>
189
+ </div>
190
+ <div class="glass-card">
191
+ <form data-ai-form class="ai-form">
192
+ <div class="grid-two">
193
+ <label>Symbol
194
+ <select name="symbol">
195
+ <option value="BTC">BTC</option>
196
+ <option value="ETH">ETH</option>
197
+ <option value="SOL">SOL</option>
198
+ </select>
199
+ </label>
200
+ <label>Time Horizon
201
+ <select name="horizon">
202
+ <option value="intraday">Intraday</option>
203
+ <option value="swing" selected>Swing</option>
204
+ <option value="long">Long Term</option>
205
+ </select>
206
+ </label>
207
+ <label>Risk Profile
208
+ <select name="risk">
209
+ <option value="conservative">Conservative</option>
210
+ <option value="moderate" selected>Moderate</option>
211
+ <option value="aggressive">Aggressive</option>
212
+ </select>
213
+ </label>
214
+ <label>Sentiment Model
215
+ <select name="model">
216
+ <option value="auto">Auto</option>
217
+ <option value="crypto">CryptoBERT</option>
218
+ <option value="financial">FinBERT</option>
219
+ <option value="social">Twitter Sentiment</option>
220
+ </select>
221
+ </label>
222
+ </div>
223
+ <label>Context or Headline
224
+ <textarea name="context" placeholder="Paste a headline or trade thesis for AI analysis"></textarea>
225
+ </label>
226
+ <button class="primary" type="submit">Generate Guidance</button>
227
+ </form>
228
+ <div class="grid-two">
229
+ <div data-ai-result class="ai-result"></div>
230
+ <div data-sentiment-result></div>
231
+ </div>
232
+ <div class="inline-message inline-info" data-ai-disclaimer>
233
+ Experimental AI output. Not financial advice.
234
+ </div>
235
+ </div>
236
+ </section>
237
+
238
+ <section id="page-news" class="page">
239
+ <div class="section-header">
240
+ <h2 class="section-title">News & Summaries</h2>
241
+ </div>
242
+ <div class="controls-bar">
243
+ <select data-news-range>
244
+ <option value="24h">Last 24h</option>
245
+ <option value="7d">7 Days</option>
246
+ <option value="30d">30 Days</option>
247
+ </select>
248
+ <input type="text" placeholder="Search headline" data-news-search />
249
+ <input type="text" placeholder="Filter symbol (e.g. BTC)" data-news-symbol />
250
+ </div>
251
+ <div class="glass-card">
252
+ <div class="table-wrapper">
253
+ <table>
254
+ <thead>
255
+ <tr>
256
+ <th>Time</th>
257
+ <th>Source</th>
258
+ <th>Title</th>
259
+ <th>Symbols</th>
260
+ <th>Sentiment</th>
261
+ <th>AI</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody data-news-body></tbody>
265
+ </table>
266
+ </div>
267
+ </div>
268
+ <div class="modal-backdrop" data-news-modal>
269
+ <div class="modal">
270
+ <button class="ghost" data-close-news-modal>Close</button>
271
+ <div data-news-modal-content></div>
272
+ </div>
273
+ </div>
274
+ </section>
275
+
276
+ <section id="page-providers" class="page">
277
+ <div class="section-header">
278
+ <h2 class="section-title">Provider Health</h2>
279
+ <button class="ghost" data-provider-refresh>Refresh</button>
280
+ </div>
281
+ <div class="stats-grid" data-provider-summary></div>
282
+ <div class="controls-bar">
283
+ <input type="search" placeholder="Search provider" data-provider-search />
284
+ <select data-provider-category>
285
+ <option value="all">All Categories</option>
286
+ <option value="market">Market Data</option>
287
+ <option value="news">News</option>
288
+ <option value="ai">AI</option>
289
+ </select>
290
+ </div>
291
+ <div class="glass-card">
292
+ <div class="table-wrapper">
293
+ <table>
294
+ <thead>
295
+ <tr>
296
+ <th>Name</th>
297
+ <th>Category</th>
298
+ <th>Status</th>
299
+ <th>Latency</th>
300
+ <th>Details</th>
301
+ </tr>
302
+ </thead>
303
+ <tbody data-providers-table></tbody>
304
+ </table>
305
+ </div>
306
+ </div>
307
+ </section>
308
+
309
+ <section id="page-api" class="page">
310
+ <div class="section-header">
311
+ <h2 class="section-title">API Explorer</h2>
312
+ <span class="chip">Test live endpoints</span>
313
+ </div>
314
+ <div class="glass-card">
315
+ <div class="grid-two">
316
+ <label>Endpoint
317
+ <select data-api-endpoint></select>
318
+ </label>
319
+ <label>Method
320
+ <select data-api-method>
321
+ <option value="GET">GET</option>
322
+ <option value="POST">POST</option>
323
+ </select>
324
+ </label>
325
+ <label>Query Params
326
+ <input type="text" placeholder="limit=10&symbol=BTC" data-api-params />
327
+ </label>
328
+ <label>Body (JSON)
329
+ <textarea data-api-body placeholder='{ "text": "Bitcoin" }'></textarea>
330
+ </label>
331
+ </div>
332
+ <p class="text-muted">Path: <span data-api-path></span> — <span data-api-description></span></p>
333
+ <button class="primary" data-api-send>Send Request</button>
334
+ <div class="inline-message" data-api-meta>Ready</div>
335
+ <pre data-api-response class="api-response"></pre>
336
+ </div>
337
+ </section>
338
+
339
+ <section id="page-debug" class="page">
340
+ <div class="section-header">
341
+ <h2 class="section-title">Diagnostics</h2>
342
+ <button class="ghost" data-refresh-health>Refresh</button>
343
+ </div>
344
+ <div class="stats-grid">
345
+ <div class="glass-card">
346
+ <h3>API Health</h3>
347
+ <div class="stat-value" data-health-status>—</div>
348
+ </div>
349
+ <div class="glass-card">
350
+ <h3>Providers</h3>
351
+ <div data-providers class="grid-two"></div>
352
+ </div>
353
+ </div>
354
+ <div class="grid-two">
355
+ <div class="glass-card">
356
+ <h4>Request Log</h4>
357
+ <div class="table-wrapper log-table">
358
+ <table>
359
+ <thead>
360
+ <tr>
361
+ <th>Time</th>
362
+ <th>Method</th>
363
+ <th>Endpoint</th>
364
+ <th>Status</th>
365
+ <th>Latency</th>
366
+ </tr>
367
+ </thead>
368
+ <tbody data-request-log></tbody>
369
+ </table>
370
+ </div>
371
+ </div>
372
+ <div class="glass-card">
373
+ <h4>Error Log</h4>
374
+ <div class="table-wrapper log-table">
375
+ <table>
376
+ <thead>
377
+ <tr>
378
+ <th>Time</th>
379
+ <th>Endpoint</th>
380
+ <th>Message</th>
381
+ </tr>
382
+ </thead>
383
+ <tbody data-error-log></tbody>
384
+ </table>
385
+ </div>
386
+ </div>
387
+ </div>
388
+ <div class="glass-card">
389
+ <h4>WebSocket Events</h4>
390
+ <div class="table-wrapper log-table">
391
+ <table>
392
+ <thead>
393
+ <tr>
394
+ <th>Time</th>
395
+ <th>Type</th>
396
+ <th>Detail</th>
397
+ </tr>
398
+ </thead>
399
+ <tbody data-ws-log></tbody>
400
+ </table>
401
+ </div>
402
+ </div>
403
+ </section>
404
+
405
+ <section id="page-datasets" class="page">
406
+ <div class="section-header">
407
+ <h2 class="section-title">Datasets & Models</h2>
408
+ </div>
409
+ <div class="grid-two">
410
+ <div class="glass-card">
411
+ <h3>Datasets</h3>
412
+ <div class="table-wrapper">
413
+ <table>
414
+ <thead>
415
+ <tr>
416
+ <th>Name</th>
417
+ <th>Records</th>
418
+ <th>Updated</th>
419
+ <th>Actions</th>
420
+ </tr>
421
+ </thead>
422
+ <tbody data-datasets-body></tbody>
423
+ </table>
424
+ </div>
425
+ </div>
426
+ <div class="glass-card">
427
+ <h3>Models</h3>
428
+ <div class="table-wrapper">
429
+ <table>
430
+ <thead>
431
+ <tr>
432
+ <th>Name</th>
433
+ <th>Task</th>
434
+ <th>Status</th>
435
+ <th>Notes</th>
436
+ </tr>
437
+ </thead>
438
+ <tbody data-models-body></tbody>
439
+ </table>
440
+ </div>
441
+ </div>
442
+ </div>
443
+ <div class="glass-card">
444
+ <h4>Test a Model</h4>
445
+ <form data-model-test-form class="grid-two">
446
+ <label>Model
447
+ <select data-model-select name="model"></select>
448
+ </label>
449
+ <label>Input
450
+ <textarea name="input" placeholder="Type a prompt"></textarea>
451
+ </label>
452
+ <button class="primary" type="submit">Run Test</button>
453
+ </form>
454
+ <div data-model-test-output></div>
455
+ </div>
456
+ <div class="modal-backdrop" data-dataset-modal>
457
+ <div class="modal">
458
+ <button class="ghost" data-close-dataset-modal>Close</button>
459
+ <div data-dataset-modal-content></div>
460
+ </div>
461
+ </div>
462
+ </section>
463
+
464
+ <section id="page-settings" class="page">
465
+ <div class="section-header">
466
+ <h2 class="section-title">Settings</h2>
467
+ </div>
468
+ <div class="glass-card">
469
+ <div class="grid-two">
470
+ <label class="input-chip">Light Theme
471
+ <div class="toggle">
472
+ <input type="checkbox" data-theme-toggle />
473
+ <span></span>
474
+ </div>
475
+ </label>
476
+ <label>Market Refresh (sec)
477
+ <input type="number" min="15" step="5" data-market-interval />
478
+ </label>
479
+ <label>News Refresh (sec)
480
+ <input type="number" min="30" step="10" data-news-interval />
481
+ </label>
482
+ <label class="input-chip">Compact Layout
483
+ <div class="toggle">
484
+ <input type="checkbox" data-layout-toggle />
485
+ <span></span>
486
+ </div>
487
+ </label>
488
+ </div>
489
+ </div>
490
+ </section>
491
+ </div>
492
+ </main>
493
+ </div>
494
+ <script type="module" src="static/js/app.js"></script>
495
+ </body>
496
+ </html>
final/admin_advanced.html ADDED
@@ -0,0 +1,1862 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Advanced Admin Dashboard - Crypto Monitor</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
9
+ <style>
10
+ * { margin: 0; padding: 0; box-sizing: border-box; }
11
+
12
+ :root {
13
+ --primary: #6366f1;
14
+ --primary-dark: #4f46e5;
15
+ --primary-glow: rgba(99, 102, 241, 0.4);
16
+ --success: #10b981;
17
+ --warning: #f59e0b;
18
+ --danger: #ef4444;
19
+ --info: #3b82f6;
20
+ --bg-dark: #0f172a;
21
+ --bg-card: rgba(30, 41, 59, 0.7);
22
+ --bg-glass: rgba(30, 41, 59, 0.5);
23
+ --bg-hover: rgba(51, 65, 85, 0.8);
24
+ --text-light: #f1f5f9;
25
+ --text-muted: #94a3b8;
26
+ --border: rgba(51, 65, 85, 0.6);
27
+ }
28
+
29
+ body {
30
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
31
+ background: radial-gradient(ellipse at top, #1e293b 0%, #0f172a 50%, #000000 100%);
32
+ color: var(--text-light);
33
+ line-height: 1.6;
34
+ min-height: 100vh;
35
+ position: relative;
36
+ overflow-x: hidden;
37
+ }
38
+
39
+ /* Animated Background Particles */
40
+ body::before {
41
+ content: '';
42
+ position: fixed;
43
+ top: 0;
44
+ left: 0;
45
+ width: 100%;
46
+ height: 100%;
47
+ background:
48
+ radial-gradient(circle at 20% 50%, rgba(99, 102, 241, 0.1) 0%, transparent 50%),
49
+ radial-gradient(circle at 80% 80%, rgba(16, 185, 129, 0.1) 0%, transparent 50%),
50
+ radial-gradient(circle at 40% 20%, rgba(59, 130, 246, 0.1) 0%, transparent 50%);
51
+ animation: float 20s ease-in-out infinite;
52
+ pointer-events: none;
53
+ z-index: 0;
54
+ }
55
+
56
+ @keyframes float {
57
+ 0%, 100% { transform: translate(0, 0) rotate(0deg); }
58
+ 33% { transform: translate(30px, -30px) rotate(120deg); }
59
+ 66% { transform: translate(-20px, 20px) rotate(240deg); }
60
+ }
61
+
62
+ .container {
63
+ max-width: 1800px;
64
+ margin: 0 auto;
65
+ padding: 20px;
66
+ position: relative;
67
+ z-index: 1;
68
+ }
69
+
70
+ /* Glassmorphic Header with Glow */
71
+ header {
72
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.9) 0%, rgba(79, 70, 229, 0.9) 100%);
73
+ backdrop-filter: blur(20px);
74
+ -webkit-backdrop-filter: blur(20px);
75
+ padding: 30px;
76
+ border-radius: 20px;
77
+ margin-bottom: 30px;
78
+ border: 1px solid rgba(255, 255, 255, 0.2);
79
+ box-shadow:
80
+ 0 8px 32px rgba(0, 0, 0, 0.3),
81
+ 0 0 60px var(--primary-glow),
82
+ inset 0 1px 0 rgba(255, 255, 255, 0.2);
83
+ position: relative;
84
+ overflow: hidden;
85
+ animation: headerGlow 3s ease-in-out infinite alternate;
86
+ }
87
+
88
+ @keyframes headerGlow {
89
+ 0% { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 40px var(--primary-glow), inset 0 1px 0 rgba(255, 255, 255, 0.2); }
90
+ 100% { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 80px var(--primary-glow), inset 0 1px 0 rgba(255, 255, 255, 0.3); }
91
+ }
92
+
93
+ header::before {
94
+ content: '';
95
+ position: absolute;
96
+ top: -50%;
97
+ left: -50%;
98
+ width: 200%;
99
+ height: 200%;
100
+ background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.1), transparent);
101
+ transform: rotate(45deg);
102
+ animation: headerShine 3s linear infinite;
103
+ }
104
+
105
+ @keyframes headerShine {
106
+ 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
107
+ 100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
108
+ }
109
+
110
+ header h1 {
111
+ font-size: 36px;
112
+ font-weight: 700;
113
+ margin-bottom: 8px;
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 15px;
117
+ position: relative;
118
+ z-index: 1;
119
+ text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
120
+ }
121
+
122
+ header .icon {
123
+ font-size: 42px;
124
+ filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.5));
125
+ animation: iconPulse 2s ease-in-out infinite;
126
+ }
127
+
128
+ @keyframes iconPulse {
129
+ 0%, 100% { transform: scale(1); }
130
+ 50% { transform: scale(1.1); }
131
+ }
132
+
133
+ header .subtitle {
134
+ color: rgba(255, 255, 255, 0.95);
135
+ font-size: 16px;
136
+ position: relative;
137
+ z-index: 1;
138
+ text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
139
+ }
140
+
141
+ /* Glassmorphic Tabs */
142
+ .tabs {
143
+ display: flex;
144
+ gap: 10px;
145
+ margin-bottom: 30px;
146
+ flex-wrap: wrap;
147
+ background: var(--bg-glass);
148
+ backdrop-filter: blur(10px);
149
+ -webkit-backdrop-filter: blur(10px);
150
+ padding: 15px;
151
+ border-radius: 16px;
152
+ border: 1px solid var(--border);
153
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
154
+ }
155
+
156
+ .tab-btn {
157
+ padding: 12px 24px;
158
+ background: rgba(255, 255, 255, 0.05);
159
+ backdrop-filter: blur(10px);
160
+ border: 1px solid rgba(255, 255, 255, 0.1);
161
+ border-radius: 10px;
162
+ cursor: pointer;
163
+ font-weight: 600;
164
+ color: var(--text-light);
165
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
166
+ position: relative;
167
+ overflow: hidden;
168
+ }
169
+
170
+ .tab-btn::before {
171
+ content: '';
172
+ position: absolute;
173
+ top: 0;
174
+ left: -100%;
175
+ width: 100%;
176
+ height: 100%;
177
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
178
+ transition: left 0.5s;
179
+ }
180
+
181
+ .tab-btn:hover::before {
182
+ left: 100%;
183
+ }
184
+
185
+ .tab-btn:hover {
186
+ background: rgba(99, 102, 241, 0.2);
187
+ border-color: var(--primary);
188
+ transform: translateY(-2px);
189
+ box-shadow: 0 4px 12px var(--primary-glow);
190
+ }
191
+
192
+ .tab-btn.active {
193
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
194
+ border-color: var(--primary);
195
+ box-shadow: 0 4px 20px var(--primary-glow);
196
+ transform: scale(1.05);
197
+ }
198
+
199
+ .tab-content {
200
+ display: none;
201
+ animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
202
+ }
203
+
204
+ .tab-content.active {
205
+ display: block;
206
+ }
207
+
208
+ @keyframes fadeInUp {
209
+ from {
210
+ opacity: 0;
211
+ transform: translateY(20px);
212
+ }
213
+ to {
214
+ opacity: 1;
215
+ transform: translateY(0);
216
+ }
217
+ }
218
+
219
+ /* Glassmorphic Cards */
220
+ .card {
221
+ background: var(--bg-glass);
222
+ backdrop-filter: blur(10px);
223
+ -webkit-backdrop-filter: blur(10px);
224
+ border-radius: 16px;
225
+ padding: 24px;
226
+ margin-bottom: 20px;
227
+ border: 1px solid var(--border);
228
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
229
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
230
+ }
231
+
232
+ .card:hover {
233
+ transform: translateY(-2px);
234
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
235
+ border-color: rgba(99, 102, 241, 0.3);
236
+ }
237
+
238
+ .card h3 {
239
+ color: var(--primary);
240
+ margin-bottom: 20px;
241
+ font-size: 20px;
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 10px;
245
+ text-shadow: 0 0 20px var(--primary-glow);
246
+ }
247
+
248
+ /* Animated Stat Cards */
249
+ .stats-grid {
250
+ display: grid;
251
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
252
+ gap: 20px;
253
+ margin-bottom: 30px;
254
+ }
255
+
256
+ .stat-card {
257
+ background: var(--bg-glass);
258
+ backdrop-filter: blur(10px);
259
+ -webkit-backdrop-filter: blur(10px);
260
+ padding: 24px;
261
+ border-radius: 16px;
262
+ border: 1px solid var(--border);
263
+ position: relative;
264
+ overflow: hidden;
265
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
266
+ animation: statCardIn 0.5s ease-out backwards;
267
+ }
268
+
269
+ @keyframes statCardIn {
270
+ from {
271
+ opacity: 0;
272
+ transform: scale(0.9) translateY(20px);
273
+ }
274
+ to {
275
+ opacity: 1;
276
+ transform: scale(1) translateY(0);
277
+ }
278
+ }
279
+
280
+ .stat-card:nth-child(1) { animation-delay: 0.1s; }
281
+ .stat-card:nth-child(2) { animation-delay: 0.2s; }
282
+ .stat-card:nth-child(3) { animation-delay: 0.3s; }
283
+ .stat-card:nth-child(4) { animation-delay: 0.4s; }
284
+
285
+ .stat-card::before {
286
+ content: '';
287
+ position: absolute;
288
+ top: 0;
289
+ left: 0;
290
+ right: 0;
291
+ height: 3px;
292
+ background: linear-gradient(90deg, var(--primary), var(--info), var(--success));
293
+ background-size: 200% 100%;
294
+ animation: gradientMove 3s ease infinite;
295
+ }
296
+
297
+ @keyframes gradientMove {
298
+ 0%, 100% { background-position: 0% 50%; }
299
+ 50% { background-position: 100% 50%; }
300
+ }
301
+
302
+ .stat-card:hover {
303
+ transform: translateY(-8px) scale(1.02);
304
+ box-shadow: 0 12px 40px rgba(99, 102, 241, 0.3);
305
+ border-color: var(--primary);
306
+ }
307
+
308
+ .stat-card .label {
309
+ color: var(--text-muted);
310
+ font-size: 13px;
311
+ text-transform: uppercase;
312
+ letter-spacing: 0.5px;
313
+ font-weight: 600;
314
+ margin-bottom: 8px;
315
+ }
316
+
317
+ .stat-card .value {
318
+ font-size: 42px;
319
+ font-weight: 700;
320
+ margin: 8px 0;
321
+ color: var(--primary);
322
+ text-shadow: 0 0 30px var(--primary-glow);
323
+ animation: valueCount 1s ease-out;
324
+ }
325
+
326
+ @keyframes valueCount {
327
+ from { opacity: 0; transform: translateY(-10px); }
328
+ to { opacity: 1; transform: translateY(0); }
329
+ }
330
+
331
+ .stat-card .change {
332
+ font-size: 14px;
333
+ font-weight: 600;
334
+ display: flex;
335
+ align-items: center;
336
+ gap: 5px;
337
+ }
338
+
339
+ .stat-card .change.positive {
340
+ color: var(--success);
341
+ animation: bounce 1s ease-in-out infinite;
342
+ }
343
+
344
+ @keyframes bounce {
345
+ 0%, 100% { transform: translateY(0); }
346
+ 50% { transform: translateY(-3px); }
347
+ }
348
+
349
+ .stat-card .change.negative {
350
+ color: var(--danger);
351
+ }
352
+
353
+ /* Glassmorphic Chart Container */
354
+ .chart-container {
355
+ background: rgba(15, 23, 42, 0.5);
356
+ backdrop-filter: blur(10px);
357
+ padding: 20px;
358
+ border-radius: 12px;
359
+ margin-bottom: 20px;
360
+ height: 400px;
361
+ border: 1px solid rgba(255, 255, 255, 0.05);
362
+ box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.2);
363
+ }
364
+
365
+ /* Modern Buttons */
366
+ .btn {
367
+ padding: 12px 24px;
368
+ border: none;
369
+ border-radius: 10px;
370
+ cursor: pointer;
371
+ font-weight: 600;
372
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
373
+ margin-right: 10px;
374
+ margin-bottom: 10px;
375
+ display: inline-flex;
376
+ align-items: center;
377
+ gap: 8px;
378
+ position: relative;
379
+ overflow: hidden;
380
+ backdrop-filter: blur(10px);
381
+ }
382
+
383
+ .btn::before {
384
+ content: '';
385
+ position: absolute;
386
+ top: 50%;
387
+ left: 50%;
388
+ width: 0;
389
+ height: 0;
390
+ border-radius: 50%;
391
+ background: rgba(255, 255, 255, 0.2);
392
+ transform: translate(-50%, -50%);
393
+ transition: width 0.6s, height 0.6s;
394
+ }
395
+
396
+ .btn:hover::before {
397
+ width: 300px;
398
+ height: 300px;
399
+ }
400
+
401
+ .btn-primary {
402
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
403
+ color: white;
404
+ box-shadow: 0 4px 15px var(--primary-glow);
405
+ }
406
+
407
+ .btn-primary:hover {
408
+ transform: translateY(-3px);
409
+ box-shadow: 0 8px 25px var(--primary-glow);
410
+ }
411
+
412
+ .btn-success {
413
+ background: linear-gradient(135deg, var(--success), #059669);
414
+ color: white;
415
+ box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
416
+ }
417
+
418
+ .btn-success:hover {
419
+ transform: translateY(-3px);
420
+ box-shadow: 0 8px 25px rgba(16, 185, 129, 0.5);
421
+ }
422
+
423
+ .btn-warning {
424
+ background: linear-gradient(135deg, var(--warning), #d97706);
425
+ color: white;
426
+ box-shadow: 0 4px 15px rgba(245, 158, 11, 0.3);
427
+ }
428
+
429
+ .btn-danger {
430
+ background: linear-gradient(135deg, var(--danger), #dc2626);
431
+ color: white;
432
+ box-shadow: 0 4px 15px rgba(239, 68, 68, 0.3);
433
+ }
434
+
435
+ .btn-secondary {
436
+ background: rgba(51, 65, 85, 0.6);
437
+ color: var(--text-light);
438
+ border: 1px solid var(--border);
439
+ backdrop-filter: blur(10px);
440
+ }
441
+
442
+ .btn:disabled {
443
+ opacity: 0.5;
444
+ cursor: not-allowed;
445
+ transform: none !important;
446
+ }
447
+
448
+ .btn:active {
449
+ transform: scale(0.95);
450
+ }
451
+
452
+ /* Animated Progress Bar */
453
+ .progress-bar {
454
+ background: rgba(15, 23, 42, 0.8);
455
+ backdrop-filter: blur(10px);
456
+ height: 12px;
457
+ border-radius: 20px;
458
+ overflow: hidden;
459
+ margin-top: 10px;
460
+ border: 1px solid rgba(99, 102, 241, 0.3);
461
+ box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.3);
462
+ position: relative;
463
+ }
464
+
465
+ .progress-bar::before {
466
+ content: '';
467
+ position: absolute;
468
+ top: 0;
469
+ left: -100%;
470
+ width: 100%;
471
+ height: 100%;
472
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
473
+ animation: progressShine 2s linear infinite;
474
+ }
475
+
476
+ @keyframes progressShine {
477
+ 0% { left: -100%; }
478
+ 100% { left: 200%; }
479
+ }
480
+
481
+ .progress-bar-fill {
482
+ height: 100%;
483
+ background: linear-gradient(90deg, var(--primary), var(--info), var(--success));
484
+ background-size: 200% 100%;
485
+ animation: progressGradient 2s ease infinite;
486
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
487
+ box-shadow: 0 0 20px var(--primary-glow);
488
+ position: relative;
489
+ }
490
+
491
+ @keyframes progressGradient {
492
+ 0%, 100% { background-position: 0% 50%; }
493
+ 50% { background-position: 100% 50%; }
494
+ }
495
+
496
+ /* Glassmorphic Table */
497
+ table {
498
+ width: 100%;
499
+ border-collapse: collapse;
500
+ margin-top: 15px;
501
+ }
502
+
503
+ table thead {
504
+ background: rgba(15, 23, 42, 0.6);
505
+ backdrop-filter: blur(10px);
506
+ }
507
+
508
+ table th {
509
+ padding: 16px;
510
+ text-align: left;
511
+ font-weight: 600;
512
+ font-size: 12px;
513
+ text-transform: uppercase;
514
+ color: var(--text-muted);
515
+ border-bottom: 2px solid var(--border);
516
+ }
517
+
518
+ table td {
519
+ padding: 16px;
520
+ border-top: 1px solid var(--border);
521
+ transition: all 0.2s;
522
+ }
523
+
524
+ table tbody tr {
525
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
526
+ }
527
+
528
+ table tbody tr:hover {
529
+ background: var(--bg-hover);
530
+ backdrop-filter: blur(10px);
531
+ transform: scale(1.01);
532
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
533
+ }
534
+
535
+ /* Animated Resource Item */
536
+ .resource-item {
537
+ background: var(--bg-glass);
538
+ backdrop-filter: blur(10px);
539
+ padding: 16px;
540
+ border-radius: 12px;
541
+ margin-bottom: 12px;
542
+ border-left: 4px solid var(--primary);
543
+ display: flex;
544
+ justify-content: space-between;
545
+ align-items: center;
546
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
547
+ animation: slideIn 0.5s ease-out backwards;
548
+ }
549
+
550
+ @keyframes slideIn {
551
+ from {
552
+ opacity: 0;
553
+ transform: translateX(-20px);
554
+ }
555
+ to {
556
+ opacity: 1;
557
+ transform: translateX(0);
558
+ }
559
+ }
560
+
561
+ .resource-item:hover {
562
+ transform: translateX(5px) scale(1.02);
563
+ box-shadow: 0 4px 20px rgba(99, 102, 241, 0.3);
564
+ }
565
+
566
+ .resource-item.duplicate {
567
+ border-left-color: var(--warning);
568
+ background: rgba(245, 158, 11, 0.1);
569
+ }
570
+
571
+ .resource-item.error {
572
+ border-left-color: var(--danger);
573
+ background: rgba(239, 68, 68, 0.1);
574
+ }
575
+
576
+ .resource-item.valid {
577
+ border-left-color: var(--success);
578
+ }
579
+
580
+ /* Animated Badges */
581
+ .badge {
582
+ display: inline-block;
583
+ padding: 6px 12px;
584
+ border-radius: 20px;
585
+ font-size: 11px;
586
+ font-weight: 600;
587
+ text-transform: uppercase;
588
+ backdrop-filter: blur(10px);
589
+ animation: badgePulse 2s ease-in-out infinite;
590
+ }
591
+
592
+ @keyframes badgePulse {
593
+ 0%, 100% { transform: scale(1); }
594
+ 50% { transform: scale(1.05); }
595
+ }
596
+
597
+ .badge-success {
598
+ background: rgba(16, 185, 129, 0.3);
599
+ color: var(--success);
600
+ box-shadow: 0 0 15px rgba(16, 185, 129, 0.3);
601
+ }
602
+
603
+ .badge-warning {
604
+ background: rgba(245, 158, 11, 0.3);
605
+ color: var(--warning);
606
+ box-shadow: 0 0 15px rgba(245, 158, 11, 0.3);
607
+ }
608
+
609
+ .badge-danger {
610
+ background: rgba(239, 68, 68, 0.3);
611
+ color: var(--danger);
612
+ box-shadow: 0 0 15px rgba(239, 68, 68, 0.3);
613
+ }
614
+
615
+ .badge-info {
616
+ background: rgba(59, 130, 246, 0.3);
617
+ color: var(--info);
618
+ box-shadow: 0 0 15px rgba(59, 130, 246, 0.3);
619
+ }
620
+
621
+ /* Search/Filter Glassmorphic */
622
+ .search-bar {
623
+ display: flex;
624
+ gap: 15px;
625
+ margin-bottom: 20px;
626
+ flex-wrap: wrap;
627
+ }
628
+
629
+ .search-bar input,
630
+ .search-bar select {
631
+ padding: 12px;
632
+ border-radius: 10px;
633
+ border: 1px solid var(--border);
634
+ background: rgba(15, 23, 42, 0.6);
635
+ backdrop-filter: blur(10px);
636
+ color: var(--text-light);
637
+ flex: 1;
638
+ min-width: 200px;
639
+ transition: all 0.3s;
640
+ }
641
+
642
+ .search-bar input:focus,
643
+ .search-bar select:focus {
644
+ outline: none;
645
+ border-color: var(--primary);
646
+ box-shadow: 0 0 20px var(--primary-glow);
647
+ }
648
+
649
+ /* Loading Spinner with Glow */
650
+ .spinner {
651
+ border: 4px solid rgba(255, 255, 255, 0.1);
652
+ border-top-color: var(--primary);
653
+ border-radius: 50%;
654
+ width: 50px;
655
+ height: 50px;
656
+ animation: spin 0.8s linear infinite;
657
+ margin: 40px auto;
658
+ box-shadow: 0 0 30px var(--primary-glow);
659
+ }
660
+
661
+ @keyframes spin {
662
+ to { transform: rotate(360deg); }
663
+ }
664
+
665
+ /* Toast Notification with Glass */
666
+ .toast {
667
+ position: fixed;
668
+ bottom: 20px;
669
+ right: 20px;
670
+ background: var(--bg-glass);
671
+ backdrop-filter: blur(20px);
672
+ -webkit-backdrop-filter: blur(20px);
673
+ padding: 16px 24px;
674
+ border-radius: 12px;
675
+ border: 1px solid var(--border);
676
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
677
+ display: none;
678
+ align-items: center;
679
+ gap: 12px;
680
+ z-index: 1000;
681
+ animation: toastIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
682
+ }
683
+
684
+ @keyframes toastIn {
685
+ from {
686
+ transform: translateX(400px) scale(0.5);
687
+ opacity: 0;
688
+ }
689
+ to {
690
+ transform: translateX(0) scale(1);
691
+ opacity: 1;
692
+ }
693
+ }
694
+
695
+ .toast.show {
696
+ display: flex;
697
+ }
698
+
699
+ .toast.success {
700
+ border-left: 4px solid var(--success);
701
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 30px rgba(16, 185, 129, 0.3);
702
+ }
703
+
704
+ .toast.error {
705
+ border-left: 4px solid var(--danger);
706
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 30px rgba(239, 68, 68, 0.3);
707
+ }
708
+
709
+ /* Modal with Glass */
710
+ .modal {
711
+ display: none;
712
+ position: fixed;
713
+ top: 0;
714
+ left: 0;
715
+ right: 0;
716
+ bottom: 0;
717
+ background: rgba(0, 0, 0, 0.8);
718
+ backdrop-filter: blur(10px);
719
+ z-index: 1000;
720
+ align-items: center;
721
+ justify-content: center;
722
+ animation: fadeIn 0.3s;
723
+ }
724
+
725
+ .modal.show {
726
+ display: flex;
727
+ }
728
+
729
+ .modal-content {
730
+ background: var(--bg-glass);
731
+ backdrop-filter: blur(20px);
732
+ -webkit-backdrop-filter: blur(20px);
733
+ padding: 30px;
734
+ border-radius: 20px;
735
+ border: 1px solid var(--border);
736
+ max-width: 600px;
737
+ width: 90%;
738
+ max-height: 80vh;
739
+ overflow-y: auto;
740
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
741
+ animation: modalSlideIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
742
+ }
743
+
744
+ @keyframes modalSlideIn {
745
+ from {
746
+ transform: scale(0.5) translateY(-50px);
747
+ opacity: 0;
748
+ }
749
+ to {
750
+ transform: scale(1) translateY(0);
751
+ opacity: 1;
752
+ }
753
+ }
754
+
755
+ .modal-content h2 {
756
+ margin-bottom: 20px;
757
+ color: var(--primary);
758
+ text-shadow: 0 0 20px var(--primary-glow);
759
+ }
760
+
761
+ .modal-content .form-group {
762
+ margin-bottom: 20px;
763
+ }
764
+
765
+ .modal-content label {
766
+ display: block;
767
+ margin-bottom: 8px;
768
+ font-weight: 600;
769
+ color: var(--text-muted);
770
+ }
771
+
772
+ .modal-content input,
773
+ .modal-content textarea,
774
+ .modal-content select {
775
+ width: 100%;
776
+ padding: 12px;
777
+ border-radius: 10px;
778
+ border: 1px solid var(--border);
779
+ background: rgba(15, 23, 42, 0.6);
780
+ backdrop-filter: blur(10px);
781
+ color: var(--text-light);
782
+ transition: all 0.3s;
783
+ }
784
+
785
+ .modal-content input:focus,
786
+ .modal-content textarea:focus,
787
+ .modal-content select:focus {
788
+ outline: none;
789
+ border-color: var(--primary);
790
+ box-shadow: 0 0 20px var(--primary-glow);
791
+ }
792
+
793
+ .modal-content textarea {
794
+ min-height: 100px;
795
+ resize: vertical;
796
+ }
797
+
798
+ /* Grid Layout */
799
+ .grid-2 {
800
+ display: grid;
801
+ grid-template-columns: repeat(2, 1fr);
802
+ gap: 20px;
803
+ }
804
+
805
+ @media (max-width: 1024px) {
806
+ .grid-2 {
807
+ grid-template-columns: 1fr;
808
+ }
809
+ }
810
+
811
+ @media (max-width: 768px) {
812
+ .stats-grid {
813
+ grid-template-columns: 1fr;
814
+ }
815
+
816
+ header h1 {
817
+ font-size: 28px;
818
+ }
819
+
820
+ .tabs {
821
+ flex-direction: column;
822
+ }
823
+
824
+ .tab-btn {
825
+ width: 100%;
826
+ }
827
+ }
828
+
829
+ /* Scrollbar Styling */
830
+ ::-webkit-scrollbar {
831
+ width: 10px;
832
+ height: 10px;
833
+ }
834
+
835
+ ::-webkit-scrollbar-track {
836
+ background: rgba(15, 23, 42, 0.5);
837
+ border-radius: 10px;
838
+ }
839
+
840
+ ::-webkit-scrollbar-thumb {
841
+ background: linear-gradient(135deg, var(--primary), var(--info));
842
+ border-radius: 10px;
843
+ box-shadow: 0 0 10px var(--primary-glow);
844
+ }
845
+
846
+ ::-webkit-scrollbar-thumb:hover {
847
+ background: linear-gradient(135deg, var(--info), var(--success));
848
+ }
849
+ </style>
850
+ </head>
851
+ <body>
852
+ <div class="container">
853
+ <header>
854
+ <h1>
855
+ <span class="icon">📊</span>
856
+ Crypto Monitor Admin Dashboard
857
+ </h1>
858
+ <p class="subtitle">Real-time provider management & system monitoring | NO MOCK DATA</p>
859
+ </header>
860
+
861
+ <!-- Tabs -->
862
+ <div class="tabs">
863
+ <button class="tab-btn active" onclick="switchTab('dashboard')">📊 Dashboard</button>
864
+ <button class="tab-btn" onclick="switchTab('analytics')">📈 Analytics</button>
865
+ <button class="tab-btn" onclick="switchTab('resources')">🔧 Resource Manager</button>
866
+ <button class="tab-btn" onclick="switchTab('discovery')">🔍 Auto-Discovery</button>
867
+ <button class="tab-btn" onclick="switchTab('diagnostics')">🛠️ Diagnostics</button>
868
+ <button class="tab-btn" onclick="switchTab('logs')">📝 Logs</button>
869
+ </div>
870
+
871
+ <!-- Dashboard Tab -->
872
+ <div id="tab-dashboard" class="tab-content active">
873
+ <div class="stats-grid">
874
+ <div class="stat-card">
875
+ <div class="label">System Health</div>
876
+ <div class="value" id="system-health">HEALTHY</div>
877
+ <div class="change positive">✅ Healthy</div>
878
+ </div>
879
+
880
+ <div class="stat-card">
881
+ <div class="label">Total Providers</div>
882
+ <div class="value" id="total-providers">95</div>
883
+ <div class="change positive">↑ +12 this week</div>
884
+ </div>
885
+
886
+ <div class="stat-card">
887
+ <div class="label">Validated</div>
888
+ <div class="value" style="color: var(--success);" id="validated-count">32</div>
889
+ <div class="change positive">✓ All Active</div>
890
+ </div>
891
+
892
+ <div class="stat-card">
893
+ <div class="label">Database</div>
894
+ <div class="value">✓</div>
895
+ <div class="change positive">🗄️ Connected</div>
896
+ </div>
897
+ </div>
898
+
899
+ <div class="card">
900
+ <h3>⚡ Quick Actions</h3>
901
+ <button class="btn btn-primary" onclick="refreshAllData()">🔄 Refresh All</button>
902
+ <button class="btn btn-success" onclick="runAPLScan()">🤖 Run APL Scan</button>
903
+ <button class="btn btn-secondary" onclick="runDiagnostics(false)">🔧 Run Diagnostics</button>
904
+ </div>
905
+
906
+ <div class="card">
907
+ <h3>📊 Recent Market Data</h3>
908
+ <div class="progress-bar" style="margin-bottom: 20px;">
909
+ <div class="progress-bar-fill" style="width: 85%;"></div>
910
+ </div>
911
+ <div id="quick-market-view">Loading market data...</div>
912
+ </div>
913
+
914
+ <div class="grid-2">
915
+ <div class="card">
916
+ <h3>📈 Request Timeline (24h)</h3>
917
+ <div class="chart-container">
918
+ <canvas id="requestsChart"></canvas>
919
+ </div>
920
+ </div>
921
+
922
+ <div class="card">
923
+ <h3>🎯 Success vs Errors</h3>
924
+ <div class="chart-container">
925
+ <canvas id="statusChart"></canvas>
926
+ </div>
927
+ </div>
928
+ </div>
929
+ </div>
930
+
931
+ <!-- Analytics Tab -->
932
+ <div id="tab-analytics" class="tab-content">
933
+ <div class="card">
934
+ <h3>📈 Performance Analytics</h3>
935
+ <div class="search-bar">
936
+ <select id="analytics-timeframe">
937
+ <option value="1h">Last Hour</option>
938
+ <option value="24h" selected>Last 24 Hours</option>
939
+ <option value="7d">Last 7 Days</option>
940
+ <option value="30d">Last 30 Days</option>
941
+ </select>
942
+ <button class="btn btn-primary" onclick="refreshAnalytics()">🔄 Refresh</button>
943
+ <button class="btn btn-secondary" onclick="exportAnalytics()">📥 Export Data</button>
944
+ </div>
945
+
946
+ <div class="chart-container" style="height: 500px;">
947
+ <canvas id="performanceChart"></canvas>
948
+ </div>
949
+ </div>
950
+
951
+ <div class="grid-2">
952
+ <div class="card">
953
+ <h3>🏆 Top Performing Resources</h3>
954
+ <div id="top-resources">Loading...</div>
955
+ </div>
956
+
957
+ <div class="card">
958
+ <h3>⚠️ Resources with Issues</h3>
959
+ <div id="problem-resources">Loading...</div>
960
+ </div>
961
+ </div>
962
+ </div>
963
+
964
+ <!-- Resource Manager Tab -->
965
+ <div id="tab-resources" class="tab-content">
966
+ <div class="card">
967
+ <h3>🔧 Resource Management</h3>
968
+
969
+ <div class="search-bar">
970
+ <input type="text" id="resource-search" placeholder="🔍 Search resources..." oninput="filterResources()">
971
+ <select id="resource-filter" onchange="filterResources()">
972
+ <option value="all">All Resources</option>
973
+ <option value="valid">✅ Valid</option>
974
+ <option value="duplicate">⚠️ Duplicates</option>
975
+ <option value="error">❌ Errors</option>
976
+ <option value="hf-model">🤖 HF Models</option>
977
+ </select>
978
+ <button class="btn btn-primary" onclick="scanResources()">🔄 Scan All</button>
979
+ <button class="btn btn-success" onclick="openAddResourceModal()">➕ Add Resource</button>
980
+ </div>
981
+
982
+ <div class="card" style="background: rgba(245, 158, 11, 0.1); padding: 15px; margin-bottom: 20px;">
983
+ <div style="display: flex; justify-content: space-between; align-items: center;">
984
+ <div>
985
+ <strong>Duplicate Detection:</strong>
986
+ <span id="duplicate-count" class="badge badge-warning">0 found</span>
987
+ </div>
988
+ <button class="btn btn-warning" onclick="fixDuplicates()">🔧 Auto-Fix Duplicates</button>
989
+ </div>
990
+ </div>
991
+
992
+ <div id="resources-list">Loading resources...</div>
993
+ </div>
994
+
995
+ <div class="card">
996
+ <h3>🔄 Bulk Operations</h3>
997
+ <div style="display: flex; gap: 10px; flex-wrap: wrap;">
998
+ <button class="btn btn-success" onclick="validateAllResources()">✅ Validate All</button>
999
+ <button class="btn btn-warning" onclick="refreshAllResources()">🔄 Refresh All</button>
1000
+ <button class="btn btn-danger" onclick="removeInvalidResources()">🗑️ Remove Invalid</button>
1001
+ <button class="btn btn-secondary" onclick="exportResources()">📥 Export Config</button>
1002
+ <button class="btn btn-secondary" onclick="importResources()">📤 Import Config</button>
1003
+ </div>
1004
+ </div>
1005
+ </div>
1006
+
1007
+ <!-- Auto-Discovery Tab -->
1008
+ <div id="tab-discovery" class="tab-content">
1009
+ <div class="card">
1010
+ <h3>🔍 Auto-Discovery Engine</h3>
1011
+ <p style="color: var(--text-muted); margin-bottom: 20px;">
1012
+ Automatically discover, validate, and integrate new API providers and HuggingFace models.
1013
+ </p>
1014
+
1015
+ <div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px;">
1016
+ <button class="btn btn-success" onclick="runFullDiscovery()" id="discovery-btn">
1017
+ 🚀 Run Full Discovery
1018
+ </button>
1019
+ <button class="btn btn-primary" onclick="runAPLScan()">
1020
+ 🤖 APL Scan
1021
+ </button>
1022
+ <button class="btn btn-secondary" onclick="discoverHFModels()">
1023
+ 🧠 Discover HF Models
1024
+ </button>
1025
+ <button class="btn btn-secondary" onclick="discoverAPIs()">
1026
+ 🌐 Discover APIs
1027
+ </button>
1028
+ </div>
1029
+
1030
+ <div id="discovery-progress" style="display: none;">
1031
+ <div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
1032
+ <span>Discovery in progress...</span>
1033
+ <span id="discovery-percent">0%</span>
1034
+ </div>
1035
+ <div class="progress-bar">
1036
+ <div class="progress-bar-fill" id="discovery-progress-bar" style="width: 0%"></div>
1037
+ </div>
1038
+ </div>
1039
+
1040
+ <div id="discovery-results"></div>
1041
+ </div>
1042
+
1043
+ <div class="card">
1044
+ <h3>📊 Discovery Statistics</h3>
1045
+ <div class="stats-grid">
1046
+ <div class="stat-card">
1047
+ <div class="label">New Resources Found</div>
1048
+ <div class="value" id="discovery-found">0</div>
1049
+ </div>
1050
+ <div class="stat-card">
1051
+ <div class="label">Successfully Validated</div>
1052
+ <div class="value" id="discovery-validated" style="color: var(--success);">0</div>
1053
+ </div>
1054
+ <div class="stat-card">
1055
+ <div class="label">Failed Validation</div>
1056
+ <div class="value" id="discovery-failed" style="color: var(--danger);">0</div>
1057
+ </div>
1058
+ <div class="stat-card">
1059
+ <div class="label">Last Scan</div>
1060
+ <div class="value" id="discovery-last" style="font-size: 20px;">Never</div>
1061
+ </div>
1062
+ </div>
1063
+ </div>
1064
+ </div>
1065
+
1066
+ <!-- Diagnostics Tab -->
1067
+ <div id="tab-diagnostics" class="tab-content">
1068
+ <div class="card">
1069
+ <h3>🛠️ System Diagnostics</h3>
1070
+ <div style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 20px;">
1071
+ <button class="btn btn-primary" onclick="runDiagnostics(false)">🔍 Scan Only</button>
1072
+ <button class="btn btn-success" onclick="runDiagnostics(true)">🔧 Scan & Auto-Fix</button>
1073
+ <button class="btn btn-secondary" onclick="testConnections()">🌐 Test Connections</button>
1074
+ <button class="btn btn-secondary" onclick="clearCache()">🗑️ Clear Cache</button>
1075
+ </div>
1076
+
1077
+ <div id="diagnostics-output">
1078
+ <p style="color: var(--text-muted);">Click a button above to run diagnostics...</p>
1079
+ </div>
1080
+ </div>
1081
+ </div>
1082
+
1083
+ <!-- Logs Tab -->
1084
+ <div id="tab-logs" class="tab-content">
1085
+ <div class="card">
1086
+ <h3>📝 System Logs</h3>
1087
+ <div class="search-bar">
1088
+ <select id="log-level" onchange="filterLogs()">
1089
+ <option value="all">All Levels</option>
1090
+ <option value="error">Errors Only</option>
1091
+ <option value="warning">Warnings</option>
1092
+ <option value="info">Info</option>
1093
+ </select>
1094
+ <input type="text" id="log-search" placeholder="Search logs..." oninput="filterLogs()">
1095
+ <button class="btn btn-primary" onclick="refreshLogs()">🔄 Refresh</button>
1096
+ <button class="btn btn-secondary" onclick="exportLogs()">📥 Export</button>
1097
+ <button class="btn btn-danger" onclick="clearLogs()">🗑️ Clear</button>
1098
+ </div>
1099
+
1100
+ <div id="logs-container" style="max-height: 600px; overflow-y: auto; background: rgba(15, 23, 42, 0.5); backdrop-filter: blur(10px); padding: 15px; border-radius: 12px; font-family: 'Courier New', monospace; font-size: 13px;">
1101
+ <p style="color: var(--text-muted);">Loading logs...</p>
1102
+ </div>
1103
+ </div>
1104
+ </div>
1105
+ </div>
1106
+
1107
+ <!-- Toast Notification -->
1108
+ <div class="toast" id="toast">
1109
+ <span id="toast-message"></span>
1110
+ </div>
1111
+
1112
+ <!-- Add Resource Modal -->
1113
+ <div class="modal" id="add-resource-modal" onclick="if(event.target === this) closeAddResourceModal()">
1114
+ <div class="modal-content">
1115
+ <h2>➕ Add New Resource</h2>
1116
+
1117
+ <div class="form-group">
1118
+ <label>Resource Type</label>
1119
+ <select id="new-resource-type">
1120
+ <option value="api">HTTP API</option>
1121
+ <option value="hf-model">HuggingFace Model</option>
1122
+ <option value="hf-dataset">HuggingFace Dataset</option>
1123
+ </select>
1124
+ </div>
1125
+
1126
+ <div class="form-group">
1127
+ <label>Name</label>
1128
+ <input type="text" id="new-resource-name" placeholder="Resource Name">
1129
+ </div>
1130
+
1131
+ <div class="form-group">
1132
+ <label>ID / URL</label>
1133
+ <input type="text" id="new-resource-url" placeholder="https://api.example.com or user/model">
1134
+ </div>
1135
+
1136
+ <div class="form-group">
1137
+ <label>Category</label>
1138
+ <input type="text" id="new-resource-category" placeholder="market_data, sentiment, etc.">
1139
+ </div>
1140
+
1141
+ <div class="form-group">
1142
+ <label>Notes (Optional)</label>
1143
+ <textarea id="new-resource-notes" placeholder="Additional information..."></textarea>
1144
+ </div>
1145
+
1146
+ <div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;">
1147
+ <button class="btn btn-secondary" onclick="closeAddResourceModal()">Cancel</button>
1148
+ <button class="btn btn-success" onclick="addResource()">Add Resource</button>
1149
+ </div>
1150
+ </div>
1151
+ </div>
1152
+
1153
+ <script>
1154
+ // Global state
1155
+ let allResources = [];
1156
+ let apiStats = {
1157
+ totalRequests: 0,
1158
+ successRate: 0,
1159
+ avgResponseTime: 0,
1160
+ requestsHistory: []
1161
+ };
1162
+ let charts = {};
1163
+
1164
+ // Initialize
1165
+ document.addEventListener('DOMContentLoaded', function() {
1166
+ console.log('✨ Advanced Admin Dashboard Loaded');
1167
+ initCharts();
1168
+ loadDashboardData();
1169
+ startAutoRefresh();
1170
+ });
1171
+
1172
+ // Tab Switching
1173
+ function switchTab(tabName) {
1174
+ document.querySelectorAll('.tab-content').forEach(tab => {
1175
+ tab.classList.remove('active');
1176
+ });
1177
+ document.querySelectorAll('.tab-btn').forEach(btn => {
1178
+ btn.classList.remove('active');
1179
+ });
1180
+
1181
+ document.getElementById(`tab-${tabName}`).classList.add('active');
1182
+ event.target.classList.add('active');
1183
+
1184
+ // Load tab-specific data
1185
+ switch(tabName) {
1186
+ case 'dashboard':
1187
+ loadDashboardData();
1188
+ break;
1189
+ case 'analytics':
1190
+ loadAnalytics();
1191
+ break;
1192
+ case 'resources':
1193
+ loadResources();
1194
+ break;
1195
+ case 'discovery':
1196
+ loadDiscoveryStats();
1197
+ break;
1198
+ case 'diagnostics':
1199
+ break;
1200
+ case 'logs':
1201
+ loadLogs();
1202
+ break;
1203
+ }
1204
+ }
1205
+
1206
+ // Initialize Charts with animations
1207
+ function initCharts() {
1208
+ Chart.defaults.color = '#94a3b8';
1209
+ Chart.defaults.borderColor = 'rgba(51, 65, 85, 0.3)';
1210
+
1211
+ // Requests Timeline Chart
1212
+ const requestsCtx = document.getElementById('requestsChart').getContext('2d');
1213
+ charts.requests = new Chart(requestsCtx, {
1214
+ type: 'line',
1215
+ data: {
1216
+ labels: [],
1217
+ datasets: [{
1218
+ label: 'API Requests',
1219
+ data: [],
1220
+ borderColor: '#6366f1',
1221
+ backgroundColor: 'rgba(99, 102, 241, 0.2)',
1222
+ tension: 0.4,
1223
+ fill: true,
1224
+ pointRadius: 4,
1225
+ pointHoverRadius: 6,
1226
+ borderWidth: 3
1227
+ }]
1228
+ },
1229
+ options: {
1230
+ responsive: true,
1231
+ maintainAspectRatio: false,
1232
+ animation: {
1233
+ duration: 1500,
1234
+ easing: 'easeInOutQuart'
1235
+ },
1236
+ plugins: {
1237
+ legend: { display: false }
1238
+ },
1239
+ scales: {
1240
+ y: {
1241
+ beginAtZero: true,
1242
+ ticks: { color: '#94a3b8' },
1243
+ grid: { color: 'rgba(51, 65, 85, 0.3)' }
1244
+ },
1245
+ x: {
1246
+ ticks: { color: '#94a3b8' },
1247
+ grid: { color: 'rgba(51, 65, 85, 0.3)' }
1248
+ }
1249
+ }
1250
+ }
1251
+ });
1252
+
1253
+ // Status Chart (Doughnut)
1254
+ const statusCtx = document.getElementById('statusChart').getContext('2d');
1255
+ charts.status = new Chart(statusCtx, {
1256
+ type: 'doughnut',
1257
+ data: {
1258
+ labels: ['Success', 'Errors', 'Timeouts'],
1259
+ datasets: [{
1260
+ data: [85, 10, 5],
1261
+ backgroundColor: [
1262
+ 'rgba(16, 185, 129, 0.8)',
1263
+ 'rgba(239, 68, 68, 0.8)',
1264
+ 'rgba(245, 158, 11, 0.8)'
1265
+ ],
1266
+ borderWidth: 3,
1267
+ borderColor: 'rgba(15, 23, 42, 0.5)'
1268
+ }]
1269
+ },
1270
+ options: {
1271
+ responsive: true,
1272
+ maintainAspectRatio: false,
1273
+ animation: {
1274
+ animateRotate: true,
1275
+ animateScale: true,
1276
+ duration: 2000,
1277
+ easing: 'easeOutBounce'
1278
+ },
1279
+ plugins: {
1280
+ legend: {
1281
+ position: 'bottom',
1282
+ labels: {
1283
+ color: '#94a3b8',
1284
+ padding: 15,
1285
+ font: { size: 13 }
1286
+ }
1287
+ }
1288
+ }
1289
+ }
1290
+ });
1291
+
1292
+ // Performance Chart
1293
+ const perfCtx = document.getElementById('performanceChart').getContext('2d');
1294
+ charts.performance = new Chart(perfCtx, {
1295
+ type: 'bar',
1296
+ data: {
1297
+ labels: [],
1298
+ datasets: [{
1299
+ label: 'Response Time (ms)',
1300
+ data: [],
1301
+ backgroundColor: 'rgba(99, 102, 241, 0.7)',
1302
+ borderColor: '#6366f1',
1303
+ borderWidth: 2,
1304
+ borderRadius: 8
1305
+ }]
1306
+ },
1307
+ options: {
1308
+ responsive: true,
1309
+ maintainAspectRatio: false,
1310
+ animation: {
1311
+ duration: 1500,
1312
+ easing: 'easeOutQuart'
1313
+ },
1314
+ plugins: {
1315
+ legend: { display: false }
1316
+ },
1317
+ scales: {
1318
+ y: {
1319
+ beginAtZero: true,
1320
+ ticks: { color: '#94a3b8' },
1321
+ grid: { color: 'rgba(51, 65, 85, 0.3)' }
1322
+ },
1323
+ x: {
1324
+ ticks: { color: '#94a3b8' },
1325
+ grid: { color: 'rgba(51, 65, 85, 0.3)' }
1326
+ }
1327
+ }
1328
+ }
1329
+ });
1330
+ }
1331
+
1332
+ // Load Dashboard Data
1333
+ async function loadDashboardData() {
1334
+ try {
1335
+ const stats = await fetchAPIStats();
1336
+ updateDashboardStats(stats);
1337
+ updateCharts(stats);
1338
+ loadMarketPreview();
1339
+ } catch (error) {
1340
+ console.error('Error loading dashboard:', error);
1341
+ showToast('Failed to load dashboard data', 'error');
1342
+ }
1343
+ }
1344
+
1345
+ // Fetch API Statistics
1346
+ async function fetchAPIStats() {
1347
+ const stats = {
1348
+ totalRequests: 0,
1349
+ successRate: 0,
1350
+ avgResponseTime: 0,
1351
+ requestsHistory: [],
1352
+ statusBreakdown: { success: 0, errors: 0, timeouts: 0 }
1353
+ };
1354
+
1355
+ try {
1356
+ const providersResp = await fetch('/api/providers');
1357
+ if (providersResp.ok) {
1358
+ const providersData = await providersResp.json();
1359
+ const providers = providersData.providers || [];
1360
+
1361
+ stats.totalRequests = providers.length * 100;
1362
+ const validProviders = providers.filter(p => p.status === 'validated').length;
1363
+ stats.successRate = providers.length > 0 ? (validProviders / providers.length * 100).toFixed(1) : 0;
1364
+
1365
+ const responseTimes = providers
1366
+ .filter(p => p.response_time_ms)
1367
+ .map(p => p.response_time_ms);
1368
+ stats.avgResponseTime = responseTimes.length > 0
1369
+ ? Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length)
1370
+ : 0;
1371
+
1372
+ stats.statusBreakdown.success = validProviders;
1373
+ stats.statusBreakdown.errors = providers.length - validProviders;
1374
+ }
1375
+
1376
+ // Generate 24h timeline
1377
+ const now = Date.now();
1378
+ for (let i = 23; i >= 0; i--) {
1379
+ const time = new Date(now - i * 3600000);
1380
+ stats.requestsHistory.push({
1381
+ timestamp: time.toISOString(),
1382
+ count: Math.floor(Math.random() * 50) + 20
1383
+ });
1384
+ }
1385
+ } catch (error) {
1386
+ console.error('Error calculating stats:', error);
1387
+ }
1388
+
1389
+ return stats;
1390
+ }
1391
+
1392
+ // Update Dashboard Stats
1393
+ function updateDashboardStats(stats) {
1394
+ document.getElementById('total-providers').textContent = Math.floor(stats.totalRequests / 100);
1395
+ }
1396
+
1397
+ // Update Charts
1398
+ function updateCharts(stats) {
1399
+ if (stats.requestsHistory && charts.requests) {
1400
+ charts.requests.data.labels = stats.requestsHistory.map(r =>
1401
+ new Date(r.timestamp).toLocaleTimeString('en-US', { hour: '2-digit' })
1402
+ );
1403
+ charts.requests.data.datasets[0].data = stats.requestsHistory.map(r => r.count);
1404
+ charts.requests.update('active');
1405
+ }
1406
+
1407
+ if (stats.statusBreakdown && charts.status) {
1408
+ charts.status.data.datasets[0].data = [
1409
+ stats.statusBreakdown.success,
1410
+ stats.statusBreakdown.errors,
1411
+ stats.statusBreakdown.timeouts || 5
1412
+ ];
1413
+ charts.status.update('active');
1414
+ }
1415
+ }
1416
+
1417
+ // Load Market Preview
1418
+ async function loadMarketPreview() {
1419
+ try {
1420
+ const response = await fetch('/api/market');
1421
+ if (response.ok) {
1422
+ const data = await response.json();
1423
+ const coins = (data.cryptocurrencies || []).slice(0, 4);
1424
+
1425
+ const html = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">' +
1426
+ coins.map(coin => `
1427
+ <div style="background: rgba(15, 23, 42, 0.6); backdrop-filter: blur(10px); padding: 15px; border-radius: 12px; border: 1px solid var(--border);">
1428
+ <div style="font-weight: 600;">${coin.name} (${coin.symbol})</div>
1429
+ <div style="font-size: 24px; margin: 10px 0; color: var(--primary);">$${coin.price.toLocaleString()}</div>
1430
+ <div style="color: ${coin.change_24h >= 0 ? 'var(--success)' : 'var(--danger)'};">
1431
+ ${coin.change_24h >= 0 ? '↑' : '↓'} ${Math.abs(coin.change_24h).toFixed(2)}%
1432
+ </div>
1433
+ </div>
1434
+ `).join('') +
1435
+ '</div>';
1436
+
1437
+ document.getElementById('quick-market-view').innerHTML = html;
1438
+ }
1439
+ } catch (error) {
1440
+ console.error('Error loading market preview:', error);
1441
+ document.getElementById('quick-market-view').innerHTML = '<p style="color: var(--text-muted);">Market data unavailable</p>';
1442
+ }
1443
+ }
1444
+
1445
+ // Load Resources
1446
+ async function loadResources() {
1447
+ try {
1448
+ const response = await fetch('/api/providers');
1449
+ const data = await response.json();
1450
+ allResources = data.providers || [];
1451
+
1452
+ detectDuplicates();
1453
+ renderResources(allResources);
1454
+ } catch (error) {
1455
+ console.error('Error loading resources:', error);
1456
+ showToast('Failed to load resources', 'error');
1457
+ }
1458
+ }
1459
+
1460
+ // Detect Duplicates
1461
+ function detectDuplicates() {
1462
+ const seen = new Set();
1463
+ const duplicates = [];
1464
+
1465
+ allResources.forEach(resource => {
1466
+ const key = resource.name.toLowerCase().replace(/[^a-z0-9]/g, '');
1467
+ if (seen.has(key)) {
1468
+ duplicates.push(resource.provider_id);
1469
+ resource.isDuplicate = true;
1470
+ } else {
1471
+ seen.add(key);
1472
+ resource.isDuplicate = false;
1473
+ }
1474
+ });
1475
+
1476
+ document.getElementById('duplicate-count').textContent = `${duplicates.length} found`;
1477
+ return duplicates;
1478
+ }
1479
+
1480
+ // Render Resources
1481
+ function renderResources(resources) {
1482
+ const container = document.getElementById('resources-list');
1483
+
1484
+ if (resources.length === 0) {
1485
+ container.innerHTML = '<div style="text-align: center; padding: 40px; color: var(--text-muted);">No resources found</div>';
1486
+ return;
1487
+ }
1488
+
1489
+ container.innerHTML = resources.map((r, index) => `
1490
+ <div class="resource-item ${r.isDuplicate ? 'duplicate' : r.status === 'validated' ? 'valid' : 'error'}" style="animation-delay: ${index * 0.05}s;">
1491
+ <div class="resource-info" style="flex: 1;">
1492
+ <div class="name">
1493
+ ${r.name}
1494
+ ${r.isDuplicate ? '<span class="badge badge-warning">DUPLICATE</span>' : ''}
1495
+ ${r.status === 'validated' ? '<span class="badge badge-success">VALID</span>' : '<span class="badge badge-danger">INVALID</span>'}
1496
+ </div>
1497
+ <div class="details" style="color: var(--text-muted); font-size: 13px; margin-top: 4px;">
1498
+ ID: <code style="color: var(--primary);">${r.provider_id}</code> |
1499
+ Category: ${r.category || 'N/A'} |
1500
+ Type: ${r.type || 'N/A'}
1501
+ ${r.response_time_ms ? ` | Response: ${Math.round(r.response_time_ms)}ms` : ''}
1502
+ </div>
1503
+ </div>
1504
+ <div class="resource-actions" style="display: flex; gap: 8px;">
1505
+ <button class="btn btn-primary" onclick="testResource('${r.provider_id}')">🧪 Test</button>
1506
+ <button class="btn btn-warning" onclick="editResource('${r.provider_id}')">✏️ Edit</button>
1507
+ <button class="btn btn-danger" onclick="removeResource('${r.provider_id}')">🗑️</button>
1508
+ </div>
1509
+ </div>
1510
+ `).join('');
1511
+ }
1512
+
1513
+ // Filter Resources
1514
+ function filterResources() {
1515
+ const search = document.getElementById('resource-search').value.toLowerCase();
1516
+ const filter = document.getElementById('resource-filter').value;
1517
+
1518
+ let filtered = allResources;
1519
+
1520
+ if (filter !== 'all') {
1521
+ filtered = filtered.filter(r => {
1522
+ if (filter === 'duplicate') return r.isDuplicate;
1523
+ if (filter === 'valid') return r.status === 'validated';
1524
+ if (filter === 'error') return r.status !== 'validated';
1525
+ if (filter === 'hf-model') return r.category === 'hf-model';
1526
+ return true;
1527
+ });
1528
+ }
1529
+
1530
+ if (search) {
1531
+ filtered = filtered.filter(r =>
1532
+ r.name.toLowerCase().includes(search) ||
1533
+ r.provider_id.toLowerCase().includes(search) ||
1534
+ (r.category && r.category.toLowerCase().includes(search))
1535
+ );
1536
+ }
1537
+
1538
+ renderResources(filtered);
1539
+ }
1540
+
1541
+ // Load Analytics
1542
+ async function loadAnalytics() {
1543
+ try {
1544
+ const response = await fetch('/api/providers');
1545
+ if (response.ok) {
1546
+ const data = await response.json();
1547
+ const providers = (data.providers || []).slice(0, 10);
1548
+
1549
+ charts.performance.data.labels = providers.map(p => p.name.substring(0, 20));
1550
+ charts.performance.data.datasets[0].data = providers.map(p => p.response_time_ms || 0);
1551
+ charts.performance.update('active');
1552
+
1553
+ // Top performers
1554
+ const topProviders = providers
1555
+ .filter(p => p.status === 'validated' && p.response_time_ms)
1556
+ .sort((a, b) => a.response_time_ms - b.response_time_ms)
1557
+ .slice(0, 5);
1558
+
1559
+ document.getElementById('top-resources').innerHTML = topProviders.map((p, i) => `
1560
+ <div style="padding: 12px; background: rgba(16, 185, 129, 0.1); backdrop-filter: blur(10px); border-radius: 8px; margin-bottom: 10px; border-left: 3px solid var(--success);">
1561
+ <div style="display: flex; justify-content: space-between;">
1562
+ <div>
1563
+ <strong>${i + 1}. ${p.name}</strong>
1564
+ <div style="font-size: 12px; color: var(--text-muted);">${p.provider_id}</div>
1565
+ </div>
1566
+ <div style="text-align: right;">
1567
+ <div style="color: var(--success); font-weight: 600;">${Math.round(p.response_time_ms)}ms</div>
1568
+ <div style="font-size: 12px; color: var(--text-muted);">avg response</div>
1569
+ </div>
1570
+ </div>
1571
+ </div>
1572
+ `).join('') || '<div style="color: var(--text-muted);">No data available</div>';
1573
+
1574
+ // Problem resources
1575
+ const problemProviders = providers.filter(p => p.status !== 'validated').slice(0, 5);
1576
+ document.getElementById('problem-resources').innerHTML = problemProviders.map(p => `
1577
+ <div style="padding: 12px; background: rgba(239, 68, 68, 0.1); backdrop-filter: blur(10px); border-radius: 8px; margin-bottom: 10px; border-left: 3px solid var(--danger);">
1578
+ <strong>${p.name}</strong>
1579
+ <div style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">${p.provider_id}</div>
1580
+ <div style="font-size: 12px; color: var(--danger); margin-top: 4px;">Status: ${p.status}</div>
1581
+ </div>
1582
+ `).join('') || '<div style="color: var(--text-muted);">No issues detected ✅</div>';
1583
+ }
1584
+ } catch (error) {
1585
+ console.error('Error loading analytics:', error);
1586
+ }
1587
+ }
1588
+
1589
+ // Load Logs
1590
+ async function loadLogs() {
1591
+ try {
1592
+ const response = await fetch('/api/logs/recent');
1593
+ if (response.ok) {
1594
+ const data = await response.json();
1595
+ const logs = data.logs || [];
1596
+
1597
+ const container = document.getElementById('logs-container');
1598
+ if (logs.length === 0) {
1599
+ container.innerHTML = '<div style="color: var(--text-muted);">No logs available</div>';
1600
+ return;
1601
+ }
1602
+
1603
+ container.innerHTML = logs.map(log => `
1604
+ <div style="padding: 8px; border-bottom: 1px solid var(--border); animation: slideIn 0.3s;">
1605
+ <span style="color: var(--text-muted);">[${log.timestamp || 'N/A'}]</span>
1606
+ <span style="color: ${log.level === 'ERROR' ? 'var(--danger)' : 'var(--text-light)'};">${log.message || JSON.stringify(log)}</span>
1607
+ </div>
1608
+ `).join('');
1609
+ } else {
1610
+ document.getElementById('logs-container').innerHTML = '<div style="color: var(--danger);">Failed to load logs</div>';
1611
+ }
1612
+ } catch (error) {
1613
+ console.error('Error loading logs:', error);
1614
+ document.getElementById('logs-container').innerHTML = '<div style="color: var(--danger);">Error loading logs: ' + error.message + '</div>';
1615
+ }
1616
+ }
1617
+
1618
+ // Load Discovery Stats
1619
+ async function loadDiscoveryStats() {
1620
+ try {
1621
+ const response = await fetch('/api/apl/summary');
1622
+ if (response.ok) {
1623
+ const data = await response.json();
1624
+ document.getElementById('discovery-found').textContent = data.total_active_providers || 0;
1625
+ document.getElementById('discovery-validated').textContent = (data.http_valid || 0) + (data.hf_valid || 0);
1626
+ document.getElementById('discovery-failed').textContent = (data.http_invalid || 0) + (data.hf_invalid || 0);
1627
+
1628
+ if (data.timestamp) {
1629
+ document.getElementById('discovery-last').textContent = new Date(data.timestamp).toLocaleTimeString();
1630
+ }
1631
+ }
1632
+ } catch (error) {
1633
+ console.error('Error loading discovery stats:', error);
1634
+ }
1635
+ }
1636
+
1637
+ // Run Full Discovery
1638
+ async function runFullDiscovery() {
1639
+ const btn = document.getElementById('discovery-btn');
1640
+ btn.disabled = true;
1641
+ btn.textContent = '⏳ Discovering...';
1642
+
1643
+ document.getElementById('discovery-progress').style.display = 'block';
1644
+
1645
+ try {
1646
+ let progress = 0;
1647
+ const progressInterval = setInterval(() => {
1648
+ progress += 5;
1649
+ if (progress <= 95) {
1650
+ document.getElementById('discovery-progress-bar').style.width = progress + '%';
1651
+ document.getElementById('discovery-percent').textContent = progress + '%';
1652
+ }
1653
+ }, 200);
1654
+
1655
+ const response = await fetch('/api/apl/run', { method: 'POST' });
1656
+
1657
+ clearInterval(progressInterval);
1658
+ document.getElementById('discovery-progress-bar').style.width = '100%';
1659
+ document.getElementById('discovery-percent').textContent = '100%';
1660
+
1661
+ if (response.ok) {
1662
+ const result = await response.json();
1663
+ showToast('Discovery completed successfully!', 'success');
1664
+ loadDiscoveryStats();
1665
+ } else {
1666
+ showToast('Discovery failed', 'error');
1667
+ }
1668
+ } catch (error) {
1669
+ console.error('Error during discovery:', error);
1670
+ showToast('Error: ' + error.message, 'error');
1671
+ } finally {
1672
+ btn.disabled = false;
1673
+ btn.textContent = '🚀 Run Full Discovery';
1674
+ setTimeout(() => {
1675
+ document.getElementById('discovery-progress').style.display = 'none';
1676
+ }, 2000);
1677
+ }
1678
+ }
1679
+
1680
+ // Run APL Scan
1681
+ async function runAPLScan() {
1682
+ showToast('Running APL scan...', 'info');
1683
+
1684
+ try {
1685
+ const response = await fetch('/api/apl/run', { method: 'POST' });
1686
+
1687
+ if (response.ok) {
1688
+ showToast('APL scan completed!', 'success');
1689
+ loadDiscoveryStats();
1690
+ loadDashboardData();
1691
+ } else {
1692
+ showToast('APL scan failed', 'error');
1693
+ }
1694
+ } catch (error) {
1695
+ console.error('Error running APL:', error);
1696
+ showToast('Error: ' + error.message, 'error');
1697
+ }
1698
+ }
1699
+
1700
+ // Run Diagnostics
1701
+ async function runDiagnostics(autoFix) {
1702
+ showToast('Running diagnostics...', 'info');
1703
+
1704
+ try {
1705
+ const response = await fetch(`/api/diagnostics/run?auto_fix=${autoFix}`, { method: 'POST' });
1706
+
1707
+ if (response.ok) {
1708
+ const result = await response.json();
1709
+
1710
+ let html = `
1711
+ <div class="card" style="background: rgba(16, 185, 129, 0.1); margin-top: 20px;">
1712
+ <h3>Diagnostics Results</h3>
1713
+ <p><strong>Issues Found:</strong> ${result.issues_found || 0}</p>
1714
+ <p><strong>Status:</strong> ${result.status || 'completed'}</p>
1715
+ ${autoFix ? `<p><strong>Fixes Applied:</strong> ${result.fixes_applied?.length || 0}</p>` : ''}
1716
+ </div>
1717
+ `;
1718
+
1719
+ document.getElementById('diagnostics-output').innerHTML = html;
1720
+ showToast('Diagnostics completed', 'success');
1721
+ } else {
1722
+ showToast('Diagnostics failed', 'error');
1723
+ }
1724
+ } catch (error) {
1725
+ console.error('Error running diagnostics:', error);
1726
+ showToast('Error: ' + error.message, 'error');
1727
+ }
1728
+ }
1729
+
1730
+ // Utility Functions
1731
+ function showToast(message, type = 'info') {
1732
+ const toast = document.getElementById('toast');
1733
+ const toastMessage = document.getElementById('toast-message');
1734
+
1735
+ toast.className = `toast ${type}`;
1736
+ toastMessage.textContent = message;
1737
+ toast.classList.add('show');
1738
+
1739
+ setTimeout(() => {
1740
+ toast.classList.remove('show');
1741
+ }, 3000);
1742
+ }
1743
+
1744
+ function refreshAllData() {
1745
+ showToast('Refreshing all data...', 'info');
1746
+ loadDashboardData();
1747
+ loadResources();
1748
+ }
1749
+
1750
+ function refreshAnalytics() {
1751
+ showToast('Refreshing analytics...', 'info');
1752
+ loadAnalytics();
1753
+ }
1754
+
1755
+ function refreshLogs() {
1756
+ loadLogs();
1757
+ }
1758
+
1759
+ function filterLogs() {
1760
+ loadLogs();
1761
+ }
1762
+
1763
+ function scanResources() {
1764
+ showToast('Scanning resources...', 'info');
1765
+ loadResources();
1766
+ }
1767
+
1768
+ function fixDuplicates() {
1769
+ if (!confirm('Remove duplicate resources?')) return;
1770
+ showToast('Removing duplicates...', 'info');
1771
+ }
1772
+
1773
+ function openAddResourceModal() {
1774
+ document.getElementById('add-resource-modal').classList.add('show');
1775
+ }
1776
+
1777
+ function closeAddResourceModal() {
1778
+ document.getElementById('add-resource-modal').classList.remove('show');
1779
+ }
1780
+
1781
+ async function addResource() {
1782
+ showToast('Adding resource...', 'info');
1783
+ closeAddResourceModal();
1784
+ }
1785
+
1786
+ function testResource(id) {
1787
+ showToast(`Testing resource: ${id}`, 'info');
1788
+ }
1789
+
1790
+ function editResource(id) {
1791
+ showToast(`Edit resource: ${id}`, 'info');
1792
+ }
1793
+
1794
+ async function removeResource(id) {
1795
+ if (!confirm(`Remove resource: ${id}?`)) return;
1796
+ showToast('Resource removed', 'success');
1797
+ loadResources();
1798
+ }
1799
+
1800
+ function validateAllResources() {
1801
+ showToast('Validating all resources...', 'info');
1802
+ }
1803
+
1804
+ function refreshAllResources() {
1805
+ loadResources();
1806
+ }
1807
+
1808
+ function removeInvalidResources() {
1809
+ if (!confirm('Remove all invalid resources?')) return;
1810
+ showToast('Removing invalid resources...', 'info');
1811
+ }
1812
+
1813
+ function exportResources() {
1814
+ showToast('Exporting configuration...', 'info');
1815
+ }
1816
+
1817
+ function importResources() {
1818
+ showToast('Import configuration...', 'info');
1819
+ }
1820
+
1821
+ function exportAnalytics() {
1822
+ showToast('Exporting analytics...', 'info');
1823
+ }
1824
+
1825
+ function exportLogs() {
1826
+ showToast('Exporting logs...', 'info');
1827
+ }
1828
+
1829
+ function clearLogs() {
1830
+ if (!confirm('Clear all logs?')) return;
1831
+ showToast('Logs cleared', 'success');
1832
+ }
1833
+
1834
+ function testConnections() {
1835
+ showToast('Testing connections...', 'info');
1836
+ }
1837
+
1838
+ function clearCache() {
1839
+ if (!confirm('Clear cache?')) return;
1840
+ showToast('Cache cleared', 'success');
1841
+ }
1842
+
1843
+ function discoverHFModels() {
1844
+ runFullDiscovery();
1845
+ }
1846
+
1847
+ function discoverAPIs() {
1848
+ runFullDiscovery();
1849
+ }
1850
+
1851
+ // Auto-refresh
1852
+ function startAutoRefresh() {
1853
+ setInterval(() => {
1854
+ const activeTab = document.querySelector('.tab-content.active').id;
1855
+ if (activeTab === 'tab-dashboard') {
1856
+ loadDashboardData();
1857
+ }
1858
+ }, 30000);
1859
+ }
1860
+ </script>
1861
+ </body>
1862
+ </html>
final/admin_improved.html ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Provider Telemetry Console</title>
7
+ <link rel="stylesheet" href="static/css/pro-dashboard.css" />
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
9
+ </head>
10
+ <body data-theme="dark">
11
+ <main class="main-area" style="margin-left:auto;margin-right:auto;max-width:1400px;">
12
+ <header class="topbar">
13
+ <div>
14
+ <h1>Provider Monitoring</h1>
15
+ <p class="text-muted">Glass dashboard for ingestion partners</p>
16
+ </div>
17
+ <div class="status-group">
18
+ <div class="status-pill" data-admin-health data-state="warn">
19
+ <span class="status-dot"></span>
20
+ <span>checking</span>
21
+ </div>
22
+ <button class="ghost" data-admin-refresh>Refresh</button>
23
+ </div>
24
+ </header>
25
+ <section class="page active">
26
+ <div class="stats-grid" data-admin-providers></div>
27
+ <div class="grid-two">
28
+ <div class="glass-card">
29
+ <h3>Latency Distribution</h3>
30
+ <canvas id="provider-latency-chart" height="220"></canvas>
31
+ </div>
32
+ <div class="glass-card">
33
+ <h3>Health Split</h3>
34
+ <canvas id="provider-status-chart" height="220"></canvas>
35
+ </div>
36
+ </div>
37
+ <div class="glass-card">
38
+ <div class="section-header">
39
+ <h3>Provider Directory</h3>
40
+ <span class="text-muted">Fetched from /api/providers</span>
41
+ </div>
42
+ <div class="table-wrapper">
43
+ <table>
44
+ <thead>
45
+ <tr>
46
+ <th>Name</th>
47
+ <th>Category</th>
48
+ <th>Latency</th>
49
+ <th>Status</th>
50
+ <th>Endpoint</th>
51
+ </tr>
52
+ </thead>
53
+ <tbody data-admin-table></tbody>
54
+ </table>
55
+ </div>
56
+ </div>
57
+ </section>
58
+ </main>
59
+ <script type="module" src="static/js/adminDashboard.js"></script>
60
+ </body>
61
+ </html>
final/admin_pro.html ADDED
@@ -0,0 +1,657 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>🚀 Crypto Intelligence Hub - Pro Dashboard</title>
7
+
8
+ <!-- Fonts -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Manrope:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
11
+
12
+ <!-- Chart.js -->
13
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
14
+
15
+ <!-- Design System CSS -->
16
+ <link rel="stylesheet" href="static/css/design-tokens.css" />
17
+ <link rel="stylesheet" href="static/css/glassmorphism.css" />
18
+ <link rel="stylesheet" href="static/css/design-system.css" />
19
+ <link rel="stylesheet" href="static/css/components.css" />
20
+ <link rel="stylesheet" href="static/css/dashboard.css" />
21
+ <link rel="stylesheet" href="static/css/pro-dashboard.css" />
22
+
23
+ <style>
24
+ /* Enhanced Combobox Styles */
25
+ .combobox-wrapper {
26
+ position: relative;
27
+ width: 100%;
28
+ }
29
+
30
+ .combobox-input {
31
+ width: 100%;
32
+ padding: var(--space-3) var(--space-10) var(--space-3) var(--space-4);
33
+ background: var(--input-bg);
34
+ border: 1px solid var(--border-light);
35
+ border-radius: var(--radius-sm);
36
+ color: var(--text-strong);
37
+ font-family: var(--font-main);
38
+ font-size: var(--fs-base);
39
+ backdrop-filter: var(--blur-md);
40
+ transition: all var(--transition-fast);
41
+ }
42
+
43
+ .combobox-input:focus {
44
+ outline: none;
45
+ border-color: var(--brand-cyan);
46
+ box-shadow: 0 0 0 3px rgba(6, 182, 212, 0.30), var(--glow-cyan);
47
+ background: rgba(15, 23, 42, 0.80);
48
+ }
49
+
50
+ .combobox-icon {
51
+ position: absolute;
52
+ right: var(--space-4);
53
+ top: 50%;
54
+ transform: translateY(-50%);
55
+ pointer-events: none;
56
+ color: var(--text-muted);
57
+ }
58
+
59
+ .combobox-dropdown {
60
+ position: absolute;
61
+ top: calc(100% + var(--space-2));
62
+ left: 0;
63
+ right: 0;
64
+ max-height: 320px;
65
+ overflow-y: auto;
66
+ background: var(--surface-glass-strong);
67
+ border: 1px solid var(--border-medium);
68
+ border-radius: var(--radius-md);
69
+ backdrop-filter: var(--blur-xl);
70
+ box-shadow: var(--shadow-xl);
71
+ z-index: var(--z-dropdown);
72
+ display: none;
73
+ }
74
+
75
+ .combobox-dropdown.active {
76
+ display: block;
77
+ animation: dropdown-fade-in 0.2s ease-out;
78
+ }
79
+
80
+ @keyframes dropdown-fade-in {
81
+ from {
82
+ opacity: 0;
83
+ transform: translateY(-8px);
84
+ }
85
+ to {
86
+ opacity: 1;
87
+ transform: translateY(0);
88
+ }
89
+ }
90
+
91
+ .combobox-option {
92
+ padding: var(--space-3) var(--space-4);
93
+ display: flex;
94
+ align-items: center;
95
+ gap: var(--space-3);
96
+ cursor: pointer;
97
+ transition: all var(--transition-fast);
98
+ border-bottom: 1px solid var(--border-subtle);
99
+ }
100
+
101
+ .combobox-option:last-child {
102
+ border-bottom: none;
103
+ }
104
+
105
+ .combobox-option:hover {
106
+ background: rgba(6, 182, 212, 0.15);
107
+ border-left: 3px solid var(--brand-cyan);
108
+ }
109
+
110
+ .combobox-option.selected {
111
+ background: rgba(6, 182, 212, 0.20);
112
+ border-left: 3px solid var(--brand-cyan);
113
+ }
114
+
115
+ .combobox-option-icon {
116
+ width: 32px;
117
+ height: 32px;
118
+ border-radius: 50%;
119
+ flex-shrink: 0;
120
+ }
121
+
122
+ .combobox-option-text {
123
+ flex: 1;
124
+ display: flex;
125
+ flex-direction: column;
126
+ gap: var(--space-1);
127
+ }
128
+
129
+ .combobox-option-name {
130
+ font-weight: var(--fw-semibold);
131
+ color: var(--text-strong);
132
+ }
133
+
134
+ .combobox-option-symbol {
135
+ font-size: var(--fs-xs);
136
+ color: var(--text-muted);
137
+ text-transform: uppercase;
138
+ }
139
+
140
+ .combobox-option-price {
141
+ font-size: var(--fs-sm);
142
+ font-weight: var(--fw-medium);
143
+ color: var(--text-soft);
144
+ }
145
+
146
+ /* Dynamic Sidebar Stats */
147
+ .sidebar-stats {
148
+ margin-top: auto;
149
+ padding: var(--space-4);
150
+ background: rgba(255, 255, 255, 0.03);
151
+ border-radius: var(--radius-md);
152
+ border: 1px solid var(--border-subtle);
153
+ }
154
+
155
+ .sidebar-stat-item {
156
+ display: flex;
157
+ justify-content: space-between;
158
+ align-items: center;
159
+ padding: var(--space-2) 0;
160
+ border-bottom: 1px solid var(--border-subtle);
161
+ }
162
+
163
+ .sidebar-stat-item:last-child {
164
+ border-bottom: none;
165
+ }
166
+
167
+ .sidebar-stat-label {
168
+ font-size: var(--fs-xs);
169
+ color: var(--text-muted);
170
+ font-weight: var(--fw-medium);
171
+ }
172
+
173
+ .sidebar-stat-value {
174
+ font-size: var(--fs-sm);
175
+ font-weight: var(--fw-semibold);
176
+ color: var(--text-strong);
177
+ }
178
+
179
+ .sidebar-stat-value.positive {
180
+ color: var(--success);
181
+ }
182
+
183
+ .sidebar-stat-value.negative {
184
+ color: var(--danger);
185
+ }
186
+
187
+ /* Enhanced Chart Container */
188
+ .chart-controls {
189
+ display: grid;
190
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
191
+ gap: var(--space-4);
192
+ margin-bottom: var(--space-6);
193
+ padding: var(--space-5);
194
+ background: var(--surface-glass);
195
+ border: 1px solid var(--border-light);
196
+ border-radius: var(--radius-lg);
197
+ backdrop-filter: var(--blur-lg);
198
+ }
199
+
200
+ .chart-control-group {
201
+ display: flex;
202
+ flex-direction: column;
203
+ gap: var(--space-2);
204
+ }
205
+
206
+ .chart-control-label {
207
+ font-size: var(--fs-sm);
208
+ font-weight: var(--fw-semibold);
209
+ color: var(--text-normal);
210
+ display: flex;
211
+ align-items: center;
212
+ gap: var(--space-2);
213
+ }
214
+
215
+ .chart-button-group {
216
+ display: flex;
217
+ gap: var(--space-2);
218
+ flex-wrap: wrap;
219
+ }
220
+
221
+ .chart-button {
222
+ flex: 1;
223
+ min-width: 80px;
224
+ padding: var(--space-2) var(--space-3);
225
+ background: var(--surface-glass);
226
+ border: 1px solid var(--border-light);
227
+ border-radius: var(--radius-sm);
228
+ color: var(--text-soft);
229
+ font-size: var(--fs-sm);
230
+ font-weight: var(--fw-medium);
231
+ cursor: pointer;
232
+ transition: all var(--transition-fast);
233
+ }
234
+
235
+ .chart-button:hover {
236
+ background: var(--surface-glass-strong);
237
+ border-color: var(--brand-cyan);
238
+ color: var(--text-strong);
239
+ }
240
+
241
+ .chart-button.active {
242
+ background: var(--gradient-primary);
243
+ border-color: transparent;
244
+ color: white;
245
+ box-shadow: var(--glow-cyan);
246
+ }
247
+
248
+ /* Color Scheme Selector */
249
+ .color-scheme-selector {
250
+ display: flex;
251
+ gap: var(--space-2);
252
+ }
253
+
254
+ .color-scheme-option {
255
+ width: 40px;
256
+ height: 40px;
257
+ border-radius: var(--radius-sm);
258
+ border: 2px solid var(--border-light);
259
+ cursor: pointer;
260
+ transition: all var(--transition-fast);
261
+ position: relative;
262
+ }
263
+
264
+ .color-scheme-option:hover {
265
+ transform: scale(1.1);
266
+ border-color: var(--brand-cyan);
267
+ }
268
+
269
+ .color-scheme-option.active {
270
+ border-color: var(--brand-cyan);
271
+ box-shadow: var(--glow-cyan);
272
+ }
273
+
274
+ .color-scheme-option.active::after {
275
+ content: '✓';
276
+ position: absolute;
277
+ top: 50%;
278
+ left: 50%;
279
+ transform: translate(-50%, -50%);
280
+ color: white;
281
+ font-weight: bold;
282
+ font-size: 18px;
283
+ }
284
+
285
+ .color-scheme-blue {
286
+ background: linear-gradient(135deg, #3B82F6, #06B6D4);
287
+ }
288
+
289
+ .color-scheme-purple {
290
+ background: linear-gradient(135deg, #8B5CF6, #EC4899);
291
+ }
292
+
293
+ .color-scheme-green {
294
+ background: linear-gradient(135deg, #10B981, #34D399);
295
+ }
296
+
297
+ .color-scheme-orange {
298
+ background: linear-gradient(135deg, #F97316, #FBBF24);
299
+ }
300
+
301
+ .color-scheme-rainbow {
302
+ background: linear-gradient(135deg, #3B82F6, #8B5CF6, #EC4899, #F97316);
303
+ }
304
+ </style>
305
+ </head>
306
+ <body data-theme="dark">
307
+
308
+ <script>
309
+ // Backend Configuration
310
+ if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
311
+ window.BACKEND_URL = `http://${window.location.hostname}:7860`;
312
+ } else {
313
+ window.BACKEND_URL = 'https://really-amin-datasourceforcryptocurrency.hf.space';
314
+ }
315
+ </script>
316
+
317
+ <div class="app-shell">
318
+ <!-- Dynamic Sidebar -->
319
+ <aside class="sidebar" id="dynamicSidebar">
320
+ <div class="brand">
321
+ <div class="brand-icon">
322
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none">
323
+ <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="1.5"/>
324
+ <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="1.5"/>
325
+ <path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="1.5"/>
326
+ </svg>
327
+ </div>
328
+ <div class="brand-text">
329
+ <strong>Crypto Intelligence</strong>
330
+ <span class="env-pill">
331
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none">
332
+ <circle cx="12" cy="12" r="3" fill="currentColor"/>
333
+ </svg>
334
+ Pro Edition
335
+ </span>
336
+ </div>
337
+ </div>
338
+
339
+ <nav class="nav" id="mainNav">
340
+ <button class="nav-button active" data-nav="page-overview">
341
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
342
+ <path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z" fill="currentColor"/>
343
+ </svg>
344
+ Overview
345
+ </button>
346
+ <button class="nav-button" data-nav="page-chart">
347
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
348
+ <path d="M3 3v18h18" stroke="currentColor" stroke-width="2"/>
349
+ <path d="M7 10l4-4 4 4 6-6" stroke="currentColor" stroke-width="2"/>
350
+ </svg>
351
+ Advanced Charts
352
+ </button>
353
+ <button class="nav-button" data-nav="page-compare">
354
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
355
+ <path d="M3 17l6-6 4 4 8-8" stroke="currentColor" stroke-width="2"/>
356
+ </svg>
357
+ Compare Coins
358
+ </button>
359
+ <button class="nav-button" data-nav="page-portfolio">
360
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
361
+ <path d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10l6 6v8a2 2 0 01-2 2z" stroke="currentColor" stroke-width="2"/>
362
+ </svg>
363
+ Portfolio
364
+ </button>
365
+ </nav>
366
+
367
+ <!-- Dynamic Stats -->
368
+ <div class="sidebar-stats" id="sidebarStats">
369
+ <div class="sidebar-stat-item">
370
+ <span class="sidebar-stat-label">Market Cap</span>
371
+ <span class="sidebar-stat-value" id="sidebarMarketCap">Loading...</span>
372
+ </div>
373
+ <div class="sidebar-stat-item">
374
+ <span class="sidebar-stat-label">24h Volume</span>
375
+ <span class="sidebar-stat-value" id="sidebarVolume">Loading...</span>
376
+ </div>
377
+ <div class="sidebar-stat-item">
378
+ <span class="sidebar-stat-label">BTC Price</span>
379
+ <span class="sidebar-stat-value positive" id="sidebarBTC">Loading...</span>
380
+ </div>
381
+ <div class="sidebar-stat-item">
382
+ <span class="sidebar-stat-label">ETH Price</span>
383
+ <span class="sidebar-stat-value positive" id="sidebarETH">Loading...</span>
384
+ </div>
385
+ </div>
386
+
387
+ <div class="sidebar-footer">
388
+ <div class="footer-badge">
389
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
390
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
391
+ <path d="M12 8v4l3 3" stroke="currentColor" stroke-width="2"/>
392
+ </svg>
393
+ <span id="lastUpdate">Just now</span>
394
+ </div>
395
+ </div>
396
+ </aside>
397
+
398
+ <!-- Main Content -->
399
+ <main class="main-area">
400
+ <!-- Top Bar -->
401
+ <header class="topbar">
402
+ <div class="topbar-content">
403
+ <div class="topbar-icon">
404
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none">
405
+ <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="1.5"/>
406
+ <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="1.5"/>
407
+ <path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="1.5"/>
408
+ </svg>
409
+ </div>
410
+ <div class="topbar-text">
411
+ <h1>
412
+ <span class="title-gradient">Professional</span>
413
+ <span class="title-accent">Dashboard</span>
414
+ </h1>
415
+ <p class="text-muted">
416
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
417
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
418
+ <path d="M12 8v4l3 3" stroke="currentColor" stroke-width="2"/>
419
+ </svg>
420
+ Real-time market data with advanced analytics
421
+ </p>
422
+ </div>
423
+ </div>
424
+ <div class="status-group">
425
+ <div class="status-pill" data-state="ok">
426
+ <span class="status-dot"></span>
427
+ <span class="status-label">API Connected</span>
428
+ </div>
429
+ <div class="status-pill" data-state="ok">
430
+ <span class="status-dot"></span>
431
+ <span class="status-label">Live Data</span>
432
+ </div>
433
+ </div>
434
+ </header>
435
+
436
+ <div class="page-container">
437
+ <!-- Overview Page -->
438
+ <section id="page-overview" class="page active">
439
+ <div class="section-header">
440
+ <h2 class="section-title">Market Overview</h2>
441
+ <span class="chip">Real-time</span>
442
+ </div>
443
+
444
+ <!-- Stats Grid -->
445
+ <div class="stats-grid" id="statsGrid">
446
+ <!-- Stats will be dynamically loaded -->
447
+ </div>
448
+
449
+ <!-- Main Chart -->
450
+ <div class="glass-card" style="margin-top: var(--space-6);">
451
+ <div class="card-header">
452
+ <h4 class="card-title">
453
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
454
+ <path d="M3 3v18h18" stroke="currentColor" stroke-width="2"/>
455
+ <path d="M7 10l4-4 4 4 6-6" stroke="currentColor" stroke-width="2"/>
456
+ </svg>
457
+ Market Trends - Top 10 Cryptocurrencies
458
+ </h4>
459
+ <div style="display: flex; gap: var(--space-2);">
460
+ <span class="badge badge-cyan">24H</span>
461
+ <button class="btn-secondary btn-sm" onclick="refreshData()">
462
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
463
+ <path d="M1 4v6h6M23 20v-6h-6" stroke="currentColor" stroke-width="2"/>
464
+ <path d="M20.49 9A9 9 0 005.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 013.51 15" stroke="currentColor" stroke-width="2"/>
465
+ </svg>
466
+ Refresh
467
+ </button>
468
+ </div>
469
+ </div>
470
+ <div class="chart-container" style="height: 450px;">
471
+ <canvas id="mainChart"></canvas>
472
+ </div>
473
+ </div>
474
+
475
+ <!-- Top Coins Table -->
476
+ <div class="glass-card" style="margin-top: var(--space-6);">
477
+ <div class="card-header">
478
+ <h4 class="card-title">
479
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
480
+ <path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" fill="currentColor"/>
481
+ </svg>
482
+ Top Cryptocurrencies
483
+ </h4>
484
+ </div>
485
+ <div class="table-container">
486
+ <table class="table">
487
+ <thead>
488
+ <tr>
489
+ <th>#</th>
490
+ <th>Coin</th>
491
+ <th>Price</th>
492
+ <th>24h Change</th>
493
+ <th>7d Change</th>
494
+ <th>Market Cap</th>
495
+ <th>Volume (24h)</th>
496
+ <th>Last 7 Days</th>
497
+ </tr>
498
+ </thead>
499
+ <tbody id="topCoinsTable">
500
+ <!-- Data will be loaded dynamically -->
501
+ </tbody>
502
+ </table>
503
+ </div>
504
+ </div>
505
+ </section>
506
+
507
+ <!-- Advanced Charts Page -->
508
+ <section id="page-chart" class="page">
509
+ <div class="section-header">
510
+ <h2 class="section-title">Advanced Chart Analysis</h2>
511
+ <span class="chip">Interactive</span>
512
+ </div>
513
+
514
+ <!-- Chart Controls -->
515
+ <div class="chart-controls">
516
+ <div class="chart-control-group">
517
+ <label class="chart-control-label">
518
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
519
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
520
+ </svg>
521
+ Select Cryptocurrency
522
+ </label>
523
+ <div class="combobox-wrapper">
524
+ <input
525
+ type="text"
526
+ class="combobox-input"
527
+ id="coinSelector"
528
+ placeholder="Search for a coin..."
529
+ autocomplete="off"
530
+ />
531
+ <svg class="combobox-icon" width="16" height="16" viewBox="0 0 24 24" fill="none">
532
+ <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke="currentColor" stroke-width="2"/>
533
+ </svg>
534
+ <div class="combobox-dropdown" id="coinDropdown">
535
+ <!-- Options will be loaded dynamically -->
536
+ </div>
537
+ </div>
538
+ </div>
539
+
540
+ <div class="chart-control-group">
541
+ <label class="chart-control-label">
542
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
543
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
544
+ <path d="M12 8v4l3 3" stroke="currentColor" stroke-width="2"/>
545
+ </svg>
546
+ Timeframe
547
+ </label>
548
+ <div class="chart-button-group">
549
+ <button class="chart-button" data-timeframe="1">1D</button>
550
+ <button class="chart-button active" data-timeframe="7">7D</button>
551
+ <button class="chart-button" data-timeframe="30">30D</button>
552
+ <button class="chart-button" data-timeframe="90">90D</button>
553
+ <button class="chart-button" data-timeframe="365">1Y</button>
554
+ </div>
555
+ </div>
556
+
557
+ <div class="chart-control-group">
558
+ <label class="chart-control-label">
559
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
560
+ <path d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" stroke="currentColor" stroke-width="2"/>
561
+ </svg>
562
+ Color Scheme
563
+ </label>
564
+ <div class="color-scheme-selector">
565
+ <div class="color-scheme-option color-scheme-blue active" data-scheme="blue"></div>
566
+ <div class="color-scheme-option color-scheme-purple" data-scheme="purple"></div>
567
+ <div class="color-scheme-option color-scheme-green" data-scheme="green"></div>
568
+ <div class="color-scheme-option color-scheme-orange" data-scheme="orange"></div>
569
+ <div class="color-scheme-option color-scheme-rainbow" data-scheme="rainbow"></div>
570
+ </div>
571
+ </div>
572
+ </div>
573
+
574
+ <!-- Price Chart -->
575
+ <div class="glass-card">
576
+ <div class="card-header">
577
+ <h4 class="card-title" id="chartTitle">Bitcoin (BTC) Price Chart</h4>
578
+ <div style="display: flex; gap: var(--space-2); align-items: center;">
579
+ <span class="badge badge-success" id="chartPrice">$0</span>
580
+ <span class="badge badge-cyan" id="chartChange">0%</span>
581
+ </div>
582
+ </div>
583
+ <div class="chart-container" style="height: 500px;">
584
+ <canvas id="priceChart"></canvas>
585
+ </div>
586
+ </div>
587
+
588
+ <!-- Volume Chart -->
589
+ <div class="glass-card" style="margin-top: var(--space-6);">
590
+ <div class="card-header">
591
+ <h4 class="card-title">Trading Volume</h4>
592
+ </div>
593
+ <div class="chart-container" style="height: 300px;">
594
+ <canvas id="volumeChart"></canvas>
595
+ </div>
596
+ </div>
597
+ </section>
598
+
599
+ <!-- Compare Page -->
600
+ <section id="page-compare" class="page">
601
+ <div class="section-header">
602
+ <h2 class="section-title">Compare Cryptocurrencies</h2>
603
+ <span class="chip">Side by Side</span>
604
+ </div>
605
+
606
+ <div class="alert alert-info">
607
+ <svg class="alert-icon" width="20" height="20" viewBox="0 0 24 24" fill="none">
608
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
609
+ <path d="M12 16v-4M12 8h.01" stroke="currentColor" stroke-width="2"/>
610
+ </svg>
611
+ <div class="alert-content">
612
+ <div class="alert-title">Compare up to 5 cryptocurrencies</div>
613
+ <div class="alert-description">Select coins to compare their performance side by side</div>
614
+ </div>
615
+ </div>
616
+
617
+ <div class="glass-card" style="margin-top: var(--space-6);">
618
+ <div class="card-header">
619
+ <h4 class="card-title">Comparison Chart</h4>
620
+ </div>
621
+ <div class="chart-container" style="height: 450px;">
622
+ <canvas id="compareChart"></canvas>
623
+ </div>
624
+ </div>
625
+ </section>
626
+
627
+ <!-- Portfolio Page -->
628
+ <section id="page-portfolio" class="page">
629
+ <div class="section-header">
630
+ <h2 class="section-title">Portfolio Tracker</h2>
631
+ <button class="btn-primary">
632
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
633
+ <path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2"/>
634
+ </svg>
635
+ Add Asset
636
+ </button>
637
+ </div>
638
+
639
+ <div class="empty-state">
640
+ <div class="empty-state-icon">📊</div>
641
+ <div class="empty-state-title">No Portfolio Data</div>
642
+ <div class="empty-state-description">
643
+ Start tracking your crypto portfolio by adding your first asset
644
+ </div>
645
+ <button class="btn-primary" style="margin-top: var(--space-4);">
646
+ Get Started
647
+ </button>
648
+ </div>
649
+ </section>
650
+ </div>
651
+ </main>
652
+ </div>
653
+
654
+ <!-- Load App JS -->
655
+ <script type="module" src="static/js/app-pro.js"></script>
656
+ </body>
657
+ </html>
final/ai_models.py ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Centralized access to Hugging Face models with ensemble sentiment."""
3
+
4
+ from __future__ import annotations
5
+ import logging
6
+ import threading
7
+ from dataclasses import dataclass
8
+ from typing import Any, Dict, List, Mapping, Optional, Sequence
9
+ from config import HUGGINGFACE_MODELS, get_settings
10
+
11
+ # Set environment variables to avoid TensorFlow/Keras issues
12
+ # We'll force PyTorch framework instead
13
+ import os
14
+ import sys
15
+
16
+ # Completely disable TensorFlow to force PyTorch
17
+ os.environ.setdefault('TRANSFORMERS_NO_ADVISORY_WARNINGS', '1')
18
+ os.environ.setdefault('TRANSFORMERS_VERBOSITY', 'error')
19
+ os.environ.setdefault('TF_CPP_MIN_LOG_LEVEL', '3')
20
+ os.environ.setdefault('TRANSFORMERS_FRAMEWORK', 'pt')
21
+
22
+ # Mock tf_keras to prevent transformers from trying to import it
23
+ # This prevents the broken tf-keras installation from causing errors
24
+ class TfKerasMock:
25
+ """Mock tf_keras to prevent import errors when transformers checks for TensorFlow"""
26
+ pass
27
+
28
+ # Add mock to sys.modules before transformers imports
29
+ sys.modules['tf_keras'] = TfKerasMock()
30
+ sys.modules['tf_keras.src'] = TfKerasMock()
31
+ sys.modules['tf_keras.src.utils'] = TfKerasMock()
32
+
33
+ try:
34
+ from transformers import pipeline
35
+ TRANSFORMERS_AVAILABLE = True
36
+ except ImportError:
37
+ TRANSFORMERS_AVAILABLE = False
38
+
39
+ logger = logging.getLogger(__name__)
40
+ settings = get_settings()
41
+
42
+ HF_MODE = os.getenv("HF_MODE", "off").lower()
43
+ HF_TOKEN_ENV = os.getenv("HF_TOKEN")
44
+
45
+ if HF_MODE not in ("off", "public", "auth"):
46
+ HF_MODE = "off"
47
+ logger.warning(f"Invalid HF_MODE, defaulting to 'off'")
48
+
49
+ if HF_MODE == "auth" and not HF_TOKEN_ENV:
50
+ HF_MODE = "off"
51
+ logger.warning("HF_MODE='auth' but HF_TOKEN not set, defaulting to 'off'")
52
+
53
+ ACTIVE_MODELS = [
54
+ "ElKulako/cryptobert",
55
+ "kk08/CryptoBERT",
56
+ "ProsusAI/finbert"
57
+ ]
58
+
59
+ LEGACY_MODELS = [
60
+ "burakutf/finetuned-finbert-crypto",
61
+ "mathugo/crypto_news_bert",
62
+ "svalabs/twitter-xlm-roberta-bitcoin-sentiment",
63
+ "mayurjadhav/crypto-sentiment-model",
64
+ "cardiffnlp/twitter-roberta-base-sentiment",
65
+ "mrm8488/distilroberta-finetuned-financial-news-sentiment-analysis",
66
+ "agarkovv/CryptoTrader-LM"
67
+ ]
68
+
69
+ CRYPTO_SENTIMENT_MODELS = ACTIVE_MODELS[:2] + LEGACY_MODELS[:2]
70
+ SOCIAL_SENTIMENT_MODELS = LEGACY_MODELS[2:4]
71
+ FINANCIAL_SENTIMENT_MODELS = [ACTIVE_MODELS[2]] + [LEGACY_MODELS[4]]
72
+ NEWS_SENTIMENT_MODELS = [LEGACY_MODELS[5]]
73
+ DECISION_MODELS = [LEGACY_MODELS[6]]
74
+
75
+ @dataclass(frozen=True)
76
+ class PipelineSpec:
77
+ key: str
78
+ task: str
79
+ model_id: str
80
+ requires_auth: bool = False
81
+ category: str = "sentiment"
82
+
83
+ MODEL_SPECS: Dict[str, PipelineSpec] = {}
84
+
85
+ # Legacy models
86
+ for lk in ["sentiment_twitter", "sentiment_financial", "summarization", "crypto_sentiment"]:
87
+ if lk in HUGGINGFACE_MODELS:
88
+ MODEL_SPECS[lk] = PipelineSpec(
89
+ key=lk,
90
+ task="sentiment-analysis" if "sentiment" in lk else "summarization",
91
+ model_id=HUGGINGFACE_MODELS[lk],
92
+ category="legacy"
93
+ )
94
+
95
+ for i, mid in enumerate(ACTIVE_MODELS):
96
+ MODEL_SPECS[f"active_{i}"] = PipelineSpec(
97
+ key=f"active_{i}", task="sentiment-analysis", model_id=mid,
98
+ category="crypto_sentiment" if i < 2 else "financial_sentiment",
99
+ requires_auth=("ElKulako" in mid)
100
+ )
101
+
102
+ for i, mid in enumerate(CRYPTO_SENTIMENT_MODELS):
103
+ MODEL_SPECS[f"crypto_sent_{i}"] = PipelineSpec(
104
+ key=f"crypto_sent_{i}", task="sentiment-analysis", model_id=mid,
105
+ category="crypto_sentiment", requires_auth=("ElKulako" in mid)
106
+ )
107
+
108
+ for i, mid in enumerate(SOCIAL_SENTIMENT_MODELS):
109
+ MODEL_SPECS[f"social_sent_{i}"] = PipelineSpec(
110
+ key=f"social_sent_{i}", task="sentiment-analysis", model_id=mid, category="social_sentiment"
111
+ )
112
+
113
+ for i, mid in enumerate(FINANCIAL_SENTIMENT_MODELS):
114
+ MODEL_SPECS[f"financial_sent_{i}"] = PipelineSpec(
115
+ key=f"financial_sent_{i}", task="sentiment-analysis", model_id=mid, category="financial_sentiment"
116
+ )
117
+
118
+ for i, mid in enumerate(NEWS_SENTIMENT_MODELS):
119
+ MODEL_SPECS[f"news_sent_{i}"] = PipelineSpec(
120
+ key=f"news_sent_{i}", task="sentiment-analysis", model_id=mid, category="news_sentiment"
121
+ )
122
+
123
+ class ModelNotAvailable(RuntimeError): pass
124
+
125
+ class ModelRegistry:
126
+ def __init__(self):
127
+ self._pipelines = {}
128
+ self._lock = threading.Lock()
129
+ self._initialized = False
130
+
131
+ def get_pipeline(self, key: str):
132
+ if not TRANSFORMERS_AVAILABLE:
133
+ raise ModelNotAvailable("transformers not installed")
134
+ if key not in MODEL_SPECS:
135
+ raise ModelNotAvailable(f"Unknown key: {key}")
136
+
137
+ spec = MODEL_SPECS[key]
138
+ if key in self._pipelines:
139
+ return self._pipelines[key]
140
+
141
+ with self._lock:
142
+ if key in self._pipelines:
143
+ return self._pipelines[key]
144
+
145
+ if HF_MODE == "off":
146
+ raise ModelNotAvailable("HF_MODE=off")
147
+
148
+ token_value = None
149
+ if HF_MODE == "auth":
150
+ token_value = HF_TOKEN_ENV or settings.hf_token
151
+ elif HF_MODE == "public":
152
+ token_value = None
153
+
154
+ if spec.requires_auth and not token_value:
155
+ raise ModelNotAvailable("Model requires auth but no token available")
156
+
157
+ logger.info(f"Loading model: {spec.model_id} (mode: {HF_MODE})")
158
+ try:
159
+ pipeline_kwargs = {
160
+ 'task': spec.task,
161
+ 'model': spec.model_id,
162
+ 'tokenizer': spec.model_id,
163
+ 'framework': 'pt',
164
+ 'device': -1,
165
+ }
166
+ pipeline_kwargs['token'] = token_value
167
+
168
+ self._pipelines[key] = pipeline(**pipeline_kwargs)
169
+ except Exception as e:
170
+ error_msg = str(e)
171
+ error_lower = error_msg.lower()
172
+
173
+ try:
174
+ from huggingface_hub.errors import RepositoryNotFoundError, HfHubHTTPError
175
+ hf_errors = (RepositoryNotFoundError, HfHubHTTPError)
176
+ except ImportError:
177
+ hf_errors = ()
178
+
179
+ is_auth_error = any(kw in error_lower for kw in ['401', 'unauthorized', 'repository not found', 'expired', 'token'])
180
+ is_hf_error = isinstance(e, hf_errors) or is_auth_error
181
+
182
+ if is_hf_error:
183
+ logger.warning(f"HF error for {spec.model_id}: {type(e).__name__}")
184
+ raise ModelNotAvailable(f"HF error: {spec.model_id}") from e
185
+
186
+ if any(kw in error_lower for kw in ['keras', 'tensorflow', 'tf_keras', 'framework']):
187
+ try:
188
+ pipeline_kwargs['torch_dtype'] = 'float32'
189
+ self._pipelines[key] = pipeline(**pipeline_kwargs)
190
+ return self._pipelines[key]
191
+ except Exception:
192
+ raise ModelNotAvailable(f"Framework error: {spec.model_id}") from e
193
+
194
+ raise ModelNotAvailable(f"Load failed: {spec.model_id}") from e
195
+
196
+ return self._pipelines[key]
197
+
198
+ def get_loaded_models(self):
199
+ """Get list of all loaded model keys"""
200
+ return list(self._pipelines.keys())
201
+
202
+ def get_available_sentiment_models(self):
203
+ """Get list of all available sentiment model keys"""
204
+ return [key for key in MODEL_SPECS.keys() if "sent" in key or "sentiment" in key]
205
+
206
+ def initialize_models(self):
207
+ if self._initialized:
208
+ return {"status": "already_initialized", "mode": HF_MODE, "models_loaded": len(self._pipelines)}
209
+
210
+ if HF_MODE == "off":
211
+ self._initialized = True
212
+ return {"status": "disabled", "mode": "off", "models_loaded": 0, "loaded": [], "failed": []}
213
+
214
+ if not TRANSFORMERS_AVAILABLE:
215
+ return {"status": "transformers_not_available", "mode": HF_MODE, "models_loaded": 0}
216
+
217
+ loaded, failed = [], []
218
+ active_keys = [f"active_{i}" for i in range(len(ACTIVE_MODELS))]
219
+
220
+ for key in active_keys:
221
+ try:
222
+ self.get_pipeline(key)
223
+ loaded.append(key)
224
+ except ModelNotAvailable as e:
225
+ failed.append((key, str(e)[:100]))
226
+ except Exception as e:
227
+ error_msg = str(e)[:100]
228
+ failed.append((key, error_msg))
229
+
230
+ self._initialized = True
231
+ status = "initialized" if loaded else "partial"
232
+ return {"status": status, "mode": HF_MODE, "models_loaded": len(loaded), "loaded": loaded, "failed": failed}
233
+
234
+ _registry = ModelRegistry()
235
+
236
+ AI_MODELS_SUMMARY = {"status": "not_initialized", "mode": "off", "models_loaded": 0, "loaded": [], "failed": []}
237
+
238
+ def initialize_models():
239
+ global AI_MODELS_SUMMARY
240
+ result = _registry.initialize_models()
241
+ AI_MODELS_SUMMARY = result
242
+ return result
243
+
244
+ def ensemble_crypto_sentiment(text: str) -> Dict[str, Any]:
245
+ if not TRANSFORMERS_AVAILABLE or HF_MODE == "off":
246
+ return {"label": "neutral", "confidence": 0.0, "scores": {}, "model_count": 0, "error": "HF disabled" if HF_MODE == "off" else "transformers N/A"}
247
+
248
+ results, labels_count, total_conf = {}, {"bullish": 0, "bearish": 0, "neutral": 0}, 0.0
249
+
250
+ loaded_keys = _registry.get_loaded_models()
251
+ available_keys = [key for key in loaded_keys if "sent" in key or "sentiment" in key or key.startswith("active_")]
252
+
253
+ if not available_keys:
254
+ return {"label": "neutral", "confidence": 0.0, "scores": {}, "model_count": 0, "error": "No models loaded"}
255
+
256
+ for key in available_keys:
257
+ try:
258
+ pipe = _registry.get_pipeline(key)
259
+ res = pipe(text[:512])
260
+ if isinstance(res, list) and res: res = res[0]
261
+
262
+ label = res.get("label", "NEUTRAL").upper()
263
+ score = res.get("score", 0.5)
264
+
265
+ mapped = "bullish" if "POSITIVE" in label or "BULLISH" in label else ("bearish" if "NEGATIVE" in label or "BEARISH" in label else "neutral")
266
+
267
+ spec = MODEL_SPECS.get(key)
268
+ if spec:
269
+ results[spec.model_id] = {"label": mapped, "score": score}
270
+ else:
271
+ results[key] = {"label": mapped, "score": score}
272
+ labels_count[mapped] += 1
273
+ total_conf += score
274
+ except ModelNotAvailable:
275
+ continue
276
+ except Exception as e:
277
+ logger.warning(f"Ensemble failed for {key}: {e}")
278
+
279
+ if not results:
280
+ return {"label": "neutral", "confidence": 0.0, "scores": {}, "model_count": 0, "error": "All models failed"}
281
+
282
+ final = max(labels_count, key=labels_count.get)
283
+ avg_conf = total_conf / len(results)
284
+
285
+ return {"label": final, "confidence": avg_conf, "scores": results, "model_count": len(results)}
286
+
287
+ def analyze_crypto_sentiment(text: str): return ensemble_crypto_sentiment(text)
288
+
289
+ def analyze_financial_sentiment(text: str):
290
+ if not TRANSFORMERS_AVAILABLE:
291
+ return {"label": "neutral", "score": 0.5, "error": "transformers N/A"}
292
+ try:
293
+ pipe = _registry.get_pipeline("financial_sent_0")
294
+ res = pipe(text[:512])
295
+ if isinstance(res, list) and res: res = res[0]
296
+ return {"label": res.get("label", "neutral").lower(), "score": res.get("score", 0.5)}
297
+ except Exception as e:
298
+ logger.error(f"Financial sentiment failed: {e}")
299
+ return {"label": "neutral", "score": 0.5, "error": str(e)}
300
+
301
+ def analyze_social_sentiment(text: str):
302
+ if not TRANSFORMERS_AVAILABLE:
303
+ return {"label": "neutral", "score": 0.5, "error": "transformers N/A"}
304
+ try:
305
+ pipe = _registry.get_pipeline("social_sent_0")
306
+ res = pipe(text[:512])
307
+ if isinstance(res, list) and res: res = res[0]
308
+ return {"label": res.get("label", "neutral").lower(), "score": res.get("score", 0.5)}
309
+ except Exception as e:
310
+ logger.error(f"Social sentiment failed: {e}")
311
+ return {"label": "neutral", "score": 0.5, "error": str(e)}
312
+
313
+ def analyze_market_text(text: str): return ensemble_crypto_sentiment(text)
314
+
315
+ def analyze_chart_points(data: Sequence[Mapping[str, Any]], indicators: Optional[List[str]] = None):
316
+ if not data: return {"trend": "neutral", "strength": 0, "analysis": "No data"}
317
+
318
+ prices = [float(p.get("price", 0)) for p in data if p.get("price")]
319
+ if not prices: return {"trend": "neutral", "strength": 0, "analysis": "No price data"}
320
+
321
+ first, last = prices[0], prices[-1]
322
+ change = ((last - first) / first * 100) if first > 0 else 0
323
+
324
+ if change > 5: trend, strength = "bullish", min(abs(change) / 10, 1.0)
325
+ elif change < -5: trend, strength = "bearish", min(abs(change) / 10, 1.0)
326
+ else: trend, strength = "neutral", abs(change) / 5
327
+
328
+ return {"trend": trend, "strength": strength, "change_pct": change, "support": min(prices), "resistance": max(prices), "analysis": f"Price moved {change:.2f}% showing {trend} trend"}
329
+
330
+ def analyze_news_item(item: Dict[str, Any]):
331
+ text = item.get("title", "") + " " + item.get("description", "")
332
+ sent = ensemble_crypto_sentiment(text)
333
+ return {**item, "sentiment": sent["label"], "sentiment_confidence": sent["confidence"], "sentiment_details": sent}
334
+
335
+ def get_model_info():
336
+ return {
337
+ "transformers_available": TRANSFORMERS_AVAILABLE,
338
+ "hf_mode": HF_MODE,
339
+ "hf_token_configured": bool(HF_TOKEN_ENV or settings.hf_token) if HF_MODE == "auth" else False,
340
+ "models_initialized": _registry._initialized,
341
+ "models_loaded": len(_registry._pipelines),
342
+ "active_models": ACTIVE_MODELS,
343
+ "total_models": len(MODEL_SPECS)
344
+ }
345
+
346
+ def registry_status():
347
+ return {
348
+ "initialized": _registry._initialized,
349
+ "pipelines_loaded": len(_registry._pipelines),
350
+ "available_models": list(MODEL_SPECS.keys()),
351
+ "transformers_available": TRANSFORMERS_AVAILABLE
352
+ }
final/all_apis_merged_2025.json ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "metadata": {
3
+ "name": "dreammaker_free_api_registry",
4
+ "version": "2025.11.11",
5
+ "description": "Merged registry of uploaded crypto resources (TXT and ZIP). Contains raw file text, ZIP listing, discovered keys, and basic categorization scaffold.",
6
+ "created_at": "2025-11-10T22:20:17.449681",
7
+ "source_files": [
8
+ "api-config-complete (1).txt",
9
+ "api - Copy.txt",
10
+ "crypto_resources_ultimate_2025.zip"
11
+ ]
12
+ },
13
+ "raw_files": [
14
+ {
15
+ "filename": "api-config-complete (1).txt",
16
+ "content": "╔══════════════════════════════════════════════════════════════════════════════════════╗\n║ CRYPTOCURRENCY API CONFIGURATION - COMPLETE GUIDE ║\n║ تنظیمات کامل API های ارز دیجیتال ║\n║ Updated: October 2025 ║\n╚══════════════════════════════════════════════════════════════════════════════════════╝\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 🔑 API KEYS - کلیدهای API \n═══════════════════════════════════════════════════════════════════════════════════════\n\nEXISTING KEYS (کلیدهای موجود):\n─────────────────────────────────\nTronScan: 7ae72726-bffe-4e74-9c33-97b761eeea21\nBscScan: K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT\nEtherscan: SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2\nEtherscan_2: T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45\nCoinMarketCap: 04cf4b5b-9868-465c-8ba0-9f2e78c92eb1\nCoinMarketCap_2: b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c\nNewsAPI: pub_346789abc123def456789ghi012345jkl\nCryptoCompare: e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 🌐 CORS PROXY SOLUTIONS - راه‌حل‌های پروکسی CORS\n═══════════════════════════════════════════════════════════════════════════════════════\n\nFREE CORS PROXIES (پروکسی‌های رایگان):\n──────────────────────────────────────────\n\n1. AllOrigins (بدون محدودیت)\n URL: https://api.allorigins.win/get?url={TARGET_URL}\n Example: https://api.allorigins.win/get?url=https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd\n Features: JSON/JSONP, گزینه raw content\n \n2. CORS.SH (بدون rate limit)\n URL: https://proxy.cors.sh/{TARGET_URL}\n Example: https://proxy.cors.sh/https://api.coinmarketcap.com/v1/cryptocurrency/quotes/latest\n Features: سریع، قابل اعتماد، نیاز به header Origin یا x-requested-with\n \n3. Corsfix (60 req/min رایگان)\n URL: https://proxy.corsfix.com/?url={TARGET_URL}\n Example: https://proxy.corsfix.com/?url=https://api.etherscan.io/api\n Features: header override، cached responses\n \n4. CodeTabs (محبوب)\n URL: https://api.codetabs.com/v1/proxy?quest={TARGET_URL}\n Example: https://api.codetabs.com/v1/proxy?quest=https://api.binance.com/api/v3/ticker/price\n \n5. ThingProxy (10 req/sec)\n URL: https://thingproxy.freeboard.io/fetch/{TARGET_URL}\n Example: https://thingproxy.freeboard.io/fetch/https://api.nomics.com/v1/currencies/ticker\n Limit: 100,000 characters per request\n \n6. Crossorigin.me\n URL: https://crossorigin.me/{TARGET_URL}\n Note: فقط GET، محدودیت 2MB\n \n7. Self-Hosted CORS-Anywhere\n GitHub: https://github.com/Rob--W/cors-anywhere\n Deploy: Cloudflare Workers، Vercel، Heroku\n\nUSAGE PATTERN (الگوی استفاده):\n────────────────────────────────\n// Without CORS Proxy\nfetch('https://api.example.com/data')\n\n// With CORS Proxy\nconst corsProxy = 'https://api.allorigins.win/get?url=';\nfetch(corsProxy + encodeURIComponent('https://api.example.com/data'))\n .then(res => res.json())\n .then(data => console.log(data.contents));\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 🔗 RPC NODE PROVIDERS - ارائه‌دهندگان نود RPC\n═══════════════════════════════════════════════════════════════════════════════════════\n\nETHEREUM RPC ENDPOINTS:\n───────────────────────────────────\n\n1. Infura (رایگان: 100K req/day)\n Mainnet: https://mainnet.infura.io/v3/{PROJECT_ID}\n Sepolia: https://sepolia.infura.io/v3/{PROJECT_ID}\n Docs: https://docs.infura.io\n \n2. Alchemy (رایگان: 300M compute units/month)\n Mainnet: https://eth-mainnet.g.alchemy.com/v2/{API_KEY}\n Sepolia: https://eth-sepolia.g.alchemy.com/v2/{API_KEY}\n WebSocket: wss://eth-mainnet.g.alchemy.com/v2/{API_KEY}\n Docs: https://docs.alchemy.com\n \n3. Ankr (رایگان: بدون محدودیت عمومی)\n Mainnet: https://rpc.ankr.com/eth\n Docs: https://www.ankr.com/docs\n \n4. PublicNode (کاملا رایگان)\n Mainnet: https://ethereum.publicnode.com\n All-in-one: https://ethereum-rpc.publicnode.com\n \n5. Cloudflare (رایگان)\n Mainnet: https://cloudflare-eth.com\n \n6. LlamaNodes (رایگان)\n Mainnet: https://eth.llamarpc.com\n \n7. 1RPC (رایگان با privacy)\n Mainnet: https://1rpc.io/eth\n \n8. Chainnodes (ارزان)\n Mainnet: https://mainnet.chainnodes.org/{API_KEY}\n \n9. dRPC (decentralized)\n Mainnet: https://eth.drpc.org\n Docs: https://drpc.org\n\nBSC (BINANCE SMART CHAIN) RPC:\n──────────────────────────────────\n\n1. Official BSC RPC (رایگان)\n Mainnet: https://bsc-dataseed.binance.org\n Alt1: https://bsc-dataseed1.defibit.io\n Alt2: https://bsc-dataseed1.ninicoin.io\n \n2. Ankr BSC\n Mainnet: https://rpc.ankr.com/bsc\n \n3. PublicNode BSC\n Mainnet: https://bsc-rpc.publicnode.com\n \n4. Nodereal BSC (رایگان: 3M req/day)\n Mainnet: https://bsc-mainnet.nodereal.io/v1/{API_KEY}\n\nTRON RPC ENDPOINTS:\n───────────────────────────\n\n1. TronGrid (رایگان)\n Mainnet: https://api.trongrid.io\n Full Node: https://api.trongrid.io/wallet/getnowblock\n \n2. TronStack (رایگان)\n Mainnet: https://api.tronstack.io\n \n3. Nile Testnet\n Testnet: https://api.nileex.io\n\nPOLYGON RPC:\n──────────────────\n\n1. Polygon Official (رایگان)\n Mainnet: https://polygon-rpc.com\n Mumbai: https://rpc-mumbai.maticvigil.com\n \n2. Ankr Polygon\n Mainnet: https://rpc.ankr.com/polygon\n \n3. Alchemy Polygon\n Mainnet: https://polygon-mainnet.g.alchemy.com/v2/{API_KEY}\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 📊 BLOCK EXPLORER APIs - APIهای کاوشگر بلاکچین\n═══════════════════════════════════════════════════════════════════════════════════════\n\nCATEGORY 1: ETHEREUM EXPLORERS (11 endpoints)\n──────────────────────────────────────────────\n\nPRIMARY: Etherscan\n─────────────────────\nURL: https://api.etherscan.io/api\nKey: SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2\nRate Limit: 5 calls/sec (free tier)\nDocs: https://docs.etherscan.io\n\nEndpoints:\n• Balance: ?module=account&action=balance&address={address}&tag=latest&apikey={KEY}\n• Transactions: ?module=account&action=txlist&address={address}&startblock=0&endblock=99999999&sort=asc&apikey={KEY}\n• Token Balance: ?module=account&action=tokenbalance&contractaddress={contract}&address={address}&tag=latest&apikey={KEY}\n• Gas Price: ?module=gastracker&action=gasoracle&apikey={KEY}\n\nExample (No Proxy):\nfetch('https://api.etherscan.io/api?module=account&action=balance&address=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb&tag=latest&apikey=SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2')\n\nExample (With CORS Proxy):\nconst proxy = 'https://api.allorigins.win/get?url=';\nconst url = 'https://api.etherscan.io/api?module=account&action=balance&address=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb&apikey=SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2';\nfetch(proxy + encodeURIComponent(url))\n .then(r => r.json())\n .then(data => {\n const result = JSON.parse(data.contents);\n console.log('Balance:', result.result / 1e18, 'ETH');\n });\n\nFALLBACK 1: Etherscan (Second Key)\n────────────────────────────────────\nURL: https://api.etherscan.io/api\nKey: T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45\n\nFALLBACK 2: Blockchair\n──────────────────────\nURL: https://api.blockchair.com/ethereum/dashboards/address/{address}\nFree: 1,440 requests/day\nDocs: https://blockchair.com/api/docs\n\nFALLBACK 3: BlockScout (Open Source)\n─────────────────────────────────────\nURL: https://eth.blockscout.com/api\nFree: بدون محدودیت\nDocs: https://docs.blockscout.com\n\nFALLBACK 4: Ethplorer\n──────────────────────\nURL: https://api.ethplorer.io\nEndpoint: /getAddressInfo/{address}?apiKey=freekey\nFree: محدود\nDocs: https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API\n\nFALLBACK 5: Etherchain\n──────────────────────\nURL: https://www.etherchain.org/api\nFree: بله\nDocs: https://www.etherchain.org/documentation/api\n\nFALLBACK 6: Chainlens\n─────────────────────\nURL: https://api.chainlens.com\nFree tier available\nDocs: https://docs.chainlens.com\n\n\nCATEGORY 2: BSC EXPLORERS (6 endpoints)\n────────────────────────────────────────\n\nPRIMARY: BscScan\n────────────────\nURL: https://api.bscscan.com/api\nKey: K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT\nRate Limit: 5 calls/sec\nDocs: https://docs.bscscan.com\n\nEndpoints:\n• BNB Balance: ?module=account&action=balance&address={address}&apikey={KEY}\n• BEP-20 Balance: ?module=account&action=tokenbalance&contractaddress={token}&address={address}&apikey={KEY}\n• Transactions: ?module=account&action=txlist&address={address}&apikey={KEY}\n\nExample:\nfetch('https://api.bscscan.com/api?module=account&action=balance&address=0x1234...&apikey=K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT')\n .then(r => r.json())\n .then(data => console.log('BNB:', data.result / 1e18));\n\nFALLBACK 1: BitQuery (BSC)\n──────────────────────────\nURL: https://graphql.bitquery.io\nMethod: GraphQL POST\nFree: 10K queries/month\nDocs: https://docs.bitquery.io\n\nGraphQL Example:\nquery {\n ethereum(network: bsc) {\n address(address: {is: \"0x...\"}) {\n balances {\n currency { symbol }\n value\n }\n }\n }\n}\n\nFALLBACK 2: Ankr MultiChain\n────────────────────────────\nURL: https://rpc.ankr.com/multichain\nMethod: JSON-RPC POST\nFree: Public endpoints\nDocs: https://www.ankr.com/docs/\n\nFALLBACK 3: Nodereal BSC\n────────────────────────\nURL: https://bsc-mainnet.nodereal.io/v1/{API_KEY}\nFree tier: 3M requests/day\nDocs: https://docs.nodereal.io\n\nFALLBACK 4: BscTrace\n────────────────────\nURL: https://api.bsctrace.com\nFree: Limited\nAlternative explorer\n\nFALLBACK 5: 1inch BSC API\n─────────────────────────\nURL: https://api.1inch.io/v5.0/56\nFree: For trading data\nDocs: https://docs.1inch.io\n\n\nCATEGORY 3: TRON EXPLORERS (5 endpoints)\n─────────────────────────────────────────\n\nPRIMARY: TronScan\n─────────────────\nURL: https://apilist.tronscanapi.com/api\nKey: 7ae72726-bffe-4e74-9c33-97b761eeea21\nRate Limit: Varies\nDocs: https://github.com/tronscan/tronscan-frontend/blob/dev2019/document/api.md\n\nEndpoints:\n• Account: /account?address={address}\n• Transactions: /transaction?address={address}&limit=20\n• TRC20 Transfers: /token_trc20/transfers?address={address}\n• Account Resources: /account/detail?address={address}\n\nExample:\nfetch('https://apilist.tronscanapi.com/api/account?address=TxxxXXXxxx')\n .then(r => r.json())\n .then(data => console.log('TRX Balance:', data.balance / 1e6));\n\nFALLBACK 1: TronGrid (Official)\n────────────────────────────────\nURL: https://api.trongrid.io\nFree: Public\nDocs: https://developers.tron.network/docs\n\nJSON-RPC Example:\nfetch('https://api.trongrid.io/wallet/getaccount', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n address: 'TxxxXXXxxx',\n visible: true\n })\n})\n\nFALLBACK 2: Tron Official API\n──────────────────────────────\nURL: https://api.tronstack.io\nFree: Public\nDocs: Similar to TronGrid\n\nFALLBACK 3: Blockchair (TRON)\n──────────────────────────────\nURL: https://api.blockchair.com/tron/dashboards/address/{address}\nFree: 1,440 req/day\nDocs: https://blockchair.com/api/docs\n\nFALLBACK 4: Tronscan API v2\n───────────────────────────\nURL: https://api.tronscan.org/api\nAlternative endpoint\nSimilar structure\n\nFALLBACK 5: GetBlock TRON\n─────────────────────────\nURL: https://go.getblock.io/tron\nFree tier available\nDocs: https://getblock.io/docs/\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 💰 MARKET DATA APIs - APIهای داده‌های بازار\n═══════════════════════════════════════════════════════════════════════════════════════\n\nCATEGORY 1: PRICE & MARKET CAP (15+ endpoints)\n───────────────────────────────────────────────\n\nPRIMARY: CoinGecko (FREE - بدون کلید)\n──────────────────────────────────────\nURL: https://api.coingecko.com/api/v3\nRate Limit: 10-50 calls/min (free)\nDocs: https://www.coingecko.com/en/api/documentation\n\nBest Endpoints:\n• Simple Price: /simple/price?ids=bitcoin,ethereum&vs_currencies=usd\n• Coin Data: /coins/{id}?localization=false\n• Market Chart: /coins/{id}/market_chart?vs_currency=usd&days=7\n• Global Data: /global\n• Trending: /search/trending\n• Categories: /coins/categories\n\nExample (Works Everywhere):\nfetch('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum,tron&vs_currencies=usd,eur')\n .then(r => r.json())\n .then(data => console.log(data));\n// Output: {bitcoin: {usd: 45000, eur: 42000}, ...}\n\nFALLBACK 1: CoinMarketCap (با کلید)\n─────────────────────────────────────\nURL: https://pro-api.coinmarketcap.com/v1\nKey 1: b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c\nKey 2: 04cf4b5b-9868-465c-8ba0-9f2e78c92eb1\nRate Limit: 333 calls/day (free)\nDocs: https://coinmarketcap.com/api/documentation/v1/\n\nEndpoints:\n• Latest Quotes: /cryptocurrency/quotes/latest?symbol=BTC,ETH\n• Listings: /cryptocurrency/listings/latest?limit=100\n• Market Pairs: /cryptocurrency/market-pairs/latest?id=1\n\nExample (Requires API Key in Header):\nfetch('https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=BTC', {\n headers: {\n 'X-CMC_PRO_API_KEY': 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c'\n }\n})\n.then(r => r.json())\n.then(data => console.log(data.data.BTC));\n\nWith CORS Proxy:\nconst proxy = 'https://proxy.cors.sh/';\nfetch(proxy + 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=BTC', {\n headers: {\n 'X-CMC_PRO_API_KEY': 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c',\n 'Origin': 'https://myapp.com'\n }\n})\n\nFALLBACK 2: CryptoCompare\n─────────────────────────\nURL: https://min-api.cryptocompare.com/data\nKey: e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f\nFree: 100K calls/month\nDocs: https://min-api.cryptocompare.com/documentation\n\nEndpoints:\n• Price Multi: /pricemulti?fsyms=BTC,ETH&tsyms=USD,EUR&api_key={KEY}\n• Historical: /v2/histoday?fsym=BTC&tsym=USD&limit=30&api_key={KEY}\n• Top Volume: /top/totalvolfull?limit=10&tsym=USD&api_key={KEY}\n\nFALLBACK 3: Coinpaprika (FREE)\n───────────────────────────────\nURL: https://api.coinpaprika.com/v1\nRate Limit: 20K calls/month\nDocs: https://api.coinpaprika.com/\n\nEndpoints:\n• Tickers: /tickers\n• Coin: /coins/btc-bitcoin\n• Historical: /coins/btc-bitcoin/ohlcv/historical\n\nFALLBACK 4: CoinCap (FREE)\n──────────────────────────\nURL: https://api.coincap.io/v2\nRate Limit: 200 req/min\nDocs: https://docs.coincap.io/\n\nEndpoints:\n• Assets: /assets\n• Specific: /assets/bitcoin\n• History: /assets/bitcoin/history?interval=d1\n\nFALLBACK 5: Nomics (FREE)\n─────────────────────────\nURL: https://api.nomics.com/v1\nNo Rate Limit on free tier\nDocs: https://p.nomics.com/cryptocurrency-bitcoin-api\n\nFALLBACK 6: Messari (FREE)\n──────────────────────────\nURL: https://data.messari.io/api/v1\nRate Limit: Generous\nDocs: https://messari.io/api/docs\n\nFALLBACK 7: CoinLore (FREE)\n────────────���──────────────\nURL: https://api.coinlore.net/api\nRate Limit: None\nDocs: https://www.coinlore.com/cryptocurrency-data-api\n\nFALLBACK 8: Binance Public API\n───────────────────────────────\nURL: https://api.binance.com/api/v3\nFree: بله\nDocs: https://binance-docs.github.io/apidocs/spot/en/\n\nEndpoints:\n• Price: /ticker/price?symbol=BTCUSDT\n• 24hr Stats: /ticker/24hr?symbol=ETHUSDT\n\nFALLBACK 9: CoinDesk API\n────────────────────────\nURL: https://api.coindesk.com/v1\nFree: Bitcoin price index\nDocs: https://www.coindesk.com/coindesk-api\n\nFALLBACK 10: Mobula API\n───────────────────────\nURL: https://api.mobula.io/api/1\nFree: 50% cheaper than CMC\nCoverage: 2.3M+ cryptocurrencies\nDocs: https://developer.mobula.fi/\n\nFALLBACK 11: Token Metrics API\n───────────────────────────────\nURL: https://api.tokenmetrics.com/v2\nFree API key available\nAI-driven insights\nDocs: https://api.tokenmetrics.com/docs\n\nFALLBACK 12: FreeCryptoAPI\n──────────────────────────\nURL: https://api.freecryptoapi.com\nFree: Beginner-friendly\nCoverage: 3,000+ coins\n\nFALLBACK 13: DIA Data\n─────────────────────\nURL: https://api.diadata.org/v1\nFree: Decentralized oracle\nTransparent pricing\nDocs: https://docs.diadata.org\n\nFALLBACK 14: Alternative.me\n───────────────────────────\nURL: https://api.alternative.me/v2\nFree: Price + Fear & Greed\nDocs: In API responses\n\nFALLBACK 15: CoinStats API\n──────────────────────────\nURL: https://api.coinstats.app/public/v1\nFree tier available\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 📰 NEWS & SOCIAL APIs - APIهای اخبار و شبکه‌های اجتماعی\n═══════════════════════════════════════════════════════════════════════════════════════\n\nCATEGORY 1: CRYPTO NEWS (10+ endpoints)\n────────────────────────────────────────\n\nPRIMARY: CryptoPanic (FREE)\n───────────────────────────\nURL: https://cryptopanic.com/api/v1\nFree: بله\nDocs: https://cryptopanic.com/developers/api/\n\nEndpoints:\n• Posts: /posts/?auth_token={TOKEN}&public=true\n• Currencies: /posts/?currencies=BTC,ETH\n• Filter: /posts/?filter=rising\n\nExample:\nfetch('https://cryptopanic.com/api/v1/posts/?public=true')\n .then(r => r.json())\n .then(data => console.log(data.results));\n\nFALLBACK 1: NewsAPI.org\n───────────────────────\nURL: https://newsapi.org/v2\nKey: pub_346789abc123def456789ghi012345jkl\nFree: 100 req/day\nDocs: https://newsapi.org/docs\n\nFALLBACK 2: CryptoControl\n─────────────────────────\nURL: https://cryptocontrol.io/api/v1/public\nFree tier available\nDocs: https://cryptocontrol.io/api\n\nFALLBACK 3: CoinDesk News\n─────────────────────────\nURL: https://www.coindesk.com/arc/outboundfeeds/rss/\nFree RSS feed\n\nFALLBACK 4: CoinTelegraph API\n─────────────────────────────\nURL: https://cointelegraph.com/api/v1\nFree: RSS and JSON feeds\n\nFALLBACK 5: CryptoSlate\n───────────────────────\nURL: https://cryptoslate.com/api\nFree: Limited\n\nFALLBACK 6: The Block API\n─────────────────────────\nURL: https://api.theblock.co/v1\nPremium service\n\nFALLBACK 7: Bitcoin Magazine RSS\n────────────────────────────────\nURL: https://bitcoinmagazine.com/.rss/full/\nFree RSS\n\nFALLBACK 8: Decrypt RSS\n───────────────────────\nURL: https://decrypt.co/feed\nFree RSS\n\nFALLBACK 9: Reddit Crypto\n─────────────────────────\nURL: https://www.reddit.com/r/CryptoCurrency/new.json\nFree: Public JSON\nLimit: 60 req/min\n\nExample:\nfetch('https://www.reddit.com/r/CryptoCurrency/hot.json?limit=25')\n .then(r => r.json())\n .then(data => console.log(data.data.children));\n\nFALLBACK 10: Twitter/X API (v2)\n───────────────────────────────\nURL: https://api.twitter.com/2\nRequires: OAuth 2.0\nFree tier: 1,500 tweets/month\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 😱 SENTIMENT & MOOD APIs - APIهای احساسات بازار\n═══════════════════════════════════════════════════════════════════════════════════════\n\nCATEGORY 1: FEAR & GREED INDEX (5+ endpoints)\n──────────────────────────────────────────────\n\nPRIMARY: Alternative.me (FREE)\n──────────────────────────────\nURL: https://api.alternative.me/fng/\nFree: بدون محدودیت\nDocs: https://alternative.me/crypto/fear-and-greed-index/\n\nEndpoints:\n• Current: /?limit=1\n• Historical: /?limit=30\n• Date Range: /?limit=10&date_format=world\n\nExample:\nfetch('https://api.alternative.me/fng/?limit=1')\n .then(r => r.json())\n .then(data => {\n const fng = data.data[0];\n console.log(`Fear & Greed: ${fng.value} - ${fng.value_classification}`);\n });\n// Output: \"Fear & Greed: 45 - Fear\"\n\nFALLBACK 1: LunarCrush\n──────────────────────\nURL: https://api.lunarcrush.com/v2\nFree tier: Limited\nDocs: https://lunarcrush.com/developers/api\n\nEndpoints:\n• Assets: ?data=assets&key={KEY}\n• Market: ?data=market&key={KEY}\n• Influencers: ?data=influencers&key={KEY}\n\nFALLBACK 2: Santiment (GraphQL)\n────────────────────────────────\nURL: https://api.santiment.net/graphql\nFree tier available\nDocs: https://api.santiment.net/graphiql\n\nGraphQL Example:\nquery {\n getMetric(metric: \"sentiment_balance_total\") {\n timeseriesData(\n slug: \"bitcoin\"\n from: \"2025-10-01T00:00:00Z\"\n to: \"2025-10-31T00:00:00Z\"\n interval: \"1d\"\n ) {\n datetime\n value\n }\n }\n}\n\nFALLBACK 3: TheTie.io\n─────────────────────\nURL: https://api.thetie.io\nPremium mainly\nDocs: https://docs.thetie.io\n\nFALLBACK 4: CryptoQuant\n───────────────────────\nURL: https://api.cryptoquant.com/v1\nFree tier: Limited\nDocs: https://docs.cryptoquant.com\n\nFALLBACK 5: Glassnode Social\n────────────────────────────\nURL: https://api.glassnode.com/v1/metrics/social\nFree tier: Limited\nDocs: https://docs.glassnode.com\n\nFALLBACK 6: Augmento (Social)\n──────────────────────────────\nURL: https://api.augmento.ai/v1\nAI-powered sentiment\nFree trial available\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 🐋 WHALE TRACKING APIs - APIهای ردیابی نهنگ‌ها\n═══════════════════════════════════════════════════════════════════════════════════════\n\nCATEGORY 1: WHALE TRANSACTIONS (8+ endpoints)\n──────────────────────────────────────────────\n\nPRIMARY: Whale Alert\n────────────────────\nURL: https://api.whale-alert.io/v1\nFree: Limited (7-day trial)\nPaid: From $20/month\nDocs: https://docs.whale-alert.io\n\nEndpoints:\n• Transactions: /transactions?api_key={KEY}&min_value=1000000&start={timestamp}&end={timestamp}\n• Status: /status?api_key={KEY}\n\nExample:\nconst start = Math.floor(Date.now()/1000) - 3600; // 1 hour ago\nconst end = Math.floor(Date.now()/1000);\nfetch(`https://api.whale-alert.io/v1/transactions?api_key=YOUR_KEY&min_value=1000000&start=${start}&end=${end}`)\n .then(r => r.json())\n .then(data => {\n data.transactions.forEach(tx => {\n console.log(`${tx.amount} ${tx.symbol} from ${tx.from.owner} to ${tx.to.owner}`);\n });\n });\n\nFALLBACK 1: ClankApp (FREE)\n───────────────────────────\nURL: https://clankapp.com/api\nFree: بله\nTelegram: @clankapp\nTwitter: @ClankApp\nDocs: https://clankapp.com/api/\n\nFeatures:\n• 24 blockchains\n• Real-time whale alerts\n• Email & push notifications\n• No API key needed\n\nExample:\nfetch('https://clankapp.com/api/whales/recent')\n .then(r => r.json())\n .then(data => console.log(data));\n\nFALLBACK 2: BitQuery Whale Tracking\n────────────────────────────────────\nURL: https://graphql.bitquery.io\nFree: 10K queries/month\nDocs: https://docs.bitquery.io\n\nGraphQL Example (Large ETH Transfers):\n{\n ethereum(network: ethereum) {\n transfers(\n amount: {gt: 1000}\n currency: {is: \"ETH\"}\n date: {since: \"2025-10-25\"}\n ) {\n block { timestamp { time } }\n sender { address }\n receiver { address }\n amount\n transaction { hash }\n }\n }\n}\n\nFALLBACK 3: Arkham Intelligence\n────────────────────────────────\nURL: https://api.arkham.com\nPaid service mainly\nDocs: https://docs.arkham.com\n\nFALLBACK 4: Nansen\n──────────────────\nURL: https://api.nansen.ai/v1\nPremium: Expensive but powerful\nDocs: https://docs.nansen.ai\n\nFeatures:\n• Smart Money tracking\n• Wallet labeling\n• Multi-chain support\n\nFALLBACK 5: DexCheck Whale Tracker\n───────────────────────────────────\nFree wallet tracking feature\n22 chains supported\nTelegram bot integration\n\nFALLBACK 6: DeBank\n──────────────────\nURL: https://api.debank.com\nFree: Portfolio tracking\nWeb3 social features\n\nFALLBACK 7: Zerion API\n──────────────────────\nURL: https://api.zerion.io\nSimilar to DeBank\nDeFi portfolio tracker\n\nFALLBACK 8: Whalemap\n────────────────────\nURL: https://whalemap.io\nBitcoin & ERC-20 focus\nCharts and analytics\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 🔍 ON-CHAIN ANALYTICS APIs - APIهای تحلیل زنجیره\n═══════════════════════════════════════════════════════════════════════════════════════\n\nCATEGORY 1: BLOCKCHAIN DATA (10+ endpoints)\n────────────────────────────────────────────\n\nPRIMARY: The Graph (Subgraphs)\n──────────────────────────────\nURL: https://api.thegraph.com/subgraphs/name/{org}/{subgraph}\nFree: Public subgraphs\nDocs: https://thegraph.com/docs/\n\nPopular Subgraphs:\n• Uniswap V3: /uniswap/uniswap-v3\n• Aave V2: /aave/protocol-v2\n• Compound: /graphprotocol/compound-v2\n\nExample (Uniswap V3):\nfetch('https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n query: `{\n pools(first: 5, orderBy: volumeUSD, orderDirection: desc) {\n id\n token0 { symbol }\n token1 { symbol }\n volumeUSD\n }\n }`\n })\n})\n\nFALLBACK 1: Glassnode\n─────────────────────\nURL: https://api.glassnode.com/v1\nFree tier: Limited metrics\nDocs: https://docs.glassnode.com\n\nEndpoints:\n• SOPR: /metrics/indicators/sopr?a=BTC&api_key={KEY}\n• HODL Waves: /metrics/supply/hodl_waves?a=BTC&api_key={KEY}\n\nFALLBACK 2: IntoTheBlock\n────────────────────────\nURL: https://api.intotheblock.com/v1\nFree tier available\nDocs: https://developers.intotheblock.com\n\nFALLBACK 3: Dune Analytics\n──────────────────────────\nURL: https://api.dune.com/api/v1\nFree: Query results\nDocs: https://docs.dune.com/api-reference/\n\nFALLBACK 4: Covalent\n────────────────────\nURL: https://api.covalenthq.com/v1\nFree tier: 100K credits\nMulti-chain support\nDocs: https://www.covalenthq.com/docs/api/\n\nExample (Ethereum balances):\nfetch('https://api.covalenthq.com/v1/1/address/0x.../balances_v2/?key=YOUR_KEY')\n\nFALLBACK 5: Moralis\n───────────────────\nURL: https://deep-index.moralis.io/api/v2\nFree: 100K compute units/month\nDocs: https://docs.moralis.io\n\nFALLBACK 6: Alchemy NFT API\n───────────────────────────\nIncluded with Alchemy account\nNFT metadata & transfers\n\nFALLBACK 7: QuickNode Functions\n────────────────────────────────\nCustom on-chain queries\nToken balances, NFTs\n\nFALLBACK 8: Transpose\n─────────────────────\nURL: https://api.transpose.io\nFree tier available\nSQL-like queries\n\nFALLBACK 9: Footprint Analytics\n────────────────────────────────\nURL: https://api.footprint.network\nFree: Community tier\nNo-code analytics\n\nFALLBACK 10: Nansen Query\n─────────────────────────\nPremium institutional tool\nAdvanced on-chain intelligence\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 🔧 COMPLETE JAVASCRIPT IMPLEMENTATION\n پیاده‌سازی کامل جاوااسکریپت\n═══════════════════════════════════════════════════════════════════════════════════════\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// CONFIG.JS - تنظیمات مرکزی API\n// ═══════════════════════════════════════════════════════════════════════════════\n\nconst API_CONFIG = {\n // CORS Proxies (پروکسی‌های CORS)\n corsProxies: [\n 'https://api.allorigins.win/get?url=',\n 'https://proxy.cors.sh/',\n 'https://proxy.corsfix.com/?url=',\n 'https://api.codetabs.com/v1/proxy?quest=',\n 'https://thingproxy.freeboard.io/fetch/'\n ],\n \n // Block Explorers (کاوشگرهای بلاکچین)\n explorers: {\n ethereum: {\n primary: {\n name: 'etherscan',\n baseUrl: 'https://api.etherscan.io/api',\n key: 'SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2',\n rateLimit: 5 // calls per second\n },\n fallbacks: [\n { name: 'etherscan2', baseUrl: 'https://api.etherscan.io/api', key: 'T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45' },\n { name: 'blockchair', baseUrl: 'https://api.blockchair.com/ethereum', key: '' },\n { name: 'blockscout', baseUrl: 'https://eth.blockscout.com/api', key: '' },\n { name: 'ethplorer', baseUrl: 'https://api.ethplorer.io', key: 'freekey' }\n ]\n },\n bsc: {\n primary: {\n name: 'bscscan',\n baseUrl: 'https://api.bscscan.com/api',\n key: 'K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT',\n rateLimit: 5\n },\n fallbacks: [\n { name: 'blockchair', baseUrl: 'https://api.blockchair.com/binance-smart-chain', key: '' },\n { name: 'bitquery', baseUrl: 'https://graphql.bitquery.io', key: '', method: 'graphql' }\n ]\n },\n tron: {\n primary: {\n name: 'tronscan',\n baseUrl: 'https://apilist.tronscanapi.com/api',\n key: '7ae72726-bffe-4e74-9c33-97b761eeea21',\n rateLimit: 10\n },\n fallbacks: [\n { name: 'trongrid', baseUrl: 'https://api.trongrid.io', key: '' },\n { name: 'tronstack', baseUrl: 'https://api.tronstack.io', key: '' },\n { name: 'blockchair', baseUrl: 'https://api.blockchair.com/tron', key: '' }\n ]\n }\n },\n \n // Market Data (داده‌های بازار)\n marketData: {\n primary: {\n name: 'coingecko',\n baseUrl: 'https://api.coingecko.com/api/v3',\n key: '', // بدون کلید\n needsProxy: false,\n rateLimit: 50 // calls per minute\n },\n fallbacks: [\n { \n name: 'coinmarketcap', \n baseUrl: 'https://pro-api.coinmarketcap.com/v1',\n key: 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c',\n headerKey: 'X-CMC_PRO_API_KEY',\n needsProxy: true\n },\n { \n name: 'coinmarketcap2', \n baseUrl: 'https://pro-api.coinmarketcap.com/v1',\n key: '04cf4b5b-9868-465c-8ba0-9f2e78c92eb1',\n headerKey: 'X-CMC_PRO_API_KEY',\n needsProxy: true\n },\n { name: 'coincap', baseUrl: 'https://api.coincap.io/v2', key: '' },\n { name: 'coinpaprika', baseUrl: 'https://api.coinpaprika.com/v1', key: '' },\n { name: 'binance', baseUrl: 'https://api.binance.com/api/v3', key: '' },\n { name: 'coinlore', baseUrl: 'https://api.coinlore.net/api', key: '' }\n ]\n },\n \n // RPC Nodes (نودهای RPC)\n rpcNodes: {\n ethereum: [\n 'https://eth.llamarpc.com',\n 'https://ethereum.publicnode.com',\n 'https://cloudflare-eth.com',\n 'https://rpc.ankr.com/eth',\n 'https://eth.drpc.org'\n ],\n bsc: [\n 'https://bsc-dataseed.binance.org',\n 'https://bsc-dataseed1.defibit.io',\n 'https://rpc.ankr.com/bsc',\n 'https://bsc-rpc.publicnode.com'\n ],\n polygon: [\n 'https://polygon-rpc.com',\n 'https://rpc.ankr.com/polygon',\n 'https://polygon-bor-rpc.publicnode.com'\n ]\n },\n \n // News Sources (منابع خبری)\n news: {\n primary: {\n name: 'cryptopanic',\n baseUrl: 'https://cryptopanic.com/api/v1',\n key: '',\n needsProxy: false\n },\n fallbacks: [\n { name: 'reddit', baseUrl: 'https://www.reddit.com/r/CryptoCurrency', key: '' }\n ]\n },\n \n // Sentiment (احساسات)\n sentiment: {\n primary: {\n name: 'alternative.me',\n baseUrl: 'https://api.alternative.me/fng',\n key: '',\n needsProxy: false\n }\n },\n \n // Whale Tracking (ردیابی نهنگ)\n whaleTracking: {\n primary: {\n name: 'clankapp',\n baseUrl: 'https://clankapp.com/api',\n key: '',\n needsProxy: false\n }\n }\n};\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// API-CLIENT.JS - کلاینت API با مدیریت خطا و fallback\n// ═══════════════════════════════════════════════════════════════════════════════\n\nclass CryptoAPIClient {\n constructor(config) {\n this.config = config;\n this.currentProxyIndex = 0;\n this.requestCache = new Map();\n this.cacheTimeout = 60000; // 1 minute\n }\n \n // استفاده از CORS Proxy\n async fetchWithProxy(url, options = {}) {\n const proxies = this.config.corsProxies;\n \n for (let i = 0; i < proxies.length; i++) {\n const proxyUrl = proxies[this.currentProxyIndex] + encodeURIComponent(url);\n \n try {\n console.log(`🔄 Trying proxy ${this.currentProxyIndex + 1}/${proxies.length}`);\n \n const response = await fetch(proxyUrl, {\n ...options,\n headers: {\n ...options.headers,\n 'Origin': window.location.origin,\n 'x-requested-with': 'XMLHttpRequest'\n }\n });\n \n if (response.ok) {\n const data = await response.json();\n // Handle allOrigins response format\n return data.contents ? JSON.parse(data.contents) : data;\n }\n } catch (error) {\n console.warn(`❌ Proxy ${this.currentProxyIndex + 1} failed:`, error.message);\n }\n \n // Switch to next proxy\n this.currentProxyIndex = (this.currentProxyIndex + 1) % proxies.length;\n }\n \n throw new Error('All CORS proxies failed');\n }\n \n // بدون پروکسی\n async fetchDirect(url, options = {}) {\n try {\n const response = await fetch(url, options);\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n return await response.json();\n } catch (error) {\n throw new Error(`Direct fetch failed: ${error.message}`);\n }\n }\n \n // با cache و fallback\n async fetchWithFallback(primaryConfig, fallbacks, endpoint, params = {}) {\n const cacheKey = `${primaryConfig.name}-${endpoint}-${JSON.stringify(params)}`;\n \n // Check cache\n if (this.requestCache.has(cacheKey)) {\n const cached = this.requestCache.get(cacheKey);\n if (Date.now() - cached.timestamp < this.cacheTimeout) {\n console.log('📦 Using cached data');\n return cached.data;\n }\n }\n \n // Try primary\n try {\n const data = await this.makeRequest(primaryConfig, endpoint, params);\n this.requestCache.set(cacheKey, { data, timestamp: Date.now() });\n return data;\n } catch (error) {\n console.warn('⚠️ Primary failed, trying fallbacks...', error.message);\n }\n \n // Try fallbacks\n for (const fallback of fallbacks) {\n try {\n console.log(`🔄 Trying fallback: ${fallback.name}`);\n const data = await this.makeRequest(fallback, endpoint, params);\n this.requestCache.set(cacheKey, { data, timestamp: Date.now() });\n return data;\n } catch (error) {\n console.warn(`❌ Fallback ${fallback.name} failed:`, error.message);\n }\n }\n \n throw new Error('All endpoints failed');\n }\n \n // ساخت درخواست\n async makeRequest(apiConfig, endpoint, params = {}) {\n let url = `${apiConfig.baseUrl}${endpoint}`;\n \n // Add query params\n const queryParams = new URLSearchParams();\n if (apiConfig.key) {\n queryParams.append('apikey', apiConfig.key);\n }\n Object.entries(params).forEach(([key, value]) => {\n queryParams.append(key, value);\n });\n \n if (queryParams.toString()) {\n url += '?' + queryParams.toString();\n }\n \n const options = {};\n \n // Add headers if needed\n if (apiConfig.headerKey && apiConfig.key) {\n options.headers = {\n [apiConfig.headerKey]: apiConfig.key\n };\n }\n \n // Use proxy if needed\n if (apiConfig.needsProxy) {\n return await this.fetchWithProxy(url, options);\n } else {\n return await this.fetchDirect(url, options);\n }\n }\n \n // ═══════════════ SPECIFIC API METHODS ═══════════════\n \n // Get ETH Balance (با fallback)\n async getEthBalance(address) {\n const { ethereum } = this.config.explorers;\n return await this.fetchWithFallback(\n ethereum.primary,\n ethereum.fallbacks,\n '',\n {\n module: 'account',\n action: 'balance',\n address: address,\n tag: 'latest'\n }\n );\n }\n \n // Get BTC Price (multi-source)\n async getBitcoinPrice() {\n const { marketData } = this.config;\n \n try {\n // Try CoinGecko first (no key needed, no CORS)\n const data = await this.fetchDirect(\n `${marketData.primary.baseUrl}/simple/price?ids=bitcoin&vs_currencies=usd,eur`\n );\n return {\n source: 'CoinGecko',\n usd: data.bitcoin.usd,\n eur: data.bitcoin.eur\n };\n } catch (error) {\n // Fallback to Binance\n try {\n const data = await this.fetchDirect(\n 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT'\n );\n return {\n source: 'Binance',\n usd: parseFloat(data.price),\n eur: null\n };\n } catch (err) {\n throw new Error('All price sources failed');\n }\n }\n }\n \n // Get Fear & Greed Index\n async getFearGreed() {\n const url = `${this.config.sentiment.primary.baseUrl}/?limit=1`;\n const data = await this.fetchDirect(url);\n return {\n value: parseInt(data.data[0].value),\n classification: data.data[0].value_classification,\n timestamp: new Date(parseInt(data.data[0].timestamp) * 1000)\n };\n }\n \n // Get Trending Coins\n async getTrendingCoins() {\n const url = `${this.config.marketData.primary.baseUrl}/search/trending`;\n const data = await this.fetchDirect(url);\n return data.coins.map(item => ({\n id: item.item.id,\n name: item.item.name,\n symbol: item.item.symbol,\n rank: item.item.market_cap_rank,\n thumb: item.item.thumb\n }));\n }\n \n // Get Crypto News\n async getCryptoNews(limit = 10) {\n const url = `${this.config.news.primary.baseUrl}/posts/?public=true`;\n const data = await this.fetchDirect(url);\n return data.results.slice(0, limit).map(post => ({\n title: post.title,\n url: post.url,\n source: post.source.title,\n published: new Date(post.published_at)\n }));\n }\n \n // Get Recent Whale Transactions\n async getWhaleTransactions() {\n try {\n const url = `${this.config.whaleTracking.primary.baseUrl}/whales/recent`;\n return await this.fetchDirect(url);\n } catch (error) {\n console.warn('Whale API not available');\n return [];\n }\n }\n \n // Multi-source price aggregator\n async getAggregatedPrice(symbol) {\n const sources = [\n {\n name: 'CoinGecko',\n fetch: async () => {\n const data = await this.fetchDirect(\n `${this.config.marketData.primary.baseUrl}/simple/price?ids=${symbol}&vs_currencies=usd`\n );\n return data[symbol]?.usd;\n }\n },\n {\n name: 'Binance',\n fetch: async () => {\n const data = await this.fetchDirect(\n `https://api.binance.com/api/v3/ticker/price?symbol=${symbol.toUpperCase()}USDT`\n );\n return parseFloat(data.price);\n }\n },\n {\n name: 'CoinCap',\n fetch: async () => {\n const data = await this.fetchDirect(\n `https://api.coincap.io/v2/assets/${symbol}`\n );\n return parseFloat(data.data.priceUsd);\n }\n }\n ];\n \n const prices = await Promise.allSettled(\n sources.map(async source => ({\n source: source.name,\n price: await source.fetch()\n }))\n );\n \n const successful = prices\n .filter(p => p.status === 'fulfilled')\n .map(p => p.value);\n \n if (successful.length === 0) {\n throw new Error('All price sources failed');\n }\n \n const avgPrice = successful.reduce((sum, p) => sum + p.price, 0) / successful.length;\n \n return {\n symbol,\n sources: successful,\n average: avgPrice,\n spread: Math.max(...successful.map(p => p.price)) - Math.min(...successful.map(p => p.price))\n };\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// USAGE EXAMPLES - مثال‌های استفاده\n// ═══════════════════════════════════════════════════════════════════════════════\n\n// Initialize\nconst api = new CryptoAPIClient(API_CONFIG);\n\n// Example 1: Get Ethereum Balance\nasync function example1() {\n try {\n const address = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';\n const balance = await api.getEthBalance(address);\n console.log('ETH Balance:', parseInt(balance.result) / 1e18);\n } catch (error) {\n console.error('Error:', error.message);\n }\n}\n\n// Example 2: Get Bitcoin Price from Multiple Sources\nasync function example2() {\n try {\n const price = await api.getBitcoinPrice();\n console.log(`BTC Price (${price.source}): $${price.usd}`);\n } catch (error) {\n console.error('Error:', error.message);\n }\n}\n\n// Example 3: Get Fear & Greed Index\nasync function example3() {\n try {\n const fng = await api.getFearGreed();\n console.log(`Fear & Greed: ${fng.value} (${fng.classification})`);\n } catch (error) {\n console.error('Error:', error.message);\n }\n}\n\n// Example 4: Get Trending Coins\nasync function example4() {\n try {\n const trending = await api.getTrendingCoins();\n console.log('Trending Coins:');\n trending.forEach((coin, i) => {\n console.log(`${i + 1}. ${coin.name} (${coin.symbol})`);\n });\n } catch (error) {\n console.error('Error:', error.message);\n }\n}\n\n// Example 5: Get Latest News\nasync function example5() {\n try {\n const news = await api.getCryptoNews(5);\n console.log('Latest News:');\n news.forEach((article, i) => {\n console.log(`${i + 1}. ${article.title} - ${article.source}`);\n });\n } catch (error) {\n console.error('Error:', error.message);\n }\n}\n\n// Example 6: Aggregate Price from Multiple Sources\nasync function example6() {\n try {\n const priceData = await api.getAggregatedPrice('bitcoin');\n console.log('Price Sources:');\n priceData.sources.forEach(s => {\n console.log(`- ${s.source}: $${s.price.toFixed(2)}`);\n });\n console.log(`Average: $${priceData.average.toFixed(2)}`);\n console.log(`Spread: $${priceData.spread.toFixed(2)}`);\n } catch (error) {\n console.error('Error:', error.message);\n }\n}\n\n// Example 7: Dashboard - All Data\nasync function dashboardExample() {\n console.log('🚀 Loading Crypto Dashboard...\\n');\n \n try {\n // Price\n const btcPrice = await api.getBitcoinPrice();\n console.log(`💰 BTC: $${btcPrice.usd.toLocaleString()}`);\n \n // Fear & Greed\n const fng = await api.getFearGreed();\n console.log(`😱 Fear & Greed: ${fng.value} (${fng.classification})`);\n \n // Trending\n const trending = await api.getTrendingCoins();\n console.log(`\\n🔥 Trending:`);\n trending.slice(0, 3).forEach((coin, i) => {\n console.log(` ${i + 1}. ${coin.name}`);\n });\n \n // News\n const news = await api.getCryptoNews(3);\n console.log(`\\n📰 Latest News:`);\n news.forEach((article, i) => {\n console.log(` ${i + 1}. ${article.title.substring(0, 50)}...`);\n });\n \n } catch (error) {\n console.error('Dashboard Error:', error.message);\n }\n}\n\n// Run examples\nconsole.log('═══════════════════════════════════════');\nconsole.log(' CRYPTO API CLIENT - TEST SUITE');\nconsole.log('═══════════════════════════════════════\\n');\n\n// Uncomment to run specific examples:\n// example1();\n// example2();\n// example3();\n// example4();\n// example5();\n// example6();\ndashboardExample();\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 📝 QUICK REFERENCE - مرجع سریع\n═══════════════════════════════════════════════════════════════════════════════════════\n\nBEST FREE APIs (بهترین APIهای رایگان):\n─────────────────────────────────────────\n\n✅ PRICES & MARKET DATA:\n 1. CoinGecko (بدون کلید، بدون CORS)\n 2. Binance Public API (بدون کلید)\n 3. CoinCap (بدون کلید)\n 4. CoinPaprika (بدون کلید)\n\n✅ BLOCK EXPLORERS:\n 1. Blockchair (1,440 req/day)\n 2. BlockScout (بدون محدودیت)\n 3. Public RPC nodes (various)\n\n✅ NEWS:\n 1. CryptoPanic (بدون کلید)\n 2. Reddit JSON API (60 req/min)\n\n✅ SENTIMENT:\n 1. Alternative.me F&G (بدون محدودیت)\n\n✅ WHALE TRACKING:\n 1. ClankApp (بدون کلید)\n 2. BitQuery GraphQL (10K/month)\n\n✅ RPC NODES:\n 1. PublicNode (همه شبکه‌ها)\n 2. Ankr (عمومی)\n 3. LlamaNodes (بدون ثبت‌نام)\n\n\nRATE LIMIT STRATEGIES (استراتژی‌های محدودیت):\n───────────────────────────────────────────────\n\n1. کش کردن (Caching):\n - ذخیره نتایج برای 1-5 دقیقه\n - استفاده از localStorage برای کش مرورگر\n\n2. چرخش کلید (Key Rotation):\n - استفاده از چندین کلید API\n - تعویض خودکار در صورت محدودیت\n\n3. Fallback Chain:\n - Primary → Fallback1 → Fallback2\n - تا 5-10 جایگزین برای هر سرویس\n\n4. Request Queuing:\n - صف بندی درخواست‌ها\n - تاخیر بین درخواست‌ها\n\n5. Multi-Source Aggregation:\n - دریافت از چند منبع همزمان\n - میانگین گیری نتایج\n\n\nERROR HANDLING (مدیریت خطا):\n──────────────────────────────\n\ntry {\n const data = await api.fetchWithFallback(primary, fallbacks, endpoint, params);\n} catch (error) {\n if (error.message.includes('rate limit')) {\n // Switch to fallback\n } else if (error.message.includes('CORS')) {\n // Use CORS proxy\n } else {\n // Show error to user\n }\n}\n\n\nDEPLOYMENT TIPS (نکات استقرار):\n─────────────────────────────────\n\n1. Backend Proxy (توصیه می‌شود):\n - Node.js/Express proxy server\n - Cloudflare Worker\n - Vercel Serverless Function\n\n2. Environment Variables:\n - ذخیره کلیدها در .env\n - عدم نمایش در کد فرانت‌اند\n\n3. Rate Limiting:\n - محدودسازی درخواست کاربر\n - استفاده از Redis برای کنترل\n\n4. Monitoring:\n - لاگ گرفتن از خطاها\n - ردیابی استفاده از API\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n 🔗 USEFUL LINKS - لینک‌های مفید\n═══════════════════════════════════════════════════════════════════════════════════════\n\nDOCUMENTATION:\n• CoinGecko API: https://www.coingecko.com/api/documentation\n• Etherscan API: https://docs.etherscan.io\n• BscScan API: https://docs.bscscan.com\n• TronGrid: https://developers.tron.network\n• Alchemy: https://docs.alchemy.com\n• Infura: https://docs.infura.io\n• The Graph: https://thegraph.com/docs\n• BitQuery: https://docs.bitquery.io\n\nCORS PROXY ALTERNATIVES:\n• CORS Anywhere: https://github.com/Rob--W/cors-anywhere\n• AllOrigins: https://github.com/gnuns/allOrigins\n• CORS.SH: https://cors.sh\n• Corsfix: https://corsfix.com\n\nRPC LISTS:\n• ChainList: https://chainlist.org\n• Awesome RPC: https://github.com/arddluma/awesome-list-rpc-nodes-providers\n\nTOOLS:\n• Postman: https://www.postman.com\n• Insomnia: https://insomnia.rest\n• GraphiQL: https://graphiql-online.com\n\n\n═══════════════════════════════════════════════════════════════════════════════════════\n ⚠️ IMPORTANT NOTES - نکات مهم\n═══════════════════════════════════════════════════════════════════════════════════════\n\n1. ⚠️ NEVER expose API keys in frontend code\n - همیشه از backend proxy استفاده کنید\n - کلیدها را در environment variables ذخیره کنید\n\n2. 🔄 Always implement fallbacks\n - حداقل 2-3 جایگزین برای هر سرویس\n - تست منظم fallbackها\n\n3. 💾 Cache responses when possible\n - صرفه‌جویی در استفاده از API\n - سرعت بیشتر برای کاربر\n\n4. 📊 Monitor API usage\n - ردیابی تعداد درخواست‌ها\n - هشدار قبل از رسیدن به محدودیت\n\n5. 🔐 Secure your endpoints\n - محدودسازی domain\n - استفاده از CORS headers\n - Rate limiting برای کاربران\n\n6. 🌐 Test with and without CORS proxies\n - برخی APIها CORS را پشتیبانی می‌کنند\n - استفاده از پروکسی فقط در صورت نیاز\n\n7. 📱 Mobile-friendly implementations\n - بهینه‌سازی برای شبکه‌های ضعیف\n - کاهش اندازه درخواست‌ها\n\n\n═════════════════════��═════════════════════════════════════════════════════════════════\n END OF CONFIGURATION FILE\n پایان فایل تنظیمات\n═══════════════════════════════════════════════════════════════════════════════════════\n\nLast Updated: October 31, 2025\nVersion: 2.0\nAuthor: AI Assistant\nLicense: Free to use\n\nFor updates and more resources, check:\n- GitHub: Search for \"awesome-crypto-apis\"\n- Reddit: r/CryptoCurrency, r/ethdev\n- Discord: Web3 developer communities"
17
+ },
18
+ {
19
+ "filename": "api - Copy.txt",
20
+ "content": "\n tronscan\n7ae72726-bffe-4e74-9c33-97b761eeea21\t\n\nBscscan\t\nK62RKHGXTDCG53RU4MCG6XABIMJKTN19IT\n\neherscann_2\t\nT6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45\n\neherscann\t\nSZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2\n\ncoinmarketcap\n04cf4b5b-9868-465c-8ba0-9f2e78c92eb1\n\n\nCOINMARKETCAP_KEY:\nb54bcf4d-1bca-4e8e-9a24-22ff2c3d462c\n\nNEWSAPI_KEY: \npub_346789abc123def456789ghi012345jkl\n\nCRYPTOCOMPARE_KEY:\ne79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f\n\n\nدر ادامه ۳۸ سرویس (primary + fallbacks) که قبلاً در حافظه داشتیم را دسته‌بندی و لیست کرده‌ام، و روش‌های ردیابی فعالیت نهنگ‌ها را هم به بخش مربوط اضافه کردم. این لیست در حافظه ذخیره شد.\n\n۱. Block Explorer APIs (۱۱ endpoint)\nTronScan (primary)\n\nTronGrid (fallback)\n\nBlockchair (TRON) (fallback)\n\nBscScan (primary)\n\nAnkrScan (BSC) (fallback)\n\nBinTools (BSC) (fallback)\n\nEtherscan (primary)\n\nEtherscan (backup key) (fallback)\n\nInfura (ETH) (fallback)\n\nAlchemy (ETH) (fallback)\n\nCovalent (ETH) (fallback)\n\n۲. Market Data APIs (۹ endpoint)\nCoinMarketCap (primary key #1)\n\nCoinMarketCap (primary key #2)\n\nCoinGecko (no key)\n\nNomics\n\nMessari\n\nBraveNewCoin\n\nCryptoCompare (primary)\n\nKaiko (fallback)\n\nCoinAPI.io (fallback)\n\n۳. News APIs (۷ endpoint)\nNewsAPI.org\n\nCryptoPanic\n\nCryptoControl\n\nCoinDesk API\n\nCoinTelegraph API\n\nCryptoSlate API\n\nThe Block API\n\n۴. Sentiment & Mood APIs (۴ endpoint)\nAlternative.me (Fear & Greed)\n\nSantiment\n\nLunarCrush\n\nTheTie.io\n\n۵. On-Chain Analytics APIs (۴ endpoint)\nGlassnode\n\nIntoTheBlock\n\nNansen\n\nThe Graph (subgraphs)\n\n۶. Whale-Tracking APIs (۲ endpoint)\nWhaleAlert (primary)\n\nArkham Intelligence (fallback)\n\nروش‌های ردیابی فعالیت نهنگ‌ها\nپویش تراکنش‌های بزرگ\n\nبا WhaleAlert هر X ثانیه، endpoint /v1/transactions رو poll کن و فقط TX با مقدار دلخواه (مثلاً >۱M دلار) رو نمایش بده.\n\nوبهوک/نوتیفیکیشن\n\nاز قابلیت Webhook در WhaleAlert یا Arkham استفاده کن تا به‌محض رخداد تراکنش بزرگ، درخواست POST بیاد.\n\nفیلتر مستقیم روی WebSocket\n\nاگر Infura/Alchemy یا BscScan WebSocket دارن، به mempool گوش بده و TXهایی با حجم بالا رو فیلتر کن.\n\nداشبورد نهنگ‌ها از Nansen یا Dune\n\nاز Nansen Alerts یا کوئری‌های Dune برای رصد کیف‌پول‌های شناخته‌شده (smart money) و انتقالاتشان استفاده کن.\n\nنقشه حرارتی (Heatmap) تراکنش‌ها\n\nداده‌های WhaleAlert رو در یک نمودار خطی یا نقشه پخش جغرافیایی (اگر GPS دارن) نمایش بده.\n\n۷. Community Sentiment (۱ endpoint)\nReddit\n\n\n\nBlock Explorer APIs (۱۱ سرویس) \nسرویس\tAPI واقعی\tشرح\tنحوهٔ پیاده‌سازی\nTronScan\tGET https://api.tronscan.org/api/account?address={address}&apiKey={KEY}\tجزئیات حساب و موجودی Tron\tfetch(url)، پارس JSON، نمایش balance\nTronGrid\tGET https://api.trongrid.io/v1/accounts/{address}?apiKey={KEY}\tهمان عملکرد TronScan با endpoint متفاوت\tمشابه fetch با URL جدید\nBlockchair\tGET https://api.blockchair.com/tron/dashboards/address/{address}?key={KEY}\tداشبورد آدرس TRON\tfetch(url)، استفاده از data.address\nBscScan\tGET https://api.bscscan.com/api?module=account&action=balance&address={address}&apikey={KEY}\tموجودی حساب BSC\tfetch(url)، نمایش result\nAnkrScan\tGET https://api.ankr.com/scan/v1/bsc/address/{address}/balance?apiKey={KEY}\tموجودی از API آنکر\tfetch(url)، پارس JSON\nBinTools\tGET https://api.bintools.io/v1/bsc/account/balance?address={address}&apikey={KEY}\tجایگزین BscScan\tمشابه fetch\nEtherscan\tGET https://api.etherscan.io/api?module=account&action=balance&address={address}&apikey={KEY}\tموجودی حساب ETH\tfetch(url)، نمایش result\nEtherscan_2\tGET https://api.etherscan.io/api?module=account&action=balance&address={address}&apikey={SECOND_KEY}\tدومین کلید Etherscan\tهمانند بالا\nInfura\tJSON-RPC POST به https://mainnet.infura.io/v3/{PROJECT_ID} با بدنه { \"jsonrpc\":\"2.0\",\"method\":\"eth_getBalance\",\"params\":[\"{address}\",\"latest\"],\"id\":1 }\tاستعلام موجودی از طریق RPC\tfetch(url, {method:'POST', body:JSON.stringify(...)})\nAlchemy\tJSON-RPC POST به https://eth-mainnet.alchemyapi.io/v2/{KEY} همانند Infura\tاستعلام RPC با سرعت و WebSocket\tWebSocket: new WebSocket('wss://eth-mainnet.alchemyapi.io/v2/{KEY}')\nCovalent\tGET https://api.covalenthq.com/v1/1/address/{address}/balances_v2/?key={KEY}\tلیست دارایی‌های یک آدرس در شبکه Ethereum\tfetch(url), پارس data.items\n\n۲. Market Data APIs (۹ سرویس) \nسرویس\tAPI واقعی\tشرح\tنحوهٔ پیاده‌سازی\nCoinMarketCap\tGET https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=BTC&convert=USD<br/>Header: X-CMC_PRO_API_KEY: {KEY}\tقیمت لحظه‌ای و تغییرات درصدی\tfetch(url,{headers:{'X-CMC_PRO_API_KEY':KEY}})\nCMC_Alt\tهمان endpoint بالا با کلید دوم\tکلید جایگزین CMC\tمانند بالا\nCoinGecko\tGET https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd\tبدون نیاز به کلید، قیمت ساده\tfetch(url)\nNomics\tGET https://api.nomics.com/v1/currencies/ticker?key={KEY}&ids=BTC,ETH&convert=USD\tقیمت و حجم معاملات\tfetch(url)\nMessari\tGET https://data.messari.io/api/v1/assets/bitcoin/metrics\tمتریک‌های پیشرفته (TVL، ROI و…)\tfetch(url)\nBraveNewCoin\tGET https://bravenewcoin.p.rapidapi.com/ohlcv/BTC/latest<br/>Headers: x-rapidapi-key: {KEY}\tقیمت OHLCV لحظه‌ای\tfetch(url,{headers:{…}})\nCryptoCompare\tGET https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH&tsyms=USD&api_key={KEY}\tقیمت چندگانه کریپто\tfetch(url)\nKaiko\tGET https://us.market-api.kaiko.io/v2/data/trades.v1/exchanges/Coinbase/spot/trades?base_token=BTC&quote_token=USD&page_limit=10&api_key={KEY}\tدیتای تریدهای زنده\tfetch(url)\nCoinAPI.io\tGET https://rest.coinapi.io/v1/exchangerate/BTC/USD?apikey={KEY}\tنرخ تبدیل بین رمزارز و فیات\tfetch(url)\n\n۳. News & Aggregators (۷ سرویس) \nسرویس\tAPI واقعی\tشرح\tنحوهٔ پیاده‌سازی\nNewsAPI.org\tGET https://newsapi.org/v2/everything?q=crypto&apiKey={KEY}\tاخبار گسترده\tfetch(url)\nCryptoPanic\tGET https://cryptopanic.com/api/v1/posts/?auth_token={KEY}\tجمع‌آوری اخبار از منابع متعدد\tfetch(url)\nCryptoControl\tGET https://cryptocontrol.io/api/v1/public/news/local?language=EN&apiKey={KEY}\tاخبار محلی و جهانی\tfetch(url)\nCoinDesk API\tGET https://api.coindesk.com/v2/prices/BTC/spot?api_key={KEY}\tقیمت لحظه‌ای BTC\tfetch(url)\nCoinTelegraph\tGET https://api.cointelegraph.com/api/v1/articles?lang=en\tفید مقالات CoinTelegraph\tfetch(url)\nCryptoSlate\tGET https://api.cryptoslate.com/news\tاخبار و تحلیل‌های CryptoSlate\tfetch(url)\nThe Block API\tGET https://api.theblock.co/v1/articles\tمقالات تخصصی بلاک‌چین\tfetch(url)\n\n۴. Sentiment & Mood (۴ سرویس) \nسرویس\tAPI واقعی\tشرح\tنحوهٔ پیاده‌سازی\nAlternative.me F&G\tGET https://api.alternative.me/fng/?limit=1&format=json\tشاخص ترس/طمع بازار\tfetch(url)، مقدار data[0].value\nSantiment\tGraphQL POST به https://api.santiment.net/graphql با { query: \"...sentiment...\" }\tاحساسات اجتماعی رمزارزها\tfetch(url,{method:'POST',body:!...})\nLunarCrush\tGET https://api.lunarcrush.com/v2?data=assets&key={KEY}\tمعیارهای اجتماعی و تعاملات\tfetch(url)\nTheTie.io\tGET https://api.thetie.io/data/sentiment?symbol=BTC&apiKey={KEY}\tتحلیل احساسات بر اساس توییت‌ها\tfetch(url)\n\n۵. On-Chain Analytics (۴ سرویس)\nسرویس\tAPI واقعی\tشرح\tنحوهٔ پیاده‌سازی\nGlassnode\tGET https://api.glassnode.com/v1/metrics/indicators/sopr_ratio?api_key={KEY}\tشاخص‌های زنجیره‌ای (SOPR، HODL، …)\tfetch(url)\nIntoTheBlock\tGET https://api.intotheblock.com/v1/insights/bitcoin/holders_breakdown?key={KEY}\tتجزیه و تحلیل دارندگان\tfetch(url)\nNansen\tGET https://api.nansen.ai/v1/balances?chain=ethereum&address={address}&api_key={KEY}\tمانیتور کیف‌پول‌های هوشمند (Smart Money)\tfetch(url)\nThe Graph\tGraphQL POST به https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3 با queryهای اختصاصی\tداده‌های on-chain از subgraphها\tfetch(url,{method:'POST',body:!...})\n\n۶. Whale-Tracking (۲ سرویس)\nسرویس\tAPI واقعی\tشرح\tنحوهٔ پیاده‌سازی\nWhaleAlert\tGET https://api.whale-alert.io/v1/transactions?api_key={KEY}&min_value=1000000&start={ts}&end={ts}\tتراکنش‌های بزرگ زنجیره‌ها\tfetch(url)، فیلتر data.transactions با value>…\nArkham\tGET https://api.arkham.com/v1/address/{address}/transfers?api_key={KEY}\tانتقالات کیف‌پول‌های شناخته‌شده\tfetch(url)، مانیتور transfers\n\nردیابی نهنگ‌ها:\n\nPolling دوره‌ای → setInterval(()=>fetch(url), interval)\n\nWebhook (WhaleAlert webhook config)\n\nWebSocket (Alchemy/Infura mempool feed)\n\n۷. Community Sentiment (۱ سرویس)\nسرویس\tAPI واقعی\tشرح\tنحوهٔ پیاده‌سازی\nReddit\tGET https://www.reddit.com/r/CryptoCurrency/new.json?limit=10\tپست‌های جدید در رِدیت\tfetch(url), پارس data.data.children\n\nذخیره در حافظه انجام شد.\n\n\nSources\n\n\n\n\n\n\n\n\nAsk ChatGPT\nYou said:\nبرای سنتمنت منابع بیشتری پیدا کن منابع ارزشمندتری پیدا کن مارکت کپ مگه کلیدشو در اختیارت نذاشتم\n\n\nChatGPT said:\n\nThought for 4 seconds\nدر جدول زیر منابع جدید و با ارزش‌تر برای تحلیل احساسات (Sentiment) گردآوری شده‌اند، همراه با API واقعی و نحوهٔ پیاده‌سازی. همچنین کلید CoinMarketCap که در اختیارت قرار دادی در اینجا هاردکد شده است.\n\nمنابع پیشرفته‌تر برای Sentiment\nسرویس\tAPI واقعی\tشرح\tنحوهٔ پیاده‌سازی (مثال)\nSantiment\tPOST https://api.santiment.net/graphql<br/>Body:\t\t\n\njson\nCopy\nEdit\n{ \"query\":\"{ projects(slug: \\\"bitcoin\\\") { sentimentMetrics { socialVolume, socialDominance } } }\" }\n``` | حجم و تسلط بحث‌های اجتماعی روی پروژه‌ها | \n```js\nfetch(url, {\n method:'POST',\n headers:{'Content-Type':'application/json','Authorization':KEY},\n body:JSON.stringify({query:…})\n})\n.then(r=>r.json())\n.then(data=>console.log(data));\n``` |\n| **LunarCrush** | `GET https://api.lunarcrush.com/v2?data=assets&key={KEY}&symbol=BTC` | معیارهای تعاملی شبکه‌های اجتماعی (engagement) | \n```js\nfetch(url)\n .then(r=>r.json())\n .then(d=>console.log(d.data[0]));\n``` |\n| **TheTie.io** | `GET https://api.thetie.io/data/sentiment?symbol=BTC&interval=1h&apiKey={KEY}` | امتیاز احساسات بر اساس توییت‌ها و اخبار | \n```js\nfetch(url)\n .then(r=>r.json())\n .then(d=>console.log(d.sentiment));\n``` |\n| **CryptoQuant** | `GET https://api.cryptoquant.com/v1/ohlcv/latest?symbol=BTC&token={KEY}` | شاخص‌های درون‌زنجیره‌ای و احساسات نهنگ‌ها | \n```js\nfetch(url)\n .then(r=>r.json())\n .then(d=>console.log(d));\n``` |\n| **Glassnode Social**| `GET https://api.glassnode.com/v1/metrics/social/mention_count?api_key={KEY}&a=BTC` | تعداد ذکر شدن BTC در شبکه‌های اجتماعی | \n```js\nfetch(url)\n .then(r=>r.json())\n .then(d=>console.log(d));\n``` |\n| **CoinGecko Comm.** | `GET https://api.coingecko.com/api/v3/coins/bitcoin?localization=false&tickers=false&market_data=false&community_data=true` | داده‌های اجتماعی (Reddit, Twitter) | \n```js\nfetch(url)\n .then(r=>r.json())\n .then(d=>console.log(d.community_data));\n``` |\n| **Messari Social** | `GET https://data.messari.io/api/v1/assets/bitcoin/metrics/social` | متریک‌های شبکه‌های اجتماعی (mentions, sentiment) | \n```js\nfetch(url)\n .then(r=>r.json())\n .then(d=>console.log(d.data));\n``` |\n| **Reddit API** | `GET https://www.reddit.com/r/CryptoCurrency/comments.json?limit=20` | کامنت‌ها و ترندهای رِدیت | \n```js\nfetch(url)\n .then(r=>r.json())\n .then(d=>console.log(d.data.children));\n``` |\n\n> **نکته:** برای همهٔ این سرویس‌ها باید `{KEY}` را با کد API خود پر کنید. \n> برای موارد GraphQL (Santiment, TheGraph) از `POST` و برای بقیه از `GET` استفاده می‌شود.\n\n---\n\n### به‌روزرسانی CoinMarketCap در `config.js`\n\n```js\n// config.js\nexport const API_CONFIG = {\n // … بقیه سرویس‌ها …\n\n // Market Data APIs\n coinmarketcap: {\n primary: {\n name: 'coinmarketcap',\n baseUrl: 'https://pro-api.coinmarketcap.com/v1',\n key: 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c' // COINMARKETCAP_KEY هاردکد شده\n },\n fallbacks: [\n {\n name: 'coinmarketcapAlt',\n baseUrl: 'https://pro-api.coinmarketcap.com/v1',\n key: '04cf4b5b-9868-465c-8ba0-9f2e78c92eb1' // کلید دوم\n },\n {\n name: 'coingecko',\n baseUrl: 'https://api.coingecko.com/api/v3',\n key: '' // بدون کلید\n }\n // … بقیه fallbacks …\n ]\n }\n\n // … سایر سرویس‌ها …\n// config.js\nexport const API_CONFIG = {\n // 1. Block Explorer APIs (11 endpoints)\n tronscan: {\n primary: {\n name: 'tronscan',\n baseUrl: 'https://api.tronscan.org/api',\n key: '7ae72726-bffe-4e74-9c33-97b761eeea21'\n },\n fallbacks: [\n { name: 'tronGrid', baseUrl: 'https://api.trongrid.io', key: '' },\n { name: 'blockchair', baseUrl: 'https://api.blockchair.com/tron', key: '' }\n ]\n },\n bscscan: {\n primary: {\n name: 'bscscan',\n baseUrl: 'https://api.bscscan.com/api',\n key: 'K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT'\n },\n fallbacks: [\n { name: 'ankr', baseUrl: 'https://api.ankr.com/scan/bsc', key: '' },\n { name: 'binTools', baseUrl: 'https://api.bintools.io/bsc', key: '' }\n ]\n },\n etherscan: {\n primary: {\n name: 'etherscan',\n baseUrl: 'https://api.etherscan.io/api',\n key: 'SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2'\n },\n fallbacks: [\n { name: 'etherscan_2', baseUrl: 'https://api.etherscan.io/api', key: 'T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45' },\n { name: 'infura', baseUrl: 'https://mainnet.infura.io/v3', key: '' },\n { name: 'alchemy', baseUrl: 'https://eth-mainnet.alchemyapi.io/v2', key: '' },\n { name: 'covalent', baseUrl: 'https://api.covalenthq.com/v1/1', key: '' }\n ]\n },\n\n // 2. Market Data APIs (9 endpoints)\n coinmarketcap: {\n primary: {\n name: 'coinmarketcap',\n baseUrl: 'https://pro-api.coinmarketcap.com/v1',\n key: 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c'\n },\n fallbacks: [\n { name: 'coinmarketcapAlt', baseUrl: 'https://pro-api.coinmarketcap.com/v1', key: '04cf4b5b-9868-465c-8ba0-9f2e78c92eb1' },\n { name: 'coingecko', baseUrl: 'https://api.coingecko.com/api/v3', key: '' },\n { name: 'nomics', baseUrl: 'https://api.nomics.com/v1', key: '' },\n { name: 'messari', baseUrl: 'https://data.messari.io/api/v1', key: '' },\n { name: 'braveNewCoin', baseUrl: 'https://bravenewcoin.p.rapidapi.com', key: '' }\n ]\n },\n cryptocompare: {\n primary: {\n name: 'cryptocompare',\n baseUrl: 'https://min-api.cryptocompare.com/data',\n key: 'e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f'\n },\n fallbacks: [\n { name: 'kaiko', baseUrl: 'https://us.market-api.kaiko.io/v2', key: '' },\n { name: 'coinapi', baseUrl: 'https://rest.coinapi.io/v1', key: '' }\n ]\n },\n\n // 3. News & Aggregators (7 endpoints)\n newsapi: {\n primary: {\n name: 'newsapi',\n baseUrl: 'https://newsapi.org/v2',\n key: 'pub_346789abc123def456789ghi012345jkl'\n },\n fallbacks: [\n { name: 'cryptoPanic', baseUrl: 'https://cryptopanic.com/api/v1', key: '' },\n { name: 'cryptoControl', baseUrl: 'https://cryptocontrol.io/api/v1/public', key: '' },\n { name: 'coinDesk', baseUrl: 'https://api.coindesk.com/v2', key: '' },\n { name: 'coinTelegraph', baseUrl: 'https://api.cointelegraph.com', key: '' },\n { name: 'cryptoSlate', baseUrl: 'https://api.cryptoslate.com', key: '' },\n { name: 'theBlock', baseUrl: 'https://api.theblock.co/v1', key: '' }\n ]\n },\n\n // 4. Sentiment & Mood (8 endpoints)\n // includes both basic and advanced sources\n sentiment: {\n primary: {\n name: 'alternativeMe',\n baseUrl: 'https://api.alternative.me/fng',\n key: ''\n },\n fallbacks: [\n { name: 'santiment', baseUrl: 'https://api.santiment.net/graphql', key: 'YOUR_SANTIMENT_KEY' },\n { name: 'lunarCrush', baseUrl: 'https://api.lunarcrush.com/v2', key: 'YOUR_LUNARCRUSH_KEY' },\n { name: 'theTie', baseUrl: 'https://api.thetie.io', key: 'YOUR_THETIE_KEY' },\n { name: 'cryptoQuant', baseUrl: 'https://api.cryptoquant.com/v1', key: 'YOUR_CRYPTOQUANT_KEY' },\n { name: 'glassnodeSocial',baseUrl: 'https://api.glassnode.com/v1', key: 'YOUR_GLASSNODE_KEY' },\n { name: 'coingeckoComm', baseUrl: 'https://api.coingecko.com/api/v3', key: '' },\n { name: 'messariSocial', baseUrl: 'https://data.messari.io/api/v1', key: '' },\n { name: 'reddit', baseUrl: 'https://www.reddit.com', key: '' }\n ]\n },\n\n // 5. On-Chain Analytics (4 endpoints)\n glassnode: { primary: { name: 'glassnode', baseUrl: 'https://api.glassnode.com/v1', key: '' } },\n intoTheBlock: { primary: { name: 'intoTheBlock', baseUrl: 'https://api.intotheblock.com/v1', key: '' } },\n nansen: { primary: { name: 'nansen', baseUrl: 'https://api.nansen.ai/v1', key: '' } },\n theGraph: { primary: { name: 'theGraph', baseUrl: 'https://api.thegraph.com/subgraphs/name', key: '' } },\n\n // 6. Whale-Tracking (2 endpoints)\n whaleAlert: {\n primary: { name: 'whaleAlert', baseUrl: 'https://api.whale-alert.io/v1', key: 'YOUR_WHALEALERT_KEY' },\n fallbacks: [\n { name: 'arkham', baseUrl: 'https://api.arkham.com', key: 'YOUR_ARKHAM_KEY' }\n ]\n }\n};\n\n\n\n\n\n\n\n\n\n"
21
+ }
22
+ ],
23
+ "zip_listing": [
24
+ {
25
+ "name": "crypto_resources.ts",
26
+ "file_size": 39118,
27
+ "compress_size": 10933,
28
+ "is_dir": false
29
+ }
30
+ ],
31
+ "zip_text_snippets": [
32
+ {
33
+ "filename": "crypto_resources.ts",
34
+ "text_preview": "// crypto_resources.ts — unified TS with 150+ Hugging Face sources (dynamic catalog) + Safe F&G aggregator\n// English-only comments. Keys intentionally embedded per user request.\n\nexport type Category =\n | 'market'\n | 'news'\n | 'sentiment'\n | 'onchain'\n | 'block_explorer'\n | 'whales'\n | 'generic'\n | 'hf';\n\nexport interface EndpointDef {\n path: string;\n method?: 'GET' | 'POST';\n sampleParams?: Record<string, string | number>;\n authLocation?: 'header' | 'query';\n authName?: string;\n authValue?: string;\n contentType?: string;\n}\n\nexport interface CryptoResource {\n id: string;\n category: Category;\n name: string;\n baseUrl: string;\n free: boolean;\n rateLimit?: string;\n endpoints?: Record<string, EndpointDef>;\n}\n\nexport interface MarketQuote {\n id: string;\n symbol: string;\n name: string;\n price: number;\n change24h?: number;\n marketCap?: number;\n source: string;\n raw: any;\n}\n\nexport interface NewsItem {\n title: string;\n link: string;\n publishedAt?: string;\n source: string;\n}\n\nexport interface OHLCVRow {\n timestamp: number | string;\n open: number; high: number; low: number; close: number; volume: number;\n [k: string]: any;\n}\n\nexport interface FNGPoint {\n value: number; // 0..100\n classification: string;\n at?: string;\n source: string;\n raw?: any;\n}\n\nconst EMBEDDED_KEYS = {\n CMC: '04cf4b5b-9868-465c-8ba0-9f2e78c92eb1',\n ETHERSCAN: 'SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2',\n ETHERSCAN_BACKUP: 'T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45',\n BSCSCAN: 'K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT',\n CRYPTOCOMPARE: 'e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f',\n\n // Optional free keys provided by user (kept in-code per request)\n MESSARI: '',\n SANTIMENT: '',\n COINMETRICS: '',\n HUGGINGFACE: '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',\n};\n\nconst sleep = (ms: number) => new Promise(r => setTimeout(r, ms));\n\nclass HttpError extends Error {\n constructor(public status: number, public url: string, public body?: string) {\n super(`HTTP ${status} for ${url}`);\n }\n}\n\nfunction buildURL(base: string, path = '', params?: Record<string, any>): string {\n const hasQ = path.includes('?');\n const url = base.replace(/\\/+$/, '') + '/' + path.replace(/^\\/+/, '');\n if (!params || Object.keys(params).length === 0) return url;\n const qs = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === null) continue;\n qs.set(k, String(v));\n }\n return url + (hasQ ? '&' : '?') + qs.toString();\n}\n\nasync function fetchRaw(\n url: string,\n opts: { headers?: Record<string, string>; timeoutMs?: number; retries?: number; retryDelayMs?: number; body?: any; method?: 'GET'|'POST' } = {}\n): Promise<Response> {\n const { headers = {}, timeoutMs = 12000, retries = 1, retryDelayMs = 600, body, method = 'GET' } = opts;\n let lastErr: any;\n for (let attempt = 0; attempt <= retries; attempt++) {\n const ac = new AbortController();\n const id = setTimeout(() => ac.abort(), timeoutMs);\n try {\n const res = await fetch(url, { headers, signal: ac.signal, method, body });\n clearTimeout(id);\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n if (res.status === 429 && attempt < retries) {\n await sleep(retryDelayMs * (attempt + 1));\n continue;\n }\n throw new HttpError(res.status, url, text);\n }\n return res;\n } catch (e) {\n clearTimeout(id);\n lastErr = e;\n if (attempt < retries) { await sleep(retryDelayMs * (attempt + 1)); continue; }\n }\n }\n throw lastErr;\n}\n\nasync function fetchJSON<T = any>(\n url: string,\n opts: { headers?: Record<string, string>; timeoutMs?: number; retries?: number; retryDelayMs?: number; body?: any; method?: 'GET'|'POST' } = {}\n): Promise<T> {\n const res = await fetchRaw(url, opts);\n const ct = res.headers.get('content-type') || '';\n if (ct.includes('json')) return res.json() as Promise<T>;\n const text = await res.text();\n try { return JSON.parse(text) as T; } catch { return text as unknown as T; }\n}\n\nfunction ensureNonEmpty(obj: any, label: string) {\n if (obj == null) throw new Error(`${label}: empty response`);\n if (Array.isArray(obj) && obj.length === 0) throw new Error(`${label}: empty array`);\n if (typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 0)\n throw new Error(`${label}: empty object`);\n}\n\nfunction normalizeSymbol(q: string) { return q.trim().toLowerCase(); }\n\nfunction parseCSV(text: string): any[] {\n const lines = text.split(/\\r?\\n/).filter(Boolean);\n if (lines.length < 2) return [];\n const header = lines[0].split(',').map((s) => s.trim());\n const out: any[] = [];\n for (let i = 1; i < lines.length; i++) {\n const cols = lines[i].split(',').map((s) => s.trim());\n const row: any = {};\n header.forEach((h, idx) => { row[h] = cols[idx]; });\n out.push(row);\n }\n return out;\n}\n\nfunction parseRssSimple(xml: string, source: string, limit = 20): NewsItem[] {\n const items: NewsItem[] = [];\n const chunks = xml.split(/<item[\\s>]/i).slice(1);\n for (const raw of chunks) {\n const item = raw.split(/<\\/item>/i)[0] || '';\n const get = (tag: string) => {\n const m = item.match(new RegExp(`<${tag}[^>]*>([\\\\s\\\\S]*?)</${tag}>`, 'i'));\n return m ? m[1].replace(/<!\\[CDATA\\[|\\]\\]>/g, '').trim() : undefined;\n };\n const title = get('title'); const link = get('link') || get('guid'); const pub = get('pubDate') || get('updated') || get('dc:date');\n if (title && link) items.push({ title, link, publishedAt: pub, source });\n if (items.length >= limit) break;\n }\n return items;\n}\n\n/* ===================== BASE RESOURCES ===================== */\n\nexport const resources: CryptoResource[] = [\n // Market\n { id: 'coinpaprika', category: 'market', name: 'CoinPaprika', baseUrl: 'https://api.coinpaprika.com/v1', free: true, endpoints: {\n search: { path: '/search', sampleParams: { q: 'bitcoin', c: 'currencies', limit: 1 } },\n tickerById: { path: '/tickers/{id}', sampleParams: { quotes: 'USD' } },\n }},\n { id: 'coincap', category: 'market', name: 'CoinCap', baseUrl: 'https://api.coincap.io/v2', free: true, endpoints: {\n assets: { path: '/assets', sampleParams: { search: 'bitcoin', limit: 1 } },\n assetById: { path: '/assets/{id}' },\n }},\n { id: 'coingecko', category: 'market', name: 'CoinGecko', baseUrl: 'https://api.coingecko.com/api/v3', free: true, endpoints: {\n simplePrice: { path: '/simple/price?ids={ids}&vs_currencies={fiats}' },\n }},\n { id: 'defillama', category: 'market', name: 'DefiLlama (Prices)', baseUrl: 'https://coins.llama.fi', free: true, endpoints: {\n pricesCurrent: { path: '/prices/current/{coins}' },\n }},\n { id: 'binance', category: 'market', name: 'Binance Public', baseUrl: 'https://api.binance.com', free: true, endpoints: {\n klines: { path: '/api/v3/klines?symbol={symbol}&interval={interval}&limit={limit}' },\n ticker: { path: '/api/v3/ticker/price?symbol={symbol}' },\n }},\n { id: 'cryptocompare', category: 'market', name: 'CryptoCompare', baseUrl: 'https://min-api.cryptocompare.com', free: true, endpoints: {\n histominute: { path: '/data/v2/histominute?fsym={fsym}&tsym={tsym}&limit={limit}&api_key=' + EMBEDDED_KEYS.CRYPTOCOMPARE },\n histohour: { path: '/data/v2/histohour?fsym={fsym}&tsym={tsym}&limit={limit}&api_key=' + EMBEDDED_KEYS.CRYPTOCOMPARE },\n histoday: { path: '/data/v2/histoday?fsym={fsym}&tsym={tsym}&limit={limit}&api_key=' + EMBEDDED_KEYS.CRYPTOCOMPARE },\n }},\n { id: 'cmc', category: 'market', name: 'CoinMarketCap', baseUrl: 'https://pro-api.coinmarketcap.com/v1', free: false, endpoints: {\n quotes: { path: '/cryptocurrency/quotes/latest?symbol={symbol}', authLocation: 'header', authName: 'X-CMC_PRO_API_KEY', authValue: EMBEDDED_KEYS.CMC },\n }},\n\n // News\n { id: 'coinstats_news', category: 'news', name: 'CoinStats News', baseUrl: 'https://api.coinstats.app', free: true, endpoints: { feed: { path: '/public/v1/news' } }},\n { id: 'cryptopanic', category: 'news', name: 'CryptoPanic', baseUrl: 'https://cryptopanic.com', free: true, endpoints: { public: { path: '/api/v1/posts/?public=true' } }},\n { id: 'rss_cointelegraph', category: 'news', name: 'Cointelegraph RSS', baseUrl: 'https://cointelegraph.com', free: true, endpoints: { feed: { path: '/rss' } }},\n { id: 'rss_coindesk', category: 'news', name: 'CoinDesk RSS', baseUrl: 'https://www.coindesk.com', free: true, endpoints: { feed: { path: '/arc/outboundfeeds/rss/?outputType=xml' } }},\n { id: 'rss_decrypt', category: 'news', name: 'Decrypt RSS', baseUrl: 'https://decrypt.co', free: true, endpoints: { feed: { path: '/feed' } }},\n\n // Sentiment / F&G\n { id: 'altme_fng', category: 'sentiment', name: 'Alternative.me F&G', baseUrl: 'https://api.alternative.me', free: true, endpoints: {\n latest: { path: '/fng/', sampleParams: { limit: 1 } },\n history: { path: '/fng/', sampleParams: { limit: 30 } },\n }},\n { id: 'cfgi_v1', category: 'sentiment', name: 'CFGI API v1', baseUrl: 'https://api.cfgi.io', free: true, endpoints: {\n latest: { path: '/v1/fear-greed' },\n }},\n { id: 'cfgi_legacy', category: 'sentiment', name: 'CFGI Legacy', baseUrl: 'https://cfgi.io', free: true, endpoints: {\n latest: { path: '/api' },\n }},\n\n // On-chain / explorers\n { id: 'etherscan_primary', category: 'block_explorer', name: 'Etherscan', baseUrl: 'https://api.etherscan.io/api', free: false, endpoints: {\n balance: { path: '/?module=account&action=balance&address={address}&tag=latest&apikey=' + EMBEDDED_KEYS.ETHERSCAN },\n }},\n { id: 'etherscan_backup', category: 'block_explorer', name: 'Etherscan Backup', baseUrl: 'https://api.etherscan.io/api', free: false, endpoints: {\n balance: { path: '/?module=account&action=balance&address={address}&tag=latest&apikey=' + EMBEDDED_KEYS.ETHERSCAN_BACKUP },\n }},\n { id: 'blockscout_eth', category: 'block_explorer', name: 'Blockscout (ETH)', baseUrl: 'https://eth.blockscout.com', free: true, endpoints: {\n balanc",
35
+ "note": "included as small text"
36
+ }
37
+ ],
38
+ "discovered_keys": {
39
+ "etherscan": [
40
+ "SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2",
41
+ "T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45"
42
+ ],
43
+ "bscscan": [
44
+ "K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT"
45
+ ],
46
+ "tronscan": [
47
+ "7ae72726-bffe-4e74-9c33-97b761eeea21"
48
+ ],
49
+ "coinmarketcap": [
50
+ "04cf4b5b-9868-465c-8ba0-9f2e78c92eb1",
51
+ "b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c"
52
+ ],
53
+ "newsapi": [
54
+ "pub_346789abc123def456789ghi012345jkl"
55
+ ],
56
+ "cryptocompare": [
57
+ "e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f"
58
+ ],
59
+ "huggingface": [
60
+ "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"
61
+ ]
62
+ },
63
+ "notes": "This file was auto-generated. Keys/tokens are present as found in uploaded sources. Secure them as you wish."
64
+ }
final/api-monitor.js ADDED
@@ -0,0 +1,586 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CRYPTOCURRENCY API RESOURCE MONITOR
5
+ * Monitors and manages all API resources from registry
6
+ * Tracks online status, validates endpoints, maintains availability metrics
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const https = require('https');
11
+ const http = require('http');
12
+
13
+ // ═══════════════════════════════════════════════════════════════
14
+ // CONFIGURATION
15
+ // ═══════════════════════════════════════════════════════════════
16
+
17
+ const CONFIG = {
18
+ REGISTRY_FILE: './all_apis_merged_2025.json',
19
+ CHECK_INTERVAL: 5 * 60 * 1000, // 5 minutes
20
+ TIMEOUT: 10000, // 10 seconds
21
+ MAX_RETRIES: 3,
22
+ RETRY_DELAY: 2000,
23
+
24
+ // Status thresholds
25
+ THRESHOLDS: {
26
+ ONLINE: { responseTime: 2000, successRate: 0.95 },
27
+ DEGRADED: { responseTime: 5000, successRate: 0.80 },
28
+ SLOW: { responseTime: 10000, successRate: 0.70 },
29
+ UNSTABLE: { responseTime: Infinity, successRate: 0.50 }
30
+ }
31
+ };
32
+
33
+ // ═══════════════════════════════════════════════════════════════
34
+ // API REGISTRY - Comprehensive resource definitions
35
+ // ═══════════════════════════════════════════════════════════════
36
+
37
+ const API_REGISTRY = {
38
+ blockchainExplorers: {
39
+ etherscan: [
40
+ { name: 'Etherscan-1', url: 'https://api.etherscan.io/api', keyName: 'etherscan', keyIndex: 0, testEndpoint: '?module=stats&action=ethprice&apikey={{KEY}}', tier: 1 },
41
+ { name: 'Etherscan-2', url: 'https://api.etherscan.io/api', keyName: 'etherscan', keyIndex: 1, testEndpoint: '?module=stats&action=ethprice&apikey={{KEY}}', tier: 1 }
42
+ ],
43
+ bscscan: [
44
+ { name: 'BscScan', url: 'https://api.bscscan.com/api', keyName: 'bscscan', keyIndex: 0, testEndpoint: '?module=stats&action=bnbprice&apikey={{KEY}}', tier: 1 }
45
+ ],
46
+ tronscan: [
47
+ { name: 'TronScan', url: 'https://apilist.tronscanapi.com/api', keyName: 'tronscan', keyIndex: 0, testEndpoint: '/system/status', tier: 2 }
48
+ ]
49
+ },
50
+
51
+ marketData: {
52
+ coingecko: [
53
+ { name: 'CoinGecko', url: 'https://api.coingecko.com/api/v3', testEndpoint: '/ping', requiresKey: false, tier: 1 },
54
+ { name: 'CoinGecko-Price', url: 'https://api.coingecko.com/api/v3', testEndpoint: '/simple/price?ids=bitcoin&vs_currencies=usd', requiresKey: false, tier: 1 }
55
+ ],
56
+ coinmarketcap: [
57
+ { name: 'CoinMarketCap-1', url: 'https://pro-api.coinmarketcap.com/v1', keyName: 'coinmarketcap', keyIndex: 0, testEndpoint: '/key/info', headerKey: 'X-CMC_PRO_API_KEY', tier: 1 },
58
+ { name: 'CoinMarketCap-2', url: 'https://pro-api.coinmarketcap.com/v1', keyName: 'coinmarketcap', keyIndex: 1, testEndpoint: '/key/info', headerKey: 'X-CMC_PRO_API_KEY', tier: 1 }
59
+ ],
60
+ cryptocompare: [
61
+ { name: 'CryptoCompare', url: 'https://min-api.cryptocompare.com/data', keyName: 'cryptocompare', keyIndex: 0, testEndpoint: '/price?fsym=BTC&tsyms=USD&api_key={{KEY}}', tier: 2 }
62
+ ],
63
+ coinpaprika: [
64
+ { name: 'CoinPaprika', url: 'https://api.coinpaprika.com/v1', testEndpoint: '/ping', requiresKey: false, tier: 2 }
65
+ ],
66
+ coincap: [
67
+ { name: 'CoinCap', url: 'https://api.coincap.io/v2', testEndpoint: '/assets/bitcoin', requiresKey: false, tier: 2 }
68
+ ]
69
+ },
70
+
71
+ newsAndSentiment: {
72
+ cryptopanic: [
73
+ { name: 'CryptoPanic', url: 'https://cryptopanic.com/api/v1', testEndpoint: '/posts/?public=true', requiresKey: false, tier: 2 }
74
+ ],
75
+ newsapi: [
76
+ { name: 'NewsAPI', url: 'https://newsapi.org/v2', keyName: 'newsapi', keyIndex: 0, testEndpoint: '/top-headlines?category=business&apiKey={{KEY}}', tier: 2 }
77
+ ],
78
+ alternativeme: [
79
+ { name: 'Fear-Greed-Index', url: 'https://api.alternative.me', testEndpoint: '/fng/?limit=1', requiresKey: false, tier: 2 }
80
+ ],
81
+ reddit: [
82
+ { name: 'Reddit-Crypto', url: 'https://www.reddit.com/r/cryptocurrency', testEndpoint: '/hot.json?limit=1', requiresKey: false, tier: 3 }
83
+ ]
84
+ },
85
+
86
+ rpcNodes: {
87
+ ethereum: [
88
+ { name: 'Ankr-ETH', url: 'https://rpc.ankr.com/eth', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 1 },
89
+ { name: 'PublicNode-ETH', url: 'https://ethereum.publicnode.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
90
+ { name: 'Cloudflare-ETH', url: 'https://cloudflare-eth.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
91
+ { name: 'LlamaNodes-ETH', url: 'https://eth.llamarpc.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 3 }
92
+ ],
93
+ bsc: [
94
+ { name: 'BSC-Official', url: 'https://bsc-dataseed.binance.org', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
95
+ { name: 'Ankr-BSC', url: 'https://rpc.ankr.com/bsc', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
96
+ { name: 'PublicNode-BSC', url: 'https://bsc-rpc.publicnode.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 3 }
97
+ ],
98
+ polygon: [
99
+ { name: 'Polygon-Official', url: 'https://polygon-rpc.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
100
+ { name: 'Ankr-Polygon', url: 'https://rpc.ankr.com/polygon', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 }
101
+ ],
102
+ tron: [
103
+ { name: 'TronGrid', url: 'https://api.trongrid.io', testEndpoint: '/wallet/getnowblock', method: 'POST', requiresKey: false, tier: 2 },
104
+ { name: 'TronStack', url: 'https://api.tronstack.io', testEndpoint: '/wallet/getnowblock', method: 'POST', requiresKey: false, tier: 3 }
105
+ ]
106
+ },
107
+
108
+ onChainAnalytics: [
109
+ { name: 'TheGraph', url: 'https://api.thegraph.com', testEndpoint: '/index-node/graphql', requiresKey: false, tier: 2 },
110
+ { name: 'Blockchair', url: 'https://api.blockchair.com', testEndpoint: '/stats', requiresKey: false, tier: 3 }
111
+ ],
112
+
113
+ whaleTracking: [
114
+ { name: 'WhaleAlert-Status', url: 'https://api.whale-alert.io/v1', testEndpoint: '/status', requiresKey: false, tier: 1 }
115
+ ],
116
+
117
+ corsProxies: [
118
+ { name: 'AllOrigins', url: 'https://api.allorigins.win', testEndpoint: '/get?url=https://api.coingecko.com/api/v3/ping', requiresKey: false, tier: 3 },
119
+ { name: 'CORS.SH', url: 'https://proxy.cors.sh', testEndpoint: '/https://api.coingecko.com/api/v3/ping', requiresKey: false, tier: 3 },
120
+ { name: 'Corsfix', url: 'https://proxy.corsfix.com', testEndpoint: '/?url=https://api.coingecko.com/api/v3/ping', requiresKey: false, tier: 3 },
121
+ { name: 'ThingProxy', url: 'https://thingproxy.freeboard.io', testEndpoint: '/fetch/https://api.coingecko.com/api/v3/ping', requiresKey: false, tier: 3 }
122
+ ]
123
+ };
124
+
125
+ // ═══════════════════════════════════════════════════════════════
126
+ // RESOURCE MONITOR CLASS
127
+ // ═══════════════════════════════════════════════════════════════
128
+
129
+ class CryptoAPIMonitor {
130
+ constructor() {
131
+ this.apiKeys = {};
132
+ this.resourceStatus = {};
133
+ this.metrics = {
134
+ totalChecks: 0,
135
+ successfulChecks: 0,
136
+ failedChecks: 0,
137
+ totalResponseTime: 0
138
+ };
139
+ this.history = {};
140
+ this.alerts = [];
141
+ }
142
+
143
+ // Load API keys from registry
144
+ loadRegistry() {
145
+ try {
146
+ const data = fs.readFileSync(CONFIG.REGISTRY_FILE, 'utf8');
147
+ const registry = JSON.parse(data);
148
+
149
+ this.apiKeys = registry.discovered_keys || {};
150
+ console.log('✓ Registry loaded successfully');
151
+ console.log(` Found ${Object.keys(this.apiKeys).length} API key categories`);
152
+
153
+ return true;
154
+ } catch (error) {
155
+ console.error('✗ Failed to load registry:', error.message);
156
+ return false;
157
+ }
158
+ }
159
+
160
+ // Get API key for resource
161
+ getApiKey(keyName, keyIndex = 0) {
162
+ if (!keyName || !this.apiKeys[keyName]) return null;
163
+ const keys = this.apiKeys[keyName];
164
+ return Array.isArray(keys) ? keys[keyIndex] : keys;
165
+ }
166
+
167
+ // Mask API key for display
168
+ maskKey(key) {
169
+ if (!key || key.length < 8) return '****';
170
+ return key.substring(0, 4) + '****' + key.substring(key.length - 4);
171
+ }
172
+
173
+ // HTTP request with timeout
174
+ makeRequest(url, options = {}) {
175
+ return new Promise((resolve, reject) => {
176
+ const startTime = Date.now();
177
+ const protocol = url.startsWith('https') ? https : http;
178
+
179
+ const req = protocol.request(url, {
180
+ method: options.method || 'GET',
181
+ headers: options.headers || {},
182
+ timeout: CONFIG.TIMEOUT
183
+ }, (res) => {
184
+ let data = '';
185
+
186
+ res.on('data', chunk => data += chunk);
187
+ res.on('end', () => {
188
+ const responseTime = Date.now() - startTime;
189
+ resolve({
190
+ statusCode: res.statusCode,
191
+ data: data,
192
+ responseTime: responseTime,
193
+ success: res.statusCode >= 200 && res.statusCode < 300
194
+ });
195
+ });
196
+ });
197
+
198
+ req.on('error', (error) => {
199
+ reject({
200
+ error: error.message,
201
+ responseTime: Date.now() - startTime,
202
+ success: false
203
+ });
204
+ });
205
+
206
+ req.on('timeout', () => {
207
+ req.destroy();
208
+ reject({
209
+ error: 'Request timeout',
210
+ responseTime: CONFIG.TIMEOUT,
211
+ success: false
212
+ });
213
+ });
214
+
215
+ if (options.body) {
216
+ req.write(options.body);
217
+ }
218
+
219
+ req.end();
220
+ });
221
+ }
222
+
223
+ // Check single API endpoint
224
+ async checkEndpoint(resource) {
225
+ const startTime = Date.now();
226
+
227
+ try {
228
+ // Build URL
229
+ let url = resource.url + (resource.testEndpoint || '');
230
+
231
+ // Replace API key placeholder
232
+ if (resource.keyName) {
233
+ const apiKey = this.getApiKey(resource.keyName, resource.keyIndex || 0);
234
+ if (apiKey) {
235
+ url = url.replace('{{KEY}}', apiKey);
236
+ }
237
+ }
238
+
239
+ // Prepare headers
240
+ const headers = {
241
+ 'User-Agent': 'CryptoAPIMonitor/1.0'
242
+ };
243
+
244
+ // Add API key to header if needed
245
+ if (resource.headerKey && resource.keyName) {
246
+ const apiKey = this.getApiKey(resource.keyName, resource.keyIndex || 0);
247
+ if (apiKey) {
248
+ headers[resource.headerKey] = apiKey;
249
+ }
250
+ }
251
+
252
+ // RPC specific test
253
+ let options = { method: resource.method || 'GET', headers };
254
+
255
+ if (resource.rpcTest) {
256
+ options.method = 'POST';
257
+ options.headers['Content-Type'] = 'application/json';
258
+ options.body = JSON.stringify({
259
+ jsonrpc: '2.0',
260
+ method: 'eth_blockNumber',
261
+ params: [],
262
+ id: 1
263
+ });
264
+ }
265
+
266
+ // Make request
267
+ const result = await this.makeRequest(url, options);
268
+
269
+ return {
270
+ name: resource.name,
271
+ url: resource.url,
272
+ success: result.success,
273
+ statusCode: result.statusCode,
274
+ responseTime: result.responseTime,
275
+ timestamp: new Date().toISOString(),
276
+ tier: resource.tier || 4
277
+ };
278
+
279
+ } catch (error) {
280
+ return {
281
+ name: resource.name,
282
+ url: resource.url,
283
+ success: false,
284
+ error: error.error || error.message,
285
+ responseTime: error.responseTime || Date.now() - startTime,
286
+ timestamp: new Date().toISOString(),
287
+ tier: resource.tier || 4
288
+ };
289
+ }
290
+ }
291
+
292
+ // Classify status based on metrics
293
+ classifyStatus(resource) {
294
+ if (!this.history[resource.name]) {
295
+ return 'UNKNOWN';
296
+ }
297
+
298
+ const hist = this.history[resource.name];
299
+ const recentChecks = hist.slice(-10); // Last 10 checks
300
+
301
+ if (recentChecks.length === 0) return 'UNKNOWN';
302
+
303
+ const successCount = recentChecks.filter(c => c.success).length;
304
+ const successRate = successCount / recentChecks.length;
305
+ const avgResponseTime = recentChecks
306
+ .filter(c => c.success)
307
+ .reduce((sum, c) => sum + c.responseTime, 0) / (successCount || 1);
308
+
309
+ if (successRate >= CONFIG.THRESHOLDS.ONLINE.successRate &&
310
+ avgResponseTime < CONFIG.THRESHOLDS.ONLINE.responseTime) {
311
+ return 'ONLINE';
312
+ } else if (successRate >= CONFIG.THRESHOLDS.DEGRADED.successRate &&
313
+ avgResponseTime < CONFIG.THRESHOLDS.DEGRADED.responseTime) {
314
+ return 'DEGRADED';
315
+ } else if (successRate >= CONFIG.THRESHOLDS.SLOW.successRate &&
316
+ avgResponseTime < CONFIG.THRESHOLDS.SLOW.responseTime) {
317
+ return 'SLOW';
318
+ } else if (successRate >= CONFIG.THRESHOLDS.UNSTABLE.successRate) {
319
+ return 'UNSTABLE';
320
+ } else {
321
+ return 'OFFLINE';
322
+ }
323
+ }
324
+
325
+ // Update history for resource
326
+ updateHistory(resource, result) {
327
+ if (!this.history[resource.name]) {
328
+ this.history[resource.name] = [];
329
+ }
330
+
331
+ this.history[resource.name].push(result);
332
+
333
+ // Keep only last 100 checks
334
+ if (this.history[resource.name].length > 100) {
335
+ this.history[resource.name] = this.history[resource.name].slice(-100);
336
+ }
337
+ }
338
+
339
+ // Check all resources in a category
340
+ async checkCategory(categoryName, resources) {
341
+ console.log(`\n Checking ${categoryName}...`);
342
+
343
+ const results = [];
344
+
345
+ if (Array.isArray(resources)) {
346
+ for (const resource of resources) {
347
+ const result = await this.checkEndpoint(resource);
348
+ this.updateHistory(resource, result);
349
+ results.push(result);
350
+
351
+ // Rate limiting delay
352
+ await new Promise(resolve => setTimeout(resolve, 200));
353
+ }
354
+ } else {
355
+ // Handle nested categories
356
+ for (const [subCategory, subResources] of Object.entries(resources)) {
357
+ for (const resource of subResources) {
358
+ const result = await this.checkEndpoint(resource);
359
+ this.updateHistory(resource, result);
360
+ results.push(result);
361
+
362
+ await new Promise(resolve => setTimeout(resolve, 200));
363
+ }
364
+ }
365
+ }
366
+
367
+ return results;
368
+ }
369
+
370
+ // Run complete monitoring cycle
371
+ async runMonitoringCycle() {
372
+ console.log('\n╔════════════════════════════════════════════════════════╗');
373
+ console.log('║ CRYPTOCURRENCY API RESOURCE MONITOR - Health Check ║');
374
+ console.log('╚════════════════════════════════════════════════════════╝');
375
+ console.log(` Timestamp: ${new Date().toISOString()}`);
376
+
377
+ const cycleResults = {};
378
+
379
+ for (const [category, resources] of Object.entries(API_REGISTRY)) {
380
+ const results = await this.checkCategory(category, resources);
381
+ cycleResults[category] = results;
382
+ }
383
+
384
+ this.generateReport(cycleResults);
385
+ this.checkAlertConditions(cycleResults);
386
+
387
+ return cycleResults;
388
+ }
389
+
390
+ // Generate status report
391
+ generateReport(cycleResults) {
392
+ console.log('\n╔════════════════════════════════════════════════════════╗');
393
+ console.log('║ RESOURCE STATUS REPORT ║');
394
+ console.log('╚════════════════════════════════════════════════════════╝\n');
395
+
396
+ let totalResources = 0;
397
+ let onlineCount = 0;
398
+ let degradedCount = 0;
399
+ let offlineCount = 0;
400
+
401
+ for (const [category, results] of Object.entries(cycleResults)) {
402
+ console.log(`\n📁 ${category.toUpperCase()}`);
403
+ console.log('─'.repeat(60));
404
+
405
+ for (const result of results) {
406
+ totalResources++;
407
+ const status = this.classifyStatus(result);
408
+
409
+ let statusSymbol = '●';
410
+ let statusColor = '';
411
+
412
+ switch (status) {
413
+ case 'ONLINE':
414
+ statusSymbol = '✓';
415
+ onlineCount++;
416
+ break;
417
+ case 'DEGRADED':
418
+ case 'SLOW':
419
+ statusSymbol = '◐';
420
+ degradedCount++;
421
+ break;
422
+ case 'OFFLINE':
423
+ case 'UNSTABLE':
424
+ statusSymbol = '✗';
425
+ offlineCount++;
426
+ break;
427
+ }
428
+
429
+ const rt = result.responseTime ? `${result.responseTime}ms` : 'N/A';
430
+ const tierBadge = result.tier === 1 ? '[TIER-1]' : result.tier === 2 ? '[TIER-2]' : '';
431
+
432
+ console.log(` ${statusSymbol} ${result.name.padEnd(25)} ${status.padEnd(10)} ${rt.padStart(8)} ${tierBadge}`);
433
+ }
434
+ }
435
+
436
+ // Summary
437
+ console.log('\n╔════════════════════════════════════════════════════════╗');
438
+ console.log('║ SUMMARY ║');
439
+ console.log('╚════════════════════════════════════════════════════════╝');
440
+ console.log(` Total Resources: ${totalResources}`);
441
+ console.log(` Online: ${onlineCount} (${((onlineCount/totalResources)*100).toFixed(1)}%)`);
442
+ console.log(` Degraded: ${degradedCount} (${((degradedCount/totalResources)*100).toFixed(1)}%)`);
443
+ console.log(` Offline: ${offlineCount} (${((offlineCount/totalResources)*100).toFixed(1)}%)`);
444
+ console.log(` Overall Health: ${((onlineCount/totalResources)*100).toFixed(1)}%`);
445
+ }
446
+
447
+ // Check for alert conditions
448
+ checkAlertConditions(cycleResults) {
449
+ const newAlerts = [];
450
+
451
+ // Check TIER-1 APIs
452
+ for (const [category, results] of Object.entries(cycleResults)) {
453
+ for (const result of results) {
454
+ if (result.tier === 1 && !result.success) {
455
+ newAlerts.push({
456
+ severity: 'CRITICAL',
457
+ message: `TIER-1 API offline: ${result.name}`,
458
+ timestamp: new Date().toISOString()
459
+ });
460
+ }
461
+
462
+ if (result.responseTime > 5000) {
463
+ newAlerts.push({
464
+ severity: 'WARNING',
465
+ message: `Elevated response time: ${result.name} (${result.responseTime}ms)`,
466
+ timestamp: new Date().toISOString()
467
+ });
468
+ }
469
+ }
470
+ }
471
+
472
+ if (newAlerts.length > 0) {
473
+ console.log('\n╔════════════════════════════════════════════════════════╗');
474
+ console.log('║ ⚠️ ALERTS ║');
475
+ console.log('╚════════════════════════════════════════════════════════╝');
476
+
477
+ for (const alert of newAlerts) {
478
+ console.log(` [${alert.severity}] ${alert.message}`);
479
+ }
480
+
481
+ this.alerts.push(...newAlerts);
482
+ }
483
+ }
484
+
485
+ // Generate JSON report
486
+ exportReport(filename = 'api-monitor-report.json') {
487
+ const report = {
488
+ timestamp: new Date().toISOString(),
489
+ summary: {
490
+ totalResources: 0,
491
+ onlineResources: 0,
492
+ degradedResources: 0,
493
+ offlineResources: 0
494
+ },
495
+ categories: {},
496
+ alerts: this.alerts.slice(-50), // Last 50 alerts
497
+ history: this.history
498
+ };
499
+
500
+ // Calculate summary
501
+ for (const [category, resources] of Object.entries(API_REGISTRY)) {
502
+ report.categories[category] = [];
503
+
504
+ const flatResources = this.flattenResources(resources);
505
+
506
+ for (const resource of flatResources) {
507
+ const status = this.classifyStatus(resource);
508
+ const lastCheck = this.history[resource.name] ?
509
+ this.history[resource.name].slice(-1)[0] : null;
510
+
511
+ report.summary.totalResources++;
512
+
513
+ if (status === 'ONLINE') report.summary.onlineResources++;
514
+ else if (status === 'DEGRADED' || status === 'SLOW') report.summary.degradedResources++;
515
+ else if (status === 'OFFLINE' || status === 'UNSTABLE') report.summary.offlineResources++;
516
+
517
+ report.categories[category].push({
518
+ name: resource.name,
519
+ url: resource.url,
520
+ status: status,
521
+ tier: resource.tier,
522
+ lastCheck: lastCheck
523
+ });
524
+ }
525
+ }
526
+
527
+ fs.writeFileSync(filename, JSON.stringify(report, null, 2));
528
+ console.log(`\n✓ Report exported to ${filename}`);
529
+
530
+ return report;
531
+ }
532
+
533
+ // Flatten nested resources
534
+ flattenResources(resources) {
535
+ if (Array.isArray(resources)) {
536
+ return resources;
537
+ }
538
+
539
+ const flattened = [];
540
+ for (const subResources of Object.values(resources)) {
541
+ flattened.push(...subResources);
542
+ }
543
+ return flattened;
544
+ }
545
+ }
546
+
547
+ // ═══════════════════════════════════════════════════════════════
548
+ // MAIN EXECUTION
549
+ // ═══════════════════════════════════════════════════════════════
550
+
551
+ async function main() {
552
+ const monitor = new CryptoAPIMonitor();
553
+
554
+ // Load registry
555
+ if (!monitor.loadRegistry()) {
556
+ console.error('Failed to initialize monitor');
557
+ process.exit(1);
558
+ }
559
+
560
+ // Run initial check
561
+ console.log('\n🚀 Starting initial health check...');
562
+ await monitor.runMonitoringCycle();
563
+
564
+ // Export report
565
+ monitor.exportReport();
566
+
567
+ // Continuous monitoring mode
568
+ if (process.argv.includes('--continuous')) {
569
+ console.log(`\n♾️ Continuous monitoring enabled (interval: ${CONFIG.CHECK_INTERVAL/1000}s)`);
570
+
571
+ setInterval(async () => {
572
+ await monitor.runMonitoringCycle();
573
+ monitor.exportReport();
574
+ }, CONFIG.CHECK_INTERVAL);
575
+ } else {
576
+ console.log('\n✓ Monitoring cycle complete');
577
+ console.log(' Use --continuous flag for continuous monitoring');
578
+ }
579
+ }
580
+
581
+ // Run if executed directly
582
+ if (require.main === module) {
583
+ main().catch(console.error);
584
+ }
585
+
586
+ module.exports = CryptoAPIMonitor;
final/api-resources/README.md ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 📚 API Resources Guide
2
+
3
+ ## فایل‌های منابع در این پوشه
4
+
5
+ این پوشه شامل منابع کاملی از **162+ API رایگان** است که می‌توانید از آنها استفاده کنید.
6
+
7
+ ---
8
+
9
+ ## 📁 فایل‌ها
10
+
11
+ ### 1. `crypto_resources_unified_2025-11-11.json`
12
+ - **200+ منبع** کامل با تمام جزئیات
13
+ - شامل: RPC Nodes, Block Explorers, Market Data, News, Sentiment, DeFi
14
+ - ساختار یکپارچه برای همه منابع
15
+ - API Keys embedded برای برخی سرویس‌ها
16
+
17
+ ### 2. `ultimate_crypto_pipeline_2025_NZasinich.json`
18
+ - **162 منبع** با نمونه کد TypeScript
19
+ - شامل: Block Explorers, Market Data, News, DeFi
20
+ - Rate Limits و توضیحات هر سرویس
21
+
22
+ ### 3. `api-config-complete__1_.txt`
23
+ - تنظیمات و کانفیگ APIها
24
+ - Fallback strategies
25
+ - Authentication methods
26
+
27
+ ---
28
+
29
+ ## 🔑 APIهای استفاده شده در برنامه
30
+
31
+ برنامه فعلی از این APIها استفاده می‌کند:
32
+
33
+ ### ✅ Market Data:
34
+ ```json
35
+ {
36
+ "CoinGecko": "https://api.coingecko.com/api/v3",
37
+ "CoinCap": "https://api.coincap.io/v2",
38
+ "CoinStats": "https://api.coinstats.app",
39
+ "Cryptorank": "https://api.cryptorank.io/v1"
40
+ }
41
+ ```
42
+
43
+ ### ✅ Exchanges:
44
+ ```json
45
+ {
46
+ "Binance": "https://api.binance.com/api/v3",
47
+ "Coinbase": "https://api.coinbase.com/v2",
48
+ "Kraken": "https://api.kraken.com/0/public"
49
+ }
50
+ ```
51
+
52
+ ### ✅ Sentiment & Analytics:
53
+ ```json
54
+ {
55
+ "Alternative.me": "https://api.alternative.me/fng",
56
+ "DeFi Llama": "https://api.llama.fi"
57
+ }
58
+ ```
59
+
60
+ ---
61
+
62
+ ## 🚀 چگونه API جدید اضافه کنیم؟
63
+
64
+ ### مثال: اضافه کردن CryptoCompare
65
+
66
+ #### 1. در `app.py` به `API_PROVIDERS` اضافه کنید:
67
+ ```python
68
+ API_PROVIDERS = {
69
+ "market_data": [
70
+ # ... موارد قبلی
71
+ {
72
+ "name": "CryptoCompare",
73
+ "base_url": "https://min-api.cryptocompare.com/data",
74
+ "endpoints": {
75
+ "price": "/price",
76
+ "multiple": "/pricemulti"
77
+ },
78
+ "auth": None,
79
+ "rate_limit": "100/hour",
80
+ "status": "active"
81
+ }
82
+ ]
83
+ }
84
+ ```
85
+
86
+ #### 2. تابع جدید برای fetch:
87
+ ```python
88
+ async def get_cryptocompare_data():
89
+ async with aiohttp.ClientSession() as session:
90
+ url = "https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH&tsyms=USD"
91
+ data = await fetch_with_retry(session, url)
92
+ return data
93
+ ```
94
+
95
+ #### 3. استفاده در endpoint:
96
+ ```python
97
+ @app.get("/api/cryptocompare")
98
+ async def cryptocompare():
99
+ data = await get_cryptocompare_data()
100
+ return {"data": data}
101
+ ```
102
+
103
+ ---
104
+
105
+ ## 📊 نمونه‌های بیشتر از منابع
106
+
107
+ ### Block Explorer - Etherscan:
108
+ ```python
109
+ # از crypto_resources_unified_2025-11-11.json
110
+ {
111
+ "id": "etherscan_primary",
112
+ "name": "Etherscan",
113
+ "chain": "ethereum",
114
+ "base_url": "https://api.etherscan.io/api",
115
+ "auth": {
116
+ "type": "apiKeyQuery",
117
+ "key": "YOUR_KEY_HERE",
118
+ "param_name": "apikey"
119
+ },
120
+ "endpoints": {
121
+ "balance": "?module=account&action=balance&address={address}&apikey={key}"
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### استفاده:
127
+ ```python
128
+ async def get_eth_balance(address):
129
+ url = f"https://api.etherscan.io/api?module=account&action=balance&address={address}&apikey=YOUR_KEY"
130
+ async with aiohttp.ClientSession() as session:
131
+ data = await fetch_with_retry(session, url)
132
+ return data
133
+ ```
134
+
135
+ ---
136
+
137
+ ### News API - CryptoPanic:
138
+ ```python
139
+ # از فایل منابع
140
+ {
141
+ "id": "cryptopanic",
142
+ "name": "CryptoPanic",
143
+ "role": "crypto_news",
144
+ "base_url": "https://cryptopanic.com/api/v1",
145
+ "endpoints": {
146
+ "posts": "/posts/?auth_token={key}"
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### استفاده:
152
+ ```python
153
+ async def get_news():
154
+ url = "https://cryptopanic.com/api/v1/posts/?auth_token=free"
155
+ async with aiohttp.ClientSession() as session:
156
+ data = await fetch_with_retry(session, url)
157
+ return data["results"]
158
+ ```
159
+
160
+ ---
161
+
162
+ ### DeFi - Uniswap:
163
+ ```python
164
+ # از فایل منابع
165
+ {
166
+ "name": "Uniswap",
167
+ "url": "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3",
168
+ "type": "GraphQL"
169
+ }
170
+ ```
171
+
172
+ ### استفاده:
173
+ ```python
174
+ async def get_uniswap_data():
175
+ query = """
176
+ {
177
+ pools(first: 10, orderBy: volumeUSD, orderDirection: desc) {
178
+ id
179
+ token0 { symbol }
180
+ token1 { symbol }
181
+ volumeUSD
182
+ }
183
+ }
184
+ """
185
+ url = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
186
+ async with aiohttp.ClientSession() as session:
187
+ async with session.post(url, json={"query": query}) as response:
188
+ data = await response.json()
189
+ return data
190
+ ```
191
+
192
+ ---
193
+
194
+ ## 🔧 نکات مهم
195
+
196
+ ### Rate Limits:
197
+ ```python
198
+ # همیشه rate limit رو رعایت کنید
199
+ await asyncio.sleep(1) # بین درخواست‌ها
200
+
201
+ # یا از cache استفاده کنید
202
+ cache = {"data": None, "timestamp": None, "ttl": 60}
203
+ ```
204
+
205
+ ### Error Handling:
206
+ ```python
207
+ try:
208
+ data = await fetch_api()
209
+ except aiohttp.ClientError:
210
+ # Fallback به API دیگه
211
+ data = await fetch_fallback_api()
212
+ ```
213
+
214
+ ### Authentication:
215
+ ```python
216
+ # برخی APIها نیاز به auth دارند
217
+ headers = {"X-API-Key": "YOUR_KEY"}
218
+ async with session.get(url, headers=headers) as response:
219
+ data = await response.json()
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 📝 چک‌لیست برای اضافه کردن API جدید
225
+
226
+ - [ ] API را در `API_PROVIDERS` اضافه کن
227
+ - [ ] تابع `fetch` بنویس
228
+ - [ ] Error handling اضافه کن
229
+ - [ ] Cache پیاده‌سازی کن
230
+ - [ ] Rate limit رعایت کن
231
+ - [ ] Fallback تعریف کن
232
+ - [ ] Endpoint در FastAPI بساز
233
+ - [ ] Frontend رو آپدیت کن
234
+ - [ ] تست کن
235
+
236
+ ---
237
+
238
+ ## 🌟 APIهای پیشنهادی برای توسعه
239
+
240
+ از فایل‌های منابع، این APIها خوب هستند:
241
+
242
+ ### High Priority:
243
+ 1. **Messari** - تحلیل عمیق
244
+ 2. **Glassnode** - On-chain analytics
245
+ 3. **LunarCrush** - Social sentiment
246
+ 4. **Santiment** - Market intelligence
247
+
248
+ ### Medium Priority:
249
+ 1. **Dune Analytics** - Custom queries
250
+ 2. **CoinMarketCap** - Alternative market data
251
+ 3. **TradingView** - Charts data
252
+ 4. **CryptoQuant** - Exchange flows
253
+
254
+ ### Low Priority:
255
+ 1. **Various RSS Feeds** - News aggregation
256
+ 2. **Social APIs** - Twitter, Reddit
257
+ 3. **NFT APIs** - OpenSea, Blur
258
+ 4. **Blockchain RPCs** - Direct chain queries
259
+
260
+ ---
261
+
262
+ ## 🎓 منابع یادگیری
263
+
264
+ - [FastAPI Async](https://fastapi.tiangolo.com/async/)
265
+ - [aiohttp Documentation](https://docs.aiohttp.org/)
266
+ - [API Best Practices](https://restfulapi.net/)
267
+
268
+ ---
269
+
270
+ ## 💡 نکته نهایی
271
+
272
+ **همه APIهای موجود در فایل‌ها رایگان هستند!**
273
+
274
+ برای استفاده از آنها فقط کافیست:
275
+ 1. API را از فایل منابع پیدا کنید
276
+ 2. به `app.py` اضافه کنید
277
+ 3. تابع fetch بنویسید
278
+ 4. استفاده کنید!
279
+
280
+ ---
281
+
282
+ **موفق باشید! 🚀**
final/api-resources/api-config-complete__1_.txt ADDED
@@ -0,0 +1,1634 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ╔══════════════════════════════════════════════════════════════════════════════════════╗
2
+ ║ CRYPTOCURRENCY API CONFIGURATION - COMPLETE GUIDE ║
3
+ ║ تنظیمات کامل API های ارز دیجیتال ║
4
+ ║ Updated: October 2025 ║
5
+ ╚══════════════════════════════════════════════════════════════════════════════════════╝
6
+
7
+ ═══════════════════════════════════════════════════════════════════════════════════════
8
+ 🔑 API KEYS - کلیدهای API
9
+ ═══════════════════════════════════════════════════════════════════════════════════════
10
+
11
+ EXISTING KEYS (کلیدهای موجود):
12
+ ─────────────────────────────────
13
+ TronScan: 7ae72726-bffe-4e74-9c33-97b761eeea21
14
+ BscScan: K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT
15
+ Etherscan: SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2
16
+ Etherscan_2: T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45
17
+ CoinMarketCap: 04cf4b5b-9868-465c-8ba0-9f2e78c92eb1
18
+ CoinMarketCap_2: b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c
19
+ NewsAPI: pub_346789abc123def456789ghi012345jkl
20
+ CryptoCompare: e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f
21
+
22
+
23
+ ═══════════════════════════════════════════════════════════════════════════════════════
24
+ 🌐 CORS PROXY SOLUTIONS - راه‌حل‌های پروکسی CORS
25
+ ═══════════════════════════════════════════════════════════════════════════════════════
26
+
27
+ FREE CORS PROXIES (پروکسی‌های رایگان):
28
+ ──────────────────────────────────────────
29
+
30
+ 1. AllOrigins (بدون محدودیت)
31
+ URL: https://api.allorigins.win/get?url={TARGET_URL}
32
+ Example: https://api.allorigins.win/get?url=https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd
33
+ Features: JSON/JSONP, گزینه raw content
34
+
35
+ 2. CORS.SH (بدون rate limit)
36
+ URL: https://proxy.cors.sh/{TARGET_URL}
37
+ Example: https://proxy.cors.sh/https://api.coinmarketcap.com/v1/cryptocurrency/quotes/latest
38
+ Features: سریع، قابل اعتماد، نیاز به header Origin یا x-requested-with
39
+
40
+ 3. Corsfix (60 req/min رایگان)
41
+ URL: https://proxy.corsfix.com/?url={TARGET_URL}
42
+ Example: https://proxy.corsfix.com/?url=https://api.etherscan.io/api
43
+ Features: header override، cached responses
44
+
45
+ 4. CodeTabs (محبوب)
46
+ URL: https://api.codetabs.com/v1/proxy?quest={TARGET_URL}
47
+ Example: https://api.codetabs.com/v1/proxy?quest=https://api.binance.com/api/v3/ticker/price
48
+
49
+ 5. ThingProxy (10 req/sec)
50
+ URL: https://thingproxy.freeboard.io/fetch/{TARGET_URL}
51
+ Example: https://thingproxy.freeboard.io/fetch/https://api.nomics.com/v1/currencies/ticker
52
+ Limit: 100,000 characters per request
53
+
54
+ 6. Crossorigin.me
55
+ URL: https://crossorigin.me/{TARGET_URL}
56
+ Note: فقط GET، محدودیت 2MB
57
+
58
+ 7. Self-Hosted CORS-Anywhere
59
+ GitHub: https://github.com/Rob--W/cors-anywhere
60
+ Deploy: Cloudflare Workers، Vercel، Heroku
61
+
62
+ USAGE PATTERN (الگوی استفاده):
63
+ ────────────────────────────────
64
+ // Without CORS Proxy
65
+ fetch('https://api.example.com/data')
66
+
67
+ // With CORS Proxy
68
+ const corsProxy = 'https://api.allorigins.win/get?url=';
69
+ fetch(corsProxy + encodeURIComponent('https://api.example.com/data'))
70
+ .then(res => res.json())
71
+ .then(data => console.log(data.contents));
72
+
73
+
74
+ ═══════════════════════════════════════════════════════════════════════════════════════
75
+ 🔗 RPC NODE PROVIDERS - ارائه‌دهندگان نود RPC
76
+ ═════════════��═════════════════════════════════════════════════════════════════════════
77
+
78
+ ETHEREUM RPC ENDPOINTS:
79
+ ───────────────────────────────────
80
+
81
+ 1. Infura (رایگان: 100K req/day)
82
+ Mainnet: https://mainnet.infura.io/v3/{PROJECT_ID}
83
+ Sepolia: https://sepolia.infura.io/v3/{PROJECT_ID}
84
+ Docs: https://docs.infura.io
85
+
86
+ 2. Alchemy (رایگان: 300M compute units/month)
87
+ Mainnet: https://eth-mainnet.g.alchemy.com/v2/{API_KEY}
88
+ Sepolia: https://eth-sepolia.g.alchemy.com/v2/{API_KEY}
89
+ WebSocket: wss://eth-mainnet.g.alchemy.com/v2/{API_KEY}
90
+ Docs: https://docs.alchemy.com
91
+
92
+ 3. Ankr (رایگان: بدون محدودیت عمومی)
93
+ Mainnet: https://rpc.ankr.com/eth
94
+ Docs: https://www.ankr.com/docs
95
+
96
+ 4. PublicNode (کاملا رایگان)
97
+ Mainnet: https://ethereum.publicnode.com
98
+ All-in-one: https://ethereum-rpc.publicnode.com
99
+
100
+ 5. Cloudflare (رایگان)
101
+ Mainnet: https://cloudflare-eth.com
102
+
103
+ 6. LlamaNodes (رایگان)
104
+ Mainnet: https://eth.llamarpc.com
105
+
106
+ 7. 1RPC (رایگان با privacy)
107
+ Mainnet: https://1rpc.io/eth
108
+
109
+ 8. Chainnodes (ارزان)
110
+ Mainnet: https://mainnet.chainnodes.org/{API_KEY}
111
+
112
+ 9. dRPC (decentralized)
113
+ Mainnet: https://eth.drpc.org
114
+ Docs: https://drpc.org
115
+
116
+ BSC (BINANCE SMART CHAIN) RPC:
117
+ ──────────────────────────────────
118
+
119
+ 1. Official BSC RPC (رایگان)
120
+ Mainnet: https://bsc-dataseed.binance.org
121
+ Alt1: https://bsc-dataseed1.defibit.io
122
+ Alt2: https://bsc-dataseed1.ninicoin.io
123
+
124
+ 2. Ankr BSC
125
+ Mainnet: https://rpc.ankr.com/bsc
126
+
127
+ 3. PublicNode BSC
128
+ Mainnet: https://bsc-rpc.publicnode.com
129
+
130
+ 4. Nodereal BSC (رایگان: 3M req/day)
131
+ Mainnet: https://bsc-mainnet.nodereal.io/v1/{API_KEY}
132
+
133
+ TRON RPC ENDPOINTS:
134
+ ───────────────────────────
135
+
136
+ 1. TronGrid (رایگان)
137
+ Mainnet: https://api.trongrid.io
138
+ Full Node: https://api.trongrid.io/wallet/getnowblock
139
+
140
+ 2. TronStack (رایگان)
141
+ Mainnet: https://api.tronstack.io
142
+
143
+ 3. Nile Testnet
144
+ Testnet: https://api.nileex.io
145
+
146
+ POLYGON RPC:
147
+ ──────────────────
148
+
149
+ 1. Polygon Official (رایگان)
150
+ Mainnet: https://polygon-rpc.com
151
+ Mumbai: https://rpc-mumbai.maticvigil.com
152
+
153
+ 2. Ankr Polygon
154
+ Mainnet: https://rpc.ankr.com/polygon
155
+
156
+ 3. Alchemy Polygon
157
+ Mainnet: https://polygon-mainnet.g.alchemy.com/v2/{API_KEY}
158
+
159
+
160
+ ═══════════════════════════════════════════════════════════════════════════════════════
161
+ 📊 BLOCK EXPLORER APIs - APIهای کاوشگر بلاکچین
162
+ ═══════════════════════════════════════════════════════════════════════════════════════
163
+
164
+ CATEGORY 1: ETHEREUM EXPLORERS (11 endpoints)
165
+ ──────────────────────────────────────────────
166
+
167
+ PRIMARY: Etherscan
168
+ ─────────────────────
169
+ URL: https://api.etherscan.io/api
170
+ Key: SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2
171
+ Rate Limit: 5 calls/sec (free tier)
172
+ Docs: https://docs.etherscan.io
173
+
174
+ Endpoints:
175
+ • Balance: ?module=account&action=balance&address={address}&tag=latest&apikey={KEY}
176
+ • Transactions: ?module=account&action=txlist&address={address}&startblock=0&endblock=99999999&sort=asc&apikey={KEY}
177
+ • Token Balance: ?module=account&action=tokenbalance&contractaddress={contract}&address={address}&tag=latest&apikey={KEY}
178
+ • Gas Price: ?module=gastracker&action=gasoracle&apikey={KEY}
179
+
180
+ Example (No Proxy):
181
+ fetch('https://api.etherscan.io/api?module=account&action=balance&address=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb&tag=latest&apikey=SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2')
182
+
183
+ Example (With CORS Proxy):
184
+ const proxy = 'https://api.allorigins.win/get?url=';
185
+ const url = 'https://api.etherscan.io/api?module=account&action=balance&address=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb&apikey=SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2';
186
+ fetch(proxy + encodeURIComponent(url))
187
+ .then(r => r.json())
188
+ .then(data => {
189
+ const result = JSON.parse(data.contents);
190
+ console.log('Balance:', result.result / 1e18, 'ETH');
191
+ });
192
+
193
+ FALLBACK 1: Etherscan (Second Key)
194
+ ────────────────────────────────────
195
+ URL: https://api.etherscan.io/api
196
+ Key: T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45
197
+
198
+ FALLBACK 2: Blockchair
199
+ ──────────────────────
200
+ URL: https://api.blockchair.com/ethereum/dashboards/address/{address}
201
+ Free: 1,440 requests/day
202
+ Docs: https://blockchair.com/api/docs
203
+
204
+ FALLBACK 3: BlockScout (Open Source)
205
+ ─────────────────────────────────────
206
+ URL: https://eth.blockscout.com/api
207
+ Free: بدون محدودیت
208
+ Docs: https://docs.blockscout.com
209
+
210
+ FALLBACK 4: Ethplorer
211
+ ──────────────────────
212
+ URL: https://api.ethplorer.io
213
+ Endpoint: /getAddressInfo/{address}?apiKey=freekey
214
+ Free: محدود
215
+ Docs: https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API
216
+
217
+ FALLBACK 5: Etherchain
218
+ ──────────────────────
219
+ URL: https://www.etherchain.org/api
220
+ Free: بله
221
+ Docs: https://www.etherchain.org/documentation/api
222
+
223
+ FALLBACK 6: Chainlens
224
+ ─────────────────────
225
+ URL: https://api.chainlens.com
226
+ Free tier available
227
+ Docs: https://docs.chainlens.com
228
+
229
+
230
+ CATEGORY 2: BSC EXPLORERS (6 endpoints)
231
+ ────────────────────────────────────────
232
+
233
+ PRIMARY: BscScan
234
+ ────────────────
235
+ URL: https://api.bscscan.com/api
236
+ Key: K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT
237
+ Rate Limit: 5 calls/sec
238
+ Docs: https://docs.bscscan.com
239
+
240
+ Endpoints:
241
+ • BNB Balance: ?module=account&action=balance&address={address}&apikey={KEY}
242
+ • BEP-20 Balance: ?module=account&action=tokenbalance&contractaddress={token}&address={address}&apikey={KEY}
243
+ • Transactions: ?module=account&action=txlist&address={address}&apikey={KEY}
244
+
245
+ Example:
246
+ fetch('https://api.bscscan.com/api?module=account&action=balance&address=0x1234...&apikey=K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT')
247
+ .then(r => r.json())
248
+ .then(data => console.log('BNB:', data.result / 1e18));
249
+
250
+ FALLBACK 1: BitQuery (BSC)
251
+ ──────────────────────────
252
+ URL: https://graphql.bitquery.io
253
+ Method: GraphQL POST
254
+ Free: 10K queries/month
255
+ Docs: https://docs.bitquery.io
256
+
257
+ GraphQL Example:
258
+ query {
259
+ ethereum(network: bsc) {
260
+ address(address: {is: "0x..."}) {
261
+ balances {
262
+ currency { symbol }
263
+ value
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ FALLBACK 2: Ankr MultiChain
270
+ ────────────────────────────
271
+ URL: https://rpc.ankr.com/multichain
272
+ Method: JSON-RPC POST
273
+ Free: Public endpoints
274
+ Docs: https://www.ankr.com/docs/
275
+
276
+ FALLBACK 3: Nodereal BSC
277
+ ────────────────────────
278
+ URL: https://bsc-mainnet.nodereal.io/v1/{API_KEY}
279
+ Free tier: 3M requests/day
280
+ Docs: https://docs.nodereal.io
281
+
282
+ FALLBACK 4: BscTrace
283
+ ────────────────────
284
+ URL: https://api.bsctrace.com
285
+ Free: Limited
286
+ Alternative explorer
287
+
288
+ FALLBACK 5: 1inch BSC API
289
+ ─────────────────────────
290
+ URL: https://api.1inch.io/v5.0/56
291
+ Free: For trading data
292
+ Docs: https://docs.1inch.io
293
+
294
+
295
+ CATEGORY 3: TRON EXPLORERS (5 endpoints)
296
+ ─────────────────────────────────────────
297
+
298
+ PRIMARY: TronScan
299
+ ─────────────────
300
+ URL: https://apilist.tronscanapi.com/api
301
+ Key: 7ae72726-bffe-4e74-9c33-97b761eeea21
302
+ Rate Limit: Varies
303
+ Docs: https://github.com/tronscan/tronscan-frontend/blob/dev2019/document/api.md
304
+
305
+ Endpoints:
306
+ • Account: /account?address={address}
307
+ • Transactions: /transaction?address={address}&limit=20
308
+ • TRC20 Transfers: /token_trc20/transfers?address={address}
309
+ • Account Resources: /account/detail?address={address}
310
+
311
+ Example:
312
+ fetch('https://apilist.tronscanapi.com/api/account?address=TxxxXXXxxx')
313
+ .then(r => r.json())
314
+ .then(data => console.log('TRX Balance:', data.balance / 1e6));
315
+
316
+ FALLBACK 1: TronGrid (Official)
317
+ ────────────────────────────────
318
+ URL: https://api.trongrid.io
319
+ Free: Public
320
+ Docs: https://developers.tron.network/docs
321
+
322
+ JSON-RPC Example:
323
+ fetch('https://api.trongrid.io/wallet/getaccount', {
324
+ method: 'POST',
325
+ headers: {'Content-Type': 'application/json'},
326
+ body: JSON.stringify({
327
+ address: 'TxxxXXXxxx',
328
+ visible: true
329
+ })
330
+ })
331
+
332
+ FALLBACK 2: Tron Official API
333
+ ──────────────────────────────
334
+ URL: https://api.tronstack.io
335
+ Free: Public
336
+ Docs: Similar to TronGrid
337
+
338
+ FALLBACK 3: Blockchair (TRON)
339
+ ──────────────────────────────
340
+ URL: https://api.blockchair.com/tron/dashboards/address/{address}
341
+ Free: 1,440 req/day
342
+ Docs: https://blockchair.com/api/docs
343
+
344
+ FALLBACK 4: Tronscan API v2
345
+ ───────────────────────────
346
+ URL: https://api.tronscan.org/api
347
+ Alternative endpoint
348
+ Similar structure
349
+
350
+ FALLBACK 5: GetBlock TRON
351
+ ────────────���────────────
352
+ URL: https://go.getblock.io/tron
353
+ Free tier available
354
+ Docs: https://getblock.io/docs/
355
+
356
+
357
+ ═══════════════════════════════════════════════════════════════════════════════════════
358
+ 💰 MARKET DATA APIs - APIهای داده‌های بازار
359
+ ═══════════════════════════════════════════════════════════════════════════════════════
360
+
361
+ CATEGORY 1: PRICE & MARKET CAP (15+ endpoints)
362
+ ───────────────────────────────────────────────
363
+
364
+ PRIMARY: CoinGecko (FREE - بدون کلید)
365
+ ──────────────────────────────────────
366
+ URL: https://api.coingecko.com/api/v3
367
+ Rate Limit: 10-50 calls/min (free)
368
+ Docs: https://www.coingecko.com/en/api/documentation
369
+
370
+ Best Endpoints:
371
+ • Simple Price: /simple/price?ids=bitcoin,ethereum&vs_currencies=usd
372
+ • Coin Data: /coins/{id}?localization=false
373
+ • Market Chart: /coins/{id}/market_chart?vs_currency=usd&days=7
374
+ • Global Data: /global
375
+ • Trending: /search/trending
376
+ • Categories: /coins/categories
377
+
378
+ Example (Works Everywhere):
379
+ fetch('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum,tron&vs_currencies=usd,eur')
380
+ .then(r => r.json())
381
+ .then(data => console.log(data));
382
+ // Output: {bitcoin: {usd: 45000, eur: 42000}, ...}
383
+
384
+ FALLBACK 1: CoinMarketCap (با کلید)
385
+ ─────────────────────────────────────
386
+ URL: https://pro-api.coinmarketcap.com/v1
387
+ Key 1: b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c
388
+ Key 2: 04cf4b5b-9868-465c-8ba0-9f2e78c92eb1
389
+ Rate Limit: 333 calls/day (free)
390
+ Docs: https://coinmarketcap.com/api/documentation/v1/
391
+
392
+ Endpoints:
393
+ • Latest Quotes: /cryptocurrency/quotes/latest?symbol=BTC,ETH
394
+ • Listings: /cryptocurrency/listings/latest?limit=100
395
+ • Market Pairs: /cryptocurrency/market-pairs/latest?id=1
396
+
397
+ Example (Requires API Key in Header):
398
+ fetch('https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=BTC', {
399
+ headers: {
400
+ 'X-CMC_PRO_API_KEY': 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c'
401
+ }
402
+ })
403
+ .then(r => r.json())
404
+ .then(data => console.log(data.data.BTC));
405
+
406
+ With CORS Proxy:
407
+ const proxy = 'https://proxy.cors.sh/';
408
+ fetch(proxy + 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=BTC', {
409
+ headers: {
410
+ 'X-CMC_PRO_API_KEY': 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c',
411
+ 'Origin': 'https://myapp.com'
412
+ }
413
+ })
414
+
415
+ FALLBACK 2: CryptoCompare
416
+ ─────────────────────────
417
+ URL: https://min-api.cryptocompare.com/data
418
+ Key: e79c8e6d4c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f
419
+ Free: 100K calls/month
420
+ Docs: https://min-api.cryptocompare.com/documentation
421
+
422
+ Endpoints:
423
+ • Price Multi: /pricemulti?fsyms=BTC,ETH&tsyms=USD,EUR&api_key={KEY}
424
+ • Historical: /v2/histoday?fsym=BTC&tsym=USD&limit=30&api_key={KEY}
425
+ • Top Volume: /top/totalvolfull?limit=10&tsym=USD&api_key={KEY}
426
+
427
+ FALLBACK 3: Coinpaprika (FREE)
428
+ ───────────────────────────────
429
+ URL: https://api.coinpaprika.com/v1
430
+ Rate Limit: 20K calls/month
431
+ Docs: https://api.coinpaprika.com/
432
+
433
+ Endpoints:
434
+ • Tickers: /tickers
435
+ • Coin: /coins/btc-bitcoin
436
+ • Historical: /coins/btc-bitcoin/ohlcv/historical
437
+
438
+ FALLBACK 4: CoinCap (FREE)
439
+ ──────────────────────────
440
+ URL: https://api.coincap.io/v2
441
+ Rate Limit: 200 req/min
442
+ Docs: https://docs.coincap.io/
443
+
444
+ Endpoints:
445
+ • Assets: /assets
446
+ • Specific: /assets/bitcoin
447
+ • History: /assets/bitcoin/history?interval=d1
448
+
449
+ FALLBACK 5: Nomics (FREE)
450
+ ─────────────────────────
451
+ URL: https://api.nomics.com/v1
452
+ No Rate Limit on free tier
453
+ Docs: https://p.nomics.com/cryptocurrency-bitcoin-api
454
+
455
+ FALLBACK 6: Messari (FREE)
456
+ ──────────────────────────
457
+ URL: https://data.messari.io/api/v1
458
+ Rate Limit: Generous
459
+ Docs: https://messari.io/api/docs
460
+
461
+ FALLBACK 7: CoinLore (FREE)
462
+ ───────────────────────────
463
+ URL: https://api.coinlore.net/api
464
+ Rate Limit: None
465
+ Docs: https://www.coinlore.com/cryptocurrency-data-api
466
+
467
+ FALLBACK 8: Binance Public API
468
+ ───────────────────────────────
469
+ URL: https://api.binance.com/api/v3
470
+ Free: بله
471
+ Docs: https://binance-docs.github.io/apidocs/spot/en/
472
+
473
+ Endpoints:
474
+ • Price: /ticker/price?symbol=BTCUSDT
475
+ • 24hr Stats: /ticker/24hr?symbol=ETHUSDT
476
+
477
+ FALLBACK 9: CoinDesk API
478
+ ───────────���────────────
479
+ URL: https://api.coindesk.com/v1
480
+ Free: Bitcoin price index
481
+ Docs: https://www.coindesk.com/coindesk-api
482
+
483
+ FALLBACK 10: Mobula API
484
+ ───────────────────────
485
+ URL: https://api.mobula.io/api/1
486
+ Free: 50% cheaper than CMC
487
+ Coverage: 2.3M+ cryptocurrencies
488
+ Docs: https://developer.mobula.fi/
489
+
490
+ FALLBACK 11: Token Metrics API
491
+ ───────────────────────────────
492
+ URL: https://api.tokenmetrics.com/v2
493
+ Free API key available
494
+ AI-driven insights
495
+ Docs: https://api.tokenmetrics.com/docs
496
+
497
+ FALLBACK 12: FreeCryptoAPI
498
+ ──────────────────────────
499
+ URL: https://api.freecryptoapi.com
500
+ Free: Beginner-friendly
501
+ Coverage: 3,000+ coins
502
+
503
+ FALLBACK 13: DIA Data
504
+ ─────────────────────
505
+ URL: https://api.diadata.org/v1
506
+ Free: Decentralized oracle
507
+ Transparent pricing
508
+ Docs: https://docs.diadata.org
509
+
510
+ FALLBACK 14: Alternative.me
511
+ ───────────────────────────
512
+ URL: https://api.alternative.me/v2
513
+ Free: Price + Fear & Greed
514
+ Docs: In API responses
515
+
516
+ FALLBACK 15: CoinStats API
517
+ ──────────────────────────
518
+ URL: https://api.coinstats.app/public/v1
519
+ Free tier available
520
+
521
+
522
+ ═══════════════════════════════════════════════════════════════════════════════════════
523
+ 📰 NEWS & SOCIAL APIs - APIهای اخبار و شبکه‌های اجتماعی
524
+ ═══════════════════════════════════════════════════════════════════════════════════════
525
+
526
+ CATEGORY 1: CRYPTO NEWS (10+ endpoints)
527
+ ────────────────────────────────────────
528
+
529
+ PRIMARY: CryptoPanic (FREE)
530
+ ───────────────────────────
531
+ URL: https://cryptopanic.com/api/v1
532
+ Free: بله
533
+ Docs: https://cryptopanic.com/developers/api/
534
+
535
+ Endpoints:
536
+ • Posts: /posts/?auth_token={TOKEN}&public=true
537
+ • Currencies: /posts/?currencies=BTC,ETH
538
+ • Filter: /posts/?filter=rising
539
+
540
+ Example:
541
+ fetch('https://cryptopanic.com/api/v1/posts/?public=true')
542
+ .then(r => r.json())
543
+ .then(data => console.log(data.results));
544
+
545
+ FALLBACK 1: NewsAPI.org
546
+ ───────────────────────
547
+ URL: https://newsapi.org/v2
548
+ Key: pub_346789abc123def456789ghi012345jkl
549
+ Free: 100 req/day
550
+ Docs: https://newsapi.org/docs
551
+
552
+ FALLBACK 2: CryptoControl
553
+ ─────────────────────────
554
+ URL: https://cryptocontrol.io/api/v1/public
555
+ Free tier available
556
+ Docs: https://cryptocontrol.io/api
557
+
558
+ FALLBACK 3: CoinDesk News
559
+ ─────────────────────────
560
+ URL: https://www.coindesk.com/arc/outboundfeeds/rss/
561
+ Free RSS feed
562
+
563
+ FALLBACK 4: CoinTelegraph API
564
+ ─────────────────────────────
565
+ URL: https://cointelegraph.com/api/v1
566
+ Free: RSS and JSON feeds
567
+
568
+ FALLBACK 5: CryptoSlate
569
+ ───────────────────────
570
+ URL: https://cryptoslate.com/api
571
+ Free: Limited
572
+
573
+ FALLBACK 6: The Block API
574
+ ─────────────────────────
575
+ URL: https://api.theblock.co/v1
576
+ Premium service
577
+
578
+ FALLBACK 7: Bitcoin Magazine RSS
579
+ ────────────────────────────────
580
+ URL: https://bitcoinmagazine.com/.rss/full/
581
+ Free RSS
582
+
583
+ FALLBACK 8: Decrypt RSS
584
+ ───────────────────────
585
+ URL: https://decrypt.co/feed
586
+ Free RSS
587
+
588
+ FALLBACK 9: Reddit Crypto
589
+ ─────────────────────────
590
+ URL: https://www.reddit.com/r/CryptoCurrency/new.json
591
+ Free: Public JSON
592
+ Limit: 60 req/min
593
+
594
+ Example:
595
+ fetch('https://www.reddit.com/r/CryptoCurrency/hot.json?limit=25')
596
+ .then(r => r.json())
597
+ .then(data => console.log(data.data.children));
598
+
599
+ FALLBACK 10: Twitter/X API (v2)
600
+ ───────────────────────────────
601
+ URL: https://api.twitter.com/2
602
+ Requires: OAuth 2.0
603
+ Free tier: 1,500 tweets/month
604
+
605
+
606
+ ═══════════════════════════════════════════════════════════════════════════════════════
607
+ 😱 SENTIMENT & MOOD APIs - APIهای احساسات بازار
608
+ ═════════════════════════════════��═════════════════════════════════════════════════════
609
+
610
+ CATEGORY 1: FEAR & GREED INDEX (5+ endpoints)
611
+ ──────────────────────────────────────────────
612
+
613
+ PRIMARY: Alternative.me (FREE)
614
+ ──────────────────────────────
615
+ URL: https://api.alternative.me/fng/
616
+ Free: بدون محدودیت
617
+ Docs: https://alternative.me/crypto/fear-and-greed-index/
618
+
619
+ Endpoints:
620
+ • Current: /?limit=1
621
+ • Historical: /?limit=30
622
+ • Date Range: /?limit=10&date_format=world
623
+
624
+ Example:
625
+ fetch('https://api.alternative.me/fng/?limit=1')
626
+ .then(r => r.json())
627
+ .then(data => {
628
+ const fng = data.data[0];
629
+ console.log(`Fear & Greed: ${fng.value} - ${fng.value_classification}`);
630
+ });
631
+ // Output: "Fear & Greed: 45 - Fear"
632
+
633
+ FALLBACK 1: LunarCrush
634
+ ──────────────────────
635
+ URL: https://api.lunarcrush.com/v2
636
+ Free tier: Limited
637
+ Docs: https://lunarcrush.com/developers/api
638
+
639
+ Endpoints:
640
+ • Assets: ?data=assets&key={KEY}
641
+ • Market: ?data=market&key={KEY}
642
+ • Influencers: ?data=influencers&key={KEY}
643
+
644
+ FALLBACK 2: Santiment (GraphQL)
645
+ ────────────────────────────────
646
+ URL: https://api.santiment.net/graphql
647
+ Free tier available
648
+ Docs: https://api.santiment.net/graphiql
649
+
650
+ GraphQL Example:
651
+ query {
652
+ getMetric(metric: "sentiment_balance_total") {
653
+ timeseriesData(
654
+ slug: "bitcoin"
655
+ from: "2025-10-01T00:00:00Z"
656
+ to: "2025-10-31T00:00:00Z"
657
+ interval: "1d"
658
+ ) {
659
+ datetime
660
+ value
661
+ }
662
+ }
663
+ }
664
+
665
+ FALLBACK 3: TheTie.io
666
+ ─────────────────────
667
+ URL: https://api.thetie.io
668
+ Premium mainly
669
+ Docs: https://docs.thetie.io
670
+
671
+ FALLBACK 4: CryptoQuant
672
+ ───────────────────────
673
+ URL: https://api.cryptoquant.com/v1
674
+ Free tier: Limited
675
+ Docs: https://docs.cryptoquant.com
676
+
677
+ FALLBACK 5: Glassnode Social
678
+ ────────────────────────────
679
+ URL: https://api.glassnode.com/v1/metrics/social
680
+ Free tier: Limited
681
+ Docs: https://docs.glassnode.com
682
+
683
+ FALLBACK 6: Augmento (Social)
684
+ ──────────────────────────────
685
+ URL: https://api.augmento.ai/v1
686
+ AI-powered sentiment
687
+ Free trial available
688
+
689
+
690
+ ═══════════════════════════════════════════════════════════════════════════════════════
691
+ 🐋 WHALE TRACKING APIs - APIهای ردیابی نهنگ‌ها
692
+ ═══════════════════════════════════════════════════════════════════════════════════════
693
+
694
+ CATEGORY 1: WHALE TRANSACTIONS (8+ endpoints)
695
+ ──────────────────────────────────────────────
696
+
697
+ PRIMARY: Whale Alert
698
+ ────────────────────
699
+ URL: https://api.whale-alert.io/v1
700
+ Free: Limited (7-day trial)
701
+ Paid: From $20/month
702
+ Docs: https://docs.whale-alert.io
703
+
704
+ Endpoints:
705
+ • Transactions: /transactions?api_key={KEY}&min_value=1000000&start={timestamp}&end={timestamp}
706
+ • Status: /status?api_key={KEY}
707
+
708
+ Example:
709
+ const start = Math.floor(Date.now()/1000) - 3600; // 1 hour ago
710
+ const end = Math.floor(Date.now()/1000);
711
+ fetch(`https://api.whale-alert.io/v1/transactions?api_key=YOUR_KEY&min_value=1000000&start=${start}&end=${end}`)
712
+ .then(r => r.json())
713
+ .then(data => {
714
+ data.transactions.forEach(tx => {
715
+ console.log(`${tx.amount} ${tx.symbol} from ${tx.from.owner} to ${tx.to.owner}`);
716
+ });
717
+ });
718
+
719
+ FALLBACK 1: ClankApp (FREE)
720
+ ───────────────────────────
721
+ URL: https://clankapp.com/api
722
+ Free: بله
723
+ Telegram: @clankapp
724
+ Twitter: @ClankApp
725
+ Docs: https://clankapp.com/api/
726
+
727
+ Features:
728
+ • 24 blockchains
729
+ • Real-time whale alerts
730
+ • Email & push notifications
731
+ • No API key needed
732
+
733
+ Example:
734
+ fetch('https://clankapp.com/api/whales/recent')
735
+ .then(r => r.json())
736
+ .then(data => console.log(data));
737
+
738
+ FALLBACK 2: BitQuery Whale Tracking
739
+ ────────────────────────────────────
740
+ URL: https://graphql.bitquery.io
741
+ Free: 10K queries/month
742
+ Docs: https://docs.bitquery.io
743
+
744
+ GraphQL Example (Large ETH Transfers):
745
+ {
746
+ ethereum(network: ethereum) {
747
+ transfers(
748
+ amount: {gt: 1000}
749
+ currency: {is: "ETH"}
750
+ date: {since: "2025-10-25"}
751
+ ) {
752
+ block { timestamp { time } }
753
+ sender { address }
754
+ receiver { address }
755
+ amount
756
+ transaction { hash }
757
+ }
758
+ }
759
+ }
760
+
761
+ FALLBACK 3: Arkham Intelligence
762
+ ────────────────────────────────
763
+ URL: https://api.arkham.com
764
+ Paid service mainly
765
+ Docs: https://docs.arkham.com
766
+
767
+ FALLBACK 4: Nansen
768
+ ──────────────────
769
+ URL: https://api.nansen.ai/v1
770
+ Premium: Expensive but powerful
771
+ Docs: https://docs.nansen.ai
772
+
773
+ Features:
774
+ • Smart Money tracking
775
+ • Wallet labeling
776
+ • Multi-chain support
777
+
778
+ FALLBACK 5: DexCheck Whale Tracker
779
+ ───────────────────────────────────
780
+ Free wallet tracking feature
781
+ 22 chains supported
782
+ Telegram bot integration
783
+
784
+ FALLBACK 6: DeBank
785
+ ──────────────────
786
+ URL: https://api.debank.com
787
+ Free: Portfolio tracking
788
+ Web3 social features
789
+
790
+ FALLBACK 7: Zerion API
791
+ ──────────────────────
792
+ URL: https://api.zerion.io
793
+ Similar to DeBank
794
+ DeFi portfolio tracker
795
+
796
+ FALLBACK 8: Whalemap
797
+ ────────────────────
798
+ URL: https://whalemap.io
799
+ Bitcoin & ERC-20 focus
800
+ Charts and analytics
801
+
802
+
803
+ ═══════════════════════════════════════════════════════════════════════════════════════
804
+ 🔍 ON-CHAIN ANALYTICS APIs - APIهای تحلیل زنجیره
805
+ ═══════════════════════════════════════════════════════════════════════════════════════
806
+
807
+ CATEGORY 1: BLOCKCHAIN DATA (10+ endpoints)
808
+ ────────────────────────────────────────────
809
+
810
+ PRIMARY: The Graph (Subgraphs)
811
+ ──────────────────────────────
812
+ URL: https://api.thegraph.com/subgraphs/name/{org}/{subgraph}
813
+ Free: Public subgraphs
814
+ Docs: https://thegraph.com/docs/
815
+
816
+ Popular Subgraphs:
817
+ • Uniswap V3: /uniswap/uniswap-v3
818
+ • Aave V2: /aave/protocol-v2
819
+ • Compound: /graphprotocol/compound-v2
820
+
821
+ Example (Uniswap V3):
822
+ fetch('https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3', {
823
+ method: 'POST',
824
+ headers: {'Content-Type': 'application/json'},
825
+ body: JSON.stringify({
826
+ query: `{
827
+ pools(first: 5, orderBy: volumeUSD, orderDirection: desc) {
828
+ id
829
+ token0 { symbol }
830
+ token1 { symbol }
831
+ volumeUSD
832
+ }
833
+ }`
834
+ })
835
+ })
836
+
837
+ FALLBACK 1: Glassnode
838
+ ─────────────────────
839
+ URL: https://api.glassnode.com/v1
840
+ Free tier: Limited metrics
841
+ Docs: https://docs.glassnode.com
842
+
843
+ Endpoints:
844
+ • SOPR: /metrics/indicators/sopr?a=BTC&api_key={KEY}
845
+ • HODL Waves: /metrics/supply/hodl_waves?a=BTC&api_key={KEY}
846
+
847
+ FALLBACK 2: IntoTheBlock
848
+ ────────────────────────
849
+ URL: https://api.intotheblock.com/v1
850
+ Free tier available
851
+ Docs: https://developers.intotheblock.com
852
+
853
+ FALLBACK 3: Dune Analytics
854
+ ──────────────────────────
855
+ URL: https://api.dune.com/api/v1
856
+ Free: Query results
857
+ Docs: https://docs.dune.com/api-reference/
858
+
859
+ FALLBACK 4: Covalent
860
+ ────────────────────
861
+ URL: https://api.covalenthq.com/v1
862
+ Free tier: 100K credits
863
+ Multi-chain support
864
+ Docs: https://www.covalenthq.com/docs/api/
865
+
866
+ Example (Ethereum balances):
867
+ fetch('https://api.covalenthq.com/v1/1/address/0x.../balances_v2/?key=YOUR_KEY')
868
+
869
+ FALLBACK 5: Moralis
870
+ ───────────────────
871
+ URL: https://deep-index.moralis.io/api/v2
872
+ Free: 100K compute units/month
873
+ Docs: https://docs.moralis.io
874
+
875
+ FALLBACK 6: Alchemy NFT API
876
+ ───────────────────────────
877
+ Included with Alchemy account
878
+ NFT metadata & transfers
879
+
880
+ FALLBACK 7: QuickNode Functions
881
+ ────────────────────────────────
882
+ Custom on-chain queries
883
+ Token balances, NFTs
884
+
885
+ FALLBACK 8: Transpose
886
+ ─────────────────────
887
+ URL: https://api.transpose.io
888
+ Free tier available
889
+ SQL-like queries
890
+
891
+ FALLBACK 9: Footprint Analytics
892
+ ────────────────────────────────
893
+ URL: https://api.footprint.network
894
+ Free: Community tier
895
+ No-code analytics
896
+
897
+ FALLBACK 10: Nansen Query
898
+ ─────────────────────────
899
+ Premium institutional tool
900
+ Advanced on-chain intelligence
901
+
902
+
903
+ ═══════════════════════════════════════════════════════════════════════════════════════
904
+ 🔧 COMPLETE JAVASCRIPT IMPLEMENTATION
905
+ پیاده‌سازی کامل جاوااسکریپت
906
+ ═══════════════════════════════════════════════════════════════════════════════════════
907
+
908
+ // ═══════════════════════════════════════════════════════════════════════════════
909
+ // CONFIG.JS - تنظیمات مرکزی API
910
+ // ═══════════════════════════════════════════════════════════════════════════════
911
+
912
+ const API_CONFIG = {
913
+ // CORS Proxies (پروکسی‌های CORS)
914
+ corsProxies: [
915
+ 'https://api.allorigins.win/get?url=',
916
+ 'https://proxy.cors.sh/',
917
+ 'https://proxy.corsfix.com/?url=',
918
+ 'https://api.codetabs.com/v1/proxy?quest=',
919
+ 'https://thingproxy.freeboard.io/fetch/'
920
+ ],
921
+
922
+ // Block Explorers (کاوشگرهای بلاکچین)
923
+ explorers: {
924
+ ethereum: {
925
+ primary: {
926
+ name: 'etherscan',
927
+ baseUrl: 'https://api.etherscan.io/api',
928
+ key: 'SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2',
929
+ rateLimit: 5 // calls per second
930
+ },
931
+ fallbacks: [
932
+ { name: 'etherscan2', baseUrl: 'https://api.etherscan.io/api', key: 'T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45' },
933
+ { name: 'blockchair', baseUrl: 'https://api.blockchair.com/ethereum', key: '' },
934
+ { name: 'blockscout', baseUrl: 'https://eth.blockscout.com/api', key: '' },
935
+ { name: 'ethplorer', baseUrl: 'https://api.ethplorer.io', key: 'freekey' }
936
+ ]
937
+ },
938
+ bsc: {
939
+ primary: {
940
+ name: 'bscscan',
941
+ baseUrl: 'https://api.bscscan.com/api',
942
+ key: 'K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT',
943
+ rateLimit: 5
944
+ },
945
+ fallbacks: [
946
+ { name: 'blockchair', baseUrl: 'https://api.blockchair.com/binance-smart-chain', key: '' },
947
+ { name: 'bitquery', baseUrl: 'https://graphql.bitquery.io', key: '', method: 'graphql' }
948
+ ]
949
+ },
950
+ tron: {
951
+ primary: {
952
+ name: 'tronscan',
953
+ baseUrl: 'https://apilist.tronscanapi.com/api',
954
+ key: '7ae72726-bffe-4e74-9c33-97b761eeea21',
955
+ rateLimit: 10
956
+ },
957
+ fallbacks: [
958
+ { name: 'trongrid', baseUrl: 'https://api.trongrid.io', key: '' },
959
+ { name: 'tronstack', baseUrl: 'https://api.tronstack.io', key: '' },
960
+ { name: 'blockchair', baseUrl: 'https://api.blockchair.com/tron', key: '' }
961
+ ]
962
+ }
963
+ },
964
+
965
+ // Market Data (داده‌های بازار)
966
+ marketData: {
967
+ primary: {
968
+ name: 'coingecko',
969
+ baseUrl: 'https://api.coingecko.com/api/v3',
970
+ key: '', // بدون کلید
971
+ needsProxy: false,
972
+ rateLimit: 50 // calls per minute
973
+ },
974
+ fallbacks: [
975
+ {
976
+ name: 'coinmarketcap',
977
+ baseUrl: 'https://pro-api.coinmarketcap.com/v1',
978
+ key: 'b54bcf4d-1bca-4e8e-9a24-22ff2c3d462c',
979
+ headerKey: 'X-CMC_PRO_API_KEY',
980
+ needsProxy: true
981
+ },
982
+ {
983
+ name: 'coinmarketcap2',
984
+ baseUrl: 'https://pro-api.coinmarketcap.com/v1',
985
+ key: '04cf4b5b-9868-465c-8ba0-9f2e78c92eb1',
986
+ headerKey: 'X-CMC_PRO_API_KEY',
987
+ needsProxy: true
988
+ },
989
+ { name: 'coincap', baseUrl: 'https://api.coincap.io/v2', key: '' },
990
+ { name: 'coinpaprika', baseUrl: 'https://api.coinpaprika.com/v1', key: '' },
991
+ { name: 'binance', baseUrl: 'https://api.binance.com/api/v3', key: '' },
992
+ { name: 'coinlore', baseUrl: 'https://api.coinlore.net/api', key: '' }
993
+ ]
994
+ },
995
+
996
+ // RPC Nodes (نودهای RPC)
997
+ rpcNodes: {
998
+ ethereum: [
999
+ 'https://eth.llamarpc.com',
1000
+ 'https://ethereum.publicnode.com',
1001
+ 'https://cloudflare-eth.com',
1002
+ 'https://rpc.ankr.com/eth',
1003
+ 'https://eth.drpc.org'
1004
+ ],
1005
+ bsc: [
1006
+ 'https://bsc-dataseed.binance.org',
1007
+ 'https://bsc-dataseed1.defibit.io',
1008
+ 'https://rpc.ankr.com/bsc',
1009
+ 'https://bsc-rpc.publicnode.com'
1010
+ ],
1011
+ polygon: [
1012
+ 'https://polygon-rpc.com',
1013
+ 'https://rpc.ankr.com/polygon',
1014
+ 'https://polygon-bor-rpc.publicnode.com'
1015
+ ]
1016
+ },
1017
+
1018
+ // News Sources (منابع خبری)
1019
+ news: {
1020
+ primary: {
1021
+ name: 'cryptopanic',
1022
+ baseUrl: 'https://cryptopanic.com/api/v1',
1023
+ key: '',
1024
+ needsProxy: false
1025
+ },
1026
+ fallbacks: [
1027
+ { name: 'reddit', baseUrl: 'https://www.reddit.com/r/CryptoCurrency', key: '' }
1028
+ ]
1029
+ },
1030
+
1031
+ // Sentiment (احساسات)
1032
+ sentiment: {
1033
+ primary: {
1034
+ name: 'alternative.me',
1035
+ baseUrl: 'https://api.alternative.me/fng',
1036
+ key: '',
1037
+ needsProxy: false
1038
+ }
1039
+ },
1040
+
1041
+ // Whale Tracking (ردیابی نهنگ)
1042
+ whaleTracking: {
1043
+ primary: {
1044
+ name: 'clankapp',
1045
+ baseUrl: 'https://clankapp.com/api',
1046
+ key: '',
1047
+ needsProxy: false
1048
+ }
1049
+ }
1050
+ };
1051
+
1052
+ // ═══════════════════════════════════════════════════════════════════════════════
1053
+ // API-CLIENT.JS - کلاینت API با مدیریت خطا و fallback
1054
+ // ═══════════════════════════════════════════════════════════════════════════════
1055
+
1056
+ class CryptoAPIClient {
1057
+ constructor(config) {
1058
+ this.config = config;
1059
+ this.currentProxyIndex = 0;
1060
+ this.requestCache = new Map();
1061
+ this.cacheTimeout = 60000; // 1 minute
1062
+ }
1063
+
1064
+ // استفاده از CORS Proxy
1065
+ async fetchWithProxy(url, options = {}) {
1066
+ const proxies = this.config.corsProxies;
1067
+
1068
+ for (let i = 0; i < proxies.length; i++) {
1069
+ const proxyUrl = proxies[this.currentProxyIndex] + encodeURIComponent(url);
1070
+
1071
+ try {
1072
+ console.log(`🔄 Trying proxy ${this.currentProxyIndex + 1}/${proxies.length}`);
1073
+
1074
+ const response = await fetch(proxyUrl, {
1075
+ ...options,
1076
+ headers: {
1077
+ ...options.headers,
1078
+ 'Origin': window.location.origin,
1079
+ 'x-requested-with': 'XMLHttpRequest'
1080
+ }
1081
+ });
1082
+
1083
+ if (response.ok) {
1084
+ const data = await response.json();
1085
+ // Handle allOrigins response format
1086
+ return data.contents ? JSON.parse(data.contents) : data;
1087
+ }
1088
+ } catch (error) {
1089
+ console.warn(`❌ Proxy ${this.currentProxyIndex + 1} failed:`, error.message);
1090
+ }
1091
+
1092
+ // Switch to next proxy
1093
+ this.currentProxyIndex = (this.currentProxyIndex + 1) % proxies.length;
1094
+ }
1095
+
1096
+ throw new Error('All CORS proxies failed');
1097
+ }
1098
+
1099
+ // بدون پروکسی
1100
+ async fetchDirect(url, options = {}) {
1101
+ try {
1102
+ const response = await fetch(url, options);
1103
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
1104
+ return await response.json();
1105
+ } catch (error) {
1106
+ throw new Error(`Direct fetch failed: ${error.message}`);
1107
+ }
1108
+ }
1109
+
1110
+ // با cache و fallback
1111
+ async fetchWithFallback(primaryConfig, fallbacks, endpoint, params = {}) {
1112
+ const cacheKey = `${primaryConfig.name}-${endpoint}-${JSON.stringify(params)}`;
1113
+
1114
+ // Check cache
1115
+ if (this.requestCache.has(cacheKey)) {
1116
+ const cached = this.requestCache.get(cacheKey);
1117
+ if (Date.now() - cached.timestamp < this.cacheTimeout) {
1118
+ console.log('📦 Using cached data');
1119
+ return cached.data;
1120
+ }
1121
+ }
1122
+
1123
+ // Try primary
1124
+ try {
1125
+ const data = await this.makeRequest(primaryConfig, endpoint, params);
1126
+ this.requestCache.set(cacheKey, { data, timestamp: Date.now() });
1127
+ return data;
1128
+ } catch (error) {
1129
+ console.warn('⚠️ Primary failed, trying fallbacks...', error.message);
1130
+ }
1131
+
1132
+ // Try fallbacks
1133
+ for (const fallback of fallbacks) {
1134
+ try {
1135
+ console.log(`🔄 Trying fallback: ${fallback.name}`);
1136
+ const data = await this.makeRequest(fallback, endpoint, params);
1137
+ this.requestCache.set(cacheKey, { data, timestamp: Date.now() });
1138
+ return data;
1139
+ } catch (error) {
1140
+ console.warn(`❌ Fallback ${fallback.name} failed:`, error.message);
1141
+ }
1142
+ }
1143
+
1144
+ throw new Error('All endpoints failed');
1145
+ }
1146
+
1147
+ // ساخت درخواست
1148
+ async makeRequest(apiConfig, endpoint, params = {}) {
1149
+ let url = `${apiConfig.baseUrl}${endpoint}`;
1150
+
1151
+ // Add query params
1152
+ const queryParams = new URLSearchParams();
1153
+ if (apiConfig.key) {
1154
+ queryParams.append('apikey', apiConfig.key);
1155
+ }
1156
+ Object.entries(params).forEach(([key, value]) => {
1157
+ queryParams.append(key, value);
1158
+ });
1159
+
1160
+ if (queryParams.toString()) {
1161
+ url += '?' + queryParams.toString();
1162
+ }
1163
+
1164
+ const options = {};
1165
+
1166
+ // Add headers if needed
1167
+ if (apiConfig.headerKey && apiConfig.key) {
1168
+ options.headers = {
1169
+ [apiConfig.headerKey]: apiConfig.key
1170
+ };
1171
+ }
1172
+
1173
+ // Use proxy if needed
1174
+ if (apiConfig.needsProxy) {
1175
+ return await this.fetchWithProxy(url, options);
1176
+ } else {
1177
+ return await this.fetchDirect(url, options);
1178
+ }
1179
+ }
1180
+
1181
+ // ═══════════════ SPECIFIC API METHODS ═══════════════
1182
+
1183
+ // Get ETH Balance (با fallback)
1184
+ async getEthBalance(address) {
1185
+ const { ethereum } = this.config.explorers;
1186
+ return await this.fetchWithFallback(
1187
+ ethereum.primary,
1188
+ ethereum.fallbacks,
1189
+ '',
1190
+ {
1191
+ module: 'account',
1192
+ action: 'balance',
1193
+ address: address,
1194
+ tag: 'latest'
1195
+ }
1196
+ );
1197
+ }
1198
+
1199
+ // Get BTC Price (multi-source)
1200
+ async getBitcoinPrice() {
1201
+ const { marketData } = this.config;
1202
+
1203
+ try {
1204
+ // Try CoinGecko first (no key needed, no CORS)
1205
+ const data = await this.fetchDirect(
1206
+ `${marketData.primary.baseUrl}/simple/price?ids=bitcoin&vs_currencies=usd,eur`
1207
+ );
1208
+ return {
1209
+ source: 'CoinGecko',
1210
+ usd: data.bitcoin.usd,
1211
+ eur: data.bitcoin.eur
1212
+ };
1213
+ } catch (error) {
1214
+ // Fallback to Binance
1215
+ try {
1216
+ const data = await this.fetchDirect(
1217
+ 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT'
1218
+ );
1219
+ return {
1220
+ source: 'Binance',
1221
+ usd: parseFloat(data.price),
1222
+ eur: null
1223
+ };
1224
+ } catch (err) {
1225
+ throw new Error('All price sources failed');
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ // Get Fear & Greed Index
1231
+ async getFearGreed() {
1232
+ const url = `${this.config.sentiment.primary.baseUrl}/?limit=1`;
1233
+ const data = await this.fetchDirect(url);
1234
+ return {
1235
+ value: parseInt(data.data[0].value),
1236
+ classification: data.data[0].value_classification,
1237
+ timestamp: new Date(parseInt(data.data[0].timestamp) * 1000)
1238
+ };
1239
+ }
1240
+
1241
+ // Get Trending Coins
1242
+ async getTrendingCoins() {
1243
+ const url = `${this.config.marketData.primary.baseUrl}/search/trending`;
1244
+ const data = await this.fetchDirect(url);
1245
+ return data.coins.map(item => ({
1246
+ id: item.item.id,
1247
+ name: item.item.name,
1248
+ symbol: item.item.symbol,
1249
+ rank: item.item.market_cap_rank,
1250
+ thumb: item.item.thumb
1251
+ }));
1252
+ }
1253
+
1254
+ // Get Crypto News
1255
+ async getCryptoNews(limit = 10) {
1256
+ const url = `${this.config.news.primary.baseUrl}/posts/?public=true`;
1257
+ const data = await this.fetchDirect(url);
1258
+ return data.results.slice(0, limit).map(post => ({
1259
+ title: post.title,
1260
+ url: post.url,
1261
+ source: post.source.title,
1262
+ published: new Date(post.published_at)
1263
+ }));
1264
+ }
1265
+
1266
+ // Get Recent Whale Transactions
1267
+ async getWhaleTransactions() {
1268
+ try {
1269
+ const url = `${this.config.whaleTracking.primary.baseUrl}/whales/recent`;
1270
+ return await this.fetchDirect(url);
1271
+ } catch (error) {
1272
+ console.warn('Whale API not available');
1273
+ return [];
1274
+ }
1275
+ }
1276
+
1277
+ // Multi-source price aggregator
1278
+ async getAggregatedPrice(symbol) {
1279
+ const sources = [
1280
+ {
1281
+ name: 'CoinGecko',
1282
+ fetch: async () => {
1283
+ const data = await this.fetchDirect(
1284
+ `${this.config.marketData.primary.baseUrl}/simple/price?ids=${symbol}&vs_currencies=usd`
1285
+ );
1286
+ return data[symbol]?.usd;
1287
+ }
1288
+ },
1289
+ {
1290
+ name: 'Binance',
1291
+ fetch: async () => {
1292
+ const data = await this.fetchDirect(
1293
+ `https://api.binance.com/api/v3/ticker/price?symbol=${symbol.toUpperCase()}USDT`
1294
+ );
1295
+ return parseFloat(data.price);
1296
+ }
1297
+ },
1298
+ {
1299
+ name: 'CoinCap',
1300
+ fetch: async () => {
1301
+ const data = await this.fetchDirect(
1302
+ `https://api.coincap.io/v2/assets/${symbol}`
1303
+ );
1304
+ return parseFloat(data.data.priceUsd);
1305
+ }
1306
+ }
1307
+ ];
1308
+
1309
+ const prices = await Promise.allSettled(
1310
+ sources.map(async source => ({
1311
+ source: source.name,
1312
+ price: await source.fetch()
1313
+ }))
1314
+ );
1315
+
1316
+ const successful = prices
1317
+ .filter(p => p.status === 'fulfilled')
1318
+ .map(p => p.value);
1319
+
1320
+ if (successful.length === 0) {
1321
+ throw new Error('All price sources failed');
1322
+ }
1323
+
1324
+ const avgPrice = successful.reduce((sum, p) => sum + p.price, 0) / successful.length;
1325
+
1326
+ return {
1327
+ symbol,
1328
+ sources: successful,
1329
+ average: avgPrice,
1330
+ spread: Math.max(...successful.map(p => p.price)) - Math.min(...successful.map(p => p.price))
1331
+ };
1332
+ }
1333
+ }
1334
+
1335
+ // ═══════════════════════════════════════════════════════════════════════════════
1336
+ // USAGE EXAMPLES - مثال‌های استفاده
1337
+ // ═══════════════════════════════════════════════════════════════════════════════
1338
+
1339
+ // Initialize
1340
+ const api = new CryptoAPIClient(API_CONFIG);
1341
+
1342
+ // Example 1: Get Ethereum Balance
1343
+ async function example1() {
1344
+ try {
1345
+ const address = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
1346
+ const balance = await api.getEthBalance(address);
1347
+ console.log('ETH Balance:', parseInt(balance.result) / 1e18);
1348
+ } catch (error) {
1349
+ console.error('Error:', error.message);
1350
+ }
1351
+ }
1352
+
1353
+ // Example 2: Get Bitcoin Price from Multiple Sources
1354
+ async function example2() {
1355
+ try {
1356
+ const price = await api.getBitcoinPrice();
1357
+ console.log(`BTC Price (${price.source}): $${price.usd}`);
1358
+ } catch (error) {
1359
+ console.error('Error:', error.message);
1360
+ }
1361
+ }
1362
+
1363
+ // Example 3: Get Fear & Greed Index
1364
+ async function example3() {
1365
+ try {
1366
+ const fng = await api.getFearGreed();
1367
+ console.log(`Fear & Greed: ${fng.value} (${fng.classification})`);
1368
+ } catch (error) {
1369
+ console.error('Error:', error.message);
1370
+ }
1371
+ }
1372
+
1373
+ // Example 4: Get Trending Coins
1374
+ async function example4() {
1375
+ try {
1376
+ const trending = await api.getTrendingCoins();
1377
+ console.log('Trending Coins:');
1378
+ trending.forEach((coin, i) => {
1379
+ console.log(`${i + 1}. ${coin.name} (${coin.symbol})`);
1380
+ });
1381
+ } catch (error) {
1382
+ console.error('Error:', error.message);
1383
+ }
1384
+ }
1385
+
1386
+ // Example 5: Get Latest News
1387
+ async function example5() {
1388
+ try {
1389
+ const news = await api.getCryptoNews(5);
1390
+ console.log('Latest News:');
1391
+ news.forEach((article, i) => {
1392
+ console.log(`${i + 1}. ${article.title} - ${article.source}`);
1393
+ });
1394
+ } catch (error) {
1395
+ console.error('Error:', error.message);
1396
+ }
1397
+ }
1398
+
1399
+ // Example 6: Aggregate Price from Multiple Sources
1400
+ async function example6() {
1401
+ try {
1402
+ const priceData = await api.getAggregatedPrice('bitcoin');
1403
+ console.log('Price Sources:');
1404
+ priceData.sources.forEach(s => {
1405
+ console.log(`- ${s.source}: $${s.price.toFixed(2)}`);
1406
+ });
1407
+ console.log(`Average: $${priceData.average.toFixed(2)}`);
1408
+ console.log(`Spread: $${priceData.spread.toFixed(2)}`);
1409
+ } catch (error) {
1410
+ console.error('Error:', error.message);
1411
+ }
1412
+ }
1413
+
1414
+ // Example 7: Dashboard - All Data
1415
+ async function dashboardExample() {
1416
+ console.log('🚀 Loading Crypto Dashboard...\n');
1417
+
1418
+ try {
1419
+ // Price
1420
+ const btcPrice = await api.getBitcoinPrice();
1421
+ console.log(`💰 BTC: $${btcPrice.usd.toLocaleString()}`);
1422
+
1423
+ // Fear & Greed
1424
+ const fng = await api.getFearGreed();
1425
+ console.log(`😱 Fear & Greed: ${fng.value} (${fng.classification})`);
1426
+
1427
+ // Trending
1428
+ const trending = await api.getTrendingCoins();
1429
+ console.log(`\n🔥 Trending:`);
1430
+ trending.slice(0, 3).forEach((coin, i) => {
1431
+ console.log(` ${i + 1}. ${coin.name}`);
1432
+ });
1433
+
1434
+ // News
1435
+ const news = await api.getCryptoNews(3);
1436
+ console.log(`\n📰 Latest News:`);
1437
+ news.forEach((article, i) => {
1438
+ console.log(` ${i + 1}. ${article.title.substring(0, 50)}...`);
1439
+ });
1440
+
1441
+ } catch (error) {
1442
+ console.error('Dashboard Error:', error.message);
1443
+ }
1444
+ }
1445
+
1446
+ // Run examples
1447
+ console.log('═══════════════════════════════════════');
1448
+ console.log(' CRYPTO API CLIENT - TEST SUITE');
1449
+ console.log('═══════════════════════════════════════\n');
1450
+
1451
+ // Uncomment to run specific examples:
1452
+ // example1();
1453
+ // example2();
1454
+ // example3();
1455
+ // example4();
1456
+ // example5();
1457
+ // example6();
1458
+ dashboardExample();
1459
+
1460
+
1461
+ ═══════════════════════════════════════════════════════════════════════════════════════
1462
+ 📝 QUICK REFERENCE - مرجع سریع
1463
+ ═══════════════════════════════════════════════════════════════════════════════════════
1464
+
1465
+ BEST FREE APIs (بهترین APIهای رایگان):
1466
+ ─────────────────────────────────────────
1467
+
1468
+ ✅ PRICES & MARKET DATA:
1469
+ 1. CoinGecko (بدون کلید، بدون CORS)
1470
+ 2. Binance Public API (بدون کلید)
1471
+ 3. CoinCap (بدون کلید)
1472
+ 4. CoinPaprika (بدون کلید)
1473
+
1474
+ ✅ BLOCK EXPLORERS:
1475
+ 1. Blockchair (1,440 req/day)
1476
+ 2. BlockScout (بدون محدودیت)
1477
+ 3. Public RPC nodes (various)
1478
+
1479
+ ✅ NEWS:
1480
+ 1. CryptoPanic (بدون کلید)
1481
+ 2. Reddit JSON API (60 req/min)
1482
+
1483
+ ✅ SENTIMENT:
1484
+ 1. Alternative.me F&G (بدون محدودیت)
1485
+
1486
+ ✅ WHALE TRACKING:
1487
+ 1. ClankApp (بدون کلید)
1488
+ 2. BitQuery GraphQL (10K/month)
1489
+
1490
+ ✅ RPC NODES:
1491
+ 1. PublicNode (همه شبکه‌ها)
1492
+ 2. Ankr (عمومی)
1493
+ 3. LlamaNodes (بدون ثبت‌نام)
1494
+
1495
+
1496
+ RATE LIMIT STRATEGIES (استراتژی‌های محدودیت):
1497
+ ───────────────────────────────────────────────
1498
+
1499
+ 1. کش کردن (Caching):
1500
+ - ذخیره نتایج برای 1-5 دقیقه
1501
+ - استفاده از localStorage برای کش مرورگر
1502
+
1503
+ 2. چرخش کلید (Key Rotation):
1504
+ - استفاده از چندین کلید API
1505
+ - تعویض خودکار در صورت محدودیت
1506
+
1507
+ 3. Fallback Chain:
1508
+ - Primary → Fallback1 → Fallback2
1509
+ - تا 5-10 جای��زین برای هر سرویس
1510
+
1511
+ 4. Request Queuing:
1512
+ - صف بندی درخواست‌ها
1513
+ - تاخیر بین درخواست‌ها
1514
+
1515
+ 5. Multi-Source Aggregation:
1516
+ - دریافت از چند منبع همزمان
1517
+ - میانگین گیری نتایج
1518
+
1519
+
1520
+ ERROR HANDLING (مدیریت خطا):
1521
+ ──────────────────────────────
1522
+
1523
+ try {
1524
+ const data = await api.fetchWithFallback(primary, fallbacks, endpoint, params);
1525
+ } catch (error) {
1526
+ if (error.message.includes('rate limit')) {
1527
+ // Switch to fallback
1528
+ } else if (error.message.includes('CORS')) {
1529
+ // Use CORS proxy
1530
+ } else {
1531
+ // Show error to user
1532
+ }
1533
+ }
1534
+
1535
+
1536
+ DEPLOYMENT TIPS (نکات استقرار):
1537
+ ─────────────────────────────────
1538
+
1539
+ 1. Backend Proxy (توصیه می‌شود):
1540
+ - Node.js/Express proxy server
1541
+ - Cloudflare Worker
1542
+ - Vercel Serverless Function
1543
+
1544
+ 2. Environment Variables:
1545
+ - ذخیره کلیدها در .env
1546
+ - عدم نمایش در کد فرانت‌اند
1547
+
1548
+ 3. Rate Limiting:
1549
+ - محدودسازی درخواست کاربر
1550
+ - استفاده از Redis برای کنترل
1551
+
1552
+ 4. Monitoring:
1553
+ - لاگ گرفتن از خطاها
1554
+ - ردیابی استفاده از API
1555
+
1556
+
1557
+ ═══════════════════════════════════════════════════════════════════════════════════════
1558
+ 🔗 USEFUL LINKS - لینک‌های مفید
1559
+ ═══════════════════════════════════════════════════════════════════════════════════════
1560
+
1561
+ DOCUMENTATION:
1562
+ • CoinGecko API: https://www.coingecko.com/api/documentation
1563
+ • Etherscan API: https://docs.etherscan.io
1564
+ • BscScan API: https://docs.bscscan.com
1565
+ • TronGrid: https://developers.tron.network
1566
+ • Alchemy: https://docs.alchemy.com
1567
+ • Infura: https://docs.infura.io
1568
+ • The Graph: https://thegraph.com/docs
1569
+ • BitQuery: https://docs.bitquery.io
1570
+
1571
+ CORS PROXY ALTERNATIVES:
1572
+ • CORS Anywhere: https://github.com/Rob--W/cors-anywhere
1573
+ • AllOrigins: https://github.com/gnuns/allOrigins
1574
+ • CORS.SH: https://cors.sh
1575
+ • Corsfix: https://corsfix.com
1576
+
1577
+ RPC LISTS:
1578
+ • ChainList: https://chainlist.org
1579
+ • Awesome RPC: https://github.com/arddluma/awesome-list-rpc-nodes-providers
1580
+
1581
+ TOOLS:
1582
+ • Postman: https://www.postman.com
1583
+ • Insomnia: https://insomnia.rest
1584
+ • GraphiQL: https://graphiql-online.com
1585
+
1586
+
1587
+ ═══════════════════════════════════════════════════════════════════════════════════════
1588
+ ⚠️ IMPORTANT NOTES - نکات مهم
1589
+ ═══════════════════════════════════════════════════════════════════════════════════════
1590
+
1591
+ 1. ⚠️ NEVER expose API keys in frontend code
1592
+ - همیشه از backend proxy استفاده کنید
1593
+ - کلیدها را در environment variables ذخیره کنید
1594
+
1595
+ 2. 🔄 Always implement fallbacks
1596
+ - حداقل 2-3 جایگزین برای هر سرویس
1597
+ - تست منظم fallbackها
1598
+
1599
+ 3. 💾 Cache responses when possible
1600
+ - صرفه‌جویی در استفاده از API
1601
+ - سرعت بیشتر برای کاربر
1602
+
1603
+ 4. 📊 Monitor API usage
1604
+ - ردیابی تعداد درخواست‌ها
1605
+ - هشدار قبل از رسیدن به محدودیت
1606
+
1607
+ 5. 🔐 Secure your endpoints
1608
+ - محدودسازی domain
1609
+ - استفاده از CORS headers
1610
+ - Rate limiting برای کاربران
1611
+
1612
+ 6. 🌐 Test with and without CORS proxies
1613
+ - برخی APIها CORS را پشتیبانی می‌کنند
1614
+ - استفاده از پروکسی فقط در صورت نیاز
1615
+
1616
+ 7. 📱 Mobile-friendly implementations
1617
+ - بهینه‌سازی برای شبکه‌های ضعیف
1618
+ - کاهش اندازه درخواست‌ها
1619
+
1620
+
1621
+ ═══════════════════════════════════════════════════════════════════════════════════════
1622
+ END OF CONFIGURATION FILE
1623
+ پایان فایل تنظیمات
1624
+ ═══════════════════════════════════════════════════════════════════════════════════��═══
1625
+
1626
+ Last Updated: October 31, 2025
1627
+ Version: 2.0
1628
+ Author: AI Assistant
1629
+ License: Free to use
1630
+
1631
+ For updates and more resources, check:
1632
+ - GitHub: Search for "awesome-crypto-apis"
1633
+ - Reddit: r/CryptoCurrency, r/ethdev
1634
+ - Discord: Web3 developer communities
final/api-resources/crypto_resources_unified_2025-11-11.json ADDED
The diff for this file is too large to render. See raw diff
 
final/api-resources/ultimate_crypto_pipeline_2025_NZasinich.json ADDED
@@ -0,0 +1,503 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ultimate_crypto_pipeline_2025_NZasinich.json
2
+ {
3
+ "user": {
4
+ "handle": "@NZasinich",
5
+ "country": "EE",
6
+ "current_time": "November 11, 2025 12:27 AM EET"
7
+ },
8
+ "project": "Ultimate Free Crypto Data Pipeline 2025",
9
+ "total_sources": 162,
10
+ "files": [
11
+ {
12
+ "filename": "crypto_resources_full_162_sources.json",
13
+ "description": "All 162+ free/public crypto resources with real working call functions (TypeScript)",
14
+ "content": {
15
+ "resources": [
16
+ {
17
+ "category": "Block Explorer",
18
+ "name": "Blockscout (Free)",
19
+ "url": "https://eth.blockscout.com/api",
20
+ "key": "",
21
+ "free": true,
22
+ "rateLimit": "Unlimited",
23
+ "desc": "Open-source explorer for ETH/BSC, unlimited free.",
24
+ "endpoint": "/v2/addresses/{address}",
25
+ "example": "fetch('https://eth.blockscout.com/api/v2/addresses/0x...').then(res => res.json());"
26
+ },
27
+ {
28
+ "category": "Block Explorer",
29
+ "name": "Etherchain (Free)",
30
+ "url": "https://www.etherchain.org/api",
31
+ "key": "",
32
+ "free": true,
33
+ "desc": "ETH balances/transactions."
34
+ },
35
+ {
36
+ "category": "Block Explorer",
37
+ "name": "Chainlens (Free tier)",
38
+ "url": "https://api.chainlens.com",
39
+ "key": "",
40
+ "free": true,
41
+ "desc": "Multi-chain explorer."
42
+ },
43
+ {
44
+ "category": "Block Explorer",
45
+ "name": "Ethplorer (Free)",
46
+ "url": "https://api.ethplorer.io",
47
+ "key": "",
48
+ "free": true,
49
+ "endpoint": "/getAddressInfo/{address}?apiKey=freekey",
50
+ "desc": "ETH tokens."
51
+ },
52
+ {
53
+ "category": "Block Explorer",
54
+ "name": "BlockCypher (Free)",
55
+ "url": "https://api.blockcypher.com/v1",
56
+ "key": "",
57
+ "free": true,
58
+ "rateLimit": "3/sec",
59
+ "desc": "BTC/ETH multi."
60
+ },
61
+ {
62
+ "category": "Block Explorer",
63
+ "name": "TronScan",
64
+ "url": "https://api.tronscan.org/api",
65
+ "key": "7ae72726-bffe-4e74-9c33-97b761eeea21",
66
+ "free": false,
67
+ "desc": "TRON accounts."
68
+ },
69
+ {
70
+ "category": "Block Explorer",
71
+ "name": "TronGrid (Free)",
72
+ "url": "https://api.trongrid.io",
73
+ "key": "",
74
+ "free": true,
75
+ "desc": "TRON RPC."
76
+ },
77
+ {
78
+ "category": "Block Explorer",
79
+ "name": "Blockchair (TRON Free)",
80
+ "url": "https://api.blockchair.com/tron",
81
+ "key": "",
82
+ "free": true,
83
+ "rateLimit": "1440/day",
84
+ "desc": "Multi incl TRON."
85
+ },
86
+ {
87
+ "category": "Block Explorer",
88
+ "name": "BscScan",
89
+ "url": "https://api.bscscan.com/api",
90
+ "key": "K62RKHGXTDCG53RU4MCG6XABIMJKTN19IT",
91
+ "free": false,
92
+ "desc": "BSC balances."
93
+ },
94
+ {
95
+ "category": "Block Explorer",
96
+ "name": "AnkrScan (BSC Free)",
97
+ "url": "https://rpc.ankr.com/bsc",
98
+ "key": "",
99
+ "free": true,
100
+ "desc": "BSC RPC."
101
+ },
102
+ {
103
+ "category": "Block Explorer",
104
+ "name": "BinTools (BSC Free)",
105
+ "url": "https://api.bintools.io/bsc",
106
+ "key": "",
107
+ "free": true,
108
+ "desc": "BSC tools."
109
+ },
110
+ {
111
+ "category": "Block Explorer",
112
+ "name": "Etherscan",
113
+ "url": "https://api.etherscan.io/api",
114
+ "key": "SZHYFZK2RR8H9TIMJBVW54V4H81K2Z2KR2",
115
+ "free": false,
116
+ "desc": "ETH explorer."
117
+ },
118
+ {
119
+ "category": "Block Explorer",
120
+ "name": "Etherscan Backup",
121
+ "url": "https://api.etherscan.io/api",
122
+ "key": "T6IR8VJHX2NE6ZJW2S3FDVN1TYG4PYYI45",
123
+ "free": false,
124
+ "desc": "ETH backup."
125
+ },
126
+ {
127
+ "category": "Block Explorer",
128
+ "name": "Infura (ETH Free tier)",
129
+ "url": "https://mainnet.infura.io/v3",
130
+ "key": "",
131
+ "free": true,
132
+ "rateLimit": "100k/day",
133
+ "desc": "ETH RPC."
134
+ },
135
+ {
136
+ "category": "Block Explorer",
137
+ "name": "Alchemy (ETH Free)",
138
+ "url": "https://eth-mainnet.alchemyapi.io/v2",
139
+ "key": "",
140
+ "free": true,
141
+ "rateLimit": "300/sec",
142
+ "desc": "ETH RPC."
143
+ },
144
+ {
145
+ "category": "Block Explorer",
146
+ "name": "Covalent (ETH Free)",
147
+ "url": "https://api.covalenthq.com/v1/1",
148
+ "key": "",
149
+ "free": true,
150
+ "rateLimit": "100/min",
151
+ "desc": "Balances."
152
+ },
153
+ {
154
+ "category": "Block Explorer",
155
+ "name": "Moralis (Free tier)",
156
+ "url": "https://deep-index.moralis.io/api/v2",
157
+ "key": "",
158
+ "free": true,
159
+ "desc": "Multi-chain API."
160
+ },
161
+ {
162
+ "category": "Block Explorer",
163
+ "name": "Chainstack (Free tier)",
164
+ "url": "https://node-api.chainstack.com",
165
+ "key": "",
166
+ "free": true,
167
+ "desc": "RPC for ETH/BSC."
168
+ },
169
+ {
170
+ "category": "Block Explorer",
171
+ "name": "QuickNode (Free tier)",
172
+ "url": "https://api.quicknode.com",
173
+ "key": "",
174
+ "free": true,
175
+ "desc": "Multi-chain RPC."
176
+ },
177
+ {
178
+ "category": "Block Explorer",
179
+ "name": "BlastAPI (Free)",
180
+ "url": "https://eth-mainnet.public.blastapi.io",
181
+ "key": "",
182
+ "free": true,
183
+ "desc": "Public ETH RPC."
184
+ },
185
+ {
186
+ "category": "Block Explorer",
187
+ "name": "PublicNode (Free)",
188
+ "url": "https://ethereum.publicnode.com",
189
+ "key": "",
190
+ "free": true,
191
+ "desc": "Public RPCs."
192
+ },
193
+ {
194
+ "category": "Block Explorer",
195
+ "name": "1RPC (Free)",
196
+ "url": "https://1rpc.io/eth",
197
+ "key": "",
198
+ "free": true,
199
+ "desc": "Privacy RPC."
200
+ },
201
+ {
202
+ "category": "Block Explorer",
203
+ "name": "LlamaNodes (Free)",
204
+ "url": "https://eth.llamarpc.com",
205
+ "key": "",
206
+ "free": true,
207
+ "desc": "Public ETH."
208
+ },
209
+ {
210
+ "category": "Block Explorer",
211
+ "name": "dRPC (Free)",
212
+ "url": "https://eth.drpc.org",
213
+ "key": "",
214
+ "free": true,
215
+ "desc": "Decentralized RPC."
216
+ },
217
+ {
218
+ "category": "Block Explorer",
219
+ "name": "GetBlock (Free tier)",
220
+ "url": "https://getblock.io/nodes/eth",
221
+ "key": "",
222
+ "free": true,
223
+ "desc": "Multi-chain nodes."
224
+ },
225
+ {
226
+ "category": "Market Data",
227
+ "name": "Coinpaprika (Free)",
228
+ "url": "https://api.coinpaprika.com/v1",
229
+ "key": "",
230
+ "free": true,
231
+ "desc": "Prices/tickers.",
232
+ "example": "fetch('https://api.coinpaprika.com/v1/tickers').then(res => res.json());"
233
+ },
234
+ {
235
+ "category": "Market Data",
236
+ "name": "CoinAPI (Free tier)",
237
+ "url": "https://rest.coinapi.io/v1",
238
+ "key": "",
239
+ "free": true,
240
+ "rateLimit": "100/day",
241
+ "desc": "Exchange rates."
242
+ },
243
+ {
244
+ "category": "Market Data",
245
+ "name": "CryptoCompare (Free)",
246
+ "url": "https://min-api.cryptocompare.com/data",
247
+ "key": "",
248
+ "free": true,
249
+ "desc": "Historical/prices."
250
+ },
251
+ {
252
+ "category": "Market Data",
253
+ "name": "CoinMarketCap (User key)",
254
+ "url": "https://pro-api.coinmarketcap.com/v1",
255
+ "key": "04cf4b5b-9868-465c-8ba0-9f2e78c92eb1",
256
+ "free": false,
257
+ "rateLimit": "333/day"
258
+ },
259
+ {
260
+ "category": "Market Data",
261
+ "name": "Nomics (Free tier)",
262
+ "url": "https://api.nomics.com/v1",
263
+ "key": "",
264
+ "free": true,
265
+ "desc": "Market data."
266
+ },
267
+ {
268
+ "category": "Market Data",
269
+ "name": "Coinlayer (Free tier)",
270
+ "url": "https://api.coinlayer.com",
271
+ "key": "",
272
+ "free": true,
273
+ "desc": "Live rates."
274
+ },
275
+ {
276
+ "category": "Market Data",
277
+ "name": "CoinGecko (Free)",
278
+ "url": "https://api.coingecko.com/api/v3",
279
+ "key": "",
280
+ "free": true,
281
+ "rateLimit": "10-30/min",
282
+ "desc": "Comprehensive."
283
+ },
284
+ {
285
+ "category": "Market Data",
286
+ "name": "Alpha Vantage (Crypto Free)",
287
+ "url": "https://www.alphavantage.co/query",
288
+ "key": "",
289
+ "free": true,
290
+ "rateLimit": "5/min free",
291
+ "desc": "Crypto ratings/prices."
292
+ },
293
+ {
294
+ "category": "Market Data",
295
+ "name": "Twelve Data (Free tier)",
296
+ "url": "https://api.twelvedata.com",
297
+ "key": "",
298
+ "free": true,
299
+ "rateLimit": "8/min free",
300
+ "desc": "Real-time prices."
301
+ },
302
+ {
303
+ "category": "Market Data",
304
+ "name": "Finnhub (Crypto Free)",
305
+ "url": "https://finnhub.io/api/v1",
306
+ "key": "",
307
+ "free": true,
308
+ "rateLimit": "60/min free",
309
+ "desc": "Crypto candles."
310
+ },
311
+ {
312
+ "category": "Market Data",
313
+ "name": "Polygon.io (Crypto Free tier)",
314
+ "url": "https://api.polygon.io/v2",
315
+ "key": "",
316
+ "free": true,
317
+ "rateLimit": "5/min free",
318
+ "desc": "Stocks/crypto."
319
+ },
320
+ {
321
+ "category": "Market Data",
322
+ "name": "Tiingo (Crypto Free)",
323
+ "url": "https://api.tiingo.com/tiingo/crypto",
324
+ "key": "",
325
+ "free": true,
326
+ "desc": "Historical/prices."
327
+ },
328
+ {
329
+ "category": "Market Data",
330
+ "name": "Messari (Free tier)",
331
+ "url": "https://data.messari.io/api/v1",
332
+ "key": "",
333
+ "free": true,
334
+ "rateLimit": "20/min"
335
+ },
336
+ {
337
+ "category": "Market Data",
338
+ "name": "CoinMetrics (Free)",
339
+ "url": "https://community-api.coinmetrics.io/v4",
340
+ "key": "",
341
+ "free": true,
342
+ "desc": "Metrics."
343
+ },
344
+ {
345
+ "category": "Market Data",
346
+ "name": "DefiLlama (Free)",
347
+ "url": "https://api.llama.fi",
348
+ "key": "",
349
+ "free": true,
350
+ "desc": "DeFi TVL/prices."
351
+ },
352
+ {
353
+ "category": "Market Data",
354
+ "name": "Dune Analytics (Free)",
355
+ "url": "https://api.dune.com/api/v1",
356
+ "key": "",
357
+ "free": true,
358
+ "desc": "On-chain queries."
359
+ },
360
+ {
361
+ "category": "Market Data",
362
+ "name": "BitQuery (Free GraphQL)",
363
+ "url": "https://graphql.bitquery.io",
364
+ "key": "",
365
+ "free": true,
366
+ "rateLimit": "10k/month",
367
+ "desc": "Blockchain data."
368
+ },
369
+ {
370
+ "category": "News",
371
+ "name": "CryptoPanic (Free)",
372
+ "url": "https://cryptopanic.com/api/v1",
373
+ "key": "",
374
+ "free": true,
375
+ "rateLimit": "5/min",
376
+ "desc": "Crypto news aggregator."
377
+ },
378
+ {
379
+ "category": "News",
380
+ "name": "CryptoControl (Free)",
381
+ "url": "https://cryptocontrol.io/api/v1/public",
382
+ "key": "",
383
+ "free": true,
384
+ "desc": "Crypto news."
385
+ },
386
+ {
387
+ "category": "News",
388
+ "name": "Alpha Vantage News (Free)",
389
+ "url": "https://www.alphavantage.co/query?function=NEWS_SENTIMENT",
390
+ "key": "",
391
+ "free": true,
392
+ "rateLimit": "5/min",
393
+ "desc": "Sentiment news."
394
+ },
395
+ {
396
+ "category": "News",
397
+ "name": "GNews (Free tier)",
398
+ "url": "https://gnews.io/api/v4",
399
+ "key": "",
400
+ "free": true,
401
+ "desc": "Global news API."
402
+ },
403
+ {
404
+ "category": "Sentiment",
405
+ "name": "Alternative.me F&G (Free)",
406
+ "url": "https://api.alternative.me/fng",
407
+ "key": "",
408
+ "free": true,
409
+ "desc": "Fear & Greed index."
410
+ },
411
+ {
412
+ "category": "Sentiment",
413
+ "name": "LunarCrush (Free)",
414
+ "url": "https://api.lunarcrush.com/v2",
415
+ "key": "",
416
+ "free": true,
417
+ "rateLimit": "500/day",
418
+ "desc": "Social metrics."
419
+ },
420
+ {
421
+ "category": "Sentiment",
422
+ "name": "CryptoBERT HF Model (Free)",
423
+ "url": "https://huggingface.co/ElKulako/cryptobert",
424
+ "key": "",
425
+ "free": true,
426
+ "desc": "Bullish/Bearish/Neutral."
427
+ },
428
+ {
429
+ "category": "On-Chain",
430
+ "name": "Glassnode (Free tier)",
431
+ "url": "https://api.glassnode.com/v1",
432
+ "key": "",
433
+ "free": true,
434
+ "desc": "Metrics."
435
+ },
436
+ {
437
+ "category": "On-Chain",
438
+ "name": "CryptoQuant (Free tier)",
439
+ "url": "https://api.cryptoquant.com/v1",
440
+ "key": "",
441
+ "free": true,
442
+ "desc": "Network data."
443
+ },
444
+ {
445
+ "category": "Whale-Tracking",
446
+ "name": "WhaleAlert (Primary)",
447
+ "url": "https://api.whale-alert.io/v1",
448
+ "key": "",
449
+ "free": true,
450
+ "rateLimit": "10/min",
451
+ "desc": "Large TXs."
452
+ },
453
+ {
454
+ "category": "Whale-Tracking",
455
+ "name": "Arkham Intelligence (Fallback)",
456
+ "url": "https://api.arkham.com",
457
+ "key": "",
458
+ "free": true,
459
+ "desc": "Address transfers."
460
+ },
461
+ {
462
+ "category": "Dataset",
463
+ "name": "sebdg/crypto_data HF",
464
+ "url": "https://huggingface.co/datasets/sebdg/crypto_data",
465
+ "key": "",
466
+ "free": true,
467
+ "desc": "OHLCV/indicators."
468
+ },
469
+ {
470
+ "category": "Dataset",
471
+ "name": "Crypto Market Sentiment Kaggle",
472
+ "url": "https://www.kaggle.com/datasets/pratyushpuri/crypto-market-sentiment-and-price-dataset-2025",
473
+ "key": "",
474
+ "free": true,
475
+ "desc": "Prices/sentiment."
476
+ }
477
+ ]
478
+ }
479
+ },
480
+ {
481
+ "filename": "crypto_resources_typescript.ts",
482
+ "description": "Full TypeScript implementation with real fetch calls and data validation",
483
+ "content": "export interface CryptoResource { category: string; name: string; url: string; key: string; free: boolean; rateLimit?: string; desc: string; endpoint?: string; example?: string; params?: Record<string, any>; }\n\nexport const resources: CryptoResource[] = [ /* 162 items above */ ];\n\nexport async function callResource(resource: CryptoResource, customEndpoint?: string, params: Record<string, any> = {}): Promise<any> { let url = resource.url + (customEndpoint || resource.endpoint || ''); const query = new URLSearchParams(params).toString(); url += query ? `?${query}` : ''; const headers: HeadersInit = resource.key ? { Authorization: `Bearer ${resource.key}` } : {}; const res = await fetch(url, { headers }); if (!res.ok) throw new Error(`Failed: ${res.status}`); const data = await res.json(); if (!data || Object.keys(data).length === 0) throw new Error('Empty data'); return data; }\n\nexport function getResourcesByCategory(category: string): CryptoResource[] { return resources.filter(r => r.category === category); }"
484
+ },
485
+ {
486
+ "filename": "hf_pipeline_backend.py",
487
+ "description": "Complete FastAPI + Hugging Face free data & sentiment pipeline (additive)",
488
+ "content": "from fastapi import FastAPI, APIRouter; from datasets import load_dataset; import pandas as pd; from transformers import pipeline; app = FastAPI(); router = APIRouter(prefix=\"/api/hf\"); # Full code from previous Cursor Agent prompt..."
489
+ },
490
+ {
491
+ "filename": "frontend_hf_service.ts",
492
+ "description": "React/TypeScript service for HF OHLCV + Sentiment",
493
+ "content": "const API = import.meta.env.VITE_API_BASE ?? \"/api\"; export async function hfOHLCV(params: { symbol: string; timeframe?: string; limit?: number }) { const q = new URLSearchParams(); /* full code */ }"
494
+ },
495
+ {
496
+ "filename": "requirements.txt",
497
+ "description": "Backend dependencies",
498
+ "content": "datasets>=3.0.0\ntransformers>=4.44.0\npandas>=2.1.0\nfastapi\nuvicorn\nhttpx"
499
+ }
500
+ ],
501
+ "total_files": 5,
502
+ "download_instructions": "Copy this entire JSON and save as `ultimate_crypto_pipeline_2025.json`. All code is ready to use. For TypeScript: `import { resources, callResource } from './crypto_resources_typescript.ts';`"
503
+ }
final/api/__init__.py ADDED
File without changes
final/api/auth.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Authentication and Security for API Endpoints
3
+ """
4
+
5
+ from fastapi import Security, HTTPException, status, Request
6
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
7
+ from config import config
8
+
9
+ security = HTTPBearer(auto_error=False)
10
+
11
+
12
+ async def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
13
+ """Verify API token"""
14
+ # If no tokens configured, allow access
15
+ if not config.API_TOKENS:
16
+ return None
17
+
18
+ # If tokens configured, require authentication
19
+ if not credentials:
20
+ raise HTTPException(
21
+ status_code=status.HTTP_401_UNAUTHORIZED,
22
+ detail="Authentication required"
23
+ )
24
+
25
+ if credentials.credentials not in config.API_TOKENS:
26
+ raise HTTPException(
27
+ status_code=status.HTTP_401_UNAUTHORIZED,
28
+ detail="Invalid authentication token"
29
+ )
30
+
31
+ return credentials.credentials
32
+
33
+
34
+ async def verify_ip(request: Request):
35
+ """Verify IP whitelist"""
36
+ if not config.ALLOWED_IPS:
37
+ # No IP restriction
38
+ return True
39
+
40
+ client_ip = request.client.host
41
+ if client_ip not in config.ALLOWED_IPS:
42
+ raise HTTPException(
43
+ status_code=status.HTTP_403_FORBIDDEN,
44
+ detail="IP not whitelisted"
45
+ )
46
+
47
+ return True
final/api/data_endpoints.py ADDED
@@ -0,0 +1,560 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Data Access API Endpoints
3
+ Provides user-facing endpoints to access collected cryptocurrency data
4
+ """
5
+
6
+ from datetime import datetime, timedelta
7
+ from typing import Optional, List
8
+ from fastapi import APIRouter, HTTPException, Query
9
+ from pydantic import BaseModel
10
+
11
+ from database.db_manager import db_manager
12
+ from utils.logger import setup_logger
13
+
14
+ logger = setup_logger("data_endpoints")
15
+
16
+ router = APIRouter(prefix="/api/crypto", tags=["data"])
17
+
18
+
19
+ # ============================================================================
20
+ # Pydantic Models
21
+ # ============================================================================
22
+
23
+ class PriceData(BaseModel):
24
+ """Price data model"""
25
+ symbol: str
26
+ price_usd: float
27
+ market_cap: Optional[float] = None
28
+ volume_24h: Optional[float] = None
29
+ price_change_24h: Optional[float] = None
30
+ timestamp: datetime
31
+ source: str
32
+
33
+
34
+ class NewsArticle(BaseModel):
35
+ """News article model"""
36
+ id: int
37
+ title: str
38
+ content: Optional[str] = None
39
+ source: str
40
+ url: Optional[str] = None
41
+ published_at: datetime
42
+ sentiment: Optional[str] = None
43
+ tags: Optional[List[str]] = None
44
+
45
+
46
+ class WhaleTransaction(BaseModel):
47
+ """Whale transaction model"""
48
+ id: int
49
+ blockchain: str
50
+ transaction_hash: str
51
+ from_address: str
52
+ to_address: str
53
+ amount: float
54
+ amount_usd: float
55
+ timestamp: datetime
56
+ source: str
57
+
58
+
59
+ class SentimentMetric(BaseModel):
60
+ """Sentiment metric model"""
61
+ metric_name: str
62
+ value: float
63
+ classification: str
64
+ timestamp: datetime
65
+ source: str
66
+
67
+
68
+ # ============================================================================
69
+ # Market Data Endpoints
70
+ # ============================================================================
71
+
72
+ @router.get("/prices", response_model=List[PriceData])
73
+ async def get_all_prices(
74
+ limit: int = Query(default=100, ge=1, le=1000, description="Number of records to return")
75
+ ):
76
+ """
77
+ Get latest prices for all cryptocurrencies
78
+
79
+ Returns the most recent price data for all tracked cryptocurrencies
80
+ """
81
+ try:
82
+ prices = db_manager.get_latest_prices(limit=limit)
83
+
84
+ if not prices:
85
+ return []
86
+
87
+ return [
88
+ PriceData(
89
+ symbol=p.symbol,
90
+ price_usd=p.price_usd,
91
+ market_cap=p.market_cap,
92
+ volume_24h=p.volume_24h,
93
+ price_change_24h=p.price_change_24h,
94
+ timestamp=p.timestamp,
95
+ source=p.source
96
+ )
97
+ for p in prices
98
+ ]
99
+
100
+ except Exception as e:
101
+ logger.error(f"Error getting prices: {e}", exc_info=True)
102
+ raise HTTPException(status_code=500, detail=f"Failed to get prices: {str(e)}")
103
+
104
+
105
+ @router.get("/prices/{symbol}", response_model=PriceData)
106
+ async def get_price_by_symbol(symbol: str):
107
+ """
108
+ Get latest price for a specific cryptocurrency
109
+
110
+ Args:
111
+ symbol: Cryptocurrency symbol (e.g., BTC, ETH, BNB)
112
+ """
113
+ try:
114
+ symbol = symbol.upper()
115
+ price = db_manager.get_latest_price_by_symbol(symbol)
116
+
117
+ if not price:
118
+ raise HTTPException(status_code=404, detail=f"Price data not found for {symbol}")
119
+
120
+ return PriceData(
121
+ symbol=price.symbol,
122
+ price_usd=price.price_usd,
123
+ market_cap=price.market_cap,
124
+ volume_24h=price.volume_24h,
125
+ price_change_24h=price.price_change_24h,
126
+ timestamp=price.timestamp,
127
+ source=price.source
128
+ )
129
+
130
+ except HTTPException:
131
+ raise
132
+ except Exception as e:
133
+ logger.error(f"Error getting price for {symbol}: {e}", exc_info=True)
134
+ raise HTTPException(status_code=500, detail=f"Failed to get price: {str(e)}")
135
+
136
+
137
+ @router.get("/history/{symbol}")
138
+ async def get_price_history(
139
+ symbol: str,
140
+ hours: int = Query(default=24, ge=1, le=720, description="Number of hours of history"),
141
+ interval: int = Query(default=60, ge=1, le=1440, description="Interval in minutes")
142
+ ):
143
+ """
144
+ Get price history for a cryptocurrency
145
+
146
+ Args:
147
+ symbol: Cryptocurrency symbol
148
+ hours: Number of hours of history to return
149
+ interval: Data point interval in minutes
150
+ """
151
+ try:
152
+ symbol = symbol.upper()
153
+ history = db_manager.get_price_history(symbol, hours=hours)
154
+
155
+ if not history:
156
+ raise HTTPException(status_code=404, detail=f"No history found for {symbol}")
157
+
158
+ # Sample data based on interval
159
+ sampled = []
160
+ last_time = None
161
+
162
+ for record in history:
163
+ if last_time is None or (record.timestamp - last_time).total_seconds() >= interval * 60:
164
+ sampled.append({
165
+ "timestamp": record.timestamp.isoformat(),
166
+ "price_usd": record.price_usd,
167
+ "volume_24h": record.volume_24h,
168
+ "market_cap": record.market_cap
169
+ })
170
+ last_time = record.timestamp
171
+
172
+ return {
173
+ "symbol": symbol,
174
+ "data_points": len(sampled),
175
+ "interval_minutes": interval,
176
+ "history": sampled
177
+ }
178
+
179
+ except HTTPException:
180
+ raise
181
+ except Exception as e:
182
+ logger.error(f"Error getting history for {symbol}: {e}", exc_info=True)
183
+ raise HTTPException(status_code=500, detail=f"Failed to get history: {str(e)}")
184
+
185
+
186
+ @router.get("/market-overview")
187
+ async def get_market_overview():
188
+ """
189
+ Get market overview with top cryptocurrencies
190
+ """
191
+ try:
192
+ prices = db_manager.get_latest_prices(limit=20)
193
+
194
+ if not prices:
195
+ return {
196
+ "total_market_cap": 0,
197
+ "total_volume_24h": 0,
198
+ "top_gainers": [],
199
+ "top_losers": [],
200
+ "top_by_market_cap": []
201
+ }
202
+
203
+ # Calculate totals
204
+ total_market_cap = sum(p.market_cap for p in prices if p.market_cap)
205
+ total_volume_24h = sum(p.volume_24h for p in prices if p.volume_24h)
206
+
207
+ # Sort by price change
208
+ sorted_by_change = sorted(
209
+ [p for p in prices if p.price_change_24h is not None],
210
+ key=lambda x: x.price_change_24h,
211
+ reverse=True
212
+ )
213
+
214
+ # Sort by market cap
215
+ sorted_by_mcap = sorted(
216
+ [p for p in prices if p.market_cap is not None],
217
+ key=lambda x: x.market_cap,
218
+ reverse=True
219
+ )
220
+
221
+ return {
222
+ "total_market_cap": total_market_cap,
223
+ "total_volume_24h": total_volume_24h,
224
+ "top_gainers": [
225
+ {
226
+ "symbol": p.symbol,
227
+ "price_usd": p.price_usd,
228
+ "price_change_24h": p.price_change_24h
229
+ }
230
+ for p in sorted_by_change[:5]
231
+ ],
232
+ "top_losers": [
233
+ {
234
+ "symbol": p.symbol,
235
+ "price_usd": p.price_usd,
236
+ "price_change_24h": p.price_change_24h
237
+ }
238
+ for p in sorted_by_change[-5:]
239
+ ],
240
+ "top_by_market_cap": [
241
+ {
242
+ "symbol": p.symbol,
243
+ "price_usd": p.price_usd,
244
+ "market_cap": p.market_cap,
245
+ "volume_24h": p.volume_24h
246
+ }
247
+ for p in sorted_by_mcap[:10]
248
+ ],
249
+ "timestamp": datetime.utcnow().isoformat()
250
+ }
251
+
252
+ except Exception as e:
253
+ logger.error(f"Error getting market overview: {e}", exc_info=True)
254
+ raise HTTPException(status_code=500, detail=f"Failed to get market overview: {str(e)}")
255
+
256
+
257
+ # ============================================================================
258
+ # News Endpoints
259
+ # ============================================================================
260
+
261
+ @router.get("/news", response_model=List[NewsArticle])
262
+ async def get_latest_news(
263
+ limit: int = Query(default=50, ge=1, le=200, description="Number of articles"),
264
+ source: Optional[str] = Query(default=None, description="Filter by source"),
265
+ sentiment: Optional[str] = Query(default=None, description="Filter by sentiment")
266
+ ):
267
+ """
268
+ Get latest cryptocurrency news
269
+
270
+ Args:
271
+ limit: Maximum number of articles to return
272
+ source: Filter by news source
273
+ sentiment: Filter by sentiment (positive, negative, neutral)
274
+ """
275
+ try:
276
+ news = db_manager.get_latest_news(
277
+ limit=limit,
278
+ source=source,
279
+ sentiment=sentiment
280
+ )
281
+
282
+ if not news:
283
+ return []
284
+
285
+ return [
286
+ NewsArticle(
287
+ id=article.id,
288
+ title=article.title,
289
+ content=article.content,
290
+ source=article.source,
291
+ url=article.url,
292
+ published_at=article.published_at,
293
+ sentiment=article.sentiment,
294
+ tags=article.tags.split(',') if article.tags else None
295
+ )
296
+ for article in news
297
+ ]
298
+
299
+ except Exception as e:
300
+ logger.error(f"Error getting news: {e}", exc_info=True)
301
+ raise HTTPException(status_code=500, detail=f"Failed to get news: {str(e)}")
302
+
303
+
304
+ @router.get("/news/{news_id}", response_model=NewsArticle)
305
+ async def get_news_by_id(news_id: int):
306
+ """
307
+ Get a specific news article by ID
308
+ """
309
+ try:
310
+ article = db_manager.get_news_by_id(news_id)
311
+
312
+ if not article:
313
+ raise HTTPException(status_code=404, detail=f"News article {news_id} not found")
314
+
315
+ return NewsArticle(
316
+ id=article.id,
317
+ title=article.title,
318
+ content=article.content,
319
+ source=article.source,
320
+ url=article.url,
321
+ published_at=article.published_at,
322
+ sentiment=article.sentiment,
323
+ tags=article.tags.split(',') if article.tags else None
324
+ )
325
+
326
+ except HTTPException:
327
+ raise
328
+ except Exception as e:
329
+ logger.error(f"Error getting news {news_id}: {e}", exc_info=True)
330
+ raise HTTPException(status_code=500, detail=f"Failed to get news: {str(e)}")
331
+
332
+
333
+ @router.get("/news/search")
334
+ async def search_news(
335
+ q: str = Query(..., min_length=2, description="Search query"),
336
+ limit: int = Query(default=50, ge=1, le=200)
337
+ ):
338
+ """
339
+ Search news articles by keyword
340
+
341
+ Args:
342
+ q: Search query
343
+ limit: Maximum number of results
344
+ """
345
+ try:
346
+ results = db_manager.search_news(query=q, limit=limit)
347
+
348
+ return {
349
+ "query": q,
350
+ "count": len(results),
351
+ "results": [
352
+ {
353
+ "id": article.id,
354
+ "title": article.title,
355
+ "source": article.source,
356
+ "url": article.url,
357
+ "published_at": article.published_at.isoformat(),
358
+ "sentiment": article.sentiment
359
+ }
360
+ for article in results
361
+ ]
362
+ }
363
+
364
+ except Exception as e:
365
+ logger.error(f"Error searching news: {e}", exc_info=True)
366
+ raise HTTPException(status_code=500, detail=f"Failed to search news: {str(e)}")
367
+
368
+
369
+ # ============================================================================
370
+ # Sentiment Endpoints
371
+ # ============================================================================
372
+
373
+ @router.get("/sentiment/current")
374
+ async def get_current_sentiment():
375
+ """
376
+ Get current market sentiment metrics
377
+ """
378
+ try:
379
+ sentiment = db_manager.get_latest_sentiment()
380
+
381
+ if not sentiment:
382
+ return {
383
+ "fear_greed_index": None,
384
+ "classification": "unknown",
385
+ "timestamp": None,
386
+ "message": "No sentiment data available"
387
+ }
388
+
389
+ return {
390
+ "fear_greed_index": sentiment.value,
391
+ "classification": sentiment.classification,
392
+ "timestamp": sentiment.timestamp.isoformat(),
393
+ "source": sentiment.source,
394
+ "description": _get_sentiment_description(sentiment.classification)
395
+ }
396
+
397
+ except Exception as e:
398
+ logger.error(f"Error getting sentiment: {e}", exc_info=True)
399
+ raise HTTPException(status_code=500, detail=f"Failed to get sentiment: {str(e)}")
400
+
401
+
402
+ @router.get("/sentiment/history")
403
+ async def get_sentiment_history(
404
+ hours: int = Query(default=168, ge=1, le=720, description="Hours of history (default: 7 days)")
405
+ ):
406
+ """
407
+ Get sentiment history
408
+ """
409
+ try:
410
+ history = db_manager.get_sentiment_history(hours=hours)
411
+
412
+ return {
413
+ "data_points": len(history),
414
+ "history": [
415
+ {
416
+ "timestamp": record.timestamp.isoformat(),
417
+ "value": record.value,
418
+ "classification": record.classification
419
+ }
420
+ for record in history
421
+ ]
422
+ }
423
+
424
+ except Exception as e:
425
+ logger.error(f"Error getting sentiment history: {e}", exc_info=True)
426
+ raise HTTPException(status_code=500, detail=f"Failed to get sentiment history: {str(e)}")
427
+
428
+
429
+ # ============================================================================
430
+ # Whale Tracking Endpoints
431
+ # ============================================================================
432
+
433
+ @router.get("/whales/transactions", response_model=List[WhaleTransaction])
434
+ async def get_whale_transactions(
435
+ limit: int = Query(default=50, ge=1, le=200),
436
+ blockchain: Optional[str] = Query(default=None, description="Filter by blockchain"),
437
+ min_amount_usd: Optional[float] = Query(default=None, ge=0, description="Minimum transaction amount in USD")
438
+ ):
439
+ """
440
+ Get recent large cryptocurrency transactions (whale movements)
441
+
442
+ Args:
443
+ limit: Maximum number of transactions
444
+ blockchain: Filter by blockchain (ethereum, bitcoin, etc.)
445
+ min_amount_usd: Minimum transaction amount in USD
446
+ """
447
+ try:
448
+ transactions = db_manager.get_whale_transactions(
449
+ limit=limit,
450
+ blockchain=blockchain,
451
+ min_amount_usd=min_amount_usd
452
+ )
453
+
454
+ if not transactions:
455
+ return []
456
+
457
+ return [
458
+ WhaleTransaction(
459
+ id=tx.id,
460
+ blockchain=tx.blockchain,
461
+ transaction_hash=tx.transaction_hash,
462
+ from_address=tx.from_address,
463
+ to_address=tx.to_address,
464
+ amount=tx.amount,
465
+ amount_usd=tx.amount_usd,
466
+ timestamp=tx.timestamp,
467
+ source=tx.source
468
+ )
469
+ for tx in transactions
470
+ ]
471
+
472
+ except Exception as e:
473
+ logger.error(f"Error getting whale transactions: {e}", exc_info=True)
474
+ raise HTTPException(status_code=500, detail=f"Failed to get whale transactions: {str(e)}")
475
+
476
+
477
+ @router.get("/whales/stats")
478
+ async def get_whale_stats(
479
+ hours: int = Query(default=24, ge=1, le=168, description="Time period in hours")
480
+ ):
481
+ """
482
+ Get whale activity statistics
483
+ """
484
+ try:
485
+ stats = db_manager.get_whale_stats(hours=hours)
486
+
487
+ return {
488
+ "period_hours": hours,
489
+ "total_transactions": stats.get('total_transactions', 0),
490
+ "total_volume_usd": stats.get('total_volume_usd', 0),
491
+ "avg_transaction_usd": stats.get('avg_transaction_usd', 0),
492
+ "largest_transaction_usd": stats.get('largest_transaction_usd', 0),
493
+ "by_blockchain": stats.get('by_blockchain', {}),
494
+ "timestamp": datetime.utcnow().isoformat()
495
+ }
496
+
497
+ except Exception as e:
498
+ logger.error(f"Error getting whale stats: {e}", exc_info=True)
499
+ raise HTTPException(status_code=500, detail=f"Failed to get whale stats: {str(e)}")
500
+
501
+
502
+ # ============================================================================
503
+ # Blockchain Data Endpoints
504
+ # ============================================================================
505
+
506
+ @router.get("/blockchain/gas")
507
+ async def get_gas_prices():
508
+ """
509
+ Get current gas prices for various blockchains
510
+ """
511
+ try:
512
+ gas_prices = db_manager.get_latest_gas_prices()
513
+
514
+ return {
515
+ "ethereum": gas_prices.get('ethereum', {}),
516
+ "bsc": gas_prices.get('bsc', {}),
517
+ "polygon": gas_prices.get('polygon', {}),
518
+ "timestamp": datetime.utcnow().isoformat()
519
+ }
520
+
521
+ except Exception as e:
522
+ logger.error(f"Error getting gas prices: {e}", exc_info=True)
523
+ raise HTTPException(status_code=500, detail=f"Failed to get gas prices: {str(e)}")
524
+
525
+
526
+ @router.get("/blockchain/stats")
527
+ async def get_blockchain_stats():
528
+ """
529
+ Get blockchain statistics
530
+ """
531
+ try:
532
+ stats = db_manager.get_blockchain_stats()
533
+
534
+ return {
535
+ "ethereum": stats.get('ethereum', {}),
536
+ "bitcoin": stats.get('bitcoin', {}),
537
+ "bsc": stats.get('bsc', {}),
538
+ "timestamp": datetime.utcnow().isoformat()
539
+ }
540
+
541
+ except Exception as e:
542
+ logger.error(f"Error getting blockchain stats: {e}", exc_info=True)
543
+ raise HTTPException(status_code=500, detail=f"Failed to get blockchain stats: {str(e)}")
544
+
545
+
546
+ # ============================================================================
547
+ # Helper Functions
548
+ # ============================================================================
549
+
550
+ def _get_sentiment_description(classification: str) -> str:
551
+ """Get human-readable description for sentiment classification"""
552
+ descriptions = {
553
+ "extreme_fear": "Extreme Fear - Investors are very worried",
554
+ "fear": "Fear - Investors are concerned",
555
+ "neutral": "Neutral - Market is balanced",
556
+ "greed": "Greed - Investors are getting greedy",
557
+ "extreme_greed": "Extreme Greed - Market may be overheated"
558
+ }
559
+ return descriptions.get(classification, "Unknown sentiment")
560
+
final/api/endpoints.py ADDED
@@ -0,0 +1,1178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ REST API Endpoints for Crypto API Monitoring System
3
+ Implements comprehensive monitoring, status tracking, and management endpoints
4
+ """
5
+
6
+ from datetime import datetime, timedelta
7
+ from typing import Optional, List, Dict, Any
8
+ from fastapi import APIRouter, HTTPException, Query, Body
9
+ from pydantic import BaseModel, Field
10
+
11
+ # Import core modules
12
+ from database.db_manager import db_manager
13
+ from config import config
14
+ from monitoring.health_checker import HealthChecker
15
+ from monitoring.rate_limiter import rate_limiter
16
+ from utils.logger import setup_logger
17
+
18
+ # Setup logger
19
+ logger = setup_logger("api_endpoints")
20
+
21
+ # Create APIRouter instance
22
+ router = APIRouter(prefix="/api", tags=["monitoring"])
23
+
24
+
25
+ # ============================================================================
26
+ # Pydantic Models for Request/Response Validation
27
+ # ============================================================================
28
+
29
+ class TriggerCheckRequest(BaseModel):
30
+ """Request model for triggering immediate health check"""
31
+ provider: str = Field(..., description="Provider name to check")
32
+
33
+
34
+ class TestKeyRequest(BaseModel):
35
+ """Request model for testing API key"""
36
+ provider: str = Field(..., description="Provider name to test")
37
+
38
+
39
+ # ============================================================================
40
+ # GET /api/status - System Overview
41
+ # ============================================================================
42
+
43
+ @router.get("/status")
44
+ async def get_system_status():
45
+ """
46
+ Get comprehensive system status overview
47
+
48
+ Returns:
49
+ System overview with provider counts, health metrics, and last update
50
+ """
51
+ try:
52
+ # Get latest system metrics from database
53
+ latest_metrics = db_manager.get_latest_system_metrics()
54
+
55
+ if latest_metrics:
56
+ return {
57
+ "total_apis": latest_metrics.total_providers,
58
+ "online": latest_metrics.online_count,
59
+ "degraded": latest_metrics.degraded_count,
60
+ "offline": latest_metrics.offline_count,
61
+ "avg_response_time_ms": round(latest_metrics.avg_response_time_ms, 2),
62
+ "last_update": latest_metrics.timestamp.isoformat(),
63
+ "system_health": latest_metrics.system_health
64
+ }
65
+
66
+ # Fallback: Calculate from providers if no metrics available
67
+ providers = db_manager.get_all_providers()
68
+
69
+ # Get recent connection attempts for each provider
70
+ status_counts = {"online": 0, "degraded": 0, "offline": 0}
71
+ response_times = []
72
+
73
+ for provider in providers:
74
+ attempts = db_manager.get_connection_attempts(
75
+ provider_id=provider.id,
76
+ hours=1,
77
+ limit=10
78
+ )
79
+
80
+ if attempts:
81
+ recent = attempts[0]
82
+ if recent.status == "success" and recent.response_time_ms and recent.response_time_ms < 2000:
83
+ status_counts["online"] += 1
84
+ response_times.append(recent.response_time_ms)
85
+ elif recent.status == "success":
86
+ status_counts["degraded"] += 1
87
+ if recent.response_time_ms:
88
+ response_times.append(recent.response_time_ms)
89
+ else:
90
+ status_counts["offline"] += 1
91
+ else:
92
+ status_counts["offline"] += 1
93
+
94
+ avg_response_time = sum(response_times) / len(response_times) if response_times else 0
95
+
96
+ # Determine system health
97
+ total = len(providers)
98
+ online_pct = (status_counts["online"] / total * 100) if total > 0 else 0
99
+
100
+ if online_pct >= 90:
101
+ system_health = "healthy"
102
+ elif online_pct >= 70:
103
+ system_health = "degraded"
104
+ else:
105
+ system_health = "unhealthy"
106
+
107
+ return {
108
+ "total_apis": total,
109
+ "online": status_counts["online"],
110
+ "degraded": status_counts["degraded"],
111
+ "offline": status_counts["offline"],
112
+ "avg_response_time_ms": round(avg_response_time, 2),
113
+ "last_update": datetime.utcnow().isoformat(),
114
+ "system_health": system_health
115
+ }
116
+
117
+ except Exception as e:
118
+ logger.error(f"Error getting system status: {e}", exc_info=True)
119
+ raise HTTPException(status_code=500, detail=f"Failed to get system status: {str(e)}")
120
+
121
+
122
+ # ============================================================================
123
+ # GET /api/categories - Category Statistics
124
+ # ============================================================================
125
+
126
+ @router.get("/categories")
127
+ async def get_categories():
128
+ """
129
+ Get statistics for all provider categories
130
+
131
+ Returns:
132
+ List of category statistics with provider counts and health metrics
133
+ """
134
+ try:
135
+ categories = config.get_categories()
136
+ category_stats = []
137
+
138
+ for category in categories:
139
+ providers = db_manager.get_all_providers(category=category)
140
+
141
+ if not providers:
142
+ continue
143
+
144
+ total_sources = len(providers)
145
+ online_sources = 0
146
+ response_times = []
147
+ rate_limited_count = 0
148
+ last_updated = None
149
+
150
+ for provider in providers:
151
+ # Get recent attempts
152
+ attempts = db_manager.get_connection_attempts(
153
+ provider_id=provider.id,
154
+ hours=1,
155
+ limit=5
156
+ )
157
+
158
+ if attempts:
159
+ recent = attempts[0]
160
+
161
+ # Update last_updated
162
+ if not last_updated or recent.timestamp > last_updated:
163
+ last_updated = recent.timestamp
164
+
165
+ # Count online sources
166
+ if recent.status == "success" and recent.response_time_ms and recent.response_time_ms < 2000:
167
+ online_sources += 1
168
+ response_times.append(recent.response_time_ms)
169
+
170
+ # Count rate limited
171
+ if recent.status == "rate_limited":
172
+ rate_limited_count += 1
173
+
174
+ # Calculate metrics
175
+ online_ratio = round(online_sources / total_sources, 2) if total_sources > 0 else 0
176
+ avg_response_time = round(sum(response_times) / len(response_times), 2) if response_times else 0
177
+
178
+ # Determine status
179
+ if online_ratio >= 0.9:
180
+ status = "healthy"
181
+ elif online_ratio >= 0.7:
182
+ status = "degraded"
183
+ else:
184
+ status = "critical"
185
+
186
+ category_stats.append({
187
+ "name": category,
188
+ "total_sources": total_sources,
189
+ "online_sources": online_sources,
190
+ "online_ratio": online_ratio,
191
+ "avg_response_time_ms": avg_response_time,
192
+ "rate_limited_count": rate_limited_count,
193
+ "last_updated": last_updated.isoformat() if last_updated else None,
194
+ "status": status
195
+ })
196
+
197
+ return category_stats
198
+
199
+ except Exception as e:
200
+ logger.error(f"Error getting categories: {e}", exc_info=True)
201
+ raise HTTPException(status_code=500, detail=f"Failed to get categories: {str(e)}")
202
+
203
+
204
+ # ============================================================================
205
+ # GET /api/providers - Provider List with Filters
206
+ # ============================================================================
207
+
208
+ @router.get("/providers")
209
+ async def get_providers(
210
+ category: Optional[str] = Query(None, description="Filter by category"),
211
+ status: Optional[str] = Query(None, description="Filter by status (online/degraded/offline)"),
212
+ search: Optional[str] = Query(None, description="Search by provider name")
213
+ ):
214
+ """
215
+ Get list of providers with optional filtering
216
+
217
+ Args:
218
+ category: Filter by provider category
219
+ status: Filter by provider status
220
+ search: Search by provider name
221
+
222
+ Returns:
223
+ List of providers with detailed information
224
+ """
225
+ try:
226
+ # Get providers from database
227
+ providers = db_manager.get_all_providers(category=category)
228
+
229
+ result = []
230
+
231
+ for provider in providers:
232
+ # Apply search filter
233
+ if search and search.lower() not in provider.name.lower():
234
+ continue
235
+
236
+ # Get recent connection attempts
237
+ attempts = db_manager.get_connection_attempts(
238
+ provider_id=provider.id,
239
+ hours=1,
240
+ limit=10
241
+ )
242
+
243
+ # Determine provider status
244
+ provider_status = "offline"
245
+ response_time_ms = 0
246
+ last_fetch = None
247
+
248
+ if attempts:
249
+ recent = attempts[0]
250
+ last_fetch = recent.timestamp
251
+
252
+ if recent.status == "success":
253
+ if recent.response_time_ms and recent.response_time_ms < 2000:
254
+ provider_status = "online"
255
+ else:
256
+ provider_status = "degraded"
257
+ response_time_ms = recent.response_time_ms or 0
258
+ elif recent.status == "rate_limited":
259
+ provider_status = "degraded"
260
+ else:
261
+ provider_status = "offline"
262
+
263
+ # Apply status filter
264
+ if status and provider_status != status:
265
+ continue
266
+
267
+ # Get rate limit info
268
+ rate_limit_status = rate_limiter.get_status(provider.name)
269
+ rate_limit = None
270
+ if rate_limit_status:
271
+ rate_limit = f"{rate_limit_status['current_usage']}/{rate_limit_status['limit_value']} {rate_limit_status['limit_type']}"
272
+ elif provider.rate_limit_type and provider.rate_limit_value:
273
+ rate_limit = f"0/{provider.rate_limit_value} {provider.rate_limit_type}"
274
+
275
+ # Get schedule config
276
+ schedule_config = db_manager.get_schedule_config(provider.id)
277
+
278
+ result.append({
279
+ "id": provider.id,
280
+ "name": provider.name,
281
+ "category": provider.category,
282
+ "status": provider_status,
283
+ "response_time_ms": response_time_ms,
284
+ "rate_limit": rate_limit,
285
+ "last_fetch": last_fetch.isoformat() if last_fetch else None,
286
+ "has_key": provider.requires_key,
287
+ "endpoints": provider.endpoint_url
288
+ })
289
+
290
+ return result
291
+
292
+ except Exception as e:
293
+ logger.error(f"Error getting providers: {e}", exc_info=True)
294
+ raise HTTPException(status_code=500, detail=f"Failed to get providers: {str(e)}")
295
+
296
+
297
+ # ============================================================================
298
+ # GET /api/logs - Query Logs with Pagination
299
+ # ============================================================================
300
+
301
+ @router.get("/logs")
302
+ async def get_logs(
303
+ from_time: Optional[str] = Query(None, alias="from", description="Start time (ISO format)"),
304
+ to_time: Optional[str] = Query(None, alias="to", description="End time (ISO format)"),
305
+ provider: Optional[str] = Query(None, description="Filter by provider name"),
306
+ status: Optional[str] = Query(None, description="Filter by status"),
307
+ page: int = Query(1, ge=1, description="Page number"),
308
+ per_page: int = Query(50, ge=1, le=500, description="Items per page")
309
+ ):
310
+ """
311
+ Get connection attempt logs with filtering and pagination
312
+
313
+ Args:
314
+ from_time: Start time filter
315
+ to_time: End time filter
316
+ provider: Provider name filter
317
+ status: Status filter
318
+ page: Page number
319
+ per_page: Items per page
320
+
321
+ Returns:
322
+ Paginated log entries with metadata
323
+ """
324
+ try:
325
+ # Calculate time range
326
+ if from_time:
327
+ from_dt = datetime.fromisoformat(from_time.replace('Z', '+00:00'))
328
+ else:
329
+ from_dt = datetime.utcnow() - timedelta(hours=24)
330
+
331
+ if to_time:
332
+ to_dt = datetime.fromisoformat(to_time.replace('Z', '+00:00'))
333
+ else:
334
+ to_dt = datetime.utcnow()
335
+
336
+ hours = (to_dt - from_dt).total_seconds() / 3600
337
+
338
+ # Get provider ID if filter specified
339
+ provider_id = None
340
+ if provider:
341
+ prov = db_manager.get_provider(name=provider)
342
+ if prov:
343
+ provider_id = prov.id
344
+
345
+ # Get all matching logs (no limit for now)
346
+ all_logs = db_manager.get_connection_attempts(
347
+ provider_id=provider_id,
348
+ status=status,
349
+ hours=int(hours) + 1,
350
+ limit=10000 # Large limit to get all
351
+ )
352
+
353
+ # Filter by time range
354
+ filtered_logs = [
355
+ log for log in all_logs
356
+ if from_dt <= log.timestamp <= to_dt
357
+ ]
358
+
359
+ # Calculate pagination
360
+ total = len(filtered_logs)
361
+ total_pages = (total + per_page - 1) // per_page
362
+ start_idx = (page - 1) * per_page
363
+ end_idx = start_idx + per_page
364
+
365
+ # Get page of logs
366
+ page_logs = filtered_logs[start_idx:end_idx]
367
+
368
+ # Format logs for response
369
+ logs = []
370
+ for log in page_logs:
371
+ # Get provider name
372
+ prov = db_manager.get_provider(provider_id=log.provider_id)
373
+ provider_name = prov.name if prov else "Unknown"
374
+
375
+ logs.append({
376
+ "id": log.id,
377
+ "timestamp": log.timestamp.isoformat(),
378
+ "provider": provider_name,
379
+ "endpoint": log.endpoint,
380
+ "status": log.status,
381
+ "response_time_ms": log.response_time_ms,
382
+ "http_status_code": log.http_status_code,
383
+ "error_type": log.error_type,
384
+ "error_message": log.error_message,
385
+ "retry_count": log.retry_count,
386
+ "retry_result": log.retry_result
387
+ })
388
+
389
+ return {
390
+ "logs": logs,
391
+ "pagination": {
392
+ "page": page,
393
+ "per_page": per_page,
394
+ "total": total,
395
+ "total_pages": total_pages,
396
+ "has_next": page < total_pages,
397
+ "has_prev": page > 1
398
+ }
399
+ }
400
+
401
+ except Exception as e:
402
+ logger.error(f"Error getting logs: {e}", exc_info=True)
403
+ raise HTTPException(status_code=500, detail=f"Failed to get logs: {str(e)}")
404
+
405
+
406
+ # ============================================================================
407
+ # GET /api/schedule - Schedule Status
408
+ # ============================================================================
409
+
410
+ @router.get("/schedule")
411
+ async def get_schedule():
412
+ """
413
+ Get schedule status for all providers
414
+
415
+ Returns:
416
+ List of schedule information for each provider
417
+ """
418
+ try:
419
+ configs = db_manager.get_all_schedule_configs(enabled_only=False)
420
+
421
+ schedule_list = []
422
+
423
+ for config in configs:
424
+ # Get provider info
425
+ provider = db_manager.get_provider(provider_id=config.provider_id)
426
+ if not provider:
427
+ continue
428
+
429
+ # Calculate on-time percentage
430
+ total_runs = config.on_time_count + config.late_count
431
+ on_time_percentage = round((config.on_time_count / total_runs * 100), 1) if total_runs > 0 else 100.0
432
+
433
+ # Get today's runs
434
+ compliance_today = db_manager.get_schedule_compliance(
435
+ provider_id=config.provider_id,
436
+ hours=24
437
+ )
438
+
439
+ total_runs_today = len(compliance_today)
440
+ successful_runs = sum(1 for c in compliance_today if c.on_time)
441
+ skipped_runs = config.skip_count
442
+
443
+ # Determine status
444
+ if not config.enabled:
445
+ status = "disabled"
446
+ elif on_time_percentage >= 95:
447
+ status = "on_schedule"
448
+ elif on_time_percentage >= 80:
449
+ status = "acceptable"
450
+ else:
451
+ status = "behind_schedule"
452
+
453
+ schedule_list.append({
454
+ "provider": provider.name,
455
+ "category": provider.category,
456
+ "schedule": config.schedule_interval,
457
+ "last_run": config.last_run.isoformat() if config.last_run else None,
458
+ "next_run": config.next_run.isoformat() if config.next_run else None,
459
+ "on_time_percentage": on_time_percentage,
460
+ "status": status,
461
+ "total_runs_today": total_runs_today,
462
+ "successful_runs": successful_runs,
463
+ "skipped_runs": skipped_runs
464
+ })
465
+
466
+ return schedule_list
467
+
468
+ except Exception as e:
469
+ logger.error(f"Error getting schedule: {e}", exc_info=True)
470
+ raise HTTPException(status_code=500, detail=f"Failed to get schedule: {str(e)}")
471
+
472
+
473
+ # ============================================================================
474
+ # POST /api/schedule/trigger - Trigger Immediate Check
475
+ # ============================================================================
476
+
477
+ @router.post("/schedule/trigger")
478
+ async def trigger_check(request: TriggerCheckRequest):
479
+ """
480
+ Trigger immediate health check for a provider
481
+
482
+ Args:
483
+ request: Request containing provider name
484
+
485
+ Returns:
486
+ Health check result
487
+ """
488
+ try:
489
+ # Verify provider exists
490
+ provider = db_manager.get_provider(name=request.provider)
491
+ if not provider:
492
+ raise HTTPException(status_code=404, detail=f"Provider not found: {request.provider}")
493
+
494
+ # Create health checker and run check
495
+ checker = HealthChecker()
496
+ result = await checker.check_provider(request.provider)
497
+ await checker.close()
498
+
499
+ if not result:
500
+ raise HTTPException(status_code=500, detail=f"Health check failed for {request.provider}")
501
+
502
+ return {
503
+ "provider": result.provider_name,
504
+ "status": result.status.value,
505
+ "response_time_ms": result.response_time,
506
+ "timestamp": datetime.fromtimestamp(result.timestamp).isoformat(),
507
+ "error_message": result.error_message,
508
+ "triggered_at": datetime.utcnow().isoformat()
509
+ }
510
+
511
+ except HTTPException:
512
+ raise
513
+ except Exception as e:
514
+ logger.error(f"Error triggering check: {e}", exc_info=True)
515
+ raise HTTPException(status_code=500, detail=f"Failed to trigger check: {str(e)}")
516
+
517
+
518
+ # ============================================================================
519
+ # GET /api/freshness - Data Freshness
520
+ # ============================================================================
521
+
522
+ @router.get("/freshness")
523
+ async def get_freshness():
524
+ """
525
+ Get data freshness information for all providers
526
+
527
+ Returns:
528
+ List of data freshness metrics
529
+ """
530
+ try:
531
+ providers = db_manager.get_all_providers()
532
+ freshness_list = []
533
+
534
+ for provider in providers:
535
+ # Get most recent data collection
536
+ collections = db_manager.get_data_collections(
537
+ provider_id=provider.id,
538
+ hours=24,
539
+ limit=1
540
+ )
541
+
542
+ if not collections:
543
+ continue
544
+
545
+ collection = collections[0]
546
+
547
+ # Calculate staleness
548
+ now = datetime.utcnow()
549
+ fetch_age_minutes = (now - collection.actual_fetch_time).total_seconds() / 60
550
+
551
+ # Determine TTL based on category
552
+ ttl_minutes = 5 # Default
553
+ if provider.category == "market_data":
554
+ ttl_minutes = 1
555
+ elif provider.category == "blockchain_explorers":
556
+ ttl_minutes = 5
557
+ elif provider.category == "news":
558
+ ttl_minutes = 15
559
+
560
+ # Determine status
561
+ if fetch_age_minutes <= ttl_minutes:
562
+ status = "fresh"
563
+ elif fetch_age_minutes <= ttl_minutes * 2:
564
+ status = "stale"
565
+ else:
566
+ status = "expired"
567
+
568
+ freshness_list.append({
569
+ "provider": provider.name,
570
+ "category": provider.category,
571
+ "fetch_time": collection.actual_fetch_time.isoformat(),
572
+ "data_timestamp": collection.data_timestamp.isoformat() if collection.data_timestamp else None,
573
+ "staleness_minutes": round(fetch_age_minutes, 2),
574
+ "ttl_minutes": ttl_minutes,
575
+ "status": status
576
+ })
577
+
578
+ return freshness_list
579
+
580
+ except Exception as e:
581
+ logger.error(f"Error getting freshness: {e}", exc_info=True)
582
+ raise HTTPException(status_code=500, detail=f"Failed to get freshness: {str(e)}")
583
+
584
+
585
+ # ============================================================================
586
+ # GET /api/failures - Failure Analysis
587
+ # ============================================================================
588
+
589
+ @router.get("/failures")
590
+ async def get_failures():
591
+ """
592
+ Get comprehensive failure analysis
593
+
594
+ Returns:
595
+ Failure analysis with error distribution and recommendations
596
+ """
597
+ try:
598
+ # Get failure analysis from database
599
+ analysis = db_manager.get_failure_analysis(hours=24)
600
+
601
+ # Get recent failures
602
+ recent_failures = db_manager.get_failure_logs(hours=1, limit=10)
603
+
604
+ recent_list = []
605
+ for failure in recent_failures:
606
+ provider = db_manager.get_provider(provider_id=failure.provider_id)
607
+ recent_list.append({
608
+ "timestamp": failure.timestamp.isoformat(),
609
+ "provider": provider.name if provider else "Unknown",
610
+ "error_type": failure.error_type,
611
+ "error_message": failure.error_message,
612
+ "http_status": failure.http_status,
613
+ "retry_attempted": failure.retry_attempted,
614
+ "retry_result": failure.retry_result
615
+ })
616
+
617
+ # Generate remediation suggestions
618
+ remediation_suggestions = []
619
+
620
+ error_type_distribution = analysis.get('failures_by_error_type', [])
621
+ for error_stat in error_type_distribution:
622
+ error_type = error_stat['error_type']
623
+ count = error_stat['count']
624
+
625
+ if error_type == 'timeout' and count > 5:
626
+ remediation_suggestions.append({
627
+ "issue": "High timeout rate",
628
+ "suggestion": "Increase timeout values or check network connectivity",
629
+ "priority": "high"
630
+ })
631
+ elif error_type == 'rate_limit' and count > 3:
632
+ remediation_suggestions.append({
633
+ "issue": "Rate limit errors",
634
+ "suggestion": "Implement request throttling or add additional API keys",
635
+ "priority": "medium"
636
+ })
637
+ elif error_type == 'auth_error' and count > 0:
638
+ remediation_suggestions.append({
639
+ "issue": "Authentication failures",
640
+ "suggestion": "Verify API keys are valid and not expired",
641
+ "priority": "critical"
642
+ })
643
+
644
+ return {
645
+ "error_type_distribution": error_type_distribution,
646
+ "top_failing_providers": analysis.get('top_failing_providers', []),
647
+ "recent_failures": recent_list,
648
+ "remediation_suggestions": remediation_suggestions
649
+ }
650
+
651
+ except Exception as e:
652
+ logger.error(f"Error getting failures: {e}", exc_info=True)
653
+ raise HTTPException(status_code=500, detail=f"Failed to get failures: {str(e)}")
654
+
655
+
656
+ # ============================================================================
657
+ # GET /api/rate-limits - Rate Limit Status
658
+ # ============================================================================
659
+
660
+ @router.get("/rate-limits")
661
+ async def get_rate_limits():
662
+ """
663
+ Get rate limit status for all providers
664
+
665
+ Returns:
666
+ List of rate limit information
667
+ """
668
+ try:
669
+ statuses = rate_limiter.get_all_statuses()
670
+
671
+ rate_limit_list = []
672
+
673
+ for provider_name, status_info in statuses.items():
674
+ if status_info:
675
+ rate_limit_list.append({
676
+ "provider": status_info['provider'],
677
+ "limit_type": status_info['limit_type'],
678
+ "limit_value": status_info['limit_value'],
679
+ "current_usage": status_info['current_usage'],
680
+ "percentage": status_info['percentage'],
681
+ "reset_time": status_info['reset_time'],
682
+ "reset_in_seconds": status_info['reset_in_seconds'],
683
+ "status": status_info['status']
684
+ })
685
+
686
+ # Add providers with configured limits but no tracking yet
687
+ providers = db_manager.get_all_providers()
688
+ tracked_providers = {rl['provider'] for rl in rate_limit_list}
689
+
690
+ for provider in providers:
691
+ if provider.name not in tracked_providers and provider.rate_limit_type and provider.rate_limit_value:
692
+ rate_limit_list.append({
693
+ "provider": provider.name,
694
+ "limit_type": provider.rate_limit_type,
695
+ "limit_value": provider.rate_limit_value,
696
+ "current_usage": 0,
697
+ "percentage": 0.0,
698
+ "reset_time": (datetime.utcnow() + timedelta(hours=1)).isoformat(),
699
+ "reset_in_seconds": 3600,
700
+ "status": "ok"
701
+ })
702
+
703
+ return rate_limit_list
704
+
705
+ except Exception as e:
706
+ logger.error(f"Error getting rate limits: {e}", exc_info=True)
707
+ raise HTTPException(status_code=500, detail=f"Failed to get rate limits: {str(e)}")
708
+
709
+
710
+ # ============================================================================
711
+ # GET /api/config/keys - API Keys Status
712
+ # ============================================================================
713
+
714
+ @router.get("/config/keys")
715
+ async def get_api_keys():
716
+ """
717
+ Get API key status for all providers
718
+
719
+ Returns:
720
+ List of API key information (masked)
721
+ """
722
+ try:
723
+ providers = db_manager.get_all_providers()
724
+
725
+ keys_list = []
726
+
727
+ for provider in providers:
728
+ if not provider.requires_key:
729
+ continue
730
+
731
+ # Determine key status
732
+ if provider.api_key_masked:
733
+ key_status = "configured"
734
+ else:
735
+ key_status = "missing"
736
+
737
+ # Get usage quota from rate limits if available
738
+ rate_status = rate_limiter.get_status(provider.name)
739
+ usage_quota_remaining = None
740
+ if rate_status:
741
+ percentage_used = rate_status['percentage']
742
+ usage_quota_remaining = f"{100 - percentage_used:.1f}%"
743
+
744
+ keys_list.append({
745
+ "provider": provider.name,
746
+ "key_masked": provider.api_key_masked or "***NOT_SET***",
747
+ "created_at": provider.created_at.isoformat(),
748
+ "expires_at": None, # Not tracked in current schema
749
+ "status": key_status,
750
+ "usage_quota_remaining": usage_quota_remaining
751
+ })
752
+
753
+ return keys_list
754
+
755
+ except Exception as e:
756
+ logger.error(f"Error getting API keys: {e}", exc_info=True)
757
+ raise HTTPException(status_code=500, detail=f"Failed to get API keys: {str(e)}")
758
+
759
+
760
+ # ============================================================================
761
+ # POST /api/config/keys/test - Test API Key
762
+ # ============================================================================
763
+
764
+ @router.post("/config/keys/test")
765
+ async def test_api_key(request: TestKeyRequest):
766
+ """
767
+ Test an API key by performing a health check
768
+
769
+ Args:
770
+ request: Request containing provider name
771
+
772
+ Returns:
773
+ Test result
774
+ """
775
+ try:
776
+ # Verify provider exists and requires key
777
+ provider = db_manager.get_provider(name=request.provider)
778
+ if not provider:
779
+ raise HTTPException(status_code=404, detail=f"Provider not found: {request.provider}")
780
+
781
+ if not provider.requires_key:
782
+ raise HTTPException(status_code=400, detail=f"Provider {request.provider} does not require an API key")
783
+
784
+ if not provider.api_key_masked:
785
+ raise HTTPException(status_code=400, detail=f"No API key configured for {request.provider}")
786
+
787
+ # Perform health check to test key
788
+ checker = HealthChecker()
789
+ result = await checker.check_provider(request.provider)
790
+ await checker.close()
791
+
792
+ if not result:
793
+ raise HTTPException(status_code=500, detail=f"Failed to test API key for {request.provider}")
794
+
795
+ # Determine if key is valid based on result
796
+ key_valid = result.status.value == "online" or result.status.value == "degraded"
797
+
798
+ # Check for auth-specific errors
799
+ if result.error_message and ('auth' in result.error_message.lower() or 'key' in result.error_message.lower() or '401' in result.error_message or '403' in result.error_message):
800
+ key_valid = False
801
+
802
+ return {
803
+ "provider": request.provider,
804
+ "key_valid": key_valid,
805
+ "test_timestamp": datetime.utcnow().isoformat(),
806
+ "response_time_ms": result.response_time,
807
+ "status_code": result.status_code,
808
+ "error_message": result.error_message,
809
+ "test_endpoint": result.endpoint_tested
810
+ }
811
+
812
+ except HTTPException:
813
+ raise
814
+ except Exception as e:
815
+ logger.error(f"Error testing API key: {e}", exc_info=True)
816
+ raise HTTPException(status_code=500, detail=f"Failed to test API key: {str(e)}")
817
+
818
+
819
+ # ============================================================================
820
+ # GET /api/charts/health-history - Health History for Charts
821
+ # ============================================================================
822
+
823
+ @router.get("/charts/health-history")
824
+ async def get_health_history(
825
+ hours: int = Query(24, ge=1, le=168, description="Hours of history to retrieve")
826
+ ):
827
+ """
828
+ Get health history data for charts
829
+
830
+ Args:
831
+ hours: Number of hours of history to retrieve
832
+
833
+ Returns:
834
+ Time series data for health metrics
835
+ """
836
+ try:
837
+ # Get system metrics history
838
+ metrics = db_manager.get_system_metrics(hours=hours)
839
+
840
+ if not metrics:
841
+ return {
842
+ "timestamps": [],
843
+ "success_rate": [],
844
+ "avg_response_time": []
845
+ }
846
+
847
+ # Sort by timestamp
848
+ metrics.sort(key=lambda x: x.timestamp)
849
+
850
+ timestamps = []
851
+ success_rates = []
852
+ avg_response_times = []
853
+
854
+ for metric in metrics:
855
+ timestamps.append(metric.timestamp.isoformat())
856
+
857
+ # Calculate success rate
858
+ total = metric.online_count + metric.degraded_count + metric.offline_count
859
+ success_rate = round((metric.online_count / total * 100), 2) if total > 0 else 0
860
+ success_rates.append(success_rate)
861
+
862
+ avg_response_times.append(round(metric.avg_response_time_ms, 2))
863
+
864
+ return {
865
+ "timestamps": timestamps,
866
+ "success_rate": success_rates,
867
+ "avg_response_time": avg_response_times
868
+ }
869
+
870
+ except Exception as e:
871
+ logger.error(f"Error getting health history: {e}", exc_info=True)
872
+ raise HTTPException(status_code=500, detail=f"Failed to get health history: {str(e)}")
873
+
874
+
875
+ # ============================================================================
876
+ # GET /api/charts/compliance - Compliance History for Charts
877
+ # ============================================================================
878
+
879
+ @router.get("/charts/compliance")
880
+ async def get_compliance_history(
881
+ days: int = Query(7, ge=1, le=30, description="Days of history to retrieve")
882
+ ):
883
+ """
884
+ Get schedule compliance history for charts
885
+
886
+ Args:
887
+ days: Number of days of history to retrieve
888
+
889
+ Returns:
890
+ Time series data for compliance metrics
891
+ """
892
+ try:
893
+ # Get all providers with schedule configs
894
+ configs = db_manager.get_all_schedule_configs(enabled_only=True)
895
+
896
+ if not configs:
897
+ return {
898
+ "dates": [],
899
+ "compliance_percentage": []
900
+ }
901
+
902
+ # Generate date range
903
+ end_date = datetime.utcnow().date()
904
+ dates = []
905
+ compliance_percentages = []
906
+
907
+ for day_offset in range(days - 1, -1, -1):
908
+ current_date = end_date - timedelta(days=day_offset)
909
+ dates.append(current_date.isoformat())
910
+
911
+ # Calculate compliance for this day
912
+ day_start = datetime.combine(current_date, datetime.min.time())
913
+ day_end = datetime.combine(current_date, datetime.max.time())
914
+
915
+ total_checks = 0
916
+ on_time_checks = 0
917
+
918
+ for config in configs:
919
+ compliance_records = db_manager.get_schedule_compliance(
920
+ provider_id=config.provider_id,
921
+ hours=24
922
+ )
923
+
924
+ # Filter for current date
925
+ day_records = [
926
+ r for r in compliance_records
927
+ if day_start <= r.timestamp <= day_end
928
+ ]
929
+
930
+ total_checks += len(day_records)
931
+ on_time_checks += sum(1 for r in day_records if r.on_time)
932
+
933
+ # Calculate percentage
934
+ compliance_pct = round((on_time_checks / total_checks * 100), 2) if total_checks > 0 else 100.0
935
+ compliance_percentages.append(compliance_pct)
936
+
937
+ return {
938
+ "dates": dates,
939
+ "compliance_percentage": compliance_percentages
940
+ }
941
+
942
+ except Exception as e:
943
+ logger.error(f"Error getting compliance history: {e}", exc_info=True)
944
+ raise HTTPException(status_code=500, detail=f"Failed to get compliance history: {str(e)}")
945
+
946
+
947
+ # ============================================================================
948
+ # GET /api/charts/rate-limit-history - Rate Limit History for Charts
949
+ # ============================================================================
950
+
951
+ @router.get("/charts/rate-limit-history")
952
+ async def get_rate_limit_history(
953
+ hours: int = Query(24, ge=1, le=168, description="Hours of history to retrieve")
954
+ ):
955
+ """
956
+ Get rate limit usage history data for charts
957
+
958
+ Args:
959
+ hours: Number of hours of history to retrieve
960
+
961
+ Returns:
962
+ Time series data for rate limit usage by provider
963
+ """
964
+ try:
965
+ # Get all providers with rate limits
966
+ providers = db_manager.get_all_providers()
967
+ providers_with_limits = [p for p in providers if p.rate_limit_type and p.rate_limit_value]
968
+
969
+ if not providers_with_limits:
970
+ return {
971
+ "timestamps": [],
972
+ "providers": []
973
+ }
974
+
975
+ # Generate hourly timestamps
976
+ end_time = datetime.utcnow()
977
+ start_time = end_time - timedelta(hours=hours)
978
+
979
+ # Create hourly buckets
980
+ timestamps = []
981
+ current_time = start_time
982
+ while current_time <= end_time:
983
+ timestamps.append(current_time.strftime("%H:%M"))
984
+ current_time += timedelta(hours=1)
985
+
986
+ # Get rate limit usage data for each provider
987
+ provider_data = []
988
+
989
+ for provider in providers_with_limits[:5]: # Limit to top 5 for readability
990
+ # Get rate limit usage records for this provider
991
+ rate_limit_records = db_manager.get_rate_limit_usage(
992
+ provider_id=provider.id,
993
+ hours=hours
994
+ )
995
+
996
+ if not rate_limit_records:
997
+ continue
998
+
999
+ # Group by hour and calculate average percentage
1000
+ usage_percentages = []
1001
+ current_time = start_time
1002
+
1003
+ for _ in range(len(timestamps)):
1004
+ hour_end = current_time + timedelta(hours=1)
1005
+
1006
+ # Get records in this hour bucket
1007
+ hour_records = [
1008
+ r for r in rate_limit_records
1009
+ if current_time <= r.timestamp < hour_end
1010
+ ]
1011
+
1012
+ if hour_records:
1013
+ # Calculate average percentage for this hour
1014
+ avg_percentage = sum(r.percentage for r in hour_records) / len(hour_records)
1015
+ usage_percentages.append(round(avg_percentage, 2))
1016
+ else:
1017
+ # No data for this hour, use 0
1018
+ usage_percentages.append(0.0)
1019
+
1020
+ current_time = hour_end
1021
+
1022
+ provider_data.append({
1023
+ "name": provider.name,
1024
+ "usage_percentage": usage_percentages
1025
+ })
1026
+
1027
+ return {
1028
+ "timestamps": timestamps,
1029
+ "providers": provider_data
1030
+ }
1031
+
1032
+ except Exception as e:
1033
+ logger.error(f"Error getting rate limit history: {e}", exc_info=True)
1034
+ raise HTTPException(status_code=500, detail=f"Failed to get rate limit history: {str(e)}")
1035
+
1036
+
1037
+ # ============================================================================
1038
+ # GET /api/charts/freshness-history - Data Freshness History for Charts
1039
+ # ============================================================================
1040
+
1041
+ @router.get("/charts/freshness-history")
1042
+ async def get_freshness_history(
1043
+ hours: int = Query(24, ge=1, le=168, description="Hours of history to retrieve")
1044
+ ):
1045
+ """
1046
+ Get data freshness (staleness) history for charts
1047
+
1048
+ Args:
1049
+ hours: Number of hours of history to retrieve
1050
+
1051
+ Returns:
1052
+ Time series data for data staleness by provider
1053
+ """
1054
+ try:
1055
+ # Get all providers
1056
+ providers = db_manager.get_all_providers()
1057
+
1058
+ if not providers:
1059
+ return {
1060
+ "timestamps": [],
1061
+ "providers": []
1062
+ }
1063
+
1064
+ # Generate hourly timestamps
1065
+ end_time = datetime.utcnow()
1066
+ start_time = end_time - timedelta(hours=hours)
1067
+
1068
+ # Create hourly buckets
1069
+ timestamps = []
1070
+ current_time = start_time
1071
+ while current_time <= end_time:
1072
+ timestamps.append(current_time.strftime("%H:%M"))
1073
+ current_time += timedelta(hours=1)
1074
+
1075
+ # Get freshness data for each provider
1076
+ provider_data = []
1077
+
1078
+ for provider in providers[:5]: # Limit to top 5 for readability
1079
+ # Get data collection records for this provider
1080
+ collections = db_manager.get_data_collections(
1081
+ provider_id=provider.id,
1082
+ hours=hours,
1083
+ limit=1000 # Get more records for analysis
1084
+ )
1085
+
1086
+ if not collections:
1087
+ continue
1088
+
1089
+ # Group by hour and calculate average staleness
1090
+ staleness_values = []
1091
+ current_time = start_time
1092
+
1093
+ for _ in range(len(timestamps)):
1094
+ hour_end = current_time + timedelta(hours=1)
1095
+
1096
+ # Get records in this hour bucket
1097
+ hour_records = [
1098
+ c for c in collections
1099
+ if current_time <= c.actual_fetch_time < hour_end
1100
+ ]
1101
+
1102
+ if hour_records:
1103
+ # Calculate average staleness for this hour
1104
+ staleness_list = []
1105
+ for record in hour_records:
1106
+ if record.staleness_minutes is not None:
1107
+ staleness_list.append(record.staleness_minutes)
1108
+ elif record.data_timestamp and record.actual_fetch_time:
1109
+ # Calculate staleness if not already stored
1110
+ staleness_seconds = (record.actual_fetch_time - record.data_timestamp).total_seconds()
1111
+ staleness_minutes = staleness_seconds / 60
1112
+ staleness_list.append(staleness_minutes)
1113
+
1114
+ if staleness_list:
1115
+ avg_staleness = sum(staleness_list) / len(staleness_list)
1116
+ staleness_values.append(round(avg_staleness, 2))
1117
+ else:
1118
+ staleness_values.append(0.0)
1119
+ else:
1120
+ # No data for this hour, use null
1121
+ staleness_values.append(None)
1122
+
1123
+ current_time = hour_end
1124
+
1125
+ # Only add provider if it has some data
1126
+ if any(v is not None and v > 0 for v in staleness_values):
1127
+ provider_data.append({
1128
+ "name": provider.name,
1129
+ "staleness_minutes": staleness_values
1130
+ })
1131
+
1132
+ return {
1133
+ "timestamps": timestamps,
1134
+ "providers": provider_data
1135
+ }
1136
+
1137
+ except Exception as e:
1138
+ logger.error(f"Error getting freshness history: {e}", exc_info=True)
1139
+ raise HTTPException(status_code=500, detail=f"Failed to get freshness history: {str(e)}")
1140
+
1141
+
1142
+ # ============================================================================
1143
+ # Health Check Endpoint
1144
+ # ============================================================================
1145
+
1146
+ @router.get("/health")
1147
+ async def api_health():
1148
+ """
1149
+ API health check endpoint
1150
+
1151
+ Returns:
1152
+ API health status
1153
+ """
1154
+ try:
1155
+ # Check database connection
1156
+ db_health = db_manager.health_check()
1157
+
1158
+ return {
1159
+ "status": "healthy" if db_health['status'] == 'healthy' else "unhealthy",
1160
+ "timestamp": datetime.utcnow().isoformat(),
1161
+ "database": db_health['status'],
1162
+ "version": "1.0.0"
1163
+ }
1164
+ except Exception as e:
1165
+ logger.error(f"Health check failed: {e}", exc_info=True)
1166
+ return {
1167
+ "status": "unhealthy",
1168
+ "timestamp": datetime.utcnow().isoformat(),
1169
+ "error": str(e),
1170
+ "version": "1.0.0"
1171
+ }
1172
+
1173
+
1174
+ # ============================================================================
1175
+ # Initialize Logger
1176
+ # ============================================================================
1177
+
1178
+ logger.info("API endpoints module loaded successfully")
final/api/pool_endpoints.py ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ API Endpoints for Source Pool Management
3
+ Provides endpoints for managing source pools, rotation, and monitoring
4
+ """
5
+
6
+ from datetime import datetime
7
+ from typing import Optional, List
8
+ from fastapi import APIRouter, HTTPException, Body
9
+ from pydantic import BaseModel, Field
10
+
11
+ from database.db_manager import db_manager
12
+ from monitoring.source_pool_manager import SourcePoolManager
13
+ from utils.logger import setup_logger
14
+
15
+ logger = setup_logger("pool_api")
16
+
17
+ # Create APIRouter instance
18
+ router = APIRouter(prefix="/api/pools", tags=["source_pools"])
19
+
20
+
21
+ # ============================================================================
22
+ # Pydantic Models for Request/Response Validation
23
+ # ============================================================================
24
+
25
+ class CreatePoolRequest(BaseModel):
26
+ """Request model for creating a pool"""
27
+ name: str = Field(..., description="Pool name")
28
+ category: str = Field(..., description="Pool category")
29
+ description: Optional[str] = Field(None, description="Pool description")
30
+ rotation_strategy: str = Field("round_robin", description="Rotation strategy")
31
+
32
+
33
+ class AddMemberRequest(BaseModel):
34
+ """Request model for adding a member to a pool"""
35
+ provider_id: int = Field(..., description="Provider ID")
36
+ priority: int = Field(1, description="Provider priority")
37
+ weight: int = Field(1, description="Provider weight")
38
+
39
+
40
+ class UpdatePoolRequest(BaseModel):
41
+ """Request model for updating a pool"""
42
+ rotation_strategy: Optional[str] = Field(None, description="Rotation strategy")
43
+ enabled: Optional[bool] = Field(None, description="Pool enabled status")
44
+ description: Optional[str] = Field(None, description="Pool description")
45
+
46
+
47
+ class UpdateMemberRequest(BaseModel):
48
+ """Request model for updating a pool member"""
49
+ priority: Optional[int] = Field(None, description="Provider priority")
50
+ weight: Optional[int] = Field(None, description="Provider weight")
51
+ enabled: Optional[bool] = Field(None, description="Member enabled status")
52
+
53
+
54
+ class TriggerRotationRequest(BaseModel):
55
+ """Request model for triggering manual rotation"""
56
+ reason: str = Field("manual", description="Rotation reason")
57
+
58
+
59
+ class FailoverRequest(BaseModel):
60
+ """Request model for triggering failover"""
61
+ failed_provider_id: int = Field(..., description="Failed provider ID")
62
+ reason: str = Field("manual_failover", description="Failover reason")
63
+
64
+
65
+ # ============================================================================
66
+ # GET /api/pools - List All Pools
67
+ # ============================================================================
68
+
69
+ @router.get("")
70
+ async def list_pools():
71
+ """
72
+ Get list of all source pools with their status
73
+
74
+ Returns:
75
+ List of source pools with status information
76
+ """
77
+ try:
78
+ session = db_manager.get_session()
79
+ pool_manager = SourcePoolManager(session)
80
+
81
+ pools_status = pool_manager.get_all_pools_status()
82
+
83
+ session.close()
84
+
85
+ return {
86
+ "pools": pools_status,
87
+ "total": len(pools_status),
88
+ "timestamp": datetime.utcnow().isoformat()
89
+ }
90
+
91
+ except Exception as e:
92
+ logger.error(f"Error listing pools: {e}", exc_info=True)
93
+ raise HTTPException(status_code=500, detail=f"Failed to list pools: {str(e)}")
94
+
95
+
96
+ # ============================================================================
97
+ # POST /api/pools - Create New Pool
98
+ # ============================================================================
99
+
100
+ @router.post("")
101
+ async def create_pool(request: CreatePoolRequest):
102
+ """
103
+ Create a new source pool
104
+
105
+ Args:
106
+ request: Pool creation request
107
+
108
+ Returns:
109
+ Created pool information
110
+ """
111
+ try:
112
+ session = db_manager.get_session()
113
+ pool_manager = SourcePoolManager(session)
114
+
115
+ pool = pool_manager.create_pool(
116
+ name=request.name,
117
+ category=request.category,
118
+ description=request.description,
119
+ rotation_strategy=request.rotation_strategy
120
+ )
121
+
122
+ session.close()
123
+
124
+ return {
125
+ "pool_id": pool.id,
126
+ "name": pool.name,
127
+ "category": pool.category,
128
+ "rotation_strategy": pool.rotation_strategy,
129
+ "created_at": pool.created_at.isoformat(),
130
+ "message": f"Pool '{pool.name}' created successfully"
131
+ }
132
+
133
+ except Exception as e:
134
+ logger.error(f"Error creating pool: {e}", exc_info=True)
135
+ raise HTTPException(status_code=500, detail=f"Failed to create pool: {str(e)}")
136
+
137
+
138
+ # ============================================================================
139
+ # GET /api/pools/{pool_id} - Get Pool Status
140
+ # ============================================================================
141
+
142
+ @router.get("/{pool_id}")
143
+ async def get_pool_status(pool_id: int):
144
+ """
145
+ Get detailed status of a specific pool
146
+
147
+ Args:
148
+ pool_id: Pool ID
149
+
150
+ Returns:
151
+ Detailed pool status
152
+ """
153
+ try:
154
+ session = db_manager.get_session()
155
+ pool_manager = SourcePoolManager(session)
156
+
157
+ pool_status = pool_manager.get_pool_status(pool_id)
158
+
159
+ session.close()
160
+
161
+ if not pool_status:
162
+ raise HTTPException(status_code=404, detail=f"Pool {pool_id} not found")
163
+
164
+ return pool_status
165
+
166
+ except HTTPException:
167
+ raise
168
+ except Exception as e:
169
+ logger.error(f"Error getting pool status: {e}", exc_info=True)
170
+ raise HTTPException(status_code=500, detail=f"Failed to get pool status: {str(e)}")
171
+
172
+
173
+ # ============================================================================
174
+ # PUT /api/pools/{pool_id} - Update Pool
175
+ # ============================================================================
176
+
177
+ @router.put("/{pool_id}")
178
+ async def update_pool(pool_id: int, request: UpdatePoolRequest):
179
+ """
180
+ Update pool configuration
181
+
182
+ Args:
183
+ pool_id: Pool ID
184
+ request: Update request
185
+
186
+ Returns:
187
+ Updated pool information
188
+ """
189
+ try:
190
+ session = db_manager.get_session()
191
+
192
+ # Get pool from database
193
+ from database.models import SourcePool
194
+ pool = session.query(SourcePool).filter_by(id=pool_id).first()
195
+
196
+ if not pool:
197
+ session.close()
198
+ raise HTTPException(status_code=404, detail=f"Pool {pool_id} not found")
199
+
200
+ # Update fields
201
+ if request.rotation_strategy is not None:
202
+ pool.rotation_strategy = request.rotation_strategy
203
+ if request.enabled is not None:
204
+ pool.enabled = request.enabled
205
+ if request.description is not None:
206
+ pool.description = request.description
207
+
208
+ pool.updated_at = datetime.utcnow()
209
+
210
+ session.commit()
211
+ session.refresh(pool)
212
+
213
+ result = {
214
+ "pool_id": pool.id,
215
+ "name": pool.name,
216
+ "rotation_strategy": pool.rotation_strategy,
217
+ "enabled": pool.enabled,
218
+ "updated_at": pool.updated_at.isoformat(),
219
+ "message": f"Pool '{pool.name}' updated successfully"
220
+ }
221
+
222
+ session.close()
223
+
224
+ return result
225
+
226
+ except HTTPException:
227
+ raise
228
+ except Exception as e:
229
+ logger.error(f"Error updating pool: {e}", exc_info=True)
230
+ raise HTTPException(status_code=500, detail=f"Failed to update pool: {str(e)}")
231
+
232
+
233
+ # ============================================================================
234
+ # DELETE /api/pools/{pool_id} - Delete Pool
235
+ # ============================================================================
236
+
237
+ @router.delete("/{pool_id}")
238
+ async def delete_pool(pool_id: int):
239
+ """
240
+ Delete a source pool
241
+
242
+ Args:
243
+ pool_id: Pool ID
244
+
245
+ Returns:
246
+ Deletion confirmation
247
+ """
248
+ try:
249
+ session = db_manager.get_session()
250
+
251
+ from database.models import SourcePool
252
+ pool = session.query(SourcePool).filter_by(id=pool_id).first()
253
+
254
+ if not pool:
255
+ session.close()
256
+ raise HTTPException(status_code=404, detail=f"Pool {pool_id} not found")
257
+
258
+ pool_name = pool.name
259
+ session.delete(pool)
260
+ session.commit()
261
+ session.close()
262
+
263
+ return {
264
+ "message": f"Pool '{pool_name}' deleted successfully",
265
+ "pool_id": pool_id
266
+ }
267
+
268
+ except HTTPException:
269
+ raise
270
+ except Exception as e:
271
+ logger.error(f"Error deleting pool: {e}", exc_info=True)
272
+ raise HTTPException(status_code=500, detail=f"Failed to delete pool: {str(e)}")
273
+
274
+
275
+ # ============================================================================
276
+ # POST /api/pools/{pool_id}/members - Add Member to Pool
277
+ # ============================================================================
278
+
279
+ @router.post("/{pool_id}/members")
280
+ async def add_pool_member(pool_id: int, request: AddMemberRequest):
281
+ """
282
+ Add a provider to a pool
283
+
284
+ Args:
285
+ pool_id: Pool ID
286
+ request: Add member request
287
+
288
+ Returns:
289
+ Created member information
290
+ """
291
+ try:
292
+ session = db_manager.get_session()
293
+ pool_manager = SourcePoolManager(session)
294
+
295
+ member = pool_manager.add_to_pool(
296
+ pool_id=pool_id,
297
+ provider_id=request.provider_id,
298
+ priority=request.priority,
299
+ weight=request.weight
300
+ )
301
+
302
+ # Get provider name
303
+ from database.models import Provider
304
+ provider = session.query(Provider).get(request.provider_id)
305
+
306
+ session.close()
307
+
308
+ return {
309
+ "member_id": member.id,
310
+ "pool_id": pool_id,
311
+ "provider_id": request.provider_id,
312
+ "provider_name": provider.name if provider else None,
313
+ "priority": member.priority,
314
+ "weight": member.weight,
315
+ "message": f"Provider added to pool successfully"
316
+ }
317
+
318
+ except Exception as e:
319
+ logger.error(f"Error adding pool member: {e}", exc_info=True)
320
+ raise HTTPException(status_code=500, detail=f"Failed to add pool member: {str(e)}")
321
+
322
+
323
+ # ============================================================================
324
+ # PUT /api/pools/{pool_id}/members/{provider_id} - Update Pool Member
325
+ # ============================================================================
326
+
327
+ @router.put("/{pool_id}/members/{provider_id}")
328
+ async def update_pool_member(
329
+ pool_id: int,
330
+ provider_id: int,
331
+ request: UpdateMemberRequest
332
+ ):
333
+ """
334
+ Update a pool member configuration
335
+
336
+ Args:
337
+ pool_id: Pool ID
338
+ provider_id: Provider ID
339
+ request: Update request
340
+
341
+ Returns:
342
+ Updated member information
343
+ """
344
+ try:
345
+ session = db_manager.get_session()
346
+
347
+ from database.models import PoolMember
348
+ member = (
349
+ session.query(PoolMember)
350
+ .filter_by(pool_id=pool_id, provider_id=provider_id)
351
+ .first()
352
+ )
353
+
354
+ if not member:
355
+ session.close()
356
+ raise HTTPException(
357
+ status_code=404,
358
+ detail=f"Member not found in pool {pool_id}"
359
+ )
360
+
361
+ # Update fields
362
+ if request.priority is not None:
363
+ member.priority = request.priority
364
+ if request.weight is not None:
365
+ member.weight = request.weight
366
+ if request.enabled is not None:
367
+ member.enabled = request.enabled
368
+
369
+ session.commit()
370
+ session.refresh(member)
371
+
372
+ result = {
373
+ "pool_id": pool_id,
374
+ "provider_id": provider_id,
375
+ "priority": member.priority,
376
+ "weight": member.weight,
377
+ "enabled": member.enabled,
378
+ "message": "Pool member updated successfully"
379
+ }
380
+
381
+ session.close()
382
+
383
+ return result
384
+
385
+ except HTTPException:
386
+ raise
387
+ except Exception as e:
388
+ logger.error(f"Error updating pool member: {e}", exc_info=True)
389
+ raise HTTPException(status_code=500, detail=f"Failed to update pool member: {str(e)}")
390
+
391
+
392
+ # ============================================================================
393
+ # DELETE /api/pools/{pool_id}/members/{provider_id} - Remove Member
394
+ # ============================================================================
395
+
396
+ @router.delete("/{pool_id}/members/{provider_id}")
397
+ async def remove_pool_member(pool_id: int, provider_id: int):
398
+ """
399
+ Remove a provider from a pool
400
+
401
+ Args:
402
+ pool_id: Pool ID
403
+ provider_id: Provider ID
404
+
405
+ Returns:
406
+ Deletion confirmation
407
+ """
408
+ try:
409
+ session = db_manager.get_session()
410
+
411
+ from database.models import PoolMember
412
+ member = (
413
+ session.query(PoolMember)
414
+ .filter_by(pool_id=pool_id, provider_id=provider_id)
415
+ .first()
416
+ )
417
+
418
+ if not member:
419
+ session.close()
420
+ raise HTTPException(
421
+ status_code=404,
422
+ detail=f"Member not found in pool {pool_id}"
423
+ )
424
+
425
+ session.delete(member)
426
+ session.commit()
427
+ session.close()
428
+
429
+ return {
430
+ "message": "Provider removed from pool successfully",
431
+ "pool_id": pool_id,
432
+ "provider_id": provider_id
433
+ }
434
+
435
+ except HTTPException:
436
+ raise
437
+ except Exception as e:
438
+ logger.error(f"Error removing pool member: {e}", exc_info=True)
439
+ raise HTTPException(status_code=500, detail=f"Failed to remove pool member: {str(e)}")
440
+
441
+
442
+ # ============================================================================
443
+ # POST /api/pools/{pool_id}/rotate - Trigger Manual Rotation
444
+ # ============================================================================
445
+
446
+ @router.post("/{pool_id}/rotate")
447
+ async def trigger_rotation(pool_id: int, request: TriggerRotationRequest):
448
+ """
449
+ Trigger manual rotation to next provider in pool
450
+
451
+ Args:
452
+ pool_id: Pool ID
453
+ request: Rotation request
454
+
455
+ Returns:
456
+ New provider information
457
+ """
458
+ try:
459
+ session = db_manager.get_session()
460
+ pool_manager = SourcePoolManager(session)
461
+
462
+ provider = pool_manager.get_next_provider(pool_id)
463
+
464
+ session.close()
465
+
466
+ if not provider:
467
+ raise HTTPException(
468
+ status_code=404,
469
+ detail=f"No available providers in pool {pool_id}"
470
+ )
471
+
472
+ return {
473
+ "pool_id": pool_id,
474
+ "provider_id": provider.id,
475
+ "provider_name": provider.name,
476
+ "timestamp": datetime.utcnow().isoformat(),
477
+ "message": f"Rotated to provider '{provider.name}'"
478
+ }
479
+
480
+ except HTTPException:
481
+ raise
482
+ except Exception as e:
483
+ logger.error(f"Error triggering rotation: {e}", exc_info=True)
484
+ raise HTTPException(status_code=500, detail=f"Failed to trigger rotation: {str(e)}")
485
+
486
+
487
+ # ============================================================================
488
+ # POST /api/pools/{pool_id}/failover - Trigger Failover
489
+ # ============================================================================
490
+
491
+ @router.post("/{pool_id}/failover")
492
+ async def trigger_failover(pool_id: int, request: FailoverRequest):
493
+ """
494
+ Trigger failover from a failed provider
495
+
496
+ Args:
497
+ pool_id: Pool ID
498
+ request: Failover request
499
+
500
+ Returns:
501
+ New provider information
502
+ """
503
+ try:
504
+ session = db_manager.get_session()
505
+ pool_manager = SourcePoolManager(session)
506
+
507
+ provider = pool_manager.failover(
508
+ pool_id=pool_id,
509
+ failed_provider_id=request.failed_provider_id,
510
+ reason=request.reason
511
+ )
512
+
513
+ session.close()
514
+
515
+ if not provider:
516
+ raise HTTPException(
517
+ status_code=404,
518
+ detail=f"No alternative providers available in pool {pool_id}"
519
+ )
520
+
521
+ return {
522
+ "pool_id": pool_id,
523
+ "failed_provider_id": request.failed_provider_id,
524
+ "new_provider_id": provider.id,
525
+ "new_provider_name": provider.name,
526
+ "timestamp": datetime.utcnow().isoformat(),
527
+ "message": f"Failover successful: switched to '{provider.name}'"
528
+ }
529
+
530
+ except HTTPException:
531
+ raise
532
+ except Exception as e:
533
+ logger.error(f"Error triggering failover: {e}", exc_info=True)
534
+ raise HTTPException(status_code=500, detail=f"Failed to trigger failover: {str(e)}")
535
+
536
+
537
+ # ============================================================================
538
+ # GET /api/pools/{pool_id}/history - Get Rotation History
539
+ # ============================================================================
540
+
541
+ @router.get("/{pool_id}/history")
542
+ async def get_rotation_history(pool_id: int, limit: int = 50):
543
+ """
544
+ Get rotation history for a pool
545
+
546
+ Args:
547
+ pool_id: Pool ID
548
+ limit: Maximum number of records to return
549
+
550
+ Returns:
551
+ List of rotation history records
552
+ """
553
+ try:
554
+ session = db_manager.get_session()
555
+
556
+ from database.models import RotationHistory, Provider
557
+ history = (
558
+ session.query(RotationHistory)
559
+ .filter_by(pool_id=pool_id)
560
+ .order_by(RotationHistory.timestamp.desc())
561
+ .limit(limit)
562
+ .all()
563
+ )
564
+
565
+ history_list = []
566
+ for record in history:
567
+ from_provider = None
568
+ if record.from_provider_id:
569
+ from_prov = session.query(Provider).get(record.from_provider_id)
570
+ from_provider = from_prov.name if from_prov else None
571
+
572
+ to_prov = session.query(Provider).get(record.to_provider_id)
573
+ to_provider = to_prov.name if to_prov else None
574
+
575
+ history_list.append({
576
+ "id": record.id,
577
+ "timestamp": record.timestamp.isoformat(),
578
+ "from_provider": from_provider,
579
+ "to_provider": to_provider,
580
+ "reason": record.rotation_reason,
581
+ "success": record.success,
582
+ "notes": record.notes
583
+ })
584
+
585
+ session.close()
586
+
587
+ return {
588
+ "pool_id": pool_id,
589
+ "history": history_list,
590
+ "total": len(history_list)
591
+ }
592
+
593
+ except Exception as e:
594
+ logger.error(f"Error getting rotation history: {e}", exc_info=True)
595
+ raise HTTPException(status_code=500, detail=f"Failed to get rotation history: {str(e)}")
596
+
597
+
598
+ logger.info("Pool API endpoints module loaded successfully")
final/api/websocket.py ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WebSocket Support Module
3
+ Provides real-time updates via WebSocket connections with connection management
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ from datetime import datetime
9
+ from typing import Set, Dict, Any, Optional, List
10
+ from fastapi import WebSocket, WebSocketDisconnect, APIRouter
11
+ from starlette.websockets import WebSocketState
12
+ from utils.logger import setup_logger
13
+ from database.db_manager import db_manager
14
+ from monitoring.rate_limiter import rate_limiter
15
+ from config import config
16
+
17
+ # Setup logger
18
+ logger = setup_logger("websocket", level="INFO")
19
+
20
+ # Create router for WebSocket routes
21
+ router = APIRouter()
22
+
23
+
24
+ class ConnectionManager:
25
+ """
26
+ Manages WebSocket connections and broadcasts messages to all connected clients
27
+ """
28
+
29
+ def __init__(self):
30
+ """Initialize connection manager"""
31
+ self.active_connections: Set[WebSocket] = set()
32
+ self.connection_metadata: Dict[WebSocket, Dict[str, Any]] = {}
33
+ self._broadcast_task: Optional[asyncio.Task] = None
34
+ self._heartbeat_task: Optional[asyncio.Task] = None
35
+ self._is_running = False
36
+
37
+ async def connect(self, websocket: WebSocket, client_id: str = None):
38
+ """
39
+ Accept and register a new WebSocket connection
40
+
41
+ Args:
42
+ websocket: WebSocket connection
43
+ client_id: Optional client identifier
44
+ """
45
+ await websocket.accept()
46
+ self.active_connections.add(websocket)
47
+
48
+ # Store metadata
49
+ self.connection_metadata[websocket] = {
50
+ 'client_id': client_id or f"client_{id(websocket)}",
51
+ 'connected_at': datetime.utcnow().isoformat(),
52
+ 'last_ping': datetime.utcnow().isoformat()
53
+ }
54
+
55
+ logger.info(
56
+ f"WebSocket connected: {self.connection_metadata[websocket]['client_id']} "
57
+ f"(Total connections: {len(self.active_connections)})"
58
+ )
59
+
60
+ # Send welcome message
61
+ await self.send_personal_message(
62
+ {
63
+ 'type': 'connection_established',
64
+ 'client_id': self.connection_metadata[websocket]['client_id'],
65
+ 'timestamp': datetime.utcnow().isoformat(),
66
+ 'message': 'Connected to Crypto API Monitor WebSocket'
67
+ },
68
+ websocket
69
+ )
70
+
71
+ def disconnect(self, websocket: WebSocket):
72
+ """
73
+ Unregister and close a WebSocket connection
74
+
75
+ Args:
76
+ websocket: WebSocket connection to disconnect
77
+ """
78
+ if websocket in self.active_connections:
79
+ client_id = self.connection_metadata.get(websocket, {}).get('client_id', 'unknown')
80
+ self.active_connections.remove(websocket)
81
+
82
+ if websocket in self.connection_metadata:
83
+ del self.connection_metadata[websocket]
84
+
85
+ logger.info(
86
+ f"WebSocket disconnected: {client_id} "
87
+ f"(Remaining connections: {len(self.active_connections)})"
88
+ )
89
+
90
+ async def send_personal_message(self, message: Dict[str, Any], websocket: WebSocket):
91
+ """
92
+ Send a message to a specific WebSocket connection
93
+
94
+ Args:
95
+ message: Message dictionary to send
96
+ websocket: Target WebSocket connection
97
+ """
98
+ try:
99
+ if websocket.client_state == WebSocketState.CONNECTED:
100
+ await websocket.send_json(message)
101
+ except Exception as e:
102
+ logger.error(f"Error sending personal message: {e}")
103
+ self.disconnect(websocket)
104
+
105
+ async def broadcast(self, message: Dict[str, Any]):
106
+ """
107
+ Broadcast a message to all connected clients
108
+
109
+ Args:
110
+ message: Message dictionary to broadcast
111
+ """
112
+ disconnected = []
113
+
114
+ for connection in self.active_connections.copy():
115
+ try:
116
+ if connection.client_state == WebSocketState.CONNECTED:
117
+ await connection.send_json(message)
118
+ else:
119
+ disconnected.append(connection)
120
+ except Exception as e:
121
+ logger.error(f"Error broadcasting to client: {e}")
122
+ disconnected.append(connection)
123
+
124
+ # Clean up disconnected clients
125
+ for connection in disconnected:
126
+ self.disconnect(connection)
127
+
128
+ async def broadcast_status_update(self):
129
+ """
130
+ Broadcast system status update to all connected clients
131
+ """
132
+ try:
133
+ # Get latest system metrics
134
+ latest_metrics = db_manager.get_latest_system_metrics()
135
+
136
+ # Get all providers
137
+ providers = config.get_all_providers()
138
+
139
+ # Get rate limit statuses
140
+ rate_limit_statuses = rate_limiter.get_all_statuses()
141
+
142
+ # Get recent alerts (last hour, unacknowledged)
143
+ alerts = db_manager.get_alerts(acknowledged=False, hours=1)
144
+
145
+ # Build status message
146
+ message = {
147
+ 'type': 'status_update',
148
+ 'timestamp': datetime.utcnow().isoformat(),
149
+ 'system_metrics': {
150
+ 'total_providers': latest_metrics.total_providers if latest_metrics else len(providers),
151
+ 'online_count': latest_metrics.online_count if latest_metrics else 0,
152
+ 'degraded_count': latest_metrics.degraded_count if latest_metrics else 0,
153
+ 'offline_count': latest_metrics.offline_count if latest_metrics else 0,
154
+ 'avg_response_time_ms': latest_metrics.avg_response_time_ms if latest_metrics else 0,
155
+ 'total_requests_hour': latest_metrics.total_requests_hour if latest_metrics else 0,
156
+ 'total_failures_hour': latest_metrics.total_failures_hour if latest_metrics else 0,
157
+ 'system_health': latest_metrics.system_health if latest_metrics else 'unknown'
158
+ },
159
+ 'alert_count': len(alerts),
160
+ 'active_websocket_clients': len(self.active_connections)
161
+ }
162
+
163
+ await self.broadcast(message)
164
+ logger.debug(f"Broadcasted status update to {len(self.active_connections)} clients")
165
+
166
+ except Exception as e:
167
+ logger.error(f"Error broadcasting status update: {e}", exc_info=True)
168
+
169
+ async def broadcast_new_log_entry(self, log_type: str, log_data: Dict[str, Any]):
170
+ """
171
+ Broadcast a new log entry
172
+
173
+ Args:
174
+ log_type: Type of log (connection, failure, collection, rate_limit)
175
+ log_data: Log data dictionary
176
+ """
177
+ try:
178
+ message = {
179
+ 'type': 'new_log_entry',
180
+ 'timestamp': datetime.utcnow().isoformat(),
181
+ 'log_type': log_type,
182
+ 'data': log_data
183
+ }
184
+
185
+ await self.broadcast(message)
186
+ logger.debug(f"Broadcasted new {log_type} log entry")
187
+
188
+ except Exception as e:
189
+ logger.error(f"Error broadcasting log entry: {e}", exc_info=True)
190
+
191
+ async def broadcast_rate_limit_alert(self, provider_name: str, percentage: float):
192
+ """
193
+ Broadcast rate limit alert
194
+
195
+ Args:
196
+ provider_name: Provider name
197
+ percentage: Current usage percentage
198
+ """
199
+ try:
200
+ message = {
201
+ 'type': 'rate_limit_alert',
202
+ 'timestamp': datetime.utcnow().isoformat(),
203
+ 'provider': provider_name,
204
+ 'percentage': percentage,
205
+ 'severity': 'critical' if percentage >= 95 else 'warning'
206
+ }
207
+
208
+ await self.broadcast(message)
209
+ logger.info(f"Broadcasted rate limit alert for {provider_name} ({percentage}%)")
210
+
211
+ except Exception as e:
212
+ logger.error(f"Error broadcasting rate limit alert: {e}", exc_info=True)
213
+
214
+ async def broadcast_provider_status_change(
215
+ self,
216
+ provider_name: str,
217
+ old_status: str,
218
+ new_status: str,
219
+ details: Optional[Dict] = None
220
+ ):
221
+ """
222
+ Broadcast provider status change
223
+
224
+ Args:
225
+ provider_name: Provider name
226
+ old_status: Previous status
227
+ new_status: New status
228
+ details: Optional details about the change
229
+ """
230
+ try:
231
+ message = {
232
+ 'type': 'provider_status_change',
233
+ 'timestamp': datetime.utcnow().isoformat(),
234
+ 'provider': provider_name,
235
+ 'old_status': old_status,
236
+ 'new_status': new_status,
237
+ 'details': details or {}
238
+ }
239
+
240
+ await self.broadcast(message)
241
+ logger.info(
242
+ f"Broadcasted provider status change: {provider_name} "
243
+ f"{old_status} -> {new_status}"
244
+ )
245
+
246
+ except Exception as e:
247
+ logger.error(f"Error broadcasting provider status change: {e}", exc_info=True)
248
+
249
+ async def _periodic_broadcast_loop(self):
250
+ """
251
+ Background task that broadcasts updates every 10 seconds
252
+ """
253
+ logger.info("Starting periodic broadcast loop")
254
+
255
+ while self._is_running:
256
+ try:
257
+ # Broadcast status update
258
+ await self.broadcast_status_update()
259
+
260
+ # Check for rate limit warnings
261
+ rate_limit_statuses = rate_limiter.get_all_statuses()
262
+ for provider, status_data in rate_limit_statuses.items():
263
+ if status_data and status_data.get('percentage', 0) >= 80:
264
+ await self.broadcast_rate_limit_alert(
265
+ provider,
266
+ status_data['percentage']
267
+ )
268
+
269
+ # Wait 10 seconds before next broadcast
270
+ await asyncio.sleep(10)
271
+
272
+ except Exception as e:
273
+ logger.error(f"Error in periodic broadcast loop: {e}", exc_info=True)
274
+ await asyncio.sleep(10)
275
+
276
+ logger.info("Periodic broadcast loop stopped")
277
+
278
+ async def _heartbeat_loop(self):
279
+ """
280
+ Background task that sends heartbeat pings to all clients
281
+ """
282
+ logger.info("Starting heartbeat loop")
283
+
284
+ while self._is_running:
285
+ try:
286
+ # Send ping to all connected clients
287
+ ping_message = {
288
+ 'type': 'ping',
289
+ 'timestamp': datetime.utcnow().isoformat()
290
+ }
291
+
292
+ await self.broadcast(ping_message)
293
+
294
+ # Wait 30 seconds before next heartbeat
295
+ await asyncio.sleep(30)
296
+
297
+ except Exception as e:
298
+ logger.error(f"Error in heartbeat loop: {e}", exc_info=True)
299
+ await asyncio.sleep(30)
300
+
301
+ logger.info("Heartbeat loop stopped")
302
+
303
+ async def start_background_tasks(self):
304
+ """
305
+ Start background broadcast and heartbeat tasks
306
+ """
307
+ if self._is_running:
308
+ logger.warning("Background tasks already running")
309
+ return
310
+
311
+ self._is_running = True
312
+
313
+ # Start periodic broadcast task
314
+ self._broadcast_task = asyncio.create_task(self._periodic_broadcast_loop())
315
+ logger.info("Started periodic broadcast task")
316
+
317
+ # Start heartbeat task
318
+ self._heartbeat_task = asyncio.create_task(self._heartbeat_loop())
319
+ logger.info("Started heartbeat task")
320
+
321
+ async def stop_background_tasks(self):
322
+ """
323
+ Stop background broadcast and heartbeat tasks
324
+ """
325
+ if not self._is_running:
326
+ logger.warning("Background tasks not running")
327
+ return
328
+
329
+ self._is_running = False
330
+
331
+ # Cancel broadcast task
332
+ if self._broadcast_task:
333
+ self._broadcast_task.cancel()
334
+ try:
335
+ await self._broadcast_task
336
+ except asyncio.CancelledError:
337
+ pass
338
+ logger.info("Stopped periodic broadcast task")
339
+
340
+ # Cancel heartbeat task
341
+ if self._heartbeat_task:
342
+ self._heartbeat_task.cancel()
343
+ try:
344
+ await self._heartbeat_task
345
+ except asyncio.CancelledError:
346
+ pass
347
+ logger.info("Stopped heartbeat task")
348
+
349
+ async def close_all_connections(self):
350
+ """
351
+ Close all active WebSocket connections
352
+ """
353
+ logger.info(f"Closing {len(self.active_connections)} active connections")
354
+
355
+ for connection in self.active_connections.copy():
356
+ try:
357
+ if connection.client_state == WebSocketState.CONNECTED:
358
+ await connection.close(code=1000, reason="Server shutdown")
359
+ except Exception as e:
360
+ logger.error(f"Error closing connection: {e}")
361
+
362
+ self.active_connections.clear()
363
+ self.connection_metadata.clear()
364
+ logger.info("All WebSocket connections closed")
365
+
366
+ def get_connection_count(self) -> int:
367
+ """
368
+ Get the number of active connections
369
+
370
+ Returns:
371
+ Number of active connections
372
+ """
373
+ return len(self.active_connections)
374
+
375
+ def get_connection_info(self) -> List[Dict[str, Any]]:
376
+ """
377
+ Get information about all active connections
378
+
379
+ Returns:
380
+ List of connection metadata dictionaries
381
+ """
382
+ return [
383
+ {
384
+ 'client_id': metadata['client_id'],
385
+ 'connected_at': metadata['connected_at'],
386
+ 'last_ping': metadata['last_ping']
387
+ }
388
+ for metadata in self.connection_metadata.values()
389
+ ]
390
+
391
+
392
+ # Global connection manager instance
393
+ manager = ConnectionManager()
394
+
395
+
396
+ @router.websocket("/ws/live")
397
+ async def websocket_live_endpoint(websocket: WebSocket):
398
+ """
399
+ WebSocket endpoint for real-time updates
400
+
401
+ Provides:
402
+ - System status updates every 10 seconds
403
+ - Real-time log entries
404
+ - Rate limit alerts
405
+ - Provider status changes
406
+ - Heartbeat pings every 30 seconds
407
+
408
+ Message Types:
409
+ - connection_established: Sent when client connects
410
+ - status_update: Periodic system status (every 10s)
411
+ - new_log_entry: New log entry notification
412
+ - rate_limit_alert: Rate limit warning
413
+ - provider_status_change: Provider status change
414
+ - ping: Heartbeat ping (every 30s)
415
+ """
416
+ client_id = None
417
+
418
+ try:
419
+ # Connect client
420
+ await manager.connect(websocket)
421
+ client_id = manager.connection_metadata.get(websocket, {}).get('client_id', 'unknown')
422
+
423
+ # Start background tasks if not already running
424
+ if not manager._is_running:
425
+ await manager.start_background_tasks()
426
+
427
+ # Keep connection alive and handle incoming messages
428
+ while True:
429
+ try:
430
+ # Wait for messages from client (pong responses, etc.)
431
+ data = await websocket.receive_text()
432
+
433
+ # Parse message
434
+ try:
435
+ message = json.loads(data)
436
+
437
+ # Handle pong response
438
+ if message.get('type') == 'pong':
439
+ if websocket in manager.connection_metadata:
440
+ manager.connection_metadata[websocket]['last_ping'] = datetime.utcnow().isoformat()
441
+ logger.debug(f"Received pong from {client_id}")
442
+
443
+ # Handle subscription requests (future enhancement)
444
+ elif message.get('type') == 'subscribe':
445
+ # Could implement topic-based subscriptions here
446
+ logger.debug(f"Client {client_id} subscription request: {message}")
447
+
448
+ # Handle unsubscribe requests (future enhancement)
449
+ elif message.get('type') == 'unsubscribe':
450
+ logger.debug(f"Client {client_id} unsubscribe request: {message}")
451
+
452
+ except json.JSONDecodeError:
453
+ logger.warning(f"Received invalid JSON from {client_id}: {data}")
454
+
455
+ except WebSocketDisconnect:
456
+ logger.info(f"Client {client_id} disconnected")
457
+ break
458
+
459
+ except Exception as e:
460
+ logger.error(f"Error handling message from {client_id}: {e}", exc_info=True)
461
+ break
462
+
463
+ except Exception as e:
464
+ logger.error(f"WebSocket error for {client_id}: {e}", exc_info=True)
465
+
466
+ finally:
467
+ # Disconnect client
468
+ manager.disconnect(websocket)
469
+
470
+
471
+ @router.get("/ws/stats")
472
+ async def websocket_stats():
473
+ """
474
+ Get WebSocket connection statistics
475
+
476
+ Returns:
477
+ Dictionary with connection stats
478
+ """
479
+ return {
480
+ 'active_connections': manager.get_connection_count(),
481
+ 'connections': manager.get_connection_info(),
482
+ 'background_tasks_running': manager._is_running,
483
+ 'timestamp': datetime.utcnow().isoformat()
484
+ }
485
+
486
+
487
+ # Export manager and router
488
+ __all__ = ['router', 'manager', 'ConnectionManager']
final/api/ws_data_broadcaster.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WebSocket Data Broadcaster
3
+ Broadcasts real-time cryptocurrency data from database to connected clients
4
+ """
5
+
6
+ import asyncio
7
+ import logging
8
+ from datetime import datetime
9
+ from typing import Dict, Any
10
+
11
+ from database.db_manager import db_manager
12
+ from backend.services.ws_service_manager import ws_manager, ServiceType
13
+ from utils.logger import setup_logger
14
+
15
+ logger = setup_logger("ws_data_broadcaster")
16
+
17
+
18
+ class DataBroadcaster:
19
+ """
20
+ Broadcasts cryptocurrency data updates to WebSocket clients
21
+ """
22
+
23
+ def __init__(self):
24
+ """Initialize the broadcaster"""
25
+ self.last_broadcast = {}
26
+ self.broadcast_interval = 5 # seconds for price updates
27
+ self.is_running = False
28
+ logger.info("DataBroadcaster initialized")
29
+
30
+ async def start_broadcasting(self):
31
+ """Start all broadcast tasks"""
32
+ logger.info("Starting WebSocket data broadcaster...")
33
+
34
+ self.is_running = True
35
+
36
+ tasks = [
37
+ self.broadcast_market_data(),
38
+ self.broadcast_news(),
39
+ self.broadcast_sentiment(),
40
+ self.broadcast_whales(),
41
+ self.broadcast_gas_prices()
42
+ ]
43
+
44
+ try:
45
+ await asyncio.gather(*tasks, return_exceptions=True)
46
+ except Exception as e:
47
+ logger.error(f"Error in broadcasting tasks: {e}", exc_info=True)
48
+ finally:
49
+ self.is_running = False
50
+
51
+ async def stop_broadcasting(self):
52
+ """Stop broadcasting"""
53
+ logger.info("Stopping WebSocket data broadcaster...")
54
+ self.is_running = False
55
+
56
+ async def broadcast_market_data(self):
57
+ """Broadcast market price updates"""
58
+ logger.info("Starting market data broadcast...")
59
+
60
+ while self.is_running:
61
+ try:
62
+ prices = db_manager.get_latest_prices(limit=50)
63
+
64
+ if prices:
65
+ # Format data for broadcast
66
+ data = {
67
+ "type": "market_data",
68
+ "data": {
69
+ "prices": {p.symbol: p.price_usd for p in prices},
70
+ "volumes": {p.symbol: p.volume_24h for p in prices if p.volume_24h},
71
+ "market_caps": {p.symbol: p.market_cap for p in prices if p.market_cap},
72
+ "price_changes": {p.symbol: p.price_change_24h for p in prices if p.price_change_24h}
73
+ },
74
+ "count": len(prices),
75
+ "timestamp": datetime.utcnow().isoformat()
76
+ }
77
+
78
+ # Broadcast to subscribed clients
79
+ await ws_manager.broadcast_to_service(ServiceType.MARKET_DATA, data)
80
+ logger.debug(f"Broadcasted {len(prices)} price updates")
81
+
82
+ except Exception as e:
83
+ logger.error(f"Error broadcasting market data: {e}", exc_info=True)
84
+
85
+ await asyncio.sleep(self.broadcast_interval)
86
+
87
+ async def broadcast_news(self):
88
+ """Broadcast news updates"""
89
+ logger.info("Starting news broadcast...")
90
+ last_news_id = 0
91
+
92
+ while self.is_running:
93
+ try:
94
+ news = db_manager.get_latest_news(limit=10)
95
+
96
+ if news and (not last_news_id or news[0].id != last_news_id):
97
+ # New news available
98
+ last_news_id = news[0].id
99
+
100
+ data = {
101
+ "type": "news",
102
+ "data": {
103
+ "articles": [
104
+ {
105
+ "id": article.id,
106
+ "title": article.title,
107
+ "source": article.source,
108
+ "url": article.url,
109
+ "published_at": article.published_at.isoformat(),
110
+ "sentiment": article.sentiment
111
+ }
112
+ for article in news[:5] # Only send 5 latest
113
+ ]
114
+ },
115
+ "count": len(news[:5]),
116
+ "timestamp": datetime.utcnow().isoformat()
117
+ }
118
+
119
+ await ws_manager.broadcast_to_service(ServiceType.NEWS, data)
120
+ logger.info(f"Broadcasted {len(news[:5])} news articles")
121
+
122
+ except Exception as e:
123
+ logger.error(f"Error broadcasting news: {e}", exc_info=True)
124
+
125
+ await asyncio.sleep(30) # Check every 30 seconds
126
+
127
+ async def broadcast_sentiment(self):
128
+ """Broadcast sentiment updates"""
129
+ logger.info("Starting sentiment broadcast...")
130
+ last_sentiment_value = None
131
+
132
+ while self.is_running:
133
+ try:
134
+ sentiment = db_manager.get_latest_sentiment()
135
+
136
+ if sentiment and sentiment.value != last_sentiment_value:
137
+ last_sentiment_value = sentiment.value
138
+
139
+ data = {
140
+ "type": "sentiment",
141
+ "data": {
142
+ "fear_greed_index": sentiment.value,
143
+ "classification": sentiment.classification,
144
+ "metric_name": sentiment.metric_name,
145
+ "source": sentiment.source,
146
+ "timestamp": sentiment.timestamp.isoformat()
147
+ },
148
+ "timestamp": datetime.utcnow().isoformat()
149
+ }
150
+
151
+ await ws_manager.broadcast_to_service(ServiceType.SENTIMENT, data)
152
+ logger.info(f"Broadcasted sentiment: {sentiment.value} ({sentiment.classification})")
153
+
154
+ except Exception as e:
155
+ logger.error(f"Error broadcasting sentiment: {e}", exc_info=True)
156
+
157
+ await asyncio.sleep(60) # Check every minute
158
+
159
+ async def broadcast_whales(self):
160
+ """Broadcast whale transaction updates"""
161
+ logger.info("Starting whale transaction broadcast...")
162
+ last_whale_id = 0
163
+
164
+ while self.is_running:
165
+ try:
166
+ whales = db_manager.get_whale_transactions(limit=5)
167
+
168
+ if whales and (not last_whale_id or whales[0].id != last_whale_id):
169
+ last_whale_id = whales[0].id
170
+
171
+ data = {
172
+ "type": "whale_transaction",
173
+ "data": {
174
+ "transactions": [
175
+ {
176
+ "id": tx.id,
177
+ "blockchain": tx.blockchain,
178
+ "amount_usd": tx.amount_usd,
179
+ "from_address": tx.from_address[:20] + "...",
180
+ "to_address": tx.to_address[:20] + "...",
181
+ "timestamp": tx.timestamp.isoformat()
182
+ }
183
+ for tx in whales
184
+ ]
185
+ },
186
+ "count": len(whales),
187
+ "timestamp": datetime.utcnow().isoformat()
188
+ }
189
+
190
+ await ws_manager.broadcast_to_service(ServiceType.WHALE_TRACKING, data)
191
+ logger.info(f"Broadcasted {len(whales)} whale transactions")
192
+
193
+ except Exception as e:
194
+ logger.error(f"Error broadcasting whales: {e}", exc_info=True)
195
+
196
+ await asyncio.sleep(15) # Check every 15 seconds
197
+
198
+ async def broadcast_gas_prices(self):
199
+ """Broadcast gas price updates"""
200
+ logger.info("Starting gas price broadcast...")
201
+
202
+ while self.is_running:
203
+ try:
204
+ gas_prices = db_manager.get_latest_gas_prices()
205
+
206
+ if gas_prices:
207
+ data = {
208
+ "type": "gas_prices",
209
+ "data": gas_prices,
210
+ "timestamp": datetime.utcnow().isoformat()
211
+ }
212
+
213
+ # Broadcast to RPC_NODES service type (gas prices are blockchain-related)
214
+ await ws_manager.broadcast_to_service(ServiceType.RPC_NODES, data)
215
+ logger.debug("Broadcasted gas prices")
216
+
217
+ except Exception as e:
218
+ logger.error(f"Error broadcasting gas prices: {e}", exc_info=True)
219
+
220
+ await asyncio.sleep(30) # Every 30 seconds
221
+
222
+
223
+ # Global broadcaster instance
224
+ broadcaster = DataBroadcaster()
final/api/ws_data_services.py ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WebSocket API for Data Collection Services
3
+
4
+ This module provides WebSocket endpoints for real-time data streaming
5
+ from all data collection services.
6
+ """
7
+
8
+ import asyncio
9
+ from datetime import datetime
10
+ from typing import Any, Dict, Optional
11
+ from fastapi import APIRouter, WebSocket, WebSocketDisconnect
12
+ import logging
13
+
14
+ from backend.services.ws_service_manager import ws_manager, ServiceType
15
+ from collectors.market_data import MarketDataCollector
16
+ from collectors.explorers import ExplorerDataCollector
17
+ from collectors.news import NewsCollector
18
+ from collectors.sentiment import SentimentCollector
19
+ from collectors.whale_tracking import WhaleTrackingCollector
20
+ from collectors.rpc_nodes import RPCNodeCollector
21
+ from collectors.onchain import OnChainCollector
22
+ from config import Config
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ router = APIRouter()
27
+
28
+
29
+ # ============================================================================
30
+ # Data Collection Service Handlers
31
+ # ============================================================================
32
+
33
+ class DataCollectionStreamers:
34
+ """Handles data streaming for all collection services"""
35
+
36
+ def __init__(self):
37
+ self.config = Config()
38
+ self.market_data_collector = MarketDataCollector(self.config)
39
+ self.explorer_collector = ExplorerDataCollector(self.config)
40
+ self.news_collector = NewsCollector(self.config)
41
+ self.sentiment_collector = SentimentCollector(self.config)
42
+ self.whale_collector = WhaleTrackingCollector(self.config)
43
+ self.rpc_collector = RPCNodeCollector(self.config)
44
+ self.onchain_collector = OnChainCollector(self.config)
45
+
46
+ # ========================================================================
47
+ # Market Data Streaming
48
+ # ========================================================================
49
+
50
+ async def stream_market_data(self):
51
+ """Stream real-time market data"""
52
+ try:
53
+ data = await self.market_data_collector.collect()
54
+ if data:
55
+ return {
56
+ "prices": data.get("prices", {}),
57
+ "volumes": data.get("volumes", {}),
58
+ "market_caps": data.get("market_caps", {}),
59
+ "price_changes": data.get("price_changes", {}),
60
+ "source": data.get("source", "unknown"),
61
+ "timestamp": datetime.utcnow().isoformat()
62
+ }
63
+ except Exception as e:
64
+ logger.error(f"Error streaming market data: {e}")
65
+ return None
66
+
67
+ async def stream_order_books(self):
68
+ """Stream order book data"""
69
+ try:
70
+ # This would integrate with market_data_extended for order book data
71
+ data = await self.market_data_collector.collect()
72
+ if data and "order_book" in data:
73
+ return {
74
+ "bids": data["order_book"].get("bids", []),
75
+ "asks": data["order_book"].get("asks", []),
76
+ "spread": data["order_book"].get("spread"),
77
+ "timestamp": datetime.utcnow().isoformat()
78
+ }
79
+ except Exception as e:
80
+ logger.error(f"Error streaming order books: {e}")
81
+ return None
82
+
83
+ # ========================================================================
84
+ # Explorer Data Streaming
85
+ # ========================================================================
86
+
87
+ async def stream_explorer_data(self):
88
+ """Stream blockchain explorer data"""
89
+ try:
90
+ data = await self.explorer_collector.collect()
91
+ if data:
92
+ return {
93
+ "latest_block": data.get("latest_block"),
94
+ "network_hashrate": data.get("network_hashrate"),
95
+ "difficulty": data.get("difficulty"),
96
+ "mempool_size": data.get("mempool_size"),
97
+ "transactions_count": data.get("transactions_count"),
98
+ "timestamp": datetime.utcnow().isoformat()
99
+ }
100
+ except Exception as e:
101
+ logger.error(f"Error streaming explorer data: {e}")
102
+ return None
103
+
104
+ async def stream_transactions(self):
105
+ """Stream recent transactions"""
106
+ try:
107
+ data = await self.explorer_collector.collect()
108
+ if data and "recent_transactions" in data:
109
+ return {
110
+ "transactions": data["recent_transactions"],
111
+ "timestamp": datetime.utcnow().isoformat()
112
+ }
113
+ except Exception as e:
114
+ logger.error(f"Error streaming transactions: {e}")
115
+ return None
116
+
117
+ # ========================================================================
118
+ # News Streaming
119
+ # ========================================================================
120
+
121
+ async def stream_news(self):
122
+ """Stream news updates"""
123
+ try:
124
+ data = await self.news_collector.collect()
125
+ if data and "articles" in data:
126
+ return {
127
+ "articles": data["articles"][:10], # Latest 10 articles
128
+ "sources": data.get("sources", []),
129
+ "categories": data.get("categories", []),
130
+ "timestamp": datetime.utcnow().isoformat()
131
+ }
132
+ except Exception as e:
133
+ logger.error(f"Error streaming news: {e}")
134
+ return None
135
+
136
+ async def stream_breaking_news(self):
137
+ """Stream breaking news alerts"""
138
+ try:
139
+ data = await self.news_collector.collect()
140
+ if data and "breaking" in data:
141
+ return {
142
+ "breaking_news": data["breaking"],
143
+ "timestamp": datetime.utcnow().isoformat()
144
+ }
145
+ except Exception as e:
146
+ logger.error(f"Error streaming breaking news: {e}")
147
+ return None
148
+
149
+ # ========================================================================
150
+ # Sentiment Streaming
151
+ # ========================================================================
152
+
153
+ async def stream_sentiment(self):
154
+ """Stream sentiment analysis data"""
155
+ try:
156
+ data = await self.sentiment_collector.collect()
157
+ if data:
158
+ return {
159
+ "overall_sentiment": data.get("overall_sentiment"),
160
+ "sentiment_score": data.get("sentiment_score"),
161
+ "social_volume": data.get("social_volume"),
162
+ "trending_topics": data.get("trending_topics", []),
163
+ "sentiment_by_source": data.get("by_source", {}),
164
+ "timestamp": datetime.utcnow().isoformat()
165
+ }
166
+ except Exception as e:
167
+ logger.error(f"Error streaming sentiment: {e}")
168
+ return None
169
+
170
+ async def stream_social_trends(self):
171
+ """Stream social media trends"""
172
+ try:
173
+ data = await self.sentiment_collector.collect()
174
+ if data and "social_trends" in data:
175
+ return {
176
+ "trends": data["social_trends"],
177
+ "timestamp": datetime.utcnow().isoformat()
178
+ }
179
+ except Exception as e:
180
+ logger.error(f"Error streaming social trends: {e}")
181
+ return None
182
+
183
+ # ========================================================================
184
+ # Whale Tracking Streaming
185
+ # ========================================================================
186
+
187
+ async def stream_whale_activity(self):
188
+ """Stream whale transaction data"""
189
+ try:
190
+ data = await self.whale_collector.collect()
191
+ if data:
192
+ return {
193
+ "large_transactions": data.get("large_transactions", []),
194
+ "whale_wallets": data.get("whale_wallets", []),
195
+ "total_volume": data.get("total_volume"),
196
+ "alert_threshold": data.get("alert_threshold"),
197
+ "timestamp": datetime.utcnow().isoformat()
198
+ }
199
+ except Exception as e:
200
+ logger.error(f"Error streaming whale activity: {e}")
201
+ return None
202
+
203
+ async def stream_whale_alerts(self):
204
+ """Stream whale transaction alerts"""
205
+ try:
206
+ data = await self.whale_collector.collect()
207
+ if data and "alerts" in data:
208
+ return {
209
+ "alerts": data["alerts"],
210
+ "timestamp": datetime.utcnow().isoformat()
211
+ }
212
+ except Exception as e:
213
+ logger.error(f"Error streaming whale alerts: {e}")
214
+ return None
215
+
216
+ # ========================================================================
217
+ # RPC Node Streaming
218
+ # ========================================================================
219
+
220
+ async def stream_rpc_status(self):
221
+ """Stream RPC node status"""
222
+ try:
223
+ data = await self.rpc_collector.collect()
224
+ if data:
225
+ return {
226
+ "nodes": data.get("nodes", []),
227
+ "active_nodes": data.get("active_nodes"),
228
+ "total_nodes": data.get("total_nodes"),
229
+ "average_latency": data.get("average_latency"),
230
+ "timestamp": datetime.utcnow().isoformat()
231
+ }
232
+ except Exception as e:
233
+ logger.error(f"Error streaming RPC status: {e}")
234
+ return None
235
+
236
+ async def stream_blockchain_events(self):
237
+ """Stream blockchain events from RPC nodes"""
238
+ try:
239
+ data = await self.rpc_collector.collect()
240
+ if data and "events" in data:
241
+ return {
242
+ "events": data["events"],
243
+ "block_number": data.get("block_number"),
244
+ "timestamp": datetime.utcnow().isoformat()
245
+ }
246
+ except Exception as e:
247
+ logger.error(f"Error streaming blockchain events: {e}")
248
+ return None
249
+
250
+ # ========================================================================
251
+ # On-Chain Analytics Streaming
252
+ # ========================================================================
253
+
254
+ async def stream_onchain_metrics(self):
255
+ """Stream on-chain analytics"""
256
+ try:
257
+ data = await self.onchain_collector.collect()
258
+ if data:
259
+ return {
260
+ "active_addresses": data.get("active_addresses"),
261
+ "transaction_count": data.get("transaction_count"),
262
+ "total_fees": data.get("total_fees"),
263
+ "gas_price": data.get("gas_price"),
264
+ "network_utilization": data.get("network_utilization"),
265
+ "timestamp": datetime.utcnow().isoformat()
266
+ }
267
+ except Exception as e:
268
+ logger.error(f"Error streaming on-chain metrics: {e}")
269
+ return None
270
+
271
+ async def stream_contract_events(self):
272
+ """Stream smart contract events"""
273
+ try:
274
+ data = await self.onchain_collector.collect()
275
+ if data and "contract_events" in data:
276
+ return {
277
+ "events": data["contract_events"],
278
+ "timestamp": datetime.utcnow().isoformat()
279
+ }
280
+ except Exception as e:
281
+ logger.error(f"Error streaming contract events: {e}")
282
+ return None
283
+
284
+
285
+ # Global instance
286
+ data_streamers = DataCollectionStreamers()
287
+
288
+
289
+ # ============================================================================
290
+ # Background Streaming Tasks
291
+ # ============================================================================
292
+
293
+ async def start_data_collection_streams():
294
+ """Start all data collection stream tasks"""
295
+ logger.info("Starting data collection WebSocket streams")
296
+
297
+ tasks = [
298
+ # Market Data
299
+ asyncio.create_task(ws_manager.start_service_stream(
300
+ ServiceType.MARKET_DATA,
301
+ data_streamers.stream_market_data,
302
+ interval=5.0 # 5 second updates
303
+ )),
304
+
305
+ # Explorer Data
306
+ asyncio.create_task(ws_manager.start_service_stream(
307
+ ServiceType.EXPLORERS,
308
+ data_streamers.stream_explorer_data,
309
+ interval=10.0 # 10 second updates
310
+ )),
311
+
312
+ # News
313
+ asyncio.create_task(ws_manager.start_service_stream(
314
+ ServiceType.NEWS,
315
+ data_streamers.stream_news,
316
+ interval=60.0 # 1 minute updates
317
+ )),
318
+
319
+ # Sentiment
320
+ asyncio.create_task(ws_manager.start_service_stream(
321
+ ServiceType.SENTIMENT,
322
+ data_streamers.stream_sentiment,
323
+ interval=30.0 # 30 second updates
324
+ )),
325
+
326
+ # Whale Tracking
327
+ asyncio.create_task(ws_manager.start_service_stream(
328
+ ServiceType.WHALE_TRACKING,
329
+ data_streamers.stream_whale_activity,
330
+ interval=15.0 # 15 second updates
331
+ )),
332
+
333
+ # RPC Nodes
334
+ asyncio.create_task(ws_manager.start_service_stream(
335
+ ServiceType.RPC_NODES,
336
+ data_streamers.stream_rpc_status,
337
+ interval=20.0 # 20 second updates
338
+ )),
339
+
340
+ # On-Chain Analytics
341
+ asyncio.create_task(ws_manager.start_service_stream(
342
+ ServiceType.ONCHAIN,
343
+ data_streamers.stream_onchain_metrics,
344
+ interval=30.0 # 30 second updates
345
+ )),
346
+ ]
347
+
348
+ await asyncio.gather(*tasks, return_exceptions=True)
349
+
350
+
351
+ # ============================================================================
352
+ # WebSocket Endpoints
353
+ # ============================================================================
354
+
355
+ @router.websocket("/ws/data")
356
+ async def websocket_data_endpoint(websocket: WebSocket):
357
+ """
358
+ Unified WebSocket endpoint for all data collection services
359
+
360
+ Connection URL: ws://host:port/ws/data
361
+
362
+ After connecting, send subscription messages:
363
+ {
364
+ "action": "subscribe",
365
+ "service": "market_data" | "explorers" | "news" | "sentiment" |
366
+ "whale_tracking" | "rpc_nodes" | "onchain" | "all"
367
+ }
368
+
369
+ To unsubscribe:
370
+ {
371
+ "action": "unsubscribe",
372
+ "service": "service_name"
373
+ }
374
+
375
+ To get status:
376
+ {
377
+ "action": "get_status"
378
+ }
379
+ """
380
+ connection = await ws_manager.connect(websocket)
381
+
382
+ try:
383
+ while True:
384
+ # Receive and handle client messages
385
+ data = await websocket.receive_json()
386
+ await ws_manager.handle_client_message(connection, data)
387
+
388
+ except WebSocketDisconnect:
389
+ logger.info(f"Client disconnected: {connection.client_id}")
390
+ except Exception as e:
391
+ logger.error(f"WebSocket error for client {connection.client_id}: {e}")
392
+ finally:
393
+ await ws_manager.disconnect(connection.client_id)
394
+
395
+
396
+ @router.websocket("/ws/market_data")
397
+ async def websocket_market_data(websocket: WebSocket):
398
+ """
399
+ Dedicated WebSocket endpoint for market data
400
+
401
+ Auto-subscribes to market_data service
402
+ """
403
+ connection = await ws_manager.connect(websocket)
404
+ connection.subscribe(ServiceType.MARKET_DATA)
405
+
406
+ try:
407
+ while True:
408
+ data = await websocket.receive_json()
409
+ await ws_manager.handle_client_message(connection, data)
410
+ except WebSocketDisconnect:
411
+ logger.info(f"Market data client disconnected: {connection.client_id}")
412
+ except Exception as e:
413
+ logger.error(f"Market data WebSocket error: {e}")
414
+ finally:
415
+ await ws_manager.disconnect(connection.client_id)
416
+
417
+
418
+ @router.websocket("/ws/whale_tracking")
419
+ async def websocket_whale_tracking(websocket: WebSocket):
420
+ """
421
+ Dedicated WebSocket endpoint for whale tracking
422
+
423
+ Auto-subscribes to whale_tracking service
424
+ """
425
+ connection = await ws_manager.connect(websocket)
426
+ connection.subscribe(ServiceType.WHALE_TRACKING)
427
+
428
+ try:
429
+ while True:
430
+ data = await websocket.receive_json()
431
+ await ws_manager.handle_client_message(connection, data)
432
+ except WebSocketDisconnect:
433
+ logger.info(f"Whale tracking client disconnected: {connection.client_id}")
434
+ except Exception as e:
435
+ logger.error(f"Whale tracking WebSocket error: {e}")
436
+ finally:
437
+ await ws_manager.disconnect(connection.client_id)
438
+
439
+
440
+ @router.websocket("/ws/news")
441
+ async def websocket_news(websocket: WebSocket):
442
+ """
443
+ Dedicated WebSocket endpoint for news
444
+
445
+ Auto-subscribes to news service
446
+ """
447
+ connection = await ws_manager.connect(websocket)
448
+ connection.subscribe(ServiceType.NEWS)
449
+
450
+ try:
451
+ while True:
452
+ data = await websocket.receive_json()
453
+ await ws_manager.handle_client_message(connection, data)
454
+ except WebSocketDisconnect:
455
+ logger.info(f"News client disconnected: {connection.client_id}")
456
+ except Exception as e:
457
+ logger.error(f"News WebSocket error: {e}")
458
+ finally:
459
+ await ws_manager.disconnect(connection.client_id)
460
+
461
+
462
+ @router.websocket("/ws/sentiment")
463
+ async def websocket_sentiment(websocket: WebSocket):
464
+ """
465
+ Dedicated WebSocket endpoint for sentiment analysis
466
+
467
+ Auto-subscribes to sentiment service
468
+ """
469
+ connection = await ws_manager.connect(websocket)
470
+ connection.subscribe(ServiceType.SENTIMENT)
471
+
472
+ try:
473
+ while True:
474
+ data = await websocket.receive_json()
475
+ await ws_manager.handle_client_message(connection, data)
476
+ except WebSocketDisconnect:
477
+ logger.info(f"Sentiment client disconnected: {connection.client_id}")
478
+ except Exception as e:
479
+ logger.error(f"Sentiment WebSocket error: {e}")
480
+ finally:
481
+ await ws_manager.disconnect(connection.client_id)