| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>RugCharts — Token Intelligence</title> |
| <style> |
| :root { |
| --bg: #0a0a0f; --panel: #12121a; --border: #1e1e2e; |
| --text: #e0e0e0; --dim: #8888aa; --accent: #7c3aed; |
| --green: #00ff88; --yellow: #ffd700; --orange: #ff8800; --red: #ff0044; |
| --safe: #00ff88; --low: #88ff00; --medium: #ffd700; --high: #ff8800; --critical: #ff0044; |
| } |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; min-height: 100vh; } |
| header { background: var(--panel); border-bottom: 1px solid var(--border); padding: 16px 24px; display: flex; align-items: center; gap: 16px; position: sticky; top: 0; z-index: 100; } |
| .logo { font-size: 22px; font-weight: 800; letter-spacing: -0.5px; } |
| .logo span:first-child { color: var(--accent); } .logo span:last-child { color: var(--text); } |
| .search-bar { flex: 1; max-width: 600px; display: flex; gap: 8px; } |
| .search-bar input { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 10px 16px; color: var(--text); font-size: 14px; outline: none; } |
| .search-bar input:focus { border-color: var(--accent); } |
| .search-bar select { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; color: var(--text); font-size: 14px; } |
| .search-bar button { background: var(--accent); color: white; border: none; border-radius: 8px; padding: 10px 24px; font-size: 14px; font-weight: 600; cursor: pointer; } |
| .search-bar button:hover { opacity: 0.9; } |
| .stats-bar { background: var(--panel); border-bottom: 1px solid var(--border); padding: 12px 24px; display: flex; gap: 24px; font-size: 13px; color: var(--dim); } |
| .stats-bar b { color: var(--text); } |
| .container { max-width: 1400px; margin: 0 auto; padding: 24px; } |
| .verdict { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 24px; margin-bottom: 24px; display: none; } |
| .verdict.active { display: block; } |
| .verdict-header { display: flex; align-items: center; gap: 16px; margin-bottom: 12px; } |
| .risk-badge { padding: 6px 16px; border-radius: 20px; font-weight: 700; font-size: 14px; color: #000; } |
| .risk-badge.SAFE { background: var(--safe); } |
| .risk-badge.LOW { background: var(--low); } |
| .risk-badge.MEDIUM { background: var(--medium); } |
| .risk-badge.HIGH { background: var(--orange); } |
| .risk-badge.CRITICAL,.risk-badge.DANGER { background: var(--red); color: #fff; } |
| .risk-score { font-size: 48px; font-weight: 800; line-height: 1; } |
| .verdict-text { font-size: 16px; color: var(--dim); margin-top: 4px; } |
| .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(420px, 1fr)); gap: 20px; } |
| .panel { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 20px; } |
| .panel-title { font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: var(--dim); margin-bottom: 16px; display: flex; align-items: center; gap: 8px; } |
| .panel-title .icon { font-size: 18px; } |
| .metric-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.04); font-size: 13px; } |
| .metric-row:last-child { border-bottom: none; } |
| .metric-label { color: var(--dim); } |
| .metric-value { font-weight: 600; } |
| .metric-value.green { color: var(--green); } |
| .metric-value.yellow { color: var(--yellow); } |
| .metric-value.orange { color: var(--orange); } |
| .metric-value.red { color: var(--red); } |
| .tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; margin: 2px; } |
| .tag.pass { background: rgba(0,255,136,0.15); color: var(--green); } |
| .tag.fail { background: rgba(255,0,68,0.15); color: var(--red); } |
| .tag.warn { background: rgba(255,136,0,0.15); color: var(--orange); } |
| .entity-badge { background: rgba(124,58,237,0.15); color: var(--accent); padding: 4px 12px; border-radius: 6px; font-size: 13px; font-weight: 600; } |
| .loading { text-align: center; padding: 40px; color: var(--dim); } |
| .spinner { display: inline-block; width: 24px; height: 24px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.6s linear infinite; } |
| @keyframes spin { to { transform: rotate(360deg); } } |
| .empty { color: var(--dim); font-size: 13px; padding: 20px; text-align: center; } |
| .progress-bar { height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; margin-top: 4px; } |
| .progress-fill { height: 100%; border-radius: 3px; transition: width 0.3s; } |
| .wallet-list { max-height: 200px; overflow-y: auto; } |
| .wallet-item { display: flex; justify-content: space-between; padding: 6px 0; font-size: 12px; border-bottom: 1px solid rgba(255,255,255,0.03); font-family: monospace; } |
| .launch-feed { max-height: 300px; overflow-y: auto; } |
| .launch-item { padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.04); font-size: 12px; display: flex; justify-content: space-between; align-items: center; } |
| footer { text-align: center; padding: 24px; color: var(--dim); font-size: 12px; } |
| footer span { color: var(--accent); } |
| .error-panel { background: rgba(255,0,68,0.1); border: 1px solid var(--red); border-radius: 8px; padding: 12px; color: var(--red); font-size: 13px; display: none; } |
| </style> |
| </head> |
| <body> |
|
|
| <header> |
| <div class="logo"><span>Rug</span><span>Charts</span></div> |
| <nav style="display:flex;gap:4px"> |
| <a href="/rugcharts" style="color:var(--text);text-decoration:none;padding:6px 14px;border-radius:6px;font-size:13px;font-weight:500;background:rgba(255,255,255,0.06)">RugCharts</a> |
| <a href="/rugmaps" style="color:var(--dim);text-decoration:none;padding:6px 14px;border-radius:6px;font-size:13px;font-weight:500">RugMaps</a> |
| <a href="/news" style="color:var(--dim);text-decoration:none;padding:6px 14px;border-radius:6px;font-size:13px;font-weight:500">News</a> |
| </nav> |
| <div class="search-bar"> |
| <input id="tokenInput" type="text" placeholder="Paste token address (Solana or EVM)..." autofocus> |
| <select id="chainSelect"> |
| <option value="solana">Solana</option> |
| <option value="ethereum">Ethereum</option> |
| <option value="base">Base</option> |
| <option value="bsc">BSC</option> |
| <option value="arbitrum">Arbitrum</option> |
| <option value="polygon">Polygon</option> |
| </select> |
| <button onclick="scanToken()">Scan</button> |
| </div> |
| </header> |
|
|
| <div class="stats-bar" id="statsBar"> |
| <span>DataBus: <b id="chainCount">...</b> chains</span> |
| <span>Cache: <b id="cacheStatus">checking...</b></span> |
| <span>Powered by <b>Arkham · Helius · GoPlus · RAG</b></span> |
| </div> |
|
|
| <div class="container"> |
| <div class="error-panel" id="errorPanel"></div> |
|
|
| |
| <div class="verdict" id="verdictBanner"> |
| <div class="verdict-header"> |
| <div class="risk-score" id="riskScore">--</div> |
| <div> |
| <div class="risk-badge" id="riskBadge">SCAN A TOKEN</div> |
| <div class="verdict-text" id="verdictText"></div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="grid" id="mainGrid"> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">🛡️</span> Security Scan</div> |
| <div id="securityPanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">👥</span> Holder Health</div> |
| <div id="holderPanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">💧</span> Liquidity Risk</div> |
| <div id="liquidityPanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">📊</span> Volume Authenticity</div> |
| <div id="volumePanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">🔍</span> Rug Patterns</div> |
| <div id="rugPanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">👤</span> Developer Reputation</div> |
| <div id="devPanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">🔗</span> Cross-Chain Entity</div> |
| <div id="entityPanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">🐋</span> Whale Alerts</div> |
| <div id="whalePanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">🕵️</span> Insider Detection</div> |
| <div id="insiderPanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">🚀</span> Token Launches (Live)</div> |
| <div id="launchPanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| <div class="panel"> |
| <div class="panel-title"><span class="icon">💰</span> Smart Money</div> |
| <div id="smartMoneyPanel" class="loading"><div class="spinner"></div></div> |
| </div> |
| </div> |
| </div> |
|
|
| <footer>RugCharts · 49 DataBus Chains · <span>No one else shows this data</span></footer> |
|
|
| <script> |
| const API = '/api/v1/databus'; |
| |
| async function apiGet(path) { |
| try { const r = await fetch(API + path); return r.ok ? r.json() : null; } |
| catch(e) { return null; } |
| } |
| |
| function riskColor(level) { |
| const m = {SAFE:'var(--green)',LOW:'var(--low)','LOW RISK':'var(--low)', |
| MEDIUM:'var(--yellow)','MEDIUM RISK':'var(--yellow)', |
| HIGH:'var(--orange)','HIGH RISK':'var(--orange)', |
| CRITICAL:'var(--red)',DANGER:'var(--red)',EXTREME:'var(--red)','NO_HOLDERS':'var(--dim)'}; |
| return m[level] || 'var(--dim)'; |
| } |
| |
| function riskBadgeClass(level) { |
| const m = {SAFE:'SAFE',LOW:'LOW','LOW RISK':'LOW',MEDIUM:'MEDIUM','MEDIUM RISK':'MEDIUM', |
| HIGH:'HIGH','HIGH RISK':'HIGH',CRITICAL:'CRITICAL',DANGER:'CRITICAL'}; |
| return m[level] || 'MEDIUM'; |
| } |
| |
| function renderSecurity(data) { |
| if (!data) return '<div class="empty">No security data available</div>'; |
| let html = ''; |
| html += `<div class="metric-row"><span class="metric-label">Risk Score</span><span class="metric-value" style="color:${riskColor(data.band)}">${data.score || '--'}/100</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Risk Band</span><span class="metric-value" style="color:${riskColor(data.band)}">${data.band || '--'}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Checks Run</span><span class="metric-value">${data.checks_run || 0}</span></div>`; |
| if (data.highlights && data.highlights.length) { |
| html += '<div style="margin-top:12px">'; |
| data.highlights.forEach(h => { |
| const cls = h.startsWith('FAIL') ? 'fail' : 'warn'; |
| html += `<span class="tag ${cls}">${h}</span> `; |
| }); |
| html += '</div>'; |
| } |
| return html; |
| } |
| |
| function renderHolders(data) { |
| if (!data) return '<div class="empty">No holder data</div>'; |
| let html = ''; |
| html += `<div class="metric-row"><span class="metric-label">Total Holders</span><span class="metric-value">${(data.total||0).toLocaleString()}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Gini Coefficient</span><span class="metric-value" style="color:${(data.gini||0)>0.6?'var(--red)':(data.gini||0)>0.4?'var(--yellow)':'var(--green)'}">${(data.gini||0).toFixed(3)}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Top 10 Concentration</span><span class="metric-value">${data.top10_pct||0}%</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Top Wallet Dominance</span><span class="metric-value" style="color:${(data.top1_pct||0)>50?'var(--red)':'var(--dim)'}">${data.top1_pct||0}%</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Whales (>1%)</span><span class="metric-value">${data.whales||0}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Decentralization</span><span class="metric-value" style="color:${(data.decentralization||0)>60?'var(--green)':(data.decentralization||0)>30?'var(--yellow)':'var(--red)'}">${data.decentralization||0}/100</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Risk</span><span class="metric-value" style="color:${riskColor(data.risk)}">${data.risk||'--'}</span></div>`; |
| return html; |
| } |
| |
| function renderLiquidity(data) { |
| if (!data) return '<div class="empty">No liquidity data</div>'; |
| let html = ''; |
| html += `<div class="metric-row"><span class="metric-label">Concentration Risk</span><span class="metric-value" style="color:${riskColor(data.concentration_risk?.toUpperCase())}">${data.concentration_risk||'unknown'}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Risk Score</span><span class="metric-value" style="color:${riskColor(data.risk_level)}">${data.risk_score||50}/100</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">LP Holders</span><span class="metric-value">${data.lp_holders||0}</span></div>`; |
| if (data.lp_holders_list && data.lp_holders_list.length) { |
| html += '<div style="margin-top:8px;font-size:11px;color:var(--dim)">Top LP Holders:</div>'; |
| data.lp_holders_list.slice(0,3).forEach(h => { |
| html += `<div class="wallet-item"><span>${h.address||'...'}</span><span>${h.share_pct||0}% ${h.entity ? '· '+h.entity : ''}</span></div>`; |
| }); |
| } |
| return html; |
| } |
| |
| function renderVolume(data) { |
| if (!data) return '<div class="empty">No volume data</div>'; |
| let html = ''; |
| const fake = data.fake_volume_pct || 0; |
| const auth = data.authentic_score || 100; |
| const fc = fake > 50 ? 'var(--red)' : fake > 20 ? 'var(--yellow)' : 'var(--green)'; |
| html += `<div class="metric-row"><span class="metric-label">Fake Volume</span><span class="metric-value" style="color:${fc}">${fake}%</span></div>`; |
| html += `<div class="progress-bar"><div class="progress-fill" style="width:${fake}%;background:${fc}"></div></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Authentic Score</span><span class="metric-value" style="color:${auth>80?'var(--green)':auth>50?'var(--yellow)':'var(--red)'}">${auth}/100</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Risk Level</span><span class="metric-value" style="color:${riskColor(data.risk_level)}">${data.risk_level||'--'}</span></div>`; |
| return html; |
| } |
| |
| function renderRugPatterns(data) { |
| if (!data) return '<div class="empty">No pattern data</div>'; |
| let html = ''; |
| html += `<div class="metric-row"><span class="metric-label">Pattern Matches</span><span class="metric-value">${data.total_matches||0}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Overall Risk</span><span class="metric-value" style="color:${riskColor(data.overall_rug_risk||data.overall_risk)}">${data.overall_rug_risk||data.overall_risk||'--'}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Top Match</span><span class="metric-value">${data.top_match||'None'}</span></div>`; |
| if (data.top_score) html += `<div class="metric-row"><span class="metric-label">Match Score</span><span class="metric-value" style="color:${(data.top_score||0)>60?'var(--red)':'var(--yellow)'}">${data.top_score}/100</span></div>`; |
| if (data.pattern_matches && data.pattern_matches.length) { |
| data.pattern_matches.slice(0,3).forEach(m => { |
| html += `<div style="margin-top:4px;font-size:11px"><span class="tag ${m.severity==='CRITICAL'?'fail':'warn'}">${m.pattern_name}</span> ${m.match_score}%</div>`; |
| }); |
| } |
| return html; |
| } |
| |
| function renderDev(data) { |
| if (!data) return '<div class="empty">No developer data</div>'; |
| let html = ''; |
| html += `<div class="metric-row"><span class="metric-label">Tokens Deployed</span><span class="metric-value">${data.tokens_deployed||'unknown'}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Activity Span</span><span class="metric-value">${data.activity_span_days||0} days</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Risk Level</span><span class="metric-value" style="color:${riskColor(data.risk_level)}">${data.risk_level||'--'}</span></div>`; |
| if (data.known_entity) html += `<div class="metric-row"><span class="metric-label">Entity</span><span class="entity-badge">${data.known_entity}</span></div>`; |
| if (data.entity_type) html += `<div class="metric-row"><span class="metric-label">Type</span><span class="metric-value">${data.entity_type}</span></div>`; |
| return html; |
| } |
| |
| function renderEntity(data) { |
| if (!data || !data.entity) return '<div class="empty">No entity data (requires Arkham)</div>'; |
| let html = ''; |
| const e = data.entity; |
| if (e.entity_name) html += `<div class="entity-badge" style="font-size:16px;margin-bottom:8px">${e.entity_name}</div>`; |
| html += `<div class="metric-row"><span class="metric-label">Type</span><span class="metric-value">${e.entity_type||'--'}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Chain</span><span class="metric-value">${e.primary_chain||'--'}</span></div>`; |
| if (e.label) html += `<div class="metric-row"><span class="metric-label">Label</span><span class="metric-value">${e.label}</span></div>`; |
| if (e.is_contract) html += `<div class="metric-row"><span class="metric-label">Contract</span><span class="metric-value">Yes</span></div>`; |
| return html; |
| } |
| |
| function renderWhales(data) { |
| if (!data) return '<div class="empty">No whale activity</div>'; |
| let html = ''; |
| html += `<div class="metric-row"><span class="metric-label">Movements Detected</span><span class="metric-value">${data.total_detected||0}</span></div>`; |
| if (data.whale_movements && data.whale_movements.length) { |
| html += '<div class="wallet-list">'; |
| data.whale_movements.slice(0,8).forEach(w => { |
| html += `<div class="wallet-item"><span>${w.wallet||'...'}</span><span style="color:var(--orange)">${w.amount||0}</span></div>`; |
| }); |
| html += '</div>'; |
| } |
| return html; |
| } |
| |
| function renderInsider(data) { |
| if (!data) return '<div class="empty">No insider signals</div>'; |
| let html = ''; |
| html += `<div class="metric-row"><span class="metric-label">Signals Found</span><span class="metric-value">${data.total_signals||0}</span></div>`; |
| html += `<div class="metric-row"><span class="metric-label">Confidence</span><span class="metric-value" style="color:${data.confidence==='HIGH'?'var(--red)':'var(--yellow)'}">${data.confidence||'--'}</span></div>`; |
| if (data.insider_signals && data.insider_signals.length) { |
| data.insider_signals.slice(0,3).forEach(s => { |
| html += `<div style="margin-top:6px;font-size:11px"><span class="tag ${s.signal_strength==='HIGH'?'fail':'warn'}">${s.signal_strength}</span> ${s.spike_multiplier}x volume spike</div>`; |
| }); |
| } |
| return html; |
| } |
| |
| function renderLaunches(data) { |
| if (!data) return '<div class="empty">No launch data</div>'; |
| let html = ''; |
| const rd = data.risk_distribution || {}; |
| html += `<div class="metric-row"><span class="metric-label">New Tokens</span><span class="metric-value">${data.total_detected||0}</span></div>`; |
| if (rd.CRITICAL) html += `<div class="metric-row"><span class="metric-label">Critical Risk</span><span class="metric-value" style="color:var(--red)">${rd.CRITICAL}</span></div>`; |
| if (rd.HIGH) html += `<div class="metric-row"><span class="metric-label">High Risk</span><span class="metric-value" style="color:var(--orange)">${rd.HIGH}</span></div>`; |
| if (data.new_tokens && data.new_tokens.length) { |
| html += '<div class="launch-feed">'; |
| data.new_tokens.slice(0,10).forEach(t => { |
| html += `<div class="launch-item"><span style="font-family:monospace;font-size:11px">${t.signature||'...'}</span><span class="tag ${t.risk_level==='CRITICAL'?'fail':t.risk_level==='HIGH'?'warn':'pass'}">${t.risk_level} · ${t.age_minutes}m</span></div>`; |
| }); |
| html += '</div>'; |
| } |
| return html; |
| } |
| |
| function renderSmartMoney(data) { |
| if (!data) return '<div class="empty">No smart money data</div>'; |
| let html = ''; |
| html += `<div class="metric-row"><span class="metric-label">Tracked Wallets</span><span class="metric-value">${data.total_tracked||0}</span></div>`; |
| if (data.smart_money_wallets && data.smart_money_wallets.length) { |
| html += '<div class="wallet-list">'; |
| data.smart_money_wallets.slice(0,8).forEach(w => { |
| const label = w.entity ? ` · ${w.entity}` : ''; |
| html += `<div class="wallet-item"><span>${(w.address||'').slice(0,12)}...</span><span>$${(w.estimated_usd||0).toLocaleString()}${label}</span></div>`; |
| }); |
| html += '</div>'; |
| } |
| return html; |
| } |
| |
| async function scanToken() { |
| const addr = document.getElementById('tokenInput').value.trim(); |
| const chain = document.getElementById('chainSelect').value; |
| if (!addr) return; |
| |
| // Show loading |
| document.querySelectorAll('.panel > div:last-child').forEach(el => { |
| if (el.id && el.id.endsWith('Panel')) el.innerHTML = '<div class="spinner"></div>'; |
| }); |
| document.getElementById('verdictBanner').classList.remove('active'); |
| document.getElementById('errorPanel').style.display = 'none'; |
| |
| // Fire all endpoints in parallel |
| const token = encodeURIComponent(addr); |
| const [report, security, holders, liquidity, volume, rug, dev, entity, whales, insider, launches, smart] = |
| await Promise.all([ |
| apiGet(`/premium/token-report/${token}?chain=${chain}`), |
| apiGet(`/premium/security-scan/${token}?chain=${chain}`), |
| apiGet(`/premium/holder-health/${token}?chain=${chain}`), |
| apiGet(`/premium/liquidity-risk/${token}?chain=${chain}`), |
| apiGet(`/premium/volume-authenticity/${token}?chain=${chain}`), |
| apiGet(`/premium/rug-patterns/${token}?chain=${chain}`), |
| apiGet(`/premium/dev-reputation/${token}?chain=${chain}`), |
| apiGet(`/premium/cross-chain/${token}`), |
| apiGet(`/premium/whale-alerts/${token}?chain=${chain}`), |
| apiGet(`/premium/insider-detection/${token}?chain=${chain}`), |
| apiGet(`/premium/token-launches?chain=${chain}&limit=20`), |
| apiGet(`/premium/smart-money?chain=${chain}`), |
| ]); |
| |
| // Render verdict |
| const v = document.getElementById('verdictBanner'); |
| v.classList.add('active'); |
| const or = report?.overall_risk || {}; |
| document.getElementById('riskScore').textContent = or.score || '--'; |
| document.getElementById('riskScore').style.color = riskColor(or.level); |
| const badge = document.getElementById('riskBadge'); |
| badge.textContent = or.level || 'UNKNOWN'; |
| badge.className = 'risk-badge ' + riskBadgeClass(or.level); |
| document.getElementById('verdictText').textContent = report?.quick_verdict || ''; |
| |
| // Render panels |
| document.getElementById('securityPanel').innerHTML = renderSecurity(report?.sections?.security || security); |
| document.getElementById('holderPanel').innerHTML = renderHolders(report?.sections?.holders || holders); |
| document.getElementById('liquidityPanel').innerHTML = renderLiquidity(report?.sections?.liquidity || liquidity); |
| document.getElementById('volumePanel').innerHTML = renderVolume(report?.sections?.volume || volume); |
| document.getElementById('rugPanel').innerHTML = renderRugPatterns(report?.sections?.rug_patterns || rug); |
| document.getElementById('devPanel').innerHTML = renderDev(report?.sections?.developer || dev); |
| document.getElementById('entityPanel').innerHTML = renderEntity(report?.sections?.entity || entity); |
| document.getElementById('whalePanel').innerHTML = renderWhales(whales); |
| document.getElementById('insiderPanel').innerHTML = renderInsider(insider); |
| document.getElementById('launchPanel').innerHTML = renderLaunches(launches); |
| document.getElementById('smartMoneyPanel').innerHTML = renderSmartMoney(smart); |
| |
| // Error handling |
| if (!report && !security && !holders) { |
| document.getElementById('errorPanel').style.display = 'block'; |
| document.getElementById('errorPanel').textContent = 'Could not fetch token data. Check the address and chain.'; |
| } |
| |
| // Update URL |
| history.pushState({}, '', `?chain=${chain}&token=${addr}`); |
| } |
| |
| async function init() { |
| // Load stats |
| try { |
| const health = await fetch(API + '/health').then(r => r.json()); |
| document.getElementById('chainCount').textContent = health?.chains || '49'; |
| document.getElementById('cacheStatus').textContent = health?.cache_hits ? `${health.cache_hits} hits` : 'live'; |
| } catch(e) {} |
| |
| // Check URL params |
| const params = new URLSearchParams(window.location.search); |
| const token = params.get('token'); |
| const chain = params.get('chain'); |
| if (token) { |
| document.getElementById('tokenInput').value = token; |
| if (chain) document.getElementById('chainSelect').value = chain; |
| scanToken(); |
| } |
| |
| // Enter key |
| document.getElementById('tokenInput').addEventListener('keydown', e => { |
| if (e.key === 'Enter') scanToken(); |
| }); |
| } |
| |
| init(); |
| </script> |
| </body> |
| </html> |
|
|