P18: Block Explorer

P18: Block Explorer

Project Overview

Attribute Value
Main Language TypeScript
Alternative Languages Python, Go
Difficulty Intermediate
Coolness Level Level 3: Genuinely Clever
Business Potential The “Service & Support” Model
Knowledge Area Infrastructure / Data
Software or Tool Etherscan Clone
Main Book “Mastering Ethereum” by Andreas M. Antonopoulos & Gavin Wood

Learning Objectives

By completing this project, you will:

  1. Master Ethereum JSON-RPC API understanding how to query blocks, transactions, receipts, and state from an Ethereum node
  2. Implement ABI encoding/decoding learning how Solidity function calls and events are serialized to and from raw bytes
  3. Build a blockchain indexer creating a data pipeline that syncs on-chain data to a queryable database in real-time
  4. Parse and interpret event logs extracting meaningful information from raw log topics and data fields
  5. Design efficient database schemas for blockchain data that supports fast queries by address, block, transaction hash, and token
  6. Implement real-time updates using WebSockets to push new blocks and transactions to connected clients
  7. Build a production-quality API with proper pagination, caching, and error handling for blockchain data

The Core Question You’re Answering

“How do you read and interpret the raw blockchain data into human-understandable information?”

The blockchain is essentially a giant, append-only database. But unlike traditional databases, the data is encoded in compact binary formats designed for efficiency, not human readability. A transaction’s input field is just hex bytes. A log’s topics are 32-byte hashes. An address’s “token balance” isn’t stored anywhere directly—it must be computed by replaying all transfer events.

Block explorers are the translation layer between the raw blockchain and human users. They answer questions like:

  • What did this transaction actually do?
  • How much ETH does this address hold?
  • What tokens does this address own?
  • When was this block mined and by whom?
  • What happened in this smart contract call?

Building a block explorer means understanding every layer of the Ethereum data model—from raw RLP-encoded blocks to high-level decoded function calls.


Deep Theoretical Foundation

How Ethereum Stores Data

Ethereum is organized into three main data structures:

                    World State
                         |
         +---------------+---------------+
         |               |               |
     Accounts        Accounts        Accounts
    (EOA/Contract)  (EOA/Contract)  (EOA/Contract)
         |
    +----+----+
    |         |
 Storage   Code
  Trie    (bytecode)

State Trie: Maps addresses to account objects containing:

  • nonce: Number of transactions sent (EOA) or contracts created (contract)
  • balance: ETH balance in wei
  • storageRoot: Root hash of contract’s storage trie
  • codeHash: Hash of contract bytecode

Blocks contain:

  • Header (parent hash, state root, transaction root, receipts root, etc.)
  • Transactions list
  • Uncle/Ommer blocks (for PoW)

Transactions contain:

  • from: Sender address (derived from signature)
  • to: Recipient address (null for contract creation)
  • value: ETH value in wei
  • input: Encoded function call or contract bytecode
  • gas, gasPrice/maxFeePerGas/maxPriorityFeePerGas
  • Signature (v, r, s)

Receipts contain:

  • status: Success (1) or failure (0)
  • gasUsed: Actual gas consumed
  • logs: Array of event logs emitted

Understanding ABI Encoding

The Application Binary Interface (ABI) defines how to encode function calls and decode return values.

Function Call Encoding:

Input data = function_selector + encoded_arguments

function_selector = first 4 bytes of keccak256(function_signature)

Example:
  transfer(address,uint256)
  keccak256("transfer(address,uint256)") = 0xa9059cbb...
  selector = 0xa9059cbb

Encoded arguments:
  address: left-padded to 32 bytes
  uint256: left-padded to 32 bytes

Full input for transfer(0x1234..., 1000):
  0xa9059cbb
  0000000000000000000000001234567890123456789012345678901234567890
  00000000000000000000000000000000000000000000000000000000000003e8

Why This Matters for Block Explorers:

When you see a raw transaction like:

{
  "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  "input": "0xa9059cbb0000000000000000000000005a52e96bacdabb82fd05763e25335261b270efcb00000000000000000000000000000000000000000000000000000000003d0900"
}

You want to display:

Transfer 4,000,000 USDT to 0x5a52...Efcb

This requires:

  1. Knowing the contract (USDT)
  2. Having the ABI (or at least the function signature)
  3. Decoding the selector to find the function name
  4. Decoding the arguments according to their types

Event Logs and Topics

Smart contracts emit events for off-chain observability:

event Transfer(address indexed from, address indexed to, uint256 value);

emit Transfer(0xAlice, 0xBob, 1000);

This creates a log entry:

{
  "address": "0xTokenContract",
  "topics": [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",  // keccak256("Transfer(address,address,uint256)")
    "0x000000000000000000000000Alice_address_padded_to_32_bytes",
    "0x000000000000000000000000Bob_address_padded_to_32_bytes"
  ],
  "data": "0x00000000000000000000000000000000000000000000000000000000000003e8"  // 1000 encoded
}

Topic Structure:

  • topics[0]: Event signature hash (identifies the event type)
  • topics[1-3]: Indexed parameters (max 3)
  • data: Non-indexed parameters (ABI-encoded)

Why Indexed vs Non-Indexed?

  • Indexed parameters can be filtered/searched efficiently
  • Non-indexed parameters are cheaper to emit (no separate topic slot)
  • Anonymous events have no signature in topics[0]

Token Standards and Balance Tracking

ERC-20 and ERC-721 don’t store balances in a way you can directly query for “all tokens owned by address X.” Instead, you must:

  1. Index all Transfer events for every token contract
  2. Compute running balances by replaying: balance[to] += value; balance[from] -= value;
  3. Store the computed state in your database

This is why block explorers are fundamentally indexers—they transform event streams into queryable state.

