以太坊生態數據儀表板建構完整指南:TVL 趨勢圖、Gas 費用走勢圖與即時監控實戰
本指南深入介紹如何建構專業的以太坊生態數據儀表板,涵蓋 TVL 趨勢圖、Gas 費用歷史走勢圖等視覺化內容的完整技術實作。我們提供多個數據來源的 API 整合方案、Chart.js 視覺化範例、即時監控與自動化警報系統,幫助開發者和投資者即時掌握以太坊生態動態。
以太坊生態數據儀表板建構完整指南:TVL 趨勢圖、Gas 費用走勢圖與即時監控實戰
概述
以太坊生態系統的健康狀況和發展趨勢需要透過多元化的數據指標來衡量。總鎖定價值(TVL)反映了 DeFi 協議的資金流入流出動態,Gas 費用揭示了網路擁堵程度和用戶需求變化,而驗證者數據則呈現網路的去中心化程度和安全狀態。建立完善的生態數據儀表板,可以幫助開發者、投資者和研究者即時掌握以太坊生態的脈動,做出更明智的決策。
本指南將從工程師視角出發,詳細介紹如何建構一個專業的以太坊生態數據儀表板。我們將涵蓋 TVL 趨勢圖的數據來源與繪製方法、Gas 費用歷史走勢圖的即時更新機制、Layer 2 數據整合、以及自訂報表的自動化生成。每個模組都會提供完整的程式碼範例,讀者可以直接複製使用或根據自身需求進行修改。
本指南的目標讀者包括:以太坊開發者需要監控協議關鍵指標、DeFi 投資者希望追蹤資金流向與收益變化、區塊鏈研究者需要收集歷史數據進行分析、以及生態系統參與者想即時掌握網路健康狀況。
第一章:數據來源與 API 整合
1.1 主流數據 API 服務比較
建構以太坊生態數據儀表板的第一步是確定數據來源。目前市場上有多種區塊鏈數據 API 服務,各有其優勢和適用場景。
主流 API 服務詳細比較:
| 服務商 | 免費額度 | 付費方案起始價 | 特色功能 | 數據延遲 |
|---|---|---|---|---|
| Etherscan | 5 calls/sec | $99/月 | 合約驗證、 代幣追蹤 | 即時 |
| Dune Analytics | 免費查詢 | $420/月 | SQL 查詢、可視化 | 即時 |
| DeBank | 1000 calls/日 | 免費 | DeFi 投資組合追蹤 | 即時 |
| DeFi Llama | 開放 API | 企業版 | TVL 聚合 | 即時 |
| The Graph | 自託管 | 免費 | 自訂子圖 | 即時 |
| Alchemy | 免費層 | $49/月 | 節點服務、追蹤 | 即時 |
| Infura | 免費層 | $95/月 | 節點服務 | 即時 |
| Covalent | 免費層 | $299/月 | 歷史數據豐富 | 即時 |
選擇建議:對於個人開發者和小型項目,推薦使用 DeFi Llama 的開放 API 獲取 TVL 數據,使用 Etherscan API 獲取鏈上交易數據。對於需要深度分析的企業級應用,則建議採用 Dune Analytics 或 The Graph 的自訂子圖方案。
1.2 DeFi Llama TVL API 整合
DeFi Llama 是目前最完整的 DeFi 協議 TVL 聚合平台,涵蓋超過 3000 個協議的數據。以下是完整的 API 整合程式碼:
// ethereum-dashboard/data-sources/defi-llama.js
const axios = require('axios');
class DeFiLlamaAPI {
constructor() {
this.baseUrl = 'https://api.llama.fi';
this.cache = new Map();
this.cacheTTL = 5 * 60 * 1000; // 5 分鐘快取
}
// 獲取以太坊總 TVL 歷史數據
async getHistoricalTVL(chain = 'Ethereum', days = 365) {
const cacheKey = `tvl_${chain}_${days}`;
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < this.cacheTTL) {
return cached.data;
}
}
try {
const response = await axios.get(
`${this.baseUrl}/charts/${chain}`
);
const data = response.data.map(item => ({
timestamp: item.date,
date: new Date(item.date * 1000).toISOString().split('T')[0],
tvl: item.totalLiquidityUSD,
tvlChange24h: item.dailyVolumeUSD ?
((item.totalLiquidityUSD - (item.totalLiquidityUSD - item.dailyVolumeUSD)) /
(item.totalLiquidityUSD - item.dailyVolumeUSD) * 100) : 0
}));
this.cache.set(cacheKey, {
timestamp: Date.now(),
data: data.slice(-days)
});
return data.slice(-days);
} catch (error) {
console.error('DeFi Llama API Error:', error.message);
return [];
}
}
// 獲取特定協議的 TVL
async getProtocolTVL(protocol, days = 30) {
try {
const response = await axios.get(
`${this.baseUrl}/protocol/${protocol}`
);
return response.data.tvl?.map(item => ({
timestamp: item.date,
date: new Date(item.date * 1000).toISOString().split('T')[0],
tvl: item.totalLiquidityUSD
})).slice(-days) || [];
} catch (error) {
console.error(`Protocol ${protocol} Error:`, error.message);
return [];
}
}
// 獲取多鏈 TVL 對比數據
async getMultiChainTVL() {
try {
const response = await axios.get(`${this.baseUrl}/chains`);
return response.data
.filter(chain => chain.tvl > 100000000) // TVL > $100M
.map(chain => ({
name: chain.name,
tvl: chain.tvl,
tvlChange24h: chain.change1d,
tvlChange7d: chain.change7d,
category: chain.category
}))
.sort((a, b) => b.tvl - a.tvl);
} catch (error) {
console.error('Multi-chain API Error:', error.message);
return [];
}
}
// 獲取以太坊 Top DeFi 協議 TVL
async getEthereumTopProtocols(limit = 20) {
try {
const response = await axios.get(
`${this.baseUrl}/overview/DeFi`
);
const ethereum = response.data.protocols
.filter(p => p.chain === 'Ethereum')
.slice(0, limit)
.map(p => ({
name: p.name,
slug: p.slug,
tvl: p.tvl,
change24h: p.change_1d,
change7d: p.change_7d,
category: p.category
}));
return ethereum;
} catch (error) {
console.error('Top Protocols Error:', error.message);
return [];
}
}
}
module.exports = DeFiLlamaAPI;
1.3 Etherscan Gas 費用 API 整合
Etherscan 提供了豐富的 Gas 費用數據 API,可以獲取歷史費用和即時報價:
// ethereum-dashboard/data-sources/etherscan.js
const axios = require('axios');
class EtherscanAPI {
constructor(apiKey) {
this.apiKey = apiKey || 'YOUR_API_KEY';
this.baseUrl = 'https://api.etherscan.io/api';
this.proxyUrl = 'https://api.etherscan.io/api';
}
// 獲取以太坊區塊難度與 Gas 限制
async getBlockData(startBlock, endBlock) {
const params = {
module: 'block',
action: 'getblocknobytime',
timestamp: Math.floor(Date.now() / 1000) - 86400,
closest: 'before',
apikey: this.apiKey
};
try {
const response = await axios.get(this.baseUrl, { params });
return response.data;
} catch (error) {
console.error('Etherscan API Error:', error.message);
return null;
}
}
// 獲取歷史 Gas 價格
async getHistoricalGasPrice(days = 30) {
const results = [];
const now = Math.floor(Date.now() / 1000);
const daySeconds = 86400;
for (let i = 0; i < days; i++) {
const timestamp = now - (i * daySeconds);
try {
// 嘗試從多個來源獲取歷史數據
const response = await axios.get(
`https://api.etherscan.io/api?module=block&action=blocknobytime×tamp=${timestamp}&closest=before&apikey=${this.apiKey}`
);
if (response.data.status === '1') {
const blockNumber = parseInt(response.data.result);
results.push({
timestamp: timestamp,
date: new Date(timestamp * 1000).toISOString().split('T')[0],
blockNumber: blockNumber
});
}
} catch (error) {
console.error(`Gas data fetch error for day ${i}:`, error.message);
}
}
return results;
}
// 估算交易費用
async estimateTransactionCost(gasLimit, gasPrice) {
const gasUsed = gasLimit;
const costWei = BigInt(gasUsed) * BigInt(gasPrice);
const costEth = Number(costWei) / 1e18;
return {
gasUsed: gasUsed,
gasPrice: gasPrice,
costWei: costWei.toString(),
costEth: costEth,
costUSD: costEth * 3000 // 假設 ETH 價格為 $3000
};
}
}
// 即時 Gas 價格監控
class GasPriceMonitor {
constructor(callback) {
this.callback = callback;
this.interval = null;
this.prices = {
slow: 0,
standard: 0,
fast: 0
};
}
async fetchGasPrices() {
try {
const response = await axios.get(
'https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken'
);
if (response.data.status === '1') {
const result = response.data.result;
this.prices = {
slow: parseInt(result.SafeGasPrice),
standard: parseInt(result.ProposeGasPrice),
fast: parseInt(result.FastGasPrice),
baseFee: parseInt(result.suggestBaseFee),
avgGasPrice: parseInt(result.gasPrice)
};
if (this.callback) {
this.callback(this.prices);
}
return this.prices;
}
} catch (error) {
console.error('Gas price fetch error:', error.message);
}
return null;
}
start(intervalMs = 30000) {
this.fetchGasPrices();
this.interval = setInterval(() => this.fetchGasPrices(), intervalMs);
}
stop() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
}
module.exports = { EtherscanAPI, GasPriceMonitor };
1.4 The Graph 子圖查詢
The Graph 提供了強大的圖形數據查詢能力,適用於複雜的 DeFi 數據分析:
// ethereum-dashboard/data-sources/the-graph.js
const axios = require('axios');
class TheGraphClient {
constructor(subgraphUrl) {
this.subgraphUrl = subgraphUrl;
}
async query(queryString, variables = {}) {
try {
const response = await axios.post(this.subgraphUrl, {
query: queryString,
variables: variables
});
return response.data.data;
} catch (error) {
console.error('The Graph Query Error:', error.message);
return null;
}
}
// 查詢 Uniswap V3 TVL 歷史
async getUniswapV3TVLHistory(days = 30) {
const query = `
query($days: Int!) {
factories(
first: 1,
where: { id: "0x1F98431c8aD98523631AE4a59f267346ea31F984" }
) {
id
totalValueLockedUSD
poolCount
txCount
volumeUSD
}
}
`;
return this.query(query, { days });
}
// 查詢 Aave 借貸數據
async getAaveMarketData() {
const query = `
query {
reserves {
symbol
name
totalDepositsUSD
totalBorrowsUSD
liquidityRate
variableBorrowRate
stableBorrowRate
utilizationRate
}
}
`;
return this.query(query);
}
// 查詢特定地址的交易歷史
async getAccountTransactions(address, first = 100) {
const query = `
query($address: String!, $first: Int!) {
swaps(
where: {
or: [
{ from: $address }
{ to: $address }
]
},
first: $first,
orderBy: timestamp,
orderDirection: desc
) {
id
timestamp
pair {
token0 {
symbol
}
token1 {
symbol
}
}
amount0In
amount0Out
amount1In
amount1Out
amountUSD
}
}
`;
return this.query(query, { address, first });
}
}
// 預設的 The Graph 端點
const SUBGRAPH_ENDPOINTS = {
uniswapV3: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3',
aaveV3: 'https://api.thegraph.com/subgraphs/name/aave/aave-v3',
compound: 'https://api.thegraph.com/subgraphs/name/compound-finance/compound-v3',
curve: 'https://api.thegraph.com/subgraphs/name/curvefi/curve-finance'
};
module.exports = { TheGraphClient, SUBGRAPH_ENDPOINTS };
第二章:TVL 趨勢圖建構
2.1 TVL 趨勢圖數據處理
TVL(Total Value Locked,總鎖定價值)是衡量 DeFi 協議規模的關鍵指標。一個完整的 TVL 趨勢圖需要處理來自多個數據源的歷史數據,並進行必要的清洗和轉換。
// ethereum-dashboard/charts/tvl-trend.js
const DeFiLlamaAPI = require('../data-sources/defi-llama');
class TVLTrendProcessor {
constructor()llama = new DeFiLlamaAPI();
}
{
this. // 處理 TVL 趨勢數據
async processTVLTrend(chain = 'Ethereum', days = 365) {
const rawData = await this.llama.getHistoricalTVL(chain, days);
if (!rawData || rawData.length === 0) {
return null;
}
// 計算移動平均線
const ma7 = this.calculateMA(rawData.map(d => d.tvl), 7);
const ma30 = this.calculateMA(rawData.map(d => d.tvl), 30);
// 計算日環比變化
const dailyChanges = this.calculateDailyChanges(rawData);
// 計算週環比變化
const weeklyChanges = this.calculateWeeklyChanges(rawData);
return rawData.map((item, index) => ({
date: item.date,
timestamp: item.timestamp,
tvl: item.tvl,
tvlFormatted: this.formatTVL(item.tvl),
tvlChange24h: dailyChanges[index] || 0,
tvlChange7d: weeklyChanges[index] || 0,
ma7: ma7[index] || null,
ma30: ma30[index] || null,
ma7Formatted: ma7[index] ? this.formatTVL(ma7[index]) : null,
ma30Formatted: ma30[index] ? this.formatTVL(ma30[index]) : null
}));
}
// 計算移動平均
calculateMA(data, period) {
const result = [];
for (let i = 0; i < data.length; i++) {
if (i < period - 1) {
result.push(null);
} else {
const sum = data.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0);
result.push(sum / period);
}
}
return result;
}
// 計算日環比變化
calculateDailyChanges(data) {
return data.map((item, index) => {
if (index === 0) return 0;
const prev = data[index - 1].tvl;
if (prev === 0) return 0;
return ((item.tvl - prev) / prev) * 100;
});
}
// 計算週環比變化
calculateWeeklyChanges(data) {
return data.map((item, index) => {
if (index < 7) return 0;
const prev = data[index - 7].tvl;
if (prev === 0) return 0;
return ((item.tvl - prev) / prev) * 100;
});
}
// 格式化 TVL 數值
formatTVL(value) {
if (value >= 1e12) {
return `$${(value / 1e12).toFixed(2)}T`;
} else if (value >= 1e9) {
return `$${(value / 1e9).toFixed(2)}B`;
} else if (value >= 1e6) {
return `$${(value / 1e6).toFixed(2)}M`;
} else if (value >= 1e3) {
return `$${(value / 1e3).toFixed(2)}K`;
}
return `$${value.toFixed(2)}`;
}
// 獲取協議 TVL 排名變化
async getProtocolRankingChanges(days = 30) {
const current = await this.llama.getEthereumTopProtocols(50);
// 模擬歷史數據(實際應從數據庫獲取)
const previous = current.map(p => ({
...p,
tvl: p.tvl / (1 + (p.change7d || 0) / 100)
}));
return current.map((protocol, index) => {
const prevIndex = previous.findIndex(p => p.slug === protocol.slug);
const rankChange = prevIndex !== -1 ? prevIndex - index : null;
return {
...protocol,
rank: index + 1,
rankChange: rankChange,
tvlChangeFormatted: this.formatTVL(protocol.tvl - (previous[index]?.tvl || 0))
};
});
}
}
module.exports = TVLTrendProcessor;
2.2 使用 Chart.js 繪製 TVL 趨勢圖
以下是一個完整的 TVL 趨勢圖繪製範例,使用 Chart.js 作為視覺化庫:
<!-- tvl-dashboard.html -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device=1.0-width, initial-scale">
<title>以太坊 TVL 趨勢儀表板</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
margin: 0;
padding: 20px;
background: #0d1117;
color: #c9d1d9;
}
.dashboard-container {
max-width: 1400px;
margin: 0 auto;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.tvl-display {
font-size: 48px;
font-weight: bold;
color: #58a6ff;
}
.tvl-change {
font-size: 18px;
margin-left: 12px;
}
.positive { color: #3fb950; }
.negative { color: #f85149; }
.chart-container {
background: #161b22;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 20px;
}
.stat-card {
background: #161b22;
border-radius: 8px;
padding: 16px;
}
.stat-label {
font-size: 12px;
color: #8b949e;
margin-bottom: 4px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
}
</style>
</head>
<body>
<div class="dashboard-container">
<div class="header">
<div>
<h1>以太坊生態 TVL 趨勢</h1>
<div class="tvl-display" id="currentTVL">
$0
<span class="tvl-change" id="tvlChange">0%</span>
</div>
</div>
<select id="timeRange" onchange="updateTimeRange()">
<option value="30">最近 30 天</option>
<option value="90">最近 90 天</option>
<option value="180">最近 180 天</option>
<option value="365" selected>最近 365 天</option>
</select>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">平均 TVL</div>
<div class="stat-value" id="avgTVL">$0</div>
</div>
<div class="stat-card">
<div class="stat-label">最高 TVL</div>
<div class="stat-value" id="maxTVL">$0</div>
</div>
<div class="stat-card">
<div class="stat-label">最低 TVL</div>
<div class="stat-value" id="minTVL">$0</div>
</div>
<div class="stat-card">
<div class="stat-label">TVL 標準差</div>
<div class="stat-value" id="stdTVL">$0</div>
</div>
</div>
<div class="chart-container">
<canvas id="tvlChart"></canvas>
</div>
</div>
<script>
// TVL 格式化函數
function formatTVL(value) {
if (value >= 1e12) return '$' + (value / 1e12).toFixed(2) + 'T';
if (value >= 1e9) return '$' + (value / 1e9).toFixed(2) + 'B';
if (value >= 1e6) return '$' + (value / 1e6).toFixed(2) + 'M';
return '$' + value.toFixed(0);
}
// 初始化圖表
let tvlChart = null;
async function fetchTVLData(days) {
// 從 DeFi Llama API 獲取數據
const response = await fetch(
`https://api.llama.fi/charts/Ethereum`
);
const data = await response.json();
// 過濾指定天數的數據
const now = Date.now() / 1000;
const cutoff = now - (days * 86400);
return data.filter(item => item.date >= cutoff);
}
async function renderChart(data) {
const ctx = document.getElementById('tvlChart').getContext('2d');
const labels = data.map(item =>
new Date(item.date * 1000).toLocaleDateString('zh-TW')
);
const tvlData = data.map(item => item.totalLiquidityUSD);
// 計算移動平均
const ma7 = calculateMA(tvlData, 7);
const ma30 = calculateMA(tvlData, 30);
if (tvlChart) {
tvlChart.destroy();
}
tvlChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'TVL',
data: tvlData,
borderColor: '#58a6ff',
backgroundColor: 'rgba(88, 166, 255, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 0,
pointHoverRadius: 6,
borderWidth: 2
},
{
label: '7日均線',
data: ma7,
borderColor: '#f0883e',
borderWidth: 1,
pointRadius: 0,
tension: 0.4
},
{
label: '30日均線',
data: ma30,
borderColor: '#a371f7',
borderWidth: 1,
pointRadius: 0,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
labels: {
color: '#c9d1d9'
}
},
tooltip: {
backgroundColor: '#161b22',
titleColor: '#c9d1d9',
bodyColor: '#c9d1d9',
borderColor: '#30363d',
borderWidth: 1,
callbacks: {
label: function(context) {
return context.dataset.label + ': ' +
formatTVL(context.raw);
}
}
}
},
scales: {
x: {
grid: {
color: '#30363d'
},
ticks: {
color: '#8b949e',
maxTicksLimit: 12
}
},
y: {
grid: {
color: '#30363d'
},
ticks: {
color: '#8b949e',
callback: function(value) {
return formatTVL(value);
}
}
}
}
}
});
}
function calculateMA(data, period) {
const result = [];
for (let i = 0; i < data.length; i++) {
if (i < period - 1) {
result.push(null);
} else {
const sum = data.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0);
result.push(sum / period);
}
}
return result;
}
function updateStats(data) {
const tvlValues = data.map(d => d.totalLiquidityUSD);
const currentTVL = tvlValues[tvlValues.length - 1];
const previousTVL = tvlValues[0];
const change = ((currentTVL - previousTVL) / previousTVL * 100).toFixed(2);
// 更新當前 TVL
document.getElementById('currentTVL').innerHTML =
formatTVL(currentTVL) +
`<span class="tvl-change ${change >= 0 ? 'positive' : 'negative'}">${change}%</span>`;
// 計算統計數據
const avg = tvlValues.reduce((a, b) => a + b, 0) / tvlValues.length;
const max = Math.max(...tvlValues);
const min = Math.min(...tvlValues);
const std = Math.sqrt(
tvlValues.reduce((sq, n) => sq + Math.pow(n - avg, 2), 0) / tvlValues.length
);
document.getElementById('avgTVL').textContent = formatTVL(avg);
document.getElementById('maxTVL').textContent = formatTVL(max);
document.getElementById('minTVL').textContent = formatTVL(min);
document.getElementById('stdTVL').textContent = formatTVL(std);
}
async function updateTimeRange() {
const days = parseInt(document.getElementById('timeRange').value);
const data = await fetchTVLData(days);
await renderChart(data);
updateStats(data);
}
// 初始化
updateTimeRange();
// 自動刷新(每 5 分鐘)
setInterval(() => updateTimeRange(), 5 * 60 * 1000);
</script>
</body>
</html>
2.3 TVL 趨勢圖的專業級呈現
對於專業級的 TVL 儀表板,還需要考慮以下進階功能:
// ethereum-dashboard/charts/tvl-advanced.js
class TVLAdvancedAnalyzer {
constructor() {
this.llama = new DeFiLlamaAPI();
}
// 計算趨勢強度指標
calculateTrendStrength(data) {
if (data.length < 14) return null;
const recent = data.slice(-14);
const previous = data.slice(-28, -14);
const recentAvg = recent.reduce((a, b) => a + b.tvl, 0) / recent.length;
const previousAvg = previous.reduce((a, b) => a + b.tvl, 0) / previous.length;
const change = (recentAvg - previousAvg) / previousAvg * 100;
return {
trend: change > 5 ? 'strong_up' : change > 0 ? 'weak_up' :
change < -5 ? 'strong_down' : 'weak_down',
changePercent: change.toFixed(2),
interpretation: this.interpretTrend(change)
};
}
interpretTrend(change) {
if (change > 10) return '強勁上升趨勢';
if (change > 5) return '溫和上升趨勢';
if (change > 0) return '輕微上升趨勢';
if (change > -5) return '輕微下降趨勢';
if (change > -10) return '溫和下降趨勢';
return '顯著下降趨勢';
}
// 識別 TVL 異常
async detectAnomalies(data, threshold = 2) {
const tvlValues = data.map(d => d.tvl);
const mean = tvlValues.reduce((a, b) => a + b, 0) / tvlValues.length;
const std = Math.sqrt(
tvlValues.reduce((sq, n) => sq + Math.pow(n - mean, 2), 0) / tvlValues.length
);
const anomalies = [];
for (let i = 1; i < data.length; i++) {
const zScore = Math.abs((data[i].tvl - mean) / std);
if (zScore > threshold) {
anomalies.push({
date: data[i].date,
tvl: data[i].tvl,
zScore: zScore.toFixed(2),
type: data[i].tvl > mean ? 'surge' : 'drop',
possibleCause: this.suggestCause(zScore, data[i].tvl > mean)
});
}
}
return anomalies;
}
suggestCause(isPositive, magnitude) {
if (isPositive) {
if (magnitude > 3) return '可能原因:重大協議升級、機構採用、牛市來臨';
return '可能原因:新協議上線、獎勵活動、TVL 移入';
} else {
if (magnitude > 3) return '可能原因:市場崩盤、黑天鵝事件、協議被攻擊';
return '可能原因:獲利了結、協議 TVs下降、負面新聞';
}
}
// TVL 預測(簡單線性回歸)
linearRegression(data, daysToPredict = 30) {
const n = data.length;
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
data.forEach((item, index) => {
sumX += index;
sumY += item.tvl;
sumXY += index * item.tvl;
sumX2 += index * index;
});
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
const intercept = (sumY - slope * sumX) / n;
const predictions = [];
for (let i = 0; i < daysToPredict; i++) {
const predictedTVL = intercept + slope * (n + i);
predictions.push({
day: i + 1,
predictedTVL: Math.max(0, predictedTVL),
date: new Date(Date.now() + (i + 1) * 86400000)
.toISOString().split('T')[0]
});
}
return {
slope: slope,
rSquared: this.calculateRSquared(data, slope, intercept),
predictions: predictions
};
}
calculateRSquared(data, slope, intercept) {
const n = data.length;
const mean = data.reduce((a, b) => a + b.tvl, 0) / n;
let ssTot = 0, ssRes = 0;
data.forEach((item, index) => {
const predicted = intercept + slope * index;
ssTot += Math.pow(item.tvl - mean, 2);
ssRes += Math.pow(item.tvl - predicted, 2);
});
return 1 - (ssRes / ssTot);
}
}
module.exports = TVLAdvancedAnalyzer;
第三章:Gas 費用歷史走勢圖
3.1 Gas 費用數據收集與處理
Gas 費用是以太坊網路擁堵程度的直接指標。完整的 Gas 費用監控系統需要收集多個維度的數據:
// ethereum-dashboard/data-sources/gas-tracker.js
const axios = require('axios');
class GasFeeTracker {
constructor() {
this.historicalData = [];
this.maxHistory = 365 * 24; // 保留一年的小時數據
}
// 從多個來源獲取 Gas 費用數據
async fetchCurrentGasPrices() {
const sources = [
this.fetchEtherscanGas(),
this.fetchEthGasStation(),
this.fetchBlocknative()
];
try {
const results = await Promise.allSettled(sources);
const validResults = results
.filter(r => r.status === 'fulfilled' && r.value)
.map(r => r.value);
if (validResults.length === 0) {
return null;
}
// 取平均值
return {
slow: this.avg(validResults.map(r => r.slow)),
standard: this.avg(validResults.map(r => r.standard)),
fast: this.avg(validResults.map(r => r.fast)),
baseFee: validResults[0].baseFee,
timestamp: Date.now()
};
} catch (error) {
console.error('Gas fetch error:', error);
return null;
}
}
async fetchEtherscanGas() {
try {
const response = await axios.get(
'https://api.etherscan.io/api?module=gastracker&action=gasoracle'
);
if (response.data.status === '1') {
const result = response.data.result;
return {
slow: parseInt(result.SafeGasPrice),
standard: parseInt(result.ProposeGasPrice),
fast: parseInt(result.FastGasPrice),
baseFee: parseFloat(result.suggestBaseFee)
};
}
} catch (error) {
console.error('Etherscan gas error:', error);
}
return null;
}
async fetchEthGasStation() {
try {
const response = await axios.get(
'https://ethgasstation.info/api/ethgasAPI.json'
);
return {
slow: response.data.safeLow / 10,
standard: response.data.average / 10,
fast: response.data.fast / 10,
baseFee: response.data.baseFee / 10
};
} catch (error) {
return null;
}
}
async fetchBlocknative() {
try {
// Blocknative 需要 API Key,這裡是示例
const response = await axios.get(
'https://api.blocknative.com/gasprices'
);
const blockPrices = response.data.result.blockPrices[0];
const estimatedPrices = blockPrices.estimatedPrices[0];
return {
slow: estimatedPrices.confidenceLevels[0].price,
standard: estimatedPrices.confidenceLevels[1].price,
fast: estimatedPrices.confidenceLevels[2].price,
baseFee: estimatedPrices.baseFee
};
} catch (error) {
return null;
}
}
avg(numbers) {
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
// 記錄歷史數據
async recordGasData() {
const currentGas = await this.fetchCurrentGasPrices();
if (currentGas) {
const record = {
timestamp: currentGas.timestamp,
date: new Date(currentGas.timestamp).toISOString(),
slow: currentGas.slow,
standard: currentGas.standard,
fast: currentGas.fast,
baseFee: currentGas.baseFee,
// 計算 USD 成本(假設 ETH 價格)
slowUSD: currentGas.slow * 21000 * 3000 / 1e9,
standardUSD: currentGas.standard * 21000 * 3000 / 1e9,
fastUSD: currentGas.fast * 21000 * 3000 / 1e9
};
this.historicalData.push(record);
// 保持歷史數據在限制內
if (this.historicalData.length > this.maxHistory) {
this.historicalData.shift();
}
return record;
}
return null;
}
// 獲取歷史數據(按小時/天匯總)
getHistoricalData(period = 30, aggregation = 'hour') {
const now = Date.now();
const periodMs = period * 24 * 60 * 60 * 1000;
const cutoff = now - periodMs;
const filtered = this.historicalData.filter(r => r.timestamp > cutoff);
if (aggregation === 'day') {
// 按天匯總
const dailyData = {};
filtered.forEach(record => {
const date = record.date.split('T')[0];
if (!dailyData[date]) {
dailyData[date] = {
slow: [],
standard: [],
fast: [],
baseFee: []
};
}
dailyData[date].slow.push(record.slow);
dailyData[date].standard.push(record.standard);
dailyData[date].fast.push(record.fast);
dailyData[date].baseFee.push(record.baseFee);
});
return Object.entries(dailyData).map(([date, values]) => ({
date: date,
slow: this.avg(values.slow),
standard: this.avg(values.standard),
fast: this.avg(values.fast),
baseFee: this.avg(values.baseFee)
}));
}
return filtered;
}
}
module.exports = GasFeeTracker;
3.2 Gas 費用走勢圖視覺化
以下是一個專業級的 Gas 費用走勢圖範例:
// ethereum-dashboard/charts/gas-chart.js
class GasChartRenderer {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.chart = null;
}
render(data, options = {}) {
const {
showBaseFee = true,
showMA = true,
maPeriod = 24,
colorScheme = 'dark'
} = options;
const isDark = colorScheme === 'dark';
const colors = {
slow: isDark ? '#3fb950' : '#16a34a',
standard: isDark ? '#58a6ff' : '#2563eb',
fast: isDark ? '#f85149' : '#dc2626',
baseFee: isDark ? '#a371f7' : '#9333ea',
ma: isDark ? '#f0883e' : '#ea580c',
grid: isDark ? '#30363d' : '#e5e7eb',
text: isDark ? '#8b949e' : '#6b7280'
};
// 準備數據
const labels = data.map(d => {
const date = new Date(d.date);
return date.toLocaleDateString('zh-TW', {
month: 'short',
day: 'numeric',
hour: '2-digit'
});
});
const slowData = data.map(d => d.slow);
const standardData = data.map(d => d.standard);
const fastData = data.map(d => d.fast);
const baseFeeData = data.map(d => d.baseFee);
// 計算移動平均
let maData = null;
if (showMA && data.length > maPeriod) {
maData = this.calculateMA(standardData, maPeriod);
}
const datasets = [
{
label: 'Slow (安全)',
data: slowData,
borderColor: colors.slow,
backgroundColor: 'transparent',
borderWidth: 2,
tension: 0.3,
pointRadius: 0
},
{
label: 'Standard (標準)',
data: standardData,
borderColor: colors.standard,
backgroundColor: 'transparent',
borderWidth: 2,
tension: 0.3,
pointRadius: 0
},
{
label: 'Fast (快速)',
data: fastData,
borderColor: colors.fast,
backgroundColor: 'transparent',
borderWidth: 2,
tension: 0.3,
pointRadius: 0
}
];
if (showBaseFee) {
datasets.push({
label: 'Base Fee',
data: baseFeeData,
borderColor: colors.baseFee,
borderDash: [5, 5],
backgroundColor: 'transparent',
borderWidth: 1,
tension: 0.3,
pointRadius: 0
});
}
if (maData) {
datasets.push({
label: `${maPeriod}小時均線`,
data: maData,
borderColor: colors.ma,
backgroundColor: 'transparent',
borderWidth: 3,
tension: 0.3,
pointRadius: 0
});
}
if (this.chart) {
this.chart.destroy();
}
this.chart = new Chart(this.ctx, {
type: 'line',
data: { labels, datasets },
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
position: 'top',
labels: {
color: colors.text,
usePointStyle: true,
padding: 20
}
},
tooltip: {
backgroundColor: isDark ? '#161b22' : '#ffffff',
titleColor: isDark ? '#c9d1d9' : '#1f2937',
bodyColor: isDark ? '#c9d1d9' : '#1f2937',
borderColor: colors.grid,
borderWidth: 1,
callbacks: {
label: function(context) {
return `${context.dataset.label}: ${context.raw.toFixed(2)} Gwei`;
}
}
}
},
scales: {
x: {
grid: { color: colors.grid },
ticks: {
color: colors.text,
maxTicksLimit: 12,
maxRotation: 0
}
},
y: {
grid: { color: colors.grid },
ticks: {
color: colors.text,
callback: function(value) {
return value + ' Gwei';
}
},
title: {
display: true,
text: 'Gas Price (Gwei)',
color: colors.text
}
}
}
}
});
}
calculateMA(data, period) {
const result = [];
for (let i = 0; i < data.length; i++) {
if (i < period - 1) {
result.push(null);
} else {
const slice = data.slice(i - period + 1, i + 1);
result.push(slice.reduce((a, b) => a + b, 0) / period);
}
}
return result;
}
}
// 使用範例
const gasTracker = new GasFeeTracker();
const gasChart = new GasChartRenderer('gasChart');
// 即時更新
async function updateGasChart() {
await gasTracker.recordGasData();
const historicalData = gasTracker.getHistoricalData(7, 'hour');
gasChart.render(historicalData);
}
// 每分鐘更新
setInterval(updateGasChart, 60000);
updateGasChart();
3.3 Gas 費用預測模型
基於歷史數據,我們可以建立簡單的 Gas 費用預測模型:
// ethereum-dashboard/analytics/gas-forecast.js
class GasPriceForecaster {
constructor(historicalData) {
this.data = historicalData;
}
// 基於簡單移動平均的預測
simpleForecast(hours = 24) {
const recent = this.data.slice(-24);
const avgStandard = recent.reduce((a, b) => a + b.standard, 0) / recent.length;
const avgFast = recent.reduce((a, b) => a + b.fast, 0) / recent.length;
const avgSlow = recent.reduce((a, b) => a + b.slow, 0) / recent.length;
return {
standard: avgStandard,
fast: avgFast,
slow: avgSlow,
confidence: this.calculateConfidence()
};
}
// 基於趨勢的預測
trendForecast(hours = 24) {
if (this.data.length < 48) {
return this.simpleForecast(hours);
}
const recent = this.data.slice(-48);
// 計算斜率
const xValues = recent.map((_, i) => i);
const yValues = recent.map(d => d.standard);
const n = xValues.length;
const sumX = xValues.reduce((a, b) => a + b, 0);
const sumY = yValues.reduce((a, b) => a + b, 0);
const sumXY = xValues.reduce((a, x, i) => a + x * yValues[i], 0);
const sumX2 = xValues.reduce((a, b) => a + b * b, 0);
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
const intercept = (sumY - slope * sumX) / n;
// 預測未來
const predictions = [];
for (let i = 0; i < hours; i++) {
const predictedValue = intercept + slope * (n + i);
predictions.push(Math.max(1, predictedValue)); // 最低 1 Gwei
}
return {
predictions: predictions,
currentSlope: slope,
baseValue: intercept,
trend: slope > 0.5 ? 'rising' : slope < -0.5 ? 'falling' : 'stable'
};
}
// 計算預測置信度
calculateConfidence() {
if (this.data.length < 24) return 'low';
const recent = this.data.slice(-24);
const variance = this.calculateVariance(recent.map(d => d.standard));
const mean = recent.reduce((a, b) => a + b.standard, 0) / recent.length;
const cv = Math.sqrt(variance) / mean; // 變異係數
if (cv < 0.1) return 'high';
if (cv < 0.3) return 'medium';
return 'low';
}
calculateVariance(values) {
const mean = values.reduce((a, b) => a + b, 0) / values.length;
return values.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / values.length;
}
// 獲取建議的 Gas 價格
getSuggestedGasPrices() {
const forecast = this.trendForecast();
// 根據預測趨勢調整價格建議
let multiplier = 1;
if (forecast.trend === 'rising') {
multiplier = 1.2;
} else if (forecast.trend === 'falling') {
multiplier = 0.9;
}
return {
slow: Math.round(forecast.baseValue * 0.8 * multiplier),
standard: Math.round(forecast.baseValue * multiplier),
fast: Math.round(forecast.baseValue * 1.3 * multiplier),
trend: forecast.trend,
confidence: this.calculateConfidence(),
updatedAt: new Date().toISOString()
};
}
}
module.exports = GasPriceForecaster;
第四章:整合儀表板建構
4.1 完整儀表板架構
以下是一個完整的以太坊生態儀表板的整合範例:
// ethereum-dashboard/dashboard.js
const DeFiLlamaAPI = require('./data-sources/defi-llama');
const GasFeeTracker = require('./data-sources/gas-tracker');
const TVLTrendProcessor = require('./charts/tvl-trend');
const TVLAdvancedAnalyzer = require('./charts/tvl-advanced');
class EthereumDashboard {
constructor(config = {}) {
this.config = {
refreshInterval: config.refreshInterval || 60000,
tvlDays: config.tvlDays || 30,
gasHistoryHours: config.gasHistoryHours || 168,
...config
};
this.llama = new DeFiLlamaAPI();
this.gasTracker = new GasFeeTracker();
this.tvlProcessor = new TVLTrendProcessor();
this.tvlAnalyzer = new TVLAdvancedAnalyzer();
this.data = {
tvl: null,
tvlHistory: null,
gas: null,
gasHistory: null,
layer2: null,
validators: null,
lastUpdate: null
};
this.subscribers = [];
}
// 初始化儀表板
async initialize() {
console.log('Initializing Ethereum Dashboard...');
// 載入歷史數據
await this.refreshAllData();
// 設定定期刷新
this.startAutoRefresh();
console.log('Dashboard initialized');
return this;
}
// 刷新所有數據
async refreshAllData() {
try {
const [
tvlData,
tvlHistory,
gasData,
gasHistory,
layer2Data
] = await Promise.all([
this.fetchTVLData(),
this.fetchTVLHistory(),
this.fetchGasData(),
this.fetchGasHistory(),
this.fetchLayer2Data()
]);
this.data = {
tvl: tvlData,
tvlHistory: tvlHistory,
gas: gasData,
gasHistory: gasHistory,
layer2: layer2Data,
lastUpdate: new Date()
};
// 通知訂閱者
this.notifySubscribers();
return this.data;
} catch (error) {
console.error('Error refreshing dashboard data:', error);
throw error;
}
}
async fetchTVLData() {
const topProtocols = await this.llama.getEthereumTopProtocols(10);
const multiChain = await this.llama.getMultiChainTVL();
const ethereumData = multiChain.find(c => c.name === 'Ethereum');
return {
total: ethereumData?.tvl || 0,
change24h: ethereumData?.tvlChange24h || 0,
change7d: ethereumData?.tvlChange7d || 0,
topProtocols: topProtocols,
multiChain: multiChain.slice(0, 10)
};
}
async fetchTVLHistory() {
return await this.tvlProcessor.processTVLTrend('Ethereum', this.config.tvlDays);
}
async fetchGasData() {
return await this.gasTracker.fetchCurrentGasPrices();
}
async fetchGasHistory() {
// 先記錄當前數據
await this.gasTracker.recordGasData();
// 獲取歷史數據(小時級別)
const hours = Math.ceil(this.config.gasHistoryHours / 24) * 24;
return this.gasTracker.getHistoricalData(hours / 24, 'hour');
}
async fetchLayer2Data() {
try {
const response = await axios.get(
'https://api.llama.fi/overview/chainvol?chains=Arbitrum,Optimism,Base,zkSync,Starknet,Polygon,Linea,Scroll'
);
return response.data.map(chain => ({
name: chain.name,
tvl: chain.tvl,
change24h: chain.change1d,
change7d: chain.change7d,
category: 'Layer2'
}));
} catch (error) {
console.error('Layer2 data fetch error:', error);
return [];
}
}
// 訂閱數據更新
subscribe(callback) {
this.subscribers.push(callback);
return () => {
this.subscribers = this.subscribers.filter(cb => cb !== callback);
};
}
notifySubscribers() {
this.subscribers.forEach(cb => cb(this.data));
}
// 自動刷新
startAutoRefresh() {
this.intervalId = setInterval(
() => this.refreshAllData(),
this.config.refreshInterval
);
}
stopAutoRefresh() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
// 獲取儀表板快照(用於導出)
getSnapshot() {
return {
generatedAt: new Date().toISOString(),
data: this.data,
summary: this.generateSummary()
};
}
generateSummary() {
if (!this.data.tvl || !this.data.gas) return null;
return {
tvlFormatted: this.formatTVL(this.data.tvl.total),
tvlChangeFormatted: `${this.data.tvl.change24h > 0 ? '+' : ''}${this.data.tvl.change24h.toFixed(2)}%`,
gasStandard: `${this.data.gas.standard} Gwei`,
gasFast: `${this.data.gas.fast} Gwei`,
gasSlow: `${this.data.gas.slow} Gwei`,
layer2Count: this.data.layer2?.length || 0,
layer2TVLFormatted: this.formatTVL(
this.data.layer2?.reduce((a, b) => a + b.tvl, 0) || 0
)
};
}
formatTVL(value) {
if (value >= 1e12) return `$${(value / 1e12).toFixed(2)}T`;
if (value >= 1e9) return `$${(value / 1e9).toFixed(2)}B`;
if (value >= 1e6) return `$${(value / 1e6).toFixed(2)}M`;
return `$${value.toFixed(0)}`;
}
}
module.exports = EthereumDashboard;
4.2 即時數據監控面板
<!-- ethereum-realtime-dashboard.html -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>以太坊生態即時監控儀表板</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #0d1117;
color: #c9d1d9;
padding: 20px;
}
.dashboard {
max-width: 1600px;
margin: 0 auto;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #30363d;
}
.header h1 {
font-size: 24px;
color: #58a6ff;
}
.last-update {
font-size: 12px;
color: #8b949e;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.card {
background: #161b22;
border-radius: 8px;
padding: 20px;
border: 1px solid #30363d;
}
.card-title {
font-size: 12px;
color: #8b949e;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.card-value {
font-size: 28px;
font-weight: bold;
}
.card-change {
font-size: 14px;
margin-top: 4px;
}
.positive { color: #3fb950; }
.negative { color: #f85149; }
.chart-row {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 16px;
margin-bottom: 24px;
}
.chart-container {
background: #161b22;
border-radius: 8px;
padding: 20px;
height: 400px;
}
.gas-indicators {
display: flex;
flex-direction: column;
gap: 12px;
}
.gas-level {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background: #0d1117;
border-radius: 6px;
}
.gas-label {
font-size: 14px;
color: #8b949e;
}
.gas-price {
font-size: 20px;
font-weight: bold;
}
.gas-usd {
font-size: 12px;
color: #8b949e;
}
.protocol-table {
width: 100%;
border-collapse: collapse;
}
.protocol-table th,
.protocol-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #30363d;
}
.protocol-table th {
font-size: 12px;
color: #8b949e;
text-transform: uppercase;
}
.loading {
text-align: center;
padding: 40px;
color: #8b949e;
}
</style>
</head>
<body>
<div class="dashboard">
<div class="header">
<h1>以太坊生態監控</h1>
<div class="last-update">最後更新: <span id="lastUpdate">載入中...</span></div>
</div>
<!-- 關鍵指標卡片 -->
<div class="grid">
<div class="card">
<div class="card-title">以太坊 TVL</div>
<div class="card-value" id="tvlValue">--</div>
<div class="card-change" id="tvlChange">--</div>
</div>
<div class="card">
<div class="card-title">Layer 2 TVL</div>
<div class="card-value" id="l2TVL">--</div>
<div class="card-change" id="l2Change">--</div>
</div>
<div class="card">
<div class="card-title">Gas 費用 (Standard)</div>
<div class="card-value" id="gasValue">--</div>
<div class="card-change" id="gasUSD">--</div>
</div>
<div class="card">
<div class="card-title">網路狀態</div>
<div class="card-value" id="networkStatus">正常</div>
<div class="card-change" id="blockTime">--</div>
</div>
</div>
<!-- 圖表區域 -->
<div class="chart-row">
<div class="chart-container">
<canvas id="tvlChart"></canvas>
</div>
<div class="chart-container">
<div class="card-title" style="margin-bottom: 16px;">即時 Gas 費用</div>
<div class="gas-indicators">
<div class="gas-level">
<div>
<div class="gas-label">慢 (Safe)</div>
<div class="gas-usd" id="gasSlowUSD">~$0</div>
</div>
<div class="gas-price" id="gasSlow" style="color: #3fb950;">--</div>
</div>
<div class="gas-level">
<div>
<div class="gas-label">標準 (Propose)</div>
<div class="gas-usd" id="gasStandardUSD">~$0</div>
</div>
<div class="gas-price" id="gasStandard" style="color: #58a6ff;">--</div>
</div>
<div class="gas-level">
<div>
<div class="gas-label">快 (Fast)</div>
<div class="gas-usd" id="gasFastUSD">~$0</div>
</div>
<div class="gas-price" id="gasFast" style="color: #f85149;">--</div>
</div>
</div>
</div>
</div>
<!-- Layer 2 數據 -->
<div class="card">
<div class="card-title" style="margin-bottom: 16px;">Layer 2 TVL 排名</div>
<table class="protocol-table">
<thead>
<tr>
<th>排名</th>
<th>名稱</th>
<th>TVL</th>
<th>24h 變化</th>
<th>7d 變化</th>
</tr>
</thead>
<tbody id="l2Table">
<tr><td colspan="5" class="loading">載入中...</td></tr>
</tbody>
</table>
</div>
</div>
<script>
// 格式化函數
function formatTVL(value) {
if (!value) return '--';
if (value >= 1e12) return '$' + (value / 1e12).toFixed(2) + 'T';
if (value >= 1e9) return '$' + (value / 1e9).toFixed(2) + 'B';
if (value >= 1e6) return '$' + (value / 1e6).toFixed(2) + 'M';
return '$' + (value / 1e3).toFixed(2) + 'K';
}
function formatChange(value) {
if (!value) return '--';
const sign = value >= 0 ? '+' : '';
return sign + value.toFixed(2) + '%';
}
function formatGweiUSD(gwei) {
// 假設 ETH 價格為 $3000,基本轉帳需要 21000 gas
const eth = gwei * 21000 / 1e9;
return '$' + eth.toFixed(2);
}
// API 獲取數據
async function fetchDashboardData() {
try {
// 獲取 TVL 數據
const tvlRes = await fetch('https://api.llama.fi/chains');
const tvlData = await tvlRes.json();
const ethereum = tvlData.find(c => c.name === 'Ethereum');
const layer2s = tvlData.filter(c =>
['Arbitrum', 'Optimism', 'Base', 'zkSync Era', 'Starknet', 'Polygon zkEVM', 'Linea', 'Scroll'].includes(c.name)
);
// 獲取 Gas 數據
const gasRes = await fetch(
'https://api.etherscan.io/api?module=gastracker&action=gasoracle'
);
const gasData = await gasRes.json();
return {
tvl: ethereum,
layer2s: layer2s,
gas: gasData.result
};
} catch (error) {
console.error('Fetch error:', error);
return null;
}
}
// 更新 UI
function updateDashboard(data) {
if (!data) return;
// 更新時間
document.getElementById('lastUpdate').textContent =
new Date().toLocaleString('zh-TW');
// 更新 TVL
if (data.tvl) {
document.getElementById('tvlValue').textContent =
formatTVL(data.tvl.tvl);
const tvlChange = document.getElementById('tvlChange');
tvlChange.textContent = formatChange(data.tvl.change7d);
tvlChange.className = 'card-change ' +
(data.tvl.change7d >= 0 ? 'positive' : 'negative');
}
// 更新 Layer 2
if (data.layer2s) {
const totalL2TVL = data.layer2s.reduce((sum, l2) => sum + l2.tvl, 0);
const avgChange = data.layer2s.reduce((sum, l2) => sum + l2.change7d, 0) / data.layer2s.length;
document.getElementById('l2TVL').textContent = formatTVL(totalL2TVL);
const l2Change = document.getElementById('l2Change');
l2Change.textContent = formatChange(avgChange);
l2Change.className = 'card-change ' + (avgChange >= 0 ? 'positive' : 'negative');
// 更新表格
const tbody = document.getElementById('l2Table');
tbody.innerHTML = data.layer2s
.sort((a, b) => b.tvl - a.tvl)
.slice(0, 10)
.map((l2, i) => `
<tr>
<td>${i + 1}</td>
<td>${l2.name}</td>
<td>${formatTVL(l2.tvl)}</td>
<td class="${l2.change1d >= 0 ? 'positive' : 'negative'}">
${formatChange(l2.change1d)}
</td>
<td class="${l2.change7d >= 0 ? 'positive' : 'negative'}">
${formatChange(l2.change7d)}
</td>
</tr>
`).join('');
}
// 更新 Gas
if (data.gas) {
const slow = data.gas.SafeGasPrice;
const standard = data.gas.ProposeGasPrice;
const fast = data.gas.FastGasPrice;
document.getElementById('gasSlow').textContent = slow + ' Gwei';
document.getElementById('gasStandard').textContent = standard + ' Gwei';
document.getElementById('gasFast').textContent = fast + ' Gwei';
document.getElementById('gasValue').textContent = standard + ' Gwei';
document.getElementById('gasUSD').textContent =
formatGweiUSD(standard);
document.getElementById('gasSlowUSD').textContent =
formatGweiUSD(slow);
document.getElementById('gasStandardUSD').textContent =
formatGweiUSD(standard);
document.getElementById('gasFastUSD').textContent =
formatGweiUSD(fast);
}
}
// 初始化
async function init() {
const data = await fetchDashboardData();
updateDashboard(data);
// 每分鐘刷新
setInterval(async () => {
const data = await fetchDashboardData();
updateDashboard(data);
}, 60000);
}
init();
</script>
</body>
</html>
第五章:自動化與警報系統
5.1 數據異常檢測
// ethereum-dashboard/alerts/anomaly-detector.js
class AnomalyDetector {
constructor(config = {}) {
this.thresholds = {
tvl: {
dropPercent: config.tvlDropThreshold || 10,
surgePercent: config.tvlSurgeThreshold || 20
},
gas: {
highPercent: config.gasHighThreshold || 100,
lowPercent: config.gasLowThreshold || -50
}
};
this.history = [];
this.alerts = [];
}
// 檢測 TVL 異常
detectTVLAnomaly(currentTVL, previousTVL) {
if (!previousTVL || previousTVL === 0) return null;
const changePercent = ((currentTVL - previousTVL) / previousTVL) * 100;
if (Math.abs(changePercent) < this.thresholds.tvl.dropPercent) {
return null;
}
const anomaly = {
type: changePercent > 0 ? 'surge' : 'drop',
metric: 'TVL',
currentValue: currentTVL,
previousValue: previousTVL,
changePercent: changePercent,
timestamp: new Date(),
severity: Math.abs(changePercent) > 20 ? 'high' : 'medium'
};
this.alerts.push(anomaly);
return anomaly;
}
// 檢測 Gas 費用異常
detectGasAnomaly(currentGas, historicalAvg) {
if (!historicalAvg || historicalAvg === 0) return null;
const changePercent = ((currentGas - historicalAvg) / historicalAvg) * 100;
if (Math.abs(changePercent) < this.thresholds.gas.highPercent) {
return null;
}
const anomaly = {
type: changePercent > 0 ? 'spike' : 'drop',
metric: 'Gas',
currentValue: currentGas,
averageValue: historicalAvg,
changePercent: changePercent,
timestamp: new Date(),
severity: currentGas > 100 ? 'high' : 'medium',
recommendation: this.getGasRecommendation(currentGas, changePercent)
};
this.alerts.push(anomaly);
return anomaly;
}
getGasRecommendation(gas, changePercent) {
if (changePercent > 100) {
return 'Gas 費用異常飆升,建議推遲非緊急性交易或使用 Layer 2';
} else if (changePercent > 50) {
return 'Gas 費用偏高,可考慮設定較高 Gas 優先級';
} else if (changePercent < -30) {
return 'Gas 費用處於低位,適合進行大額交易';
}
return 'Gas 費用正常';
}
// 獲取最近警報
getRecentAlerts(hours = 24) {
const cutoff = Date.now() - hours * 60 * 60 * 1000;
return this.alerts.filter(a => a.timestamp.getTime() > cutoff);
}
// 清空警報
clearAlerts() {
this.alerts = [];
}
}
module.exports = AnomalyDetector;
5.2 自動化報告生成
// ethereum-dashboard/reports/auto-reporter.js
class EthereumReportGenerator {
constructor(dashboard) {
this.dashboard = dashboard;
}
// 生成每日報告
generateDailyReport() {
const data = this.dashboard.getSnapshot();
const summary = data.summary;
return {
title: `以太坊生態每日報告 - ${data.generatedAt.split('T')[0]}`,
summary: {
tvl: summary.tvlFormatted,
tvlChange: summary.tvlChangeFormatted,
gasStandard: summary.gasStandard,
layer2TVL: summary.layer2TVLFormatted
},
recommendations: this.generateRecommendations(data),
generatedAt: data.generatedAt
};
}
generateRecommendations(data) {
const recommendations = [];
const gas = data.data.gas;
const tvl = data.data.tvl;
// Gas 建議
if (gas && gas.standard > 50) {
recommendations.push({
type: 'gas',
priority: 'high',
message: 'Gas 費用偏高 (>50 Gwei),建議推遲大額交易或考慮使用 Layer 2'
});
}
// TVL 建議
if (tvl && tvl.change24h < -5) {
recommendations.push({
type: 'tvl',
priority: 'medium',
message: 'TVL 日變化下降超過 5%,建議關注 DeFi 市場風險'
});
}
return recommendations;
}
// 導出為 Markdown 格式
exportAsMarkdown() {
const report = this.generateDailyReport();
return `# ${report.title}
## 摘要
- **以太坊 TVL**: ${report.summary.tvl} (${report.summary.tvlChange})
- **Gas 費用 (標準)**: ${report.summary.gasStandard}
- **Layer 2 TVL**: ${report.summary.layer2TVL}
## 建議
${report.recommendations.map(r => `- [${r.priority.toUpperCase()}] ${r.message}`).join('\n')}
---
*報告生成時間: ${report.generatedAt}*
`;
}
}
module.exports = EthereumReportGenerator;
結論
本指南詳細介紹了如何建構一個專業的以太坊生態數據儀表板。從數據來源的選擇與 API 整合、TVL 趨勢圖的繪製與分析、Gas 費用走勢圖的即時監控,到完整的儀表板架構與自動化警報系統,我們提供了完整的技術解決方案和可直接部署的程式碼範例。
關鍵要點總結:
首先,在數據來源方面,推薦使用 DeFi Llama 獲取 TVL 數據、Etherscan API 獲取 Gas 費用數據、以及 The Graph 查詢深度的 DeFi 協議數據。這些服務都有免費層可以使用,足以支撐中小型項目。
其次,在數據視覺化方面,Chart.js 是一個功能強大且易於使用的圖表庫,支援多種圖表類型和豐富的自訂選項。對於更複雜的需求,可以考慮使用 D3.js 或專業的 BI 工具如 Metabase。
第三,在即時監控方面,設定適當的刷新頻率很重要。對於 Gas 費用等高頻變化的數據,建議每分鐘刷新一次;對於 TVL 等相對穩定的數據,每 5-15 分鐘刷新一次即可。
最後,在警報系統方面,建立異常檢測機制可以幫助及時發現問題。可以根據實際需求設定閾值,並通過 Email、Discord、Webhook 等方式發送通知。
有了這些工具和技術,開發者可以根據自己的需求構建客製化的以太坊生態監控儀表板,及時掌握以太坊生態的發展動態。
相關資源
- DeFi Llama API 文檔:https://docs.llama.fi
- Etherscan API:https://docs.etherscan.io
- The Graph 文檔:https://thegraph.com/docs/en
- Chart.js 文檔:https://www.chartjs.org/docs/
聲明:本指南僅供技術參考,不構成投資建議。加密貨幣投資具有高度風險,請在做出任何投資決策前進行獨立研究。
相關文章
- 以太坊市場數據與發展動態定期更新完整指南:從數據來源到自動化監控 — 在快速變化的加密貨幣市場中,及時掌握最新數據與發展動態對於投資決策、風險管理與技術研究至關重要。本指南深入探討如何建立完善的以太坊市場數據與發展動態追蹤系統,涵蓋數據來源、監控工具、自動化方案與更新頻率設計,幫助讀者建立適合自身需求的數據更新流程。
- 以太坊 Gas 費用預測模型完整指南:從基礎算法到機器學習模型的深度技術分析 — 以太坊網路的 Gas 費用預測是 DeFi 交易者和智慧合約開發者的核心技能需求。本指南深入探討 Gas 費用預測的各種技術方法,從最基礎的簡單移動平均算法到最先進的機器學習模型,提供完整的數學推導、實作範例與實際應用場景分析。我們涵蓋 EIP-1559 費用機制、基於客戶端的預測算法、線性回歸、梯度提升樹、LSTM 神經網路等多種預測方法,並提供可重現的 Python 程式碼範例。
- CoinGecko API 以太坊數據獲取完整指南:即時價格、Gas 費用與市場數據技術實作 — 本文深入介紹 CoinGecko API 的使用方法,特別針對以太坊生態系統的數據獲取需求。我們提供完整的程式碼範例,涵蓋價格查詢、Gas 費用監控、歷史數據分析、與 DeFi 協議數據的整合應用。同時討論數據來源的可靠性考量、投資者風險警示、以及常見問題解答,幫助讀者建立完整的以太坊數據獲取解決方案。
- 以太坊市場數據與即時統計完整指南:市值、Gas 費用、TVL 排行與生態系統儀表板 — 本文深入探討以太坊生態系統的即時市場數據與關鍵統計指標,涵蓋 ETH 市場數據、Gas 費用機制與實時監控、TVL 排行分析,以及機構採用的最新進展。根據截至 2026 年 3 月的最新數據,本指南提供專業投資者、開發者與研究者所需的完整數據分析框架。
- 以太坊 Layer 2 TVL 趨勢與 Gas 費用週期性分析完整指南:2024-2026 年數據驅動研究 — 本文深入分析以太坊 Layer 2 生態系統在 2024 至 2026 年間的總鎖定價值(TVL)變化趨勢、Gas 費用的週期性規律,以及這些數據背後的技術與經濟驅動因素。我們將從工程師視角提供詳實的量化數據分析,幫助開發者、投資者與研究者理解以太坊擴容解決方案的實際表現與市場動態。
延伸閱讀與來源
- Ethereum.org 以太坊官方入口
- EthHub 以太坊知識庫
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!