Разработка трекера стоимости газа (gas tracker)
Gas tracker — это не просто "показываем eth_gasPrice". Полезный инструмент показывает историческую динамику, предсказывает оптимальное время транзакции, разделяет EIP-1559 компоненты и даёт оценки по типам транзакций. Разберём что внутри у нормального gas tracker-а.
EIP-1559: как работает gas pricing на Ethereum
После EIP-1559 (лондонский хардфорк, август 2021) цена газа состоит из двух частей:
baseFeePerGas — базовая комиссия, сжигается. Определяется протоколом: если блок > 50% заполненности, base fee растёт на 12.5%, если < 50% — падает. Предсказуема на 1-2 блока вперёд.
maxPriorityFeePerGas (tip) — чаевые майнеру/валидатору. Рыночная часть — конкурируете за включение в блок.
Реальная стоимость транзакции: min(maxFeePerGas, baseFeePerGas + priorityFee) * gasUsed.
Для gas tracker это важно: нужно отслеживать оба компонента отдельно, не только итоговую цену.
Архитектура: сбор и хранение данных
Источники данных
eth_feeHistory — основной метод для исторических данных. Возвращает baseFee, gasUsedRatio и percentile-статистику priorityFee за указанный диапазон блоков:
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({ chain: mainnet, transport: http(RPC_URL) })
// Получаем данные за последние 100 блоков
// percentiles: [25, 50, 75] — slow/standard/fast
const feeHistory = await client.getFeeHistory({
blockCount: 100,
rewardPercentiles: [25, 50, 75, 95],
})
// feeHistory.baseFeePerGas: BigInt[] (на 1 больше — включает следующий блок)
// feeHistory.reward: BigInt[][] (matrix: block x percentile)
// feeHistory.gasUsedRatio: number[] (0..1)
eth_gasPrice — актуальная цена на moment запроса. Legacy метод, для EIP-1559 сетей возвращает baseFee + минимальный tip.
Mempool данные — pending транзакции. eth_getBlockByNumber("pending") или подписка на pending txs через WebSocket. Нужно для real-time анализа конкуренции.
Схема хранения
TimescaleDB (PostgreSQL extension) — оптимально для time-series gas данных:
CREATE TABLE gas_stats (
time TIMESTAMPTZ NOT NULL,
block_number BIGINT NOT NULL,
base_fee_gwei NUMERIC(20, 9) NOT NULL,
tip_slow NUMERIC(20, 9), -- 25th percentile
tip_standard NUMERIC(20, 9), -- 50th percentile
tip_fast NUMERIC(20, 9), -- 75th percentile
tip_instant NUMERIC(20, 9), -- 95th percentile
gas_used_ratio NUMERIC(5, 4), -- 0..1, заполненность блока
network VARCHAR(50) NOT NULL DEFAULT 'ethereum'
);
SELECT create_hypertable('gas_stats', 'time');
-- Агрегаты по часам для исторических графиков
SELECT add_continuous_aggregate_policy('gas_stats_hourly', ...);
Retention policy: детальные данные по блокам — 7 дней, часовые агрегаты — 1 год, дневные агрегаты — бессрочно.
Коллектор данных
Коллектор запускается в фоне и пишет данные по каждому новому блоку:
async function collectGasData() {
const client = createWsClient(WS_RPC_URL)
// Подписка на новые блоки через WebSocket
client.watchBlocks({
onBlock: async (block) => {
const baseFee = block.baseFeePerGas
? Number(block.baseFeePerGas) / 1e9
: null
// Получаем percentile статистику за этот блок
const history = await client.getFeeHistory({
blockCount: 1,
blockNumber: block.number,
rewardPercentiles: [25, 50, 75, 95],
})
const [slow, standard, fast, instant] = history.reward[0].map(
(r) => Number(r) / 1e9
)
await db.query(
`INSERT INTO gas_stats (time, block_number, base_fee_gwei, tip_slow, tip_standard, tip_fast, tip_instant, gas_used_ratio)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
[new Date(Number(block.timestamp) * 1000), block.number, baseFee, slow, standard, fast, instant, history.gasUsedRatio[0]]
)
},
onError: (err) => handleCollectorError(err),
})
}
Алгоритм предсказания газа
Простой вывод текущего baseFee недостаточен. Нужна оценка: "сколько поставить чтобы попасть в следующий блок с вероятностью X%?"
Следующий baseFee предсказуем точно:
function predictNextBaseFee(currentBaseFee: bigint, gasUsedRatio: number): bigint {
// EIP-1559: baseFee изменяется пропорционально отклонению от 50% цели
const targetRatio = 0.5
const maxChangeDenominator = 8n // 12.5% max change
const delta = gasUsedRatio - targetRatio
const change = (currentBaseFee * BigInt(Math.round(delta * 1000))) / (maxChangeDenominator * 1000n)
return currentBaseFee + change
}
Priority fee — рыночная, предсказать сложнее. Используем EMA (exponential moving average) последних N блоков по каждому percentile:
def calculate_priority_fee_estimate(
recent_tips: list[float], # последние 20 блоков, 50th percentile
alpha: float = 0.3, # вес новых данных
) -> float:
ema = recent_tips[0]
for tip in recent_tips[1:]:
ema = alpha * tip + (1 - alpha) * ema
return ema
Паттерны по времени суток. Gas существенно дешевле в UTC 02:00-08:00 (ночь Европы и утро Азии до открытия рынка). Если транзакция не срочная — показываем "оптимальное окно":
-- Средний газ по часу UTC за последние 30 дней
SELECT
EXTRACT(HOUR FROM time) AS hour_utc,
AVG(base_fee_gwei + tip_standard) AS avg_total_fee
FROM gas_stats
WHERE time > NOW() - INTERVAL '30 days'
GROUP BY hour_utc
ORDER BY avg_total_fee ASC;
API и фронтенд
REST endpoints для интеграции:
GET /api/gas/current — текущие рекомендации {slow, standard, fast, instant}
GET /api/gas/history?period=24h — история с агрегацией
GET /api/gas/predict?blocks=5 — прогноз на N блоков
GET /api/gas/networks — данные по нескольким сетям
Для реального времени — WebSocket subscription: клиент подписывается и получает обновление на каждый новый блок без polling.
Мультичейн поддержка — отдельный коллектор на каждую сеть. L2 (Arbitrum, Optimism, Base) имеют двухкомпонентный gas: L2 execution fee + L1 data fee. Для Optimism L1 data fee считается через precompile GasPriceOracle (0x420000000000000000000000000000000000000F).
Технологический стек
| Компонент | Выбор |
|---|---|
| Коллектор | TypeScript + viem |
| База данных | TimescaleDB (PostgreSQL) |
| API | Fastify или Express |
| Кэш | Redis (текущие рекомендации) |
| Фронтенд | React + Recharts/Highcharts |
| Деплой | Docker Compose |
Разработка базовой версии (Ethereum mainnet, история + текущие рекомендации) — 5-8 дней. Мультичейн с предсказаниями и детальной аналитикой — 2-3 недели.