Raw Blockchain Events:
  Transfer(0x0, Alice, 1000)  // Mint
  Transfer(Alice, Bob, 300)
  Transfer(Alice, Carol, 200)

Computed Token Balances:
  Alice: 500
  Bob: 300
  Carol: 200

The JSON-RPC Interface

Ethereum nodes expose a JSON-RPC API for querying blockchain data:

+-------------+     JSON-RPC      +------------------+
|   Your      | ----------------> |   Ethereum Node  |
|   Indexer   |                   |   (Geth/Erigon)  |
+-------------+ <---------------- +------------------+
                  Block/Tx Data

Key Methods:

Method Purpose
eth_blockNumber Get latest block number
eth_getBlockByNumber Get block with transactions
eth_getBlockByHash Get block by hash
eth_getTransactionByHash Get single transaction
eth_getTransactionReceipt Get receipt with logs
eth_getLogs Query logs with filters
eth_getBalance Get ETH balance
eth_getCode Get contract bytecode
eth_call Execute read-only call

Rate Limits and Providers:

  • Infura: 100k requests/day (free tier)
  • Alchemy: 300M compute units/month (free tier)
  • Running your own node: No limits, ~1TB storage

Concepts You Must Understand First

Before building, ensure you understand these concepts deeply:

1. Ethereum Data Structures

Chapter Reference: “Mastering Ethereum” Chapter 4 (Transactions) & Chapter 7 (Smart Contracts)

  • How blocks, transactions, and receipts are structured
  • The difference between transaction and receipt
  • How gas accounting works

2. ABI Encoding Specification

Reference: Solidity ABI Specification

  • Static vs dynamic types
  • How arrays and strings are encoded
  • Function selectors (first 4 bytes of keccak256)
  • Event topic encoding

3. ERC Token Standards

References:

4. JSON-RPC Protocol

Reference: Ethereum JSON-RPC

  • Request/response format
  • Batch requests for efficiency
  • Error codes and handling

5. Database Indexing Strategies

Reference: “Designing Data-Intensive Applications” Chapters 3 & 5

  • B-tree vs LSM-tree indexes
  • Composite indexes for multi-column queries
  • Trade-offs between write and read performance

6. Event-Driven Architecture

  • Handling chain reorganizations (reorgs)
  • Eventual consistency considerations
  • Idempotent event processing

7. WebSocket Real-Time Communication

  • Subscription-based updates
  • Connection management and reconnection
  • Backpressure handling

Questions to Guide Your Design

Indexer Architecture

  1. How will you handle chain reorganizations (reorgs)? What happens if block 1000 is replaced?
  2. Should you index from genesis or start from a recent block? What are the trade-offs?
  3. How will you parallelize block fetching without missing or duplicating data?
  4. How do you handle the indexer crashing mid-block? (Atomicity)
  5. Should you index all contracts or only “known” ones with verified ABIs?

Database Schema Design

  1. What are the primary query patterns? (By address, by block, by hash)
  2. How will you store decoded function calls? (Denormalized or separate table?)
  3. Should you store raw transaction data or just parsed fields?
  4. How will you handle token balances? (Materialized view vs computed on-demand)
  5. What indexes are essential for performance? What’s the write penalty?

ABI Handling

  1. Where will you get ABIs? (Etherscan API, 4byte.directory, user upload)
  2. How do you handle contracts without verified source code?
  3. Should you decode using known function signatures (4byte database) when ABI is unavailable?
  4. How do you handle proxy contracts? (delegatecall pattern)

Real-Time Updates

  1. How will you detect new blocks? (Polling vs WebSocket subscription)
  2. How do you push updates to connected web clients?
  3. What’s your strategy for handling burst traffic during high-activity periods?
  4. How do you handle the delay between block mining and database indexing?

Search Optimization

  1. How will you implement address search? (Exact match vs prefix search)
  2. Should transaction hashes be indexed? (Large index, rarely used)
  3. How do you handle searching by block range efficiently?
  4. Do you need full-text search for contract names/labels?

API Design

  1. What pagination strategy? (Offset vs cursor-based)
  2. How do you handle rate limiting?
  3. Should you cache responses? What’s the invalidation strategy?
  4. How do you version your API for breaking changes?

Thinking Exercise: Trace Through the System

Before writing code, trace through these scenarios on paper:

Exercise 1: Decode a Raw Transaction

Given this raw transaction input:

0xa9059cbb0000000000000000000000005a52e96bacdabb82fd05763e25335261b270efcb00000000000000000000000000000000000000000000000000000000003d0900
  1. Extract the function selector (first 4 bytes)
  2. Look up 0xa9059cbb in a function signature database
  3. Parse the remaining bytes according to transfer(address,uint256)
  4. Convert the address and amount to human-readable format

Exercise 2: Parse Transfer Events from Logs

Given this log from a transaction receipt:

{
  "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  "topics": [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
    "0x0000000000000000000000005a52e96bacdabb82fd05763e25335261b270efcb",
    "0x000000000000000000000000a9d1e08c7793af67e9d92fe308d5697fb81d3e43"
  ],
  "data": "0x00000000000000000000000000000000000000000000000000000000003d0900"
}
  1. Verify this is a Transfer event by checking topics[0]
  2. Extract the from address from topics[1]
  3. Extract the to address from topics[2]
  4. Decode the value from data
  5. Look up the token’s decimals (USDT = 6 decimals)
  6. Calculate the human-readable amount

Exercise 3: Query Address Transaction History

Design the database queries needed to show:

  1. The 20 most recent transactions for an address (as sender OR receiver)
  2. All ERC-20 token transfers involving this address
  3. Current token balances for this address

