Atlas is a minimal, fast blockchain explorer for custom Ethereum L2 networks. It replaces Blockscout with a focused feature set, trading breadth for simplicity and performance.
Target users: Development team, community members, general public.
- Simplicity - Lean codebase, minimal dependencies, easy to operate
- Performance - Fast queries, responsive UI, efficient indexing
- Full history - Complete chain indexing with reindex capability
- NFT-first - Rich NFT metadata indexing and display
- Multi-chain support (single L2 only)
- ERC-1155 support (ERC-721 only)
- User accounts or authentication
- Smart contract IDE/debugging
- Gas price oracles or analytics dashboards
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ React │────▶│ Rust API │────▶│ Postgres │
│ Frontend │ │ (Axum) │ │ │
└─────────────┘ └──────┬──────┘ └─────────────┘
│ ▲
│ │
┌──────▼──────┐ │
│ Indexer │────────────┘
│ (async) │
└──────┬──────┘
│
┌──────▼──────┐
│ L2 Node │
│ (RPC) │
└─────────────┘
| Component | Tech | Purpose |
|---|---|---|
| API Server | Rust (Axum) | REST API, serves frontend |
| Indexer | Rust (tokio) | Polls RPC, indexes blocks/txs/NFTs |
| Database | PostgreSQL | Persistent storage, full-text search |
| Frontend | React | UI, can be SSR or SPA |
| Metadata Fetcher | Rust (async) | Resolves and caches NFT tokenURIs |
- number: u64 (PK)
- hash: bytes32
- parent_hash: bytes32
- timestamp: u64
- gas_used: u64
- gas_limit: u64
- transaction_count: u32
- indexed_at: timestamp
- hash: bytes32 (PK)
- block_number: u64 (FK)
- block_index: u32
- from_address: address
- to_address: address (nullable, for contract creation)
- value: u256
- gas_price: u256
- gas_used: u64
- input_data: bytes
- status: bool
- contract_created: address (nullable)
- timestamp: u64
- address: bytes20 (PK)
- is_contract: bool
- first_seen_block: u64
- tx_count: u32
- balance: u256 (optional, can be fetched live)
- address: bytes20 (PK)
- name: string
- symbol: string
- total_supply: u64 (if enumerable)
- first_seen_block: u64
- contract_address: bytes20 (PK)
- token_id: u256 (PK)
- owner: address
- token_uri: string
- metadata_fetched: bool
- metadata: jsonb (nullable)
- image_url: string (nullable)
- last_transfer_block: u64
- id: serial (PK)
- tx_hash: bytes32 (FK)
- log_index: u32
- contract_address: bytes20
- token_id: u256
- from_address: address
- to_address: address
- block_number: u64
- timestamp: u64
- View latest blocks with pagination
- Block detail page (hash, txs, gas, timestamp)
- Navigate between blocks (prev/next)
- View transactions in a block
- Transaction detail page (hash, from, to, value, gas, input data, status)
- Decode common function selectors (transfer, approve, etc.)
- View address balance (live RPC call)
- List transactions for address (sent/received)
- Identify contract vs EOA
- List NFTs owned by address
- List all indexed ERC-721 contracts
- View NFT collection page (name, symbol, tokens)
- View individual NFT (image, metadata, owner, transfer history)
- NFT transfer history per token
- Search by transaction hash
- Search by address
- Search by block number/hash
- Search by NFT token name (from metadata)
- Index blocks from genesis or configured start block
- Index transactions and receipts
- Detect and index ERC-721 contracts (via Transfer events)
- Queue NFT metadata fetching
- Support reindex from scratch (wipe and rebuild)
- Resume from last indexed block on restart
- Fetch and cache tokenURI metadata
- Handle IPFS URIs (via gateway)
- Handle HTTP URIs
- Store and display NFT attributes
- Retry failed metadata fetches
- Full-text search on NFT names/descriptions
- Search NFTs by attribute values
- Real-time block updates (websocket or polling)
- Copy-to-clipboard for addresses/hashes
- Mobile-responsive design
- Loading states and error handling
- Upload and verify Solidity source
- Display verified source code
- Decode transaction input against verified ABI
- Documented REST API
- Rate limiting
- API usage stats
| Query Type | Detection | Implementation |
|---|---|---|
| Tx hash | 66 chars, starts with 0x | Direct lookup |
| Address | 42 chars, starts with 0x | Direct lookup |
| Block number | Numeric | Direct lookup |
| Block hash | 66 chars, starts with 0x | Direct lookup |
| NFT/Token name | String | Postgres full-text search on metadata |
- Check last indexed block in DB
- If reindex flag set, truncate tables and start from genesis/config
- Otherwise, resume from last indexed block + 1
- Fetch block via
eth_getBlockByNumber(with txs) - Fetch receipts via
eth_getBlockReceipts(or individual) - Parse Transfer events for ERC-721 detection
- Insert block, transactions, addresses, NFT data
- Queue metadata fetch jobs for new NFTs
- Commit transaction
- Update last indexed block
- Sleep or continue based on head distance
- Separate async task pool
- Fetch tokenURI from contract
- Resolve URI (IPFS via configurable gateway, HTTP direct)
- Parse JSON metadata
- Store in database
- Retry with exponential backoff on failure
[rpc]
url = "https://your-l2-rpc.example.com"
requests_per_second = 100 # rate limit
[database]
url = "postgres://user:pass@localhost/atlas"
max_connections = 20
[indexer]
start_block = 0 # or "genesis"
batch_size = 100
reindex = false
[metadata]
ipfs_gateway = "https://ipfs.io/ipfs/"
fetch_workers = 4
retry_attempts = 3
[server]
host = "0.0.0.0"
port = 3000GET /api/blocks- List blocks (paginated)GET /api/blocks/:number- Block detailGET /api/blocks/:number/transactions- Transactions in block
GET /api/transactions/:hash- Transaction detail
GET /api/addresses/:address- Address detailGET /api/addresses/:address/transactions- Address transactionsGET /api/addresses/:address/nfts- NFTs owned by address
GET /api/nfts/collections- List NFT contractsGET /api/nfts/collections/:address- Collection detailGET /api/nfts/collections/:address/tokens- Tokens in collectionGET /api/nfts/collections/:address/tokens/:id- Token detailGET /api/nfts/collections/:address/tokens/:id/transfers- Token transfers
GET /api/search?q=:query- Universal search
| Layer | Choice | Rationale |
|---|---|---|
| Language | Rust | Performance, safety, low resource usage |
| Web framework | Axum | Async, ergonomic, well-maintained |
| Database | PostgreSQL | Full-text search, JSONB, reliable |
| ORM/Query | SQLx | Compile-time checked queries, async |
| HTTP client | reqwest | Async, well-maintained |
| RPC | ethers-rs or alloy | Ethereum types and RPC |
| Frontend | React | Familiar, adequate for scope |
| Styling | Tailwind CSS | Fast iteration, no custom CSS overhead |
- 2 vCPU, 4GB RAM (indexer + API)
- PostgreSQL 14+
- Network access to L2 RPC
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: atlas
POSTGRES_USER: atlas
POSTGRES_PASSWORD: atlas
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
atlas:
build: .
environment:
DATABASE_URL: postgres://atlas:atlas@postgres/atlas
RPC_URL: ${RPC_URL}
ports:
- "3000:3000"
depends_on:
- postgres
volumes:
pgdata:| Risk | Impact | Mitigation |
|---|---|---|
| RPC rate limiting | Slow indexing | Configurable rate limits, batch requests |
| Large metadata (images) | Storage bloat | Store URLs only, don't cache images |
| IPFS gateway unreliable | Missing metadata | Multiple gateway fallbacks, retry queue |
| Chain reorgs | Data inconsistency | Track finalized blocks, reorg handling |
| Full history = slow initial sync | Long bootstrap | Configurable start block, progress API |
-
Image caching - Should we proxy/cache NFT images or just link to source?
- Recommendation: Link to source initially, add caching later if needed
-
Reorg handling - How deep can reorgs be on your L2?
- Need to know finality guarantees to set confirmation depth
-
Token standards - Any custom ERC-721 extensions in use?
- May need custom event parsing
-
Internal transactions - Do you need to trace internal calls?
- Requires debug/trace APIs, significantly more complex
Not providing time estimates per instructions. Work breakdown:
Phase 1: Foundation
- Database schema and migrations
- Indexer core (blocks, transactions)
- Basic API endpoints
Phase 2: NFT Indexing
- ERC-721 detection and indexing
- Metadata fetching pipeline
- NFT API endpoints
Phase 3: Frontend
- Block/transaction views
- Address pages
- NFT gallery and detail views
- Search
Phase 4: Polish
- Real-time updates
- Error handling
- Performance optimization
- Documentation
- Indexer keeps up with chain head (< 10 block lag)
- API p99 latency < 200ms
- Search returns results < 500ms
- 95%+ NFT metadata successfully fetched
- Zero data loss on restart/crash