Реализация блокчейн-эксплорера на сайте
Блокчейн-эксплорер — веб-приложение для просмотра транзакций, блоков, адресов и смарт-контрактов в блокчейне. Полноценный эксплорер требует ноды с архивным режимом, индексатора и быстрого поискового бэкенда.
Компоненты системы
Блокчейн-нода (archive)
│
├── RPC/WebSocket (eth_getBlock, eth_getTransaction...)
│
Индексатор (собственный или The Graph)
│ Читает блоки → парсит транзакции → сохраняет
│
PostgreSQL + Redis (кеш)
│
REST API / GraphQL
│
Frontend (Next.js)
├── Поиск (tx hash, address, block)
├── Список блоков
├── Детали транзакции
├── Профиль адреса (баланс + история)
└── Декодирование смарт-контракт вызовов
Индексатор: чтение блоков
import { createPublicClient, webSocket } from 'viem';
import { mainnet } from 'viem/chains';
const client = createPublicClient({
chain: mainnet,
transport: webSocket(process.env.ETH_WS_URL)
});
class BlockIndexer {
async indexBlock(blockNumber: bigint): Promise<void> {
const block = await client.getBlock({
blockNumber,
includeTransactions: true
});
await db.transaction(async (trx) => {
// Сохранить блок
await trx('blocks').insert({
number: Number(block.number),
hash: block.hash,
parent_hash: block.parentHash,
timestamp: new Date(Number(block.timestamp) * 1000),
miner: block.miner,
gas_used: block.gasUsed.toString(),
gas_limit: block.gasLimit.toString(),
transaction_count: block.transactions.length,
base_fee_per_gas: block.baseFeePerGas?.toString() ?? null
});
// Сохранить транзакции
for (const tx of block.transactions) {
await trx('transactions').insert({
hash: tx.hash,
block_number: Number(tx.blockNumber),
from_address: tx.from.toLowerCase(),
to_address: tx.to?.toLowerCase() ?? null,
value: tx.value.toString(),
gas: tx.gas.toString(),
gas_price: tx.gasPrice?.toString() ?? null,
max_fee_per_gas: tx.maxFeePerGas?.toString() ?? null,
input: tx.input,
nonce: tx.nonce,
transaction_index: tx.transactionIndex
});
// Обновить баланс адресов
await this.updateAddressStats(trx, tx.from.toLowerCase());
if (tx.to) await this.updateAddressStats(trx, tx.to.toLowerCase());
}
});
}
// Отслеживание новых блоков в реальном времени
async watchNewBlocks(): Promise<void> {
const unwatch = client.watchBlocks({
onBlock: async (block) => {
await this.indexBlock(block.number);
},
onError: (error) => {
logger.error('Block watch error', error);
}
});
// Backfill пропущенных блоков
const latestIndexed = await this.getLatestIndexedBlock();
const currentBlock = await client.getBlockNumber();
for (let i = latestIndexed + 1n; i <= currentBlock; i++) {
await this.indexBlock(i);
}
}
}
API: поиск
app.get('/api/search', async (req, res) => {
const query = req.query.q as string;
if (!query) return res.status(400).json({ error: 'Query required' });
// Определить тип поискового запроса
if (/^0x[0-9a-f]{64}$/i.test(query)) {
// Хеш транзакции или блока
const tx = await db('transactions').where('hash', query.toLowerCase()).first();
if (tx) return res.json({ type: 'transaction', data: tx });
const block = await db('blocks').where('hash', query.toLowerCase()).first();
if (block) return res.json({ type: 'block', data: block });
} else if (/^0x[0-9a-f]{40}$/i.test(query)) {
// Ethereum адрес
return res.json({ type: 'address', address: query.toLowerCase() });
} else if (/^\d+$/.test(query)) {
// Номер блока
const block = await db('blocks').where('number', parseInt(query)).first();
if (block) return res.json({ type: 'block', data: block });
}
res.json({ type: 'not_found' });
});
Frontend: декодирование input data
import { decodeFunctionData } from 'viem';
async function decodeTransactionInput(
input: string,
contractAddress: string
): Promise<DecodedInput | null> {
if (input === '0x') return null;
// Загрузить ABI из verified contracts (Etherscan API или собственная БД)
const abi = await getContractAbi(contractAddress);
if (!abi) return { raw: input };
try {
const decoded = decodeFunctionData({ abi, data: input as `0x${string}` });
return {
functionName: decoded.functionName,
args: decoded.args,
raw: input
};
} catch {
return { raw: input };
}
}
Готовые индексаторы
Вместо написания с нуля:
- The Graph — GraphQL-индексатор, бесплатный self-hosted вариант
- Ponder — TypeScript-фреймворк для индексаторов
- Moralis — managed API с индексированием
- Alchemy / QuickNode — RPC с трансформациями и webhooks
Сроки реализации
- MVP-эксплорер (транзакции, блоки, адреса) без индексатора (через RPC) — 2–3 недели
- Полный индексатор с PostgreSQL + API + Frontend — 6–10 недель
- Эксплорер для кастомной EVM-сети — 2–3 месяца