Consider:

  • How do you ORDER BY block number and transaction index efficiently?
  • How do you handle the UNION of sent and received transactions?
  • For token balances, do you query a pre-computed table or aggregate on-the-fly?

The Interview Questions They’ll Ask

JSON-RPC and Node Interaction

  1. What’s the difference between eth_getBlockByNumber with and without full transactions?
    • Without: Returns only transaction hashes (faster, less data)
    • With: Returns full transaction objects (one request vs N+1)
  2. How do you get all logs for a specific event across many contracts?
    • Use eth_getLogs with topic filter but null address
    • Be aware of block range limits (typically 2000-10000 blocks)
  3. What’s the difference between a transaction and a transaction receipt?
    • Transaction: What was submitted (intent)
    • Receipt: What actually happened (result, logs, gas used, status)
  4. How does eth_call differ from eth_sendTransaction?
    • eth_call: Read-only simulation, no state change, no gas cost
    • eth_sendTransaction: Actual execution, costs gas, changes state
  5. What happens when you query a block number that doesn’t exist yet?
    • Returns null or error depending on method
    • For latest, always returns the current head

ABI Encoding/Decoding

  1. What is a function selector and how is it computed?
    • First 4 bytes of keccak256(function_signature)
    • Signature uses canonical types (uint256, not uint)
  2. How are dynamic types (strings, arrays) encoded differently from static types?
    • Static: Inline at fixed offset
    • Dynamic: Pointer + data section at end
  3. How do you decode a function call when you don’t have the ABI?
    • Use 4byte.directory to look up selector
    • Attempt common signatures
    • Fall back to raw hex display
  4. What’s the difference between topics[0] and the other topics in an event log?
    • topics[0]: Event signature hash (unless anonymous)
    • topics[1-3]: Indexed parameter values (padded to 32 bytes)
  5. How do you handle events from proxy contracts?
    • Events are emitted with the proxy’s address
    • Need to resolve the implementation contract for ABI
    • Watch for delegate calls

Database and Indexing

  1. Why can’t you just query the node for “all transactions for address X”?
    • Nodes don’t index by arbitrary address
    • Would require scanning every block
    • This is exactly what indexers solve
  2. How do you handle chain reorganizations in your indexer?
    • Store block hashes, not just numbers
    • On reorg, delete blocks above the fork point
    • Re-index from the new canonical chain
  3. What’s the difference between computing token balances on-the-fly vs pre-computing?
    • On-the-fly: Always accurate, slow for large histories
    • Pre-computed: Fast queries, complex to maintain with reorgs
  4. How would you design indexes for “last 20 transactions for address”?
    • Composite index on (address, block_number DESC, tx_index DESC)
    • Separate tables for sent vs received, or UNION query
  5. How do you handle the “finality” problem for displayed data?
    • Mark recent blocks as “pending confirmation”
    • Use block confirmations count
    • Different strategies for PoW vs PoS

Real-Time Updates

  1. How do you detect new blocks in real-time?
    • WebSocket subscription to newHeads
    • Polling eth_blockNumber (fallback)
    • Node-specific APIs (Alchemy, Infura)
  2. What’s the difference between WebSocket and Server-Sent Events for pushing updates?
    • WebSocket: Bidirectional, more complex
    • SSE: Server-to-client only, simpler, HTTP-based
  3. How do you handle the delay between block creation and indexing completion?
    • Show “pending” state for recent blocks
    • Optimistic display before full indexing
    • Background job queue with status

Architecture and Scale

  1. How does Etherscan handle millions of requests per day?
    • Heavy caching (CDN, Redis)
    • Read replicas for database
    • Separate write (indexer) and read (API) paths
  2. What’s the approximate storage requirement for indexing all Ethereum mainnet data?
    • Raw block/tx data: ~1TB
    • With decoded events and indexes: 2-5TB
    • Archive node access for historical state

Hints in Layers

Layer 1: Project Setup and Node Connection

Start with a basic TypeScript project that can connect to an Ethereum node:

import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider(
  process.env.ETH_RPC_URL || "https://eth.llamarpc.com"
);

async function getLatestBlock() {
  const blockNumber = await provider.getBlockNumber();
  const block = await provider.getBlock(blockNumber, true); // true = include transactions
  console.log(`Block ${blockNumber}: ${block?.transactions.length} transactions`);
  return block;
}

Key insight: The true parameter includes full transaction objects. Without it, you only get hashes and need N additional RPC calls.

Layer 2: Block and Transaction Fetching

Build a basic indexer loop that processes blocks sequentially:

                      +----------------+
                      |   Start Block  |
                      +----------------+
                              |
                              v
+-------------------+  +----------------+  +------------------+
|  Fetch Block      |->|  Process Txs   |->|  Store in DB     |
|  eth_getBlock...  |  |  For each tx   |  |  Insert block,   |
+-------------------+  +----------------+  |  txs, logs       |
         ^                    |            +------------------+
         |                    v                     |
         |             +----------------+           |
         |             | Fetch Receipts |           |
         |             | eth_getTx...   |           |
         |             +----------------+           |
         |                    |                     |
         +--------------------+---------------------+
                              |
                              v
                      +----------------+
                      |  Next Block    |
                      +----------------+

Important: You need both the transaction AND the receipt. The receipt contains logs (events) and actual gas used.

Layer 3: ABI Decoding Infrastructure

Build or integrate an ABI decoder. ethers.js provides this:

const erc20Interface = new ethers.Interface([
  "function transfer(address to, uint256 amount)",
  "function approve(address spender, uint256 amount)",
  "event Transfer(address indexed from, address indexed to, uint256 value)",
]);

// Decode function call
const decoded = erc20Interface.parseTransaction({ data: tx.data });
console.log(`Function: ${decoded?.name}, Args: ${decoded?.args}`);

// Decode event log
const parsedLog = erc20Interface.parseLog({ topics: log.topics, data: log.data });
console.log(`Event: ${parsedLog?.name}, Args: ${parsedLog?.args}`);

Strategy for unknown contracts:

  1. Check if address is in your verified contracts table (with full ABI)
  2. Try common ABIs (ERC-20, ERC-721, etc.)
  3. Look up function selector in 4byte.directory
  4. Fall back to raw hex display

Layer 4: Event Parsing and Token Tracking

The Transfer event is the same across ERC-20 and ERC-721 (different semantics though):

Transfer(address indexed from, address indexed to, uint256 value)
Topic0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef

To track token balances:

For each Transfer event:
  if (from != 0x0) {
    token_balances[token][from] -= value
  }
  if (to != 0x0) {
    token_balances[token][to] += value
  }

Special cases:

  • from == 0x0: Mint (new tokens created)
  • to == 0x0: Burn (tokens destroyed)

Layer 5: Database Schema Design

Design tables for efficient querying:

+------------------+     +-------------------+     +------------------+
|     blocks       |     |   transactions    |     |      logs        |
+------------------+     +-------------------+     +------------------+
| number (PK)      |<----| block_number (FK) |     | tx_hash (FK)     |
| hash             |     | tx_hash (PK)      |<----| log_index (PK)   |
| parent_hash      |     | tx_index          |     | address          |
| timestamp        |     | from_address      |     | topic0           |
| miner            |     | to_address        |     | topic1           |
| gas_used         |     | value             |     | topic2           |
| gas_limit        |     | gas_used          |     | topic3           |
| base_fee         |     | gas_price         |     | data             |
| tx_count         |     | input             |     | decoded_name     |
+------------------+     | status            |     | decoded_args     |
                         | decoded_func      |     +------------------+
                         | decoded_args      |
                         +-------------------+

+------------------+     +-------------------+
| token_transfers  |     | token_balances    |
+------------------+     +-------------------+
| tx_hash (FK)     |     | address           |
| log_index        |     | token_address     |
| token_address    |     | balance           |
| from_address     |     | last_updated_block|
| to_address       |     +-------------------+
| value            |
| token_type       |  (ERC20, ERC721, ERC1155)
+------------------+

Critical Indexes:

CREATE INDEX idx_tx_from ON transactions(from_address, block_number DESC);
CREATE INDEX idx_tx_to ON transactions(to_address, block_number DESC);
CREATE INDEX idx_logs_address ON logs(address, block_number DESC);
CREATE INDEX idx_transfers_from ON token_transfers(from_address, block_number DESC);
CREATE INDEX idx_transfers_to ON token_transfers(to_address, block_number DESC);
CREATE INDEX idx_balances_address ON token_balances(address);

Layer 6: API Layer Implementation

Build a REST API with proper pagination:

                +-------------+
                |   Frontend  |
                +-------------+
                      |
                      v
+---------------------|---------------------+
|                   API                     |
|  +---------------+  +------------------+  |
|  | /blocks       |  | /address/:addr   |  |
|  | /block/:num   |  | /addr/:a/txs     |  |
|  | /tx/:hash     |  | /addr/:a/tokens  |  |
|  +---------------+  +------------------+  |
+------------------------------------------+
                      |
                      v
              +---------------+
              |   PostgreSQL  |
              +---------------+

Pagination Strategy (cursor-based is better for blockchain):

GET /address/0x123/transactions?cursor=18000000_99&limit=20

Response:
{
  "data": [...],
  "next_cursor": "17999980_45",
  "has_more": true
}

Cursor format: {block_number}_{tx_index} ensures stable pagination even as new blocks arrive.

Layer 7: Real-Time WebSocket Updates

Implement subscriptions for live data:

// Server-side
wss.on("connection", (ws) => {
  ws.on("message", (msg) => {
    const { subscribe, channel } = JSON.parse(msg);
    if (channel === "newBlocks") {
      // Add to subscribers list
      blockSubscribers.add(ws);
    }
  });
});

// When new block is indexed
function onBlockIndexed(block) {
  for (const ws of blockSubscribers) {
    ws.send(JSON.stringify({ type: "newBlock", data: block }));
  }
}

Subscription Types:

  • newBlocks: All new blocks
  • address:0x123: Transactions involving specific address
  • token:0x456: Transfers for specific token

Layer 8: Contract Verification and Source Code

To display contract source code (like Etherscan’s “Contract” tab):

  1. Accept source code upload with compiler settings
  2. Recompile and verify bytecode matches on-chain
  3. Store source code, ABI, and compiler version
  4. Enable decoded views for this contract
User uploads source.sol + compiler version
         |
         v
+------------------+     +-------------------+
|  Compile with    |     | Compare bytecode  |
|  same settings   |---->| with on-chain     |
+------------------+     +-------------------+
                               |
                    Match?     |     Mismatch?
                      +--------+--------+
                      |                 |
                      v                 v
              +-------------+    +--------------+
              | Store as    |    | Reject       |
              | verified    |    | verification |
              +-------------+    +--------------+

Real World Outcome

Web Interface: Homepage

+==============================================================================+
|  [ Logo ] Mini Explorer                        [ Search: Address/Tx/Block ]  |
+==============================================================================+

Latest Blocks                              Latest Transactions
+-------------------------------------+    +----------------------------------+
| Block   | Age    | Txs   | Gas Used|    | Tx Hash      | From   | To       |
|---------|--------|-------|---------|    |--------------|--------|----------|
| 18543210| 12s ago|  147  |  58.3%  |    | 0xabc1...    | 0x12...| 0x45...  |
| 18543209| 24s ago|  189  |  72.1%  |    | 0xdef2...    | 0x78...| Contract |
| 18543208| 36s ago|  156  |  61.8%  |    | 0x890a...    | 0x90...| 0x12...  |
| 18543207| 48s ago|  201  |  78.4%  |    | 0xbcd3...    | 0xab...| Contract |
| 18543206| 60s ago|  133  |  52.7%  |    | 0xef01...    | 0xcd...| 0xef...  |
+-------------------------------------+    +----------------------------------+
         [View All Blocks]                          [View All Transactions]

+------------------------------------------------------------------------------+
|  Network Stats                                                               |
|  ETH Price: $2,345.67  |  Gas: 25 gwei  |  Total Tx: 2.1B  |  Blocks: 18.5M |
+------------------------------------------------------------------------------+

Web Interface: Block Details Page

+==============================================================================+
|  Block #18543210                                                   [Copy]    |
+==============================================================================+

+------------------------------------------------------------------------------+
| Overview                                                                      |
+------------------------------------------------------------------------------+
| Block Height        | 18,543,210                                             |
| Timestamp           | Dec 27, 2024, 10:23:45 AM UTC (12 seconds ago)         |
| Transactions        | 147 transactions in this block                         |
| Withdrawals         | 16 withdrawals (32.5 ETH)                              |
+------------------------------------------------------------------------------+
| Block Hash          | 0x8a7b3c4d5e6f...9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3    |
| Parent Hash         | 0x7f6e5d4c3b2a...1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7    |
| State Root          | 0x1234567890ab...cdef1234567890abcdef1234567890abcd    |
+------------------------------------------------------------------------------+
| Fee Recipient       | 0xdafea492d9c6733ae3d56b7ed1adb60692c98bc5 (Lido)     |
| Total Difficulty    | 58,750,003,716,598,352,816,469                         |
| Size                | 98,432 bytes                                           |
+------------------------------------------------------------------------------+
| Gas Used            | 14,234,567 (47.45% of limit)                          |
|                     | [===================                    ] 30M limit    |
| Gas Limit           | 30,000,000                                            |
| Base Fee Per Gas    | 25.234 Gwei                                           |
| Burnt Fees          | 0.359 ETH                                             |
+------------------------------------------------------------------------------+

Transactions (147)
+------------------------------------------------------------------------------+
| Hash           | Method     | From      | To         | Value    | Fee      |
|----------------|------------|-----------|------------|----------|----------|
| 0xabc123...    | Transfer   | 0x12a...  | 0x45b...   | 2.5 ETH  | 0.003 ETH|
| 0xdef456...    | Swap       | 0x78c...  | Uniswap V3 | 0 ETH    | 0.008 ETH|
| 0x789abc...    | Approve    | 0x90d...  | USDT       | 0 ETH    | 0.001 ETH|
| ...            | ...        | ...       | ...        | ...      | ...      |
+------------------------------------------------------------------------------+
                              [Show More]

Web Interface: Transaction Details Page

+==============================================================================+
|  Transaction Details                                                          |
+==============================================================================+

+------------------------------------------------------------------------------+
| Transaction Hash    | 0xabc123def456789...                          [Copy]  |
| Status              | [Success] (Confirmed in 24 blocks)                    |
| Block               | 18543210 (Confirmed)                                  |
| Timestamp           | Dec 27, 2024, 10:23:45 AM UTC (12 minutes ago)        |
+------------------------------------------------------------------------------+

+------------------------------------------------------------------------------+
| From                | 0x5a52e96bacdabb82fd05763e25335261b270efcb            |
| To (Contract)       | 0xdAC17F958D2ee523a2206206994597C13D831ec7 (USDT)     |
| Value               | 0 ETH                                                  |
+------------------------------------------------------------------------------+

+------------------------------------------------------------------------------+
| Input Data (Decoded)                                                         |
+------------------------------------------------------------------------------+
| Function: transfer(address _to, uint256 _value)                              |
|                                                                              |
| Parameters:                                                                  |
|   _to     : 0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43                       |
|   _value  : 4000000 (4.0 USDT)                                               |
|                                                                              |
| [View Raw Input Data]                                                        |
+------------------------------------------------------------------------------+

+------------------------------------------------------------------------------+
| Transaction Fee                                                              |
+------------------------------------------------------------------------------+
| Gas Used            | 46,109 (of 60,000 limit)                              |
| Gas Price           | 25.234 Gwei                                           |
| Transaction Fee     | 0.001163 ETH ($2.73)                                  |
| Burnt               | 0.001048 ETH                                          |
| Savings             | 0.000115 ETH (to validator)                           |
+------------------------------------------------------------------------------+

+------------------------------------------------------------------------------+
| Event Logs (1)                                                               |
+------------------------------------------------------------------------------+
| #0  Transfer (address from, address to, uint256 value)                       |
|                                                                              |
|     from  : 0x5a52e96bacdabb82fd05763e25335261b270efcb                       |
|     to    : 0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43                       |
|     value : 4000000 (4.0 USDT)                                               |
+------------------------------------------------------------------------------+

Web Interface: Address Page

+==============================================================================+
|  Address 0x5a52e96bacdabb82fd05763e25335261b270efcb                          |
+==============================================================================+

+--------------------------------+  +------------------------------------------+
| Overview                       |  | More Info                                |
+--------------------------------+  +------------------------------------------+
| ETH Balance   | 12.456 ETH     |  | Token Tracker    | 15 ERC-20 tokens     |
| ETH Value     | $29,213.54     |  | Transactions     | 1,234 total          |
+--------------------------------+  +------------------------------------------+

+------------------------------------------------------------------------------+
| Token Holdings                                                               |
+------------------------------------------------------------------------------+
| Token          | Balance           | Value         | Contract               |
|----------------|-------------------|---------------|------------------------|
| USDT           | 10,500.00         | $10,500.00    | 0xdAC17F958D2ee...    |
| USDC           | 5,234.56          | $5,234.56     | 0xA0b86991c62...      |
| WETH           | 2.345             | $5,499.76     | 0xC02aaA39b22...      |
| UNI            | 125.50            | $876.55       | 0x1f9840a85d5...      |
| LINK           | 89.25             | $1,249.50     | 0x514910771AF...      |
+------------------------------------------------------------------------------+
                        [View All Token Holdings]

+------------------------------------------------------------------------------+
| Transactions                                                                 |
+------------------------------------------------------------------------------+
[ All ] [ Sent ] [ Received ] [ Token Transfers ] [ NFT Transfers ]

| Tx Hash      | Method   | Block     | Age      | From    | To       | Value  |
|--------------|----------|-----------|----------|---------|----------|--------|
| 0xabc123...  | Transfer | 18543210  | 12m ago  | (self)  | 0x45b... | 2.5 ETH|
| 0xdef456...  | Swap     | 18543102  | 2h ago   | (self)  | Uniswap  | 1.0 ETH|
| 0x789abc...  | IN       | 18542890  | 6h ago   | 0x12a...| (self)   | 5.0 ETH|
| 0xcde890...  | Approve  | 18542567  | 12h ago  | (self)  | USDT     | 0 ETH  |
+------------------------------------------------------------------------------+
                              [Load More]

Web Interface: Contract Page

+==============================================================================+
|  Contract 0xdAC17F958D2ee523a2206206994597C13D831ec7                         |
|  [ Verified ] Tether USD (USDT)                                              |
+==============================================================================+

[ Contract ] [ Read Contract ] [ Write Contract ] [ Transactions ] [ Events ]

+------------------------------------------------------------------------------+
| Contract Overview                                                            |
+------------------------------------------------------------------------------+
| Balance          | 0 ETH                                                     |
| Transactions     | 234,567,890                                              |
| Token Tracker    | Tether USD (USDT)                                        |
| Creator          | 0xc6cde7c39eb2f0f0095f41570af89efc2c1ea828               |
| Created At       | Block 4634748 (Nov 28, 2017)                             |
+------------------------------------------------------------------------------+

+------------------------------------------------------------------------------+
| Contract Source Code                                                         |
| Compiler: v0.4.18+commit.9cf6e910  |  Optimization: Yes (200 runs)          |
+------------------------------------------------------------------------------+
| File 1 of 1: TetherToken.sol                                                 |
|                                                                              |
| pragma solidity ^0.4.17;                                                     |
|                                                                              |
| library SafeMath {                                                           |
|     function mul(uint256 a, uint256 b) internal pure returns (uint256) {     |
|         if (a == 0) {                                                        |
|             return 0;                                                        |
|         }                                                                    |
|         uint256 c = a * b;                                                   |
|         assert(c / a == b);                                                  |
|         return c;                                                            |
|     }                                                                        |
|     // ... more source code                                                  |
| }                                                                            |
+------------------------------------------------------------------------------+

+------------------------------------------------------------------------------+
| Contract ABI                                                                 |
+------------------------------------------------------------------------------+
| [                                                                            |
|   {                                                                          |
|     "constant": true,                                                        |
|     "inputs": [],                                                            |
|     "name": "name",                                                          |
|     "outputs": [{"name": "", "type": "string"}],                             |
|     "payable": false,                                                        |
|     "stateMutability": "view",                                               |
|     "type": "function"                                                       |
|   },                                                                         |
|   // ... more ABI entries                                                    |
| ]                                                                            |
|                                                [Copy ABI] [Download JSON]    |
+------------------------------------------------------------------------------+

API Response Examples

GET /api/block/18543210

{
  "number": 18543210,
  "hash": "0x8a7b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
  "parentHash": "0x7f6e5d4c3b2a1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b",
  "timestamp": 1703671425,
  "miner": "0xdafea492d9c6733ae3d56b7ed1adb60692c98bc5",
  "gasUsed": "14234567",
  "gasLimit": "30000000",
  "baseFeePerGas": "25234000000",
  "transactionCount": 147,
  "transactions": [
    {
      "hash": "0xabc123...",
      "from": "0x5a52e96bacdabb82fd05763e25335261b270efcb",
      "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
      "value": "0",
      "gasUsed": "46109",
      "status": 1,
      "decodedInput": {
        "function": "transfer",
        "args": {
          "_to": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43",
          "_value": "4000000"
        }
      }
    }
  ]
}

GET /api/tx/0xabc123…

{
  "hash": "0xabc123def456789...",
  "status": 1,
  "blockNumber": 18543210,
  "blockHash": "0x8a7b3c4d...",
  "timestamp": 1703671425,
  "from": "0x5a52e96bacdabb82fd05763e25335261b270efcb",
  "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  "value": "0",
  "gasUsed": "46109",
  "gasPrice": "25234000000",
  "input": "0xa9059cbb0000000000000000000000005a52e96bacdabb82fd05763e25335261b270efcb00000000000000000000000000000000000000000000000000000000003d0900",
  "decodedInput": {
    "function": "transfer",
    "signature": "transfer(address,uint256)",
    "args": {
      "_to": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43",
      "_value": "4000000"
    }
  },
  "logs": [
    {
      "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
      "topics": ["0xddf252ad...", "0x000...5a52e96...", "0x000...a9d1e08..."],
      "data": "0x00000000000000000000000000000000000000000000000000000000003d0900",
      "decoded": {
        "event": "Transfer",
        "args": {
          "from": "0x5a52e96bacdabb82fd05763e25335261b270efcb",
          "to": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43",
          "value": "4000000"
        }
      }
    }
  ],
  "tokenTransfers": [
    {
      "token": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
      "tokenName": "Tether USD",
      "tokenSymbol": "USDT",
      "tokenDecimals": 6,
      "from": "0x5a52e96bacdabb82fd05763e25335261b270efcb",
      "to": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43",
      "value": "4000000",
      "valueFormatted": "4.0"
    }
  ]
}

GET /api/address/0x5a52…/transactions?limit=20&cursor=18543210_99

{
  "address": "0x5a52e96bacdabb82fd05763e25335261b270efcb",
  "balance": "12456000000000000000",
  "transactionCount": 1234,
  "data": [
    {
      "hash": "0xabc123...",
      "blockNumber": 18543210,
      "txIndex": 45,
      "timestamp": 1703671425,
      "from": "0x5a52e96bacdabb82fd05763e25335261b270efcb",
      "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
      "value": "0",
      "direction": "out",
      "method": "Transfer"
    }
  ],
  "nextCursor": "18543102_12",
  "hasMore": true
}

GET /api/address/0x5a52…/tokens

{
  "address": "0x5a52e96bacdabb82fd05763e25335261b270efcb",
  "tokens": [
    {
      "contract": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
      "name": "Tether USD",
      "symbol": "USDT",
      "decimals": 6,
      "balance": "10500000000",
      "balanceFormatted": "10500.0"
    },
    {
      "contract": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "name": "USD Coin",
      "symbol": "USDC",
      "decimals": 6,
      "balance": "5234560000",
      "balanceFormatted": "5234.56"
    }
  ]
}

Complete Architecture Diagram

+==============================================================================+
|                           BLOCK EXPLORER ARCHITECTURE                        |
+==============================================================================+

                                  USERS
                                    |
                                    v
+------------------------------------------------------------------------------+
|                             FRONTEND (React/Vue)                             |
|  +----------------+  +----------------+  +----------------+  +-----------+  |
|  | Block View     |  | Transaction    |  | Address View   |  | Search    |  |
|  |                |  | View           |  |                |  |           |  |
|  +----------------+  +----------------+  +----------------+  +-----------+  |
|                               |                                              |
|                    WebSocket  |  HTTP/REST                                   |
+------------------------------------------------------------------------------+
                                |
                                v
+------------------------------------------------------------------------------+
|                              API LAYER                                       |
|  +------------------+  +------------------+  +-------------------+           |
|  | REST Endpoints   |  | WebSocket        |  | Rate Limiter      |           |
|  | /block/:num      |  | Server           |  | & Cache           |           |
|  | /tx/:hash        |  | newBlocks        |  | (Redis)           |           |
|  | /address/:addr   |  | addressActivity  |  |                   |           |
|  +------------------+  +------------------+  +-------------------+           |
+------------------------------------------------------------------------------+
                                |
                                v
+------------------------------------------------------------------------------+
|                          DATABASE (PostgreSQL)                               |
|                                                                              |
|   +----------+    +-------------+    +--------+    +-----------------+      |
|   | blocks   |<-->| transactions|<-->| logs   |<-->| token_transfers |      |
|   +----------+    +-------------+    +--------+    +-----------------+      |
|                                                             |               |
|   +---------------+    +------------------+                 v               |
|   | contracts     |    | verified_sources |    +------------------+         |
|   | (metadata)    |    | (source, ABI)    |    | token_balances   |         |
|   +---------------+    +------------------+    +------------------+         |
+------------------------------------------------------------------------------+
                                ^
                                |
+------------------------------------------------------------------------------+
|                            INDEXER SERVICE                                   |
|                                                                              |
|   +-------------------+                                                      |
|   | Block Processor   |  For each new block:                                 |
|   |                   |    1. Fetch block + transactions                     |
|   |   +-----------+   |    2. Fetch all receipts                            |
|   |   | Fetch     |   |    3. Decode function calls (if ABI known)          |
|   |   | Block     |   |    4. Parse event logs                              |
|   |   +-----------+   |    5. Extract token transfers                        |
|   |        |          |    6. Update token balances                          |
|   |        v          |    7. Store in database                              |
|   |   +-----------+   |    8. Notify WebSocket subscribers                   |
|   |   | Fetch     |   |                                                      |
|   |   | Receipts  |   |                                                      |
|   |   +-----------+   |                                                      |
|   |        |          |                                                      |
|   |        v          |                                                      |
|   |   +-----------+   |                                                      |
|   |   | Decode    |   |                                                      |
|   |   | & Parse   |   |                                                      |
|   |   +-----------+   |                                                      |
|   |        |          |                                                      |
|   |        v          |                                                      |
|   |   +-----------+   |                                                      |
|   |   | Store     |   |                                                      |
|   |   +-----------+   |                                                      |
|   +-------------------+                                                      |
|             ^                                                                |
|             |                                                                |
+-------------|----------------------------------------------------------------+
              |
              v
+------------------------------------------------------------------------------+
|                          ETHEREUM NODE                                       |
|  +--------------------+  +--------------------+  +--------------------+      |
|  | Infura/Alchemy     |  | Local Geth/Erigon  |  | Archive Node       |      |
|  | (managed)          |  | (self-hosted)      |  | (historical state) |      |
|  +--------------------+  +--------------------+  +--------------------+      |
|                                                                              |
|  JSON-RPC API:                                                               |
|    eth_getBlockByNumber, eth_getTransactionReceipt, eth_getLogs, etc.        |
+------------------------------------------------------------------------------+

Data Flow: Indexing a New Block

+------------------+
| 1. Detect New    |     eth_blockNumber or WebSocket subscription
|    Block         |     returns block 18543210
+------------------+
         |
         v
+------------------+
| 2. Fetch Block   |     eth_getBlockByNumber(18543210, true)
|    + Transactions|     Returns block header + 147 transaction objects
+------------------+
         |
         v
+------------------+
| 3. Fetch All     |     eth_getTransactionReceipt(tx_hash) x 147
|    Receipts      |     Returns status, gasUsed, logs for each
+------------------+     (Use batch RPC for efficiency)
         |
         v
+------------------+
| 4. Decode        |     For each transaction:
|    Function Calls|       - Extract selector (first 4 bytes of input)
+------------------+       - Lookup ABI or use 4byte.directory
         |                 - Decode arguments
         v
+------------------+
| 5. Parse Event   |     For each log:
|    Logs          |       - Match topic[0] to known event signatures
+------------------+       - Decode indexed params from topics[1-3]
         |                 - Decode data field
         v
+------------------+
| 6. Extract Token |     For Transfer events:
|    Transfers     |       - Identify token contract
+------------------+       - Extract from, to, value
         |                 - Determine ERC-20 vs ERC-721 vs ERC-1155
         v
+------------------+
| 7. Update Token  |     For each transfer:
|    Balances      |       - Decrement sender balance
+------------------+       - Increment receiver balance
         |                 - Handle mint (from=0x0) and burn (to=0x0)
         v
+------------------+
| 8. Database      |     BEGIN TRANSACTION
|    Transaction   |       INSERT INTO blocks ...
+------------------+       INSERT INTO transactions ... (bulk)
         |                 INSERT INTO logs ... (bulk)
         v                 INSERT INTO token_transfers ... (bulk)
+------------------+       UPSERT token_balances ...
| 9. Notify        |     COMMIT
|    WebSocket     |
|    Subscribers   |     Push new block to subscribed clients
+------------------+

Books That Will Help

Book Author(s) Why It Helps
Mastering Ethereum Andreas M. Antonopoulos & Gavin Wood Essential reference for understanding transactions, receipts, events, and the EVM. Chapter 7 on events is particularly relevant.
Designing Data-Intensive Applications Martin Kleppmann Crucial for understanding database design, indexing strategies, and handling streaming data. Chapter 3 on storage engines and Chapter 11 on stream processing directly apply.
Building Microservices Sam Newman Helpful for designing the API layer and understanding how to split the indexer from the API service.
Web Scalability for Startup Engineers Artur Ejsmont Practical guidance on caching strategies, database scaling, and handling high traffic.
High Performance Browser Networking Ilya Grigorik For understanding WebSocket optimization and real-time communication patterns.

Additional Resources

Ethereum JSON-RPC

ABI and Encoding

Libraries

  • ethers.js - Ethereum library for TypeScript/JavaScript
  • viem - Modern TypeScript Ethereum library
  • web3.py - Python Ethereum library

Open Source Block Explorers

  • Blockscout - Full-featured open source explorer (Elixir)
  • Otterscan - Fast block explorer for Erigon nodes
  • Expedition - Simple React-based explorer

Database and Indexing

  • The Graph - Decentralized indexing protocol
  • Subsquid - Blockchain indexing framework
  • Ponder - Framework for building blockchain indexers

Token Standards


Learning Milestones

Track your progress with these milestones:

Milestone 1: Basic Block/Transaction Display

  • Connect to Ethereum node via JSON-RPC
  • Fetch and display latest blocks
  • Fetch and display transactions within a block
  • Display basic block details (hash, timestamp, gas used)

You understand: How to query the blockchain

Milestone 2: Transaction Decoding

  • Decode function calls using known ABIs
  • Display human-readable function names and arguments
  • Handle unknown contracts gracefully (show raw hex)
  • Implement 4byte.directory lookup for unknown selectors

You understand: ABI encoding

Milestone 3: Event Log Parsing

  • Parse Transfer events from receipts
  • Identify ERC-20 vs ERC-721 transfers
  • Display decoded event arguments
  • Handle multiple events per transaction

You understand: Ethereum events and indexing

Milestone 4: Address Pages with Token Balances

  • Track token transfers in database
  • Compute and display token balances
  • Show transaction history for addresses
  • Handle chain reorganizations

You understand: State reconstruction from events

Milestone 5: Real-Time Updates

  • Subscribe to new blocks via WebSocket
  • Push updates to connected frontend clients
  • Handle disconnections and reconnections
  • Display “pending” confirmations

You understand: Real-time blockchain data

Milestone 6: Search and Scale

  • Implement address/hash search
  • Add proper pagination (cursor-based)
  • Optimize database queries with indexes
  • Add caching for frequently accessed data

You understand: Blockchain data infrastructure


Time Estimate

Phase Duration Description
Setup & Node Connection 2-3 days Project structure, RPC connection, basic fetching
Database Schema 2-3 days Design schema, set up PostgreSQL, create migrations
Indexer Core 3-5 days Block processing, receipt fetching, event parsing
ABI Decoding 2-3 days Function/event decoding, signature lookup
Token Tracking 2-3 days Balance computation, transfer indexing
API Layer 3-4 days REST endpoints, pagination, caching
Frontend 4-5 days React app, block/tx/address pages
WebSocket Updates 2-3 days Real-time subscriptions, push notifications
Polish & Testing 2-3 days Error handling, edge cases, documentation
Total 2-3 weeks  

Prerequisites

Before starting this project, you should have completed:

  1. Project 9: ERC-20 Token from Scratch - Understanding token standards and events
  2. Basic web development - React/Vue, REST APIs, PostgreSQL
  3. Familiarity with async programming - Promises, async/await in TypeScript

Recommended but not required:

  • Experience with WebSockets
  • Basic understanding of database indexing

Building a block explorer teaches you that the blockchain is just data—complex, encoded, distributed data. But once you can read it, parse it, and present it, you become a translator between the raw cryptographic world and human understanding. And that’s a skill that applies far beyond just block explorers.