Really-amin's picture
Upload 295 files
d6d843f verified
#!/usr/bin/env node
/**
* CRYPTOCURRENCY API RESOURCE MONITOR
* Monitors and manages all API resources from registry
* Tracks online status, validates endpoints, maintains availability metrics
*/
const fs = require('fs');
const https = require('https');
const http = require('http');
// ═══════════════════════════════════════════════════════════════
// CONFIGURATION
// ═══════════════════════════════════════════════════════════════
const CONFIG = {
REGISTRY_FILE: './all_apis_merged_2025.json',
CHECK_INTERVAL: 5 * 60 * 1000, // 5 minutes
TIMEOUT: 10000, // 10 seconds
MAX_RETRIES: 3,
RETRY_DELAY: 2000,
// Status thresholds
THRESHOLDS: {
ONLINE: { responseTime: 2000, successRate: 0.95 },
DEGRADED: { responseTime: 5000, successRate: 0.80 },
SLOW: { responseTime: 10000, successRate: 0.70 },
UNSTABLE: { responseTime: Infinity, successRate: 0.50 }
}
};
// ═══════════════════════════════════════════════════════════════
// API REGISTRY - Comprehensive resource definitions
// ═══════════════════════════════════════════════════════════════
const API_REGISTRY = {
blockchainExplorers: {
etherscan: [
{ name: 'Etherscan-1', url: 'https://api.etherscan.io/api', keyName: 'etherscan', keyIndex: 0, testEndpoint: '?module=stats&action=ethprice&apikey={{KEY}}', tier: 1 },
{ name: 'Etherscan-2', url: 'https://api.etherscan.io/api', keyName: 'etherscan', keyIndex: 1, testEndpoint: '?module=stats&action=ethprice&apikey={{KEY}}', tier: 1 }
],
bscscan: [
{ name: 'BscScan', url: 'https://api.bscscan.com/api', keyName: 'bscscan', keyIndex: 0, testEndpoint: '?module=stats&action=bnbprice&apikey={{KEY}}', tier: 1 }
],
tronscan: [
{ name: 'TronScan', url: 'https://apilist.tronscanapi.com/api', keyName: 'tronscan', keyIndex: 0, testEndpoint: '/system/status', tier: 2 }
]
},
marketData: {
coingecko: [
{ name: 'CoinGecko', url: 'https://api.coingecko.com/api/v3', testEndpoint: '/ping', requiresKey: false, tier: 1 },
{ name: 'CoinGecko-Price', url: 'https://api.coingecko.com/api/v3', testEndpoint: '/simple/price?ids=bitcoin&vs_currencies=usd', requiresKey: false, tier: 1 }
],
coinmarketcap: [
{ 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 },
{ 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 }
],
cryptocompare: [
{ name: 'CryptoCompare', url: 'https://min-api.cryptocompare.com/data', keyName: 'cryptocompare', keyIndex: 0, testEndpoint: '/price?fsym=BTC&tsyms=USD&api_key={{KEY}}', tier: 2 }
],
coinpaprika: [
{ name: 'CoinPaprika', url: 'https://api.coinpaprika.com/v1', testEndpoint: '/ping', requiresKey: false, tier: 2 }
],
coincap: [
{ name: 'CoinCap', url: 'https://api.coincap.io/v2', testEndpoint: '/assets/bitcoin', requiresKey: false, tier: 2 }
]
},
newsAndSentiment: {
cryptopanic: [
{ name: 'CryptoPanic', url: 'https://cryptopanic.com/api/v1', testEndpoint: '/posts/?public=true', requiresKey: false, tier: 2 }
],
newsapi: [
{ name: 'NewsAPI', url: 'https://newsapi.org/v2', keyName: 'newsapi', keyIndex: 0, testEndpoint: '/top-headlines?category=business&apiKey={{KEY}}', tier: 2 }
],
alternativeme: [
{ name: 'Fear-Greed-Index', url: 'https://api.alternative.me', testEndpoint: '/fng/?limit=1', requiresKey: false, tier: 2 }
],
reddit: [
{ name: 'Reddit-Crypto', url: 'https://www.reddit.com/r/cryptocurrency', testEndpoint: '/hot.json?limit=1', requiresKey: false, tier: 3 }
]
},
rpcNodes: {
ethereum: [
{ name: 'Ankr-ETH', url: 'https://rpc.ankr.com/eth', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 1 },
{ name: 'PublicNode-ETH', url: 'https://ethereum.publicnode.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
{ name: 'Cloudflare-ETH', url: 'https://cloudflare-eth.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
{ name: 'LlamaNodes-ETH', url: 'https://eth.llamarpc.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 3 }
],
bsc: [
{ name: 'BSC-Official', url: 'https://bsc-dataseed.binance.org', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
{ name: 'Ankr-BSC', url: 'https://rpc.ankr.com/bsc', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
{ name: 'PublicNode-BSC', url: 'https://bsc-rpc.publicnode.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 3 }
],
polygon: [
{ name: 'Polygon-Official', url: 'https://polygon-rpc.com', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 },
{ name: 'Ankr-Polygon', url: 'https://rpc.ankr.com/polygon', testEndpoint: '', method: 'POST', rpcTest: true, requiresKey: false, tier: 2 }
],
tron: [
{ name: 'TronGrid', url: 'https://api.trongrid.io', testEndpoint: '/wallet/getnowblock', method: 'POST', requiresKey: false, tier: 2 },
{ name: 'TronStack', url: 'https://api.tronstack.io', testEndpoint: '/wallet/getnowblock', method: 'POST', requiresKey: false, tier: 3 }
]
},
onChainAnalytics: [
{ name: 'TheGraph', url: 'https://api.thegraph.com', testEndpoint: '/index-node/graphql', requiresKey: false, tier: 2 },
{ name: 'Blockchair', url: 'https://api.blockchair.com', testEndpoint: '/stats', requiresKey: false, tier: 3 }
],
whaleTracking: [
{ name: 'WhaleAlert-Status', url: 'https://api.whale-alert.io/v1', testEndpoint: '/status', requiresKey: false, tier: 1 }
],
corsProxies: [
{ name: 'AllOrigins', url: 'https://api.allorigins.win', testEndpoint: '/get?url=https://api.coingecko.com/api/v3/ping', requiresKey: false, tier: 3 },
{ name: 'CORS.SH', url: 'https://proxy.cors.sh', testEndpoint: '/https://api.coingecko.com/api/v3/ping', requiresKey: false, tier: 3 },
{ name: 'Corsfix', url: 'https://proxy.corsfix.com', testEndpoint: '/?url=https://api.coingecko.com/api/v3/ping', requiresKey: false, tier: 3 },
{ name: 'ThingProxy', url: 'https://thingproxy.freeboard.io', testEndpoint: '/fetch/https://api.coingecko.com/api/v3/ping', requiresKey: false, tier: 3 }
]
};
// ═══════════════════════════════════════════════════════════════
// RESOURCE MONITOR CLASS
// ═══════════════════════════════════════════════════════════════
class CryptoAPIMonitor {
constructor() {
this.apiKeys = {};
this.resourceStatus = {};
this.metrics = {
totalChecks: 0,
successfulChecks: 0,
failedChecks: 0,
totalResponseTime: 0
};
this.history = {};
this.alerts = [];
}
// Load API keys from registry
loadRegistry() {
try {
const data = fs.readFileSync(CONFIG.REGISTRY_FILE, 'utf8');
const registry = JSON.parse(data);
this.apiKeys = registry.discovered_keys || {};
console.log('βœ“ Registry loaded successfully');
console.log(` Found ${Object.keys(this.apiKeys).length} API key categories`);
return true;
} catch (error) {
console.error('βœ— Failed to load registry:', error.message);
return false;
}
}
// Get API key for resource
getApiKey(keyName, keyIndex = 0) {
if (!keyName || !this.apiKeys[keyName]) return null;
const keys = this.apiKeys[keyName];
return Array.isArray(keys) ? keys[keyIndex] : keys;
}
// Mask API key for display
maskKey(key) {
if (!key || key.length < 8) return '****';
return key.substring(0, 4) + '****' + key.substring(key.length - 4);
}
// HTTP request with timeout
makeRequest(url, options = {}) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const protocol = url.startsWith('https') ? https : http;
const req = protocol.request(url, {
method: options.method || 'GET',
headers: options.headers || {},
timeout: CONFIG.TIMEOUT
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
const responseTime = Date.now() - startTime;
resolve({
statusCode: res.statusCode,
data: data,
responseTime: responseTime,
success: res.statusCode >= 200 && res.statusCode < 300
});
});
});
req.on('error', (error) => {
reject({
error: error.message,
responseTime: Date.now() - startTime,
success: false
});
});
req.on('timeout', () => {
req.destroy();
reject({
error: 'Request timeout',
responseTime: CONFIG.TIMEOUT,
success: false
});
});
if (options.body) {
req.write(options.body);
}
req.end();
});
}
// Check single API endpoint
async checkEndpoint(resource) {
const startTime = Date.now();
try {
// Build URL
let url = resource.url + (resource.testEndpoint || '');
// Replace API key placeholder
if (resource.keyName) {
const apiKey = this.getApiKey(resource.keyName, resource.keyIndex || 0);
if (apiKey) {
url = url.replace('{{KEY}}', apiKey);
}
}
// Prepare headers
const headers = {
'User-Agent': 'CryptoAPIMonitor/1.0'
};
// Add API key to header if needed
if (resource.headerKey && resource.keyName) {
const apiKey = this.getApiKey(resource.keyName, resource.keyIndex || 0);
if (apiKey) {
headers[resource.headerKey] = apiKey;
}
}
// RPC specific test
let options = { method: resource.method || 'GET', headers };
if (resource.rpcTest) {
options.method = 'POST';
options.headers['Content-Type'] = 'application/json';
options.body = JSON.stringify({
jsonrpc: '2.0',
method: 'eth_blockNumber',
params: [],
id: 1
});
}
// Make request
const result = await this.makeRequest(url, options);
return {
name: resource.name,
url: resource.url,
success: result.success,
statusCode: result.statusCode,
responseTime: result.responseTime,
timestamp: new Date().toISOString(),
tier: resource.tier || 4
};
} catch (error) {
return {
name: resource.name,
url: resource.url,
success: false,
error: error.error || error.message,
responseTime: error.responseTime || Date.now() - startTime,
timestamp: new Date().toISOString(),
tier: resource.tier || 4
};
}
}
// Classify status based on metrics
classifyStatus(resource) {
if (!this.history[resource.name]) {
return 'UNKNOWN';
}
const hist = this.history[resource.name];
const recentChecks = hist.slice(-10); // Last 10 checks
if (recentChecks.length === 0) return 'UNKNOWN';
const successCount = recentChecks.filter(c => c.success).length;
const successRate = successCount / recentChecks.length;
const avgResponseTime = recentChecks
.filter(c => c.success)
.reduce((sum, c) => sum + c.responseTime, 0) / (successCount || 1);
if (successRate >= CONFIG.THRESHOLDS.ONLINE.successRate &&
avgResponseTime < CONFIG.THRESHOLDS.ONLINE.responseTime) {
return 'ONLINE';
} else if (successRate >= CONFIG.THRESHOLDS.DEGRADED.successRate &&
avgResponseTime < CONFIG.THRESHOLDS.DEGRADED.responseTime) {
return 'DEGRADED';
} else if (successRate >= CONFIG.THRESHOLDS.SLOW.successRate &&
avgResponseTime < CONFIG.THRESHOLDS.SLOW.responseTime) {
return 'SLOW';
} else if (successRate >= CONFIG.THRESHOLDS.UNSTABLE.successRate) {
return 'UNSTABLE';
} else {
return 'OFFLINE';
}
}
// Update history for resource
updateHistory(resource, result) {
if (!this.history[resource.name]) {
this.history[resource.name] = [];
}
this.history[resource.name].push(result);
// Keep only last 100 checks
if (this.history[resource.name].length > 100) {
this.history[resource.name] = this.history[resource.name].slice(-100);
}
}
// Check all resources in a category
async checkCategory(categoryName, resources) {
console.log(`\n Checking ${categoryName}...`);
const results = [];
if (Array.isArray(resources)) {
for (const resource of resources) {
const result = await this.checkEndpoint(resource);
this.updateHistory(resource, result);
results.push(result);
// Rate limiting delay
await new Promise(resolve => setTimeout(resolve, 200));
}
} else {
// Handle nested categories
for (const [subCategory, subResources] of Object.entries(resources)) {
for (const resource of subResources) {
const result = await this.checkEndpoint(resource);
this.updateHistory(resource, result);
results.push(result);
await new Promise(resolve => setTimeout(resolve, 200));
}
}
}
return results;
}
// Run complete monitoring cycle
async runMonitoringCycle() {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('β•‘ CRYPTOCURRENCY API RESOURCE MONITOR - Health Check β•‘');
console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•');
console.log(` Timestamp: ${new Date().toISOString()}`);
const cycleResults = {};
for (const [category, resources] of Object.entries(API_REGISTRY)) {
const results = await this.checkCategory(category, resources);
cycleResults[category] = results;
}
this.generateReport(cycleResults);
this.checkAlertConditions(cycleResults);
return cycleResults;
}
// Generate status report
generateReport(cycleResults) {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('β•‘ RESOURCE STATUS REPORT β•‘');
console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n');
let totalResources = 0;
let onlineCount = 0;
let degradedCount = 0;
let offlineCount = 0;
for (const [category, results] of Object.entries(cycleResults)) {
console.log(`\nπŸ“ ${category.toUpperCase()}`);
console.log('─'.repeat(60));
for (const result of results) {
totalResources++;
const status = this.classifyStatus(result);
let statusSymbol = '●';
let statusColor = '';
switch (status) {
case 'ONLINE':
statusSymbol = 'βœ“';
onlineCount++;
break;
case 'DEGRADED':
case 'SLOW':
statusSymbol = '◐';
degradedCount++;
break;
case 'OFFLINE':
case 'UNSTABLE':
statusSymbol = 'βœ—';
offlineCount++;
break;
}
const rt = result.responseTime ? `${result.responseTime}ms` : 'N/A';
const tierBadge = result.tier === 1 ? '[TIER-1]' : result.tier === 2 ? '[TIER-2]' : '';
console.log(` ${statusSymbol} ${result.name.padEnd(25)} ${status.padEnd(10)} ${rt.padStart(8)} ${tierBadge}`);
}
}
// Summary
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('β•‘ SUMMARY β•‘');
console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•');
console.log(` Total Resources: ${totalResources}`);
console.log(` Online: ${onlineCount} (${((onlineCount/totalResources)*100).toFixed(1)}%)`);
console.log(` Degraded: ${degradedCount} (${((degradedCount/totalResources)*100).toFixed(1)}%)`);
console.log(` Offline: ${offlineCount} (${((offlineCount/totalResources)*100).toFixed(1)}%)`);
console.log(` Overall Health: ${((onlineCount/totalResources)*100).toFixed(1)}%`);
}
// Check for alert conditions
checkAlertConditions(cycleResults) {
const newAlerts = [];
// Check TIER-1 APIs
for (const [category, results] of Object.entries(cycleResults)) {
for (const result of results) {
if (result.tier === 1 && !result.success) {
newAlerts.push({
severity: 'CRITICAL',
message: `TIER-1 API offline: ${result.name}`,
timestamp: new Date().toISOString()
});
}
if (result.responseTime > 5000) {
newAlerts.push({
severity: 'WARNING',
message: `Elevated response time: ${result.name} (${result.responseTime}ms)`,
timestamp: new Date().toISOString()
});
}
}
}
if (newAlerts.length > 0) {
console.log('\n╔════════════════════════════════════════════════════════╗');
console.log('β•‘ ⚠️ ALERTS β•‘');
console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•');
for (const alert of newAlerts) {
console.log(` [${alert.severity}] ${alert.message}`);
}
this.alerts.push(...newAlerts);
}
}
// Generate JSON report
exportReport(filename = 'api-monitor-report.json') {
const report = {
timestamp: new Date().toISOString(),
summary: {
totalResources: 0,
onlineResources: 0,
degradedResources: 0,
offlineResources: 0
},
categories: {},
alerts: this.alerts.slice(-50), // Last 50 alerts
history: this.history
};
// Calculate summary
for (const [category, resources] of Object.entries(API_REGISTRY)) {
report.categories[category] = [];
const flatResources = this.flattenResources(resources);
for (const resource of flatResources) {
const status = this.classifyStatus(resource);
const lastCheck = this.history[resource.name] ?
this.history[resource.name].slice(-1)[0] : null;
report.summary.totalResources++;
if (status === 'ONLINE') report.summary.onlineResources++;
else if (status === 'DEGRADED' || status === 'SLOW') report.summary.degradedResources++;
else if (status === 'OFFLINE' || status === 'UNSTABLE') report.summary.offlineResources++;
report.categories[category].push({
name: resource.name,
url: resource.url,
status: status,
tier: resource.tier,
lastCheck: lastCheck
});
}
}
fs.writeFileSync(filename, JSON.stringify(report, null, 2));
console.log(`\nβœ“ Report exported to ${filename}`);
return report;
}
// Flatten nested resources
flattenResources(resources) {
if (Array.isArray(resources)) {
return resources;
}
const flattened = [];
for (const subResources of Object.values(resources)) {
flattened.push(...subResources);
}
return flattened;
}
}
// ═══════════════════════════════════════════════════════════════
// MAIN EXECUTION
// ═══════════════════════════════════════════════════════════════
async function main() {
const monitor = new CryptoAPIMonitor();
// Load registry
if (!monitor.loadRegistry()) {
console.error('Failed to initialize monitor');
process.exit(1);
}
// Run initial check
console.log('\nπŸš€ Starting initial health check...');
await monitor.runMonitoringCycle();
// Export report
monitor.exportReport();
// Continuous monitoring mode
if (process.argv.includes('--continuous')) {
console.log(`\n♾️ Continuous monitoring enabled (interval: ${CONFIG.CHECK_INTERVAL/1000}s)`);
setInterval(async () => {
await monitor.runMonitoringCycle();
monitor.exportReport();
}, CONFIG.CHECK_INTERVAL);
} else {
console.log('\nβœ“ Monitoring cycle complete');
console.log(' Use --continuous flag for continuous monitoring');
}
}
// Run if executed directly
if (require.main === module) {
main().catch(console.error);
}
module.exports = CryptoAPIMonitor;