Spaces:
Paused
Paused
| /** | |
| * Real Data Fetcher | |
| * Fetches real cryptocurrency data from multiple providers with intelligent fallback | |
| * Uses crypto_resources_unified with 200+ endpoints | |
| */ | |
| import { API_REGISTRY, getTotalEndpointsCount } from './api-registry.js'; | |
| export class RealDataFetcher { | |
| constructor() { | |
| this.failedProviders = new Map(); | |
| this.providerStats = new Map(); | |
| this.cache = new Map(); | |
| } | |
| /** | |
| * Fetch market data with provider fallback | |
| */ | |
| async fetchMarketData(limit = 50) { | |
| const providers = [ | |
| { name: 'CoinGecko', fetcher: () => this.fetchFromCoinGecko(limit) }, | |
| { name: 'Binance', fetcher: () => this.fetchFromBinance(limit) }, | |
| { name: 'CoinMarketCap', fetcher: () => this.fetchFromCoinMarketCap(limit) } | |
| ]; | |
| return this.tryProviders(providers, 'market_data'); | |
| } | |
| /** | |
| * Fetch trending coins | |
| */ | |
| async fetchTrendingCoins() { | |
| const providers = [ | |
| { name: 'CoinGecko Trending', fetcher: () => this.fetchCoinGeckoTrending() }, | |
| { name: 'CoinCap Top', fetcher: () => this.fetchCoinCapTop() } | |
| ]; | |
| return this.tryProviders(providers, 'trending'); | |
| } | |
| /** | |
| * Fetch sentiment data | |
| */ | |
| async fetchSentimentData(timeframe = '1D') { | |
| const providers = [ | |
| { name: 'Fear & Greed', fetcher: () => this.fetchFearGreedIndex() }, | |
| { name: 'LunarCrush', fetcher: () => this.fetchLunarCrushSentiment() } | |
| ]; | |
| return this.tryProviders(providers, 'sentiment'); | |
| } | |
| /** | |
| * Fetch on-chain analytics | |
| */ | |
| async fetchOnChainAnalytics() { | |
| const providers = [ | |
| { name: 'Glassnode', fetcher: () => this.fetchGlassnodeData() }, | |
| { name: 'Covalent', fetcher: () => this.fetchCovalentData() } | |
| ]; | |
| return this.tryProviders(providers, 'onchain'); | |
| } | |
| /** | |
| * Fetch latest news | |
| */ | |
| async fetchLatestNews(query = 'cryptocurrency') { | |
| const providers = [ | |
| { name: 'NewsAPI', fetcher: () => this.fetchNewsAPI(query) }, | |
| { name: 'CryptoPanic', fetcher: () => this.fetchCryptoPanic() } | |
| ]; | |
| return this.tryProviders(providers, 'news'); | |
| } | |
| /** | |
| * Try multiple providers with fallback | |
| */ | |
| async tryProviders(providers, category) { | |
| for (const provider of providers) { | |
| try { | |
| console.log(`[RealDataFetcher] Trying ${provider.name}...`); | |
| const data = await provider.fetcher(); | |
| if (data) { | |
| console.log(`[RealDataFetcher] ✅ ${provider.name} succeeded`); | |
| this.recordProviderSuccess(provider.name); | |
| return data; | |
| } | |
| } catch (error) { | |
| console.warn(`[RealDataFetcher] ❌ ${provider.name} failed:`, error.message); | |
| this.recordProviderFailure(provider.name); | |
| } | |
| } | |
| console.error('[RealDataFetcher] All providers failed for', category); | |
| return null; | |
| } | |
| /** | |
| * ======================================================================== | |
| * COINGECKO ENDPOINTS | |
| * ======================================================================== | |
| */ | |
| async fetchFromCoinGecko(limit = 50) { | |
| try { | |
| const url = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=${Math.min(limit, 250)}&sparkline=true&price_change_percentage=7d`; | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| const data = await response.json(); | |
| return { | |
| coins: data.map(coin => ({ | |
| rank: coin.market_cap_rank, | |
| name: coin.name, | |
| symbol: coin.symbol.toUpperCase(), | |
| price: coin.current_price, | |
| volume_24h: coin.total_volume, | |
| market_cap: coin.market_cap, | |
| change_24h: coin.price_change_percentage_24h, | |
| change_7d: coin.price_change_percentage_7d_in_currency, | |
| image: coin.image | |
| })), | |
| timestamp: new Date().toISOString(), | |
| source: 'coingecko' | |
| }; | |
| } catch (error) { | |
| console.error('[CoinGecko] Error:', error); | |
| throw error; | |
| } | |
| } | |
| async fetchCoinGeckoTrending() { | |
| try { | |
| const url = 'https://api.coingecko.com/api/v3/search/trending'; | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| const data = await response.json(); | |
| return { | |
| coins: data.coins.slice(0, 10).map((item, i) => ({ | |
| rank: i + 1, | |
| name: item.item.name, | |
| symbol: item.item.symbol.toUpperCase(), | |
| price: item.item.data.price, | |
| market_cap: item.item.data.market_cap, | |
| change_24h: item.item.data.price_change_percentage_24h, | |
| image: item.item.large | |
| })), | |
| source: 'coingecko_trending' | |
| }; | |
| } catch (error) { | |
| console.error('[CoinGecko Trending] Error:', error); | |
| throw error; | |
| } | |
| } | |
| async fetchGlobalMarketData() { | |
| try { | |
| const url = 'https://api.coingecko.com/api/v3/global'; | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| const data = await response.json(); | |
| return { | |
| total_market_cap: data.data.total_market_cap.usd, | |
| total_volume: data.data.total_24h_vol.usd, | |
| btc_dominance: data.data.btc_dominance, | |
| active_cryptocurrencies: data.data.active_cryptocurrencies | |
| }; | |
| } catch (error) { | |
| console.error('[CoinGecko Global] Error:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * ======================================================================== | |
| * BINANCE ENDPOINTS | |
| * ======================================================================== | |
| */ | |
| async fetchFromBinance(limit = 50) { | |
| try { | |
| const url = 'https://api.binance.com/api/v3/ticker/24hr'; | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| const data = await response.json(); | |
| // Filter to top trading pairs | |
| return { | |
| coins: data.slice(0, limit).map((ticker, i) => ({ | |
| rank: i + 1, | |
| symbol: ticker.symbol.replace('USDT', ''), | |
| price: parseFloat(ticker.lastPrice), | |
| volume_24h: parseFloat(ticker.volume), | |
| change_24h: parseFloat(ticker.priceChangePercent) | |
| })), | |
| source: 'binance' | |
| }; | |
| } catch (error) { | |
| console.error('[Binance] Error:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * ======================================================================== | |
| * COINMARKETCAP ENDPOINTS | |
| * ======================================================================== | |
| */ | |
| async fetchFromCoinMarketCap(limit = 50) { | |
| try { | |
| // Note: This requires a CMC API key | |
| const key = API_REGISTRY.market.coinmarketcap.key; | |
| if (!key) throw new Error('CoinMarketCap key not configured'); | |
| const url = `https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?limit=${limit}&convert=USD`; | |
| const response = await fetch(url, { | |
| headers: { | |
| 'X-CMC_PRO_API_KEY': key | |
| } | |
| }); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| const data = await response.json(); | |
| return { | |
| coins: data.data.map((coin, i) => ({ | |
| rank: coin.cmc_rank, | |
| name: coin.name, | |
| symbol: coin.symbol, | |
| price: coin.quote.USD.price, | |
| volume_24h: coin.quote.USD.volume_24h, | |
| market_cap: coin.quote.USD.market_cap, | |
| change_24h: coin.quote.USD.percent_change_24h | |
| })), | |
| source: 'coinmarketcap' | |
| }; | |
| } catch (error) { | |
| console.error('[CoinMarketCap] Error:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * ======================================================================== | |
| * COINCAP ENDPOINTS | |
| * ======================================================================== | |
| */ | |
| async fetchCoinCapTop() { | |
| try { | |
| const url = 'https://api.coincap.io/v2/assets?limit=50'; | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| const data = await response.json(); | |
| return { | |
| coins: data.data.map((coin, i) => ({ | |
| rank: parseInt(coin.rank), | |
| name: coin.name, | |
| symbol: coin.symbol, | |
| price: parseFloat(coin.priceUsd), | |
| volume_24h: parseFloat(coin.volumeUsd24Hr), | |
| market_cap: parseFloat(coin.marketCapUsd), | |
| change_24h: parseFloat(coin.changePercent24Hr) | |
| })), | |
| source: 'coincap' | |
| }; | |
| } catch (error) { | |
| console.error('[CoinCap] Error:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * ======================================================================== | |
| * SENTIMENT ENDPOINTS | |
| * ======================================================================== | |
| */ | |
| async fetchFearGreedIndex() { | |
| try { | |
| const url = 'https://api.alternative.me/fng/?limit=30'; | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| const data = await response.json(); | |
| return { | |
| current: data.data[0], | |
| history: data.data, | |
| source: 'fear_greed' | |
| }; | |
| } catch (error) { | |
| console.error('[Fear & Greed] Error:', error); | |
| throw error; | |
| } | |
| } | |
| async fetchLunarCrushSentiment() { | |
| try { | |
| // This would need a real LunarCrush API key | |
| throw new Error('LunarCrush requires API key'); | |
| } catch (error) { | |
| console.error('[LunarCrush] Error:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * ======================================================================== | |
| * ON-CHAIN ANALYTICS ENDPOINTS | |
| * ======================================================================== | |
| */ | |
| async fetchGlassnodeData() { | |
| try { | |
| // Glassnode requires API key | |
| throw new Error('Glassnode requires API key'); | |
| } catch (error) { | |
| console.error('[Glassnode] Error:', error); | |
| throw error; | |
| } | |
| } | |
| async fetchCovalentData() { | |
| try { | |
| // Covalent requires API key | |
| throw new Error('Covalent requires API key'); | |
| } catch (error) { | |
| console.error('[Covalent] Error:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * ======================================================================== | |
| * NEWS ENDPOINTS | |
| * ======================================================================== | |
| */ | |
| async fetchNewsAPI(query = 'cryptocurrency') { | |
| try { | |
| const key = 'pub_346789abc123def456789ghi012345jkl'; | |
| const url = `https://newsapi.org/v2/everything?q=${query}&sortBy=publishedAt&language=en&pageSize=50&apiKey=${key}`; | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| const data = await response.json(); | |
| return { | |
| articles: data.articles.slice(0, 50).map(article => ({ | |
| title: article.title, | |
| description: article.description, | |
| url: article.url, | |
| source: article.source.name, | |
| published_at: article.publishedAt, | |
| image: article.urlToImage | |
| })), | |
| source: 'newsapi' | |
| }; | |
| } catch (error) { | |
| console.error('[NewsAPI] Error:', error); | |
| throw error; | |
| } | |
| } | |
| async fetchCryptoPanic() { | |
| try { | |
| const url = 'https://cryptopanic.com/api/v1/posts/?auth_token=optional&limit=50'; | |
| const response = await fetch(url); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| const data = await response.json(); | |
| return { | |
| articles: data.results.slice(0, 50).map(article => ({ | |
| title: article.title, | |
| url: article.link, | |
| source: article.source.title, | |
| kind: article.kind, | |
| published_at: article.published_at | |
| })), | |
| source: 'cryptopanic' | |
| }; | |
| } catch (error) { | |
| console.error('[CryptoPanic] Error:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * ======================================================================== | |
| * PROVIDER STATISTICS | |
| * ======================================================================== | |
| */ | |
| recordProviderSuccess(providerName) { | |
| const stats = this.providerStats.get(providerName) || { success: 0, failures: 0 }; | |
| stats.success++; | |
| this.providerStats.set(providerName, stats); | |
| // Reset failure count | |
| this.failedProviders.delete(providerName); | |
| } | |
| recordProviderFailure(providerName) { | |
| const stats = this.providerStats.get(providerName) || { success: 0, failures: 0 }; | |
| stats.failures++; | |
| this.providerStats.set(providerName, stats); | |
| // Mark as failed if too many failures | |
| const failures = (this.failedProviders.get(providerName) || 0) + 1; | |
| this.failedProviders.set(providerName, failures); | |
| } | |
| getProviderStats() { | |
| return Object.fromEntries(this.providerStats); | |
| } | |
| getTotalEndpoints() { | |
| return getTotalEndpointsCount(); | |
| } | |
| } | |
| export const realDataFetcher = new RealDataFetcher(); | |
| export default realDataFetcher; | |