Real-time Bitcoin blockchain visualizer with chain reorganization detection, historical block browsing, and compliance-grade PDF exports.
Kaalvti turns Bitcoin's raw blockchain data into an interactive, explorable visual. Think Mempool.space meets Excalidraw — but purpose-built for chain structure analysis, reorg auditing, and compliance reporting.
It connects to public Bitcoin APIs (no node required), builds a live directed graph of the chain, automatically detects fork events, and lets you travel back to any date in Bitcoin's history.
The main screen renders Bitcoin blocks as an interactive horizontal tree using D3.js. Blocks flow left to right, newest on the right. You can scroll to zoom, drag to pan, and click any block to open its detail panel.
- Canonical chain — solid black nodes running along the center axis
- Orphaned / stale blocks — dashed pink border, branching downward from their parent
- Each node shows block height, miner name, timestamp, and block size
- Handles forks visually — if two miners produced blocks at the same height, both appear and the losing block is marked STALE
Use the date picker in the header to jump to any date in Bitcoin's history, back to the genesis block on January 3, 2009. The app estimates the correct block height using the 10-minute block interval, fetches and displays the 15 blocks nearest to midnight UTC on that date, and switches to Historical mode with an amber banner. Hit × to return to live data instantly.
The left sidebar is the live reorg event feed. When running, the backend polls Mempool.space every 30 seconds and compares block hashes at each height against the database. If two different hashes exist at the same height — a chain reorganization — it:
- Logs the event to PostgreSQL with both block hashes, depths, miner attribution, and timestamps
- Emits a WebSocket event to all connected browsers
- Shows a pink toast notification: "⚡ Reorg detected at block #XXXXXX"
- Adds the event to the left sidebar feed
Why it says "No reorgs detected yet": Bitcoin mainnet reorgs are rare — they happen a handful of times per year, usually 1-block deep, when two miners find a valid block within seconds of each other. The sidebar is correct and healthy when empty. It is actively monitoring; it just hasn't seen one since you started the server. Historical reorgs from before your server started will not appear unless you seed the database manually.
Click any block node to slide open a right-side panel with three tabs:
- Overview — height, hash, previous hash, timestamp, miner, size, transaction count, difficulty, merkle root, nonce, version, bits — all copyable
- Raw Data — full JSON response from the API
- Export — one-click compliance PDF if a reorg event is associated with this block
Every detected reorg event gets a unique shareable URL: /event/:id. Visiting it loads a full detail page with timing, block hashes, miner attribution, and a plain English summary. Open Graph meta tags are set dynamically so link previews work in Slack, Twitter, and other platforms.
On any reorg event page, clicking "Export Compliance Report (PDF)" generates a server-side PDF containing:
- Event ID and detection timestamp
- Block height and fork depth
- Canonical and competing block hashes
- Miner attribution for both sides
- Plain English summary of what happened and whether it was resolved
- Kaalvti branding and generation timestamp
Used by exchanges, custodians, and blockchain analytics teams to document reorg incidents for internal audit trails or regulatory filings.
All data is accessible as JSON. The API is CORS-enabled and returns structured responses.
| Method | Path | Description |
|---|---|---|
| GET | /api |
Endpoint index |
| GET | /api/events |
List all reorg events (paginated) |
| GET | /api/events/:id |
Single event detail |
| GET | /api/events/:id/export.pdf |
Download compliance PDF |
| GET | /api/chain/tip |
Current chain tip height and hash |
| GET | /api/chain/recent |
Latest 15 blocks |
| GET | /api/chain/blocks?height=:n |
Blocks around a given height |
| GET | /api/chain/at?date=YYYY-MM-DD |
Blocks nearest to a given UTC date |
Bitcoin reorgs, even 1-block deep, can cause double-spend risk windows. Kaalvti gives operations teams a real-time alert when a reorg occurs and a one-click PDF export for the incident record — without needing to run a full Bitcoin node.
The date jump feature lets you pull up any day in Bitcoin's history and see exactly which miners were active, what blocks were produced, and at what times. Useful for post-hoc analysis of mining pool behavior, hashrate distribution changes, or anomaly detection.
The compliance PDF export produces a structured, branded document covering every material fact about a reorg event. It answers the five questions an auditor asks: what happened, when, at what height, which blocks were involved, and who mined them.
The chain graph makes fork structure immediately visual. You can see orphaned blocks, their parent relationships, and which path the network ultimately chose — without parsing raw block headers.
The interactive visualizer makes it easy to explain how the blockchain actually works as a data structure — not a metaphor. The branching behavior during a fork is directly visible.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, D3.js, Tailwind CSS |
| Backend | Node.js, Express |
| Database | PostgreSQL 15 (Supabase) |
| Realtime | Socket.io |
| PDF Export | Puppeteer (headless Chrome) |
| Data Sources | Mempool.space API, Blockstream Esplora API |
| Hosting | Render (Docker) |
| Tests | Vitest (client), Jest + Supertest (server) |
No Bitcoin node required. All data comes from public APIs.
- Node.js 18+
- PostgreSQL 15+
cd server && npm install
cd ../client && npm install# macOS with Homebrew
brew services start postgresql@15
createdb kaalvti
psql kaalvti -f server/schema.sqlcp .env.example server/.env
# Edit server/.env if your Postgres URL differs from the defaultDefault server/.env:
DATABASE_URL=postgresql://localhost:5432/kaalvti
PORT=3001
MEMPOOL_API=https://mempool.space/api
BLOCKSTREAM_API=https://blockstream.info/api
# Terminal 1 — backend (port 3001)
cd server && npm run dev
# Terminal 2 — frontend (port 3000)
cd client && npm run devcd server && npm test # 33 tests
cd client && npm test # 29 testsThis project is deployed using:
- Render — hosts the Docker container (web service, free tier)
- Supabase — managed PostgreSQL database (free tier)
- Create a free project at supabase.com
- Go to SQL Editor → run
server/schema.sql - Go to Project Settings → Database → Connection String → Session pooler → copy the URI
- Go to render.com → New Web Service → connect
Hijanhv/kaalvti - Set Environment to
Docker(uses the includedDockerfile) - Add these environment variables:
| Key | Value |
|---|---|
NODE_ENV |
production |
PORT |
3001 |
MEMPOOL_API |
https://mempool.space/api |
BLOCKSTREAM_API |
https://blockstream.info/api |
PUPPETEER_EXECUTABLE_PATH |
/usr/bin/chromium |
DB_HOST |
<your-supabase-pooler-host> |
DB_PORT |
6543 |
DB_NAME |
postgres |
DB_USER |
postgres.<your-project-ref> |
DB_PASSWORD |
<your-supabase-password> |
- Click Deploy — frontend and backend are served from the same URL.
docker build -t kaalvti .
docker run -p 3001:3001 \
-e NODE_ENV=production \
-e DB_HOST=your-host \
-e DB_PORT=6543 \
-e DB_NAME=postgres \
-e DB_USER=postgres.yourref \
-e DB_PASSWORD=yourpassword \
-e PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \
kaalvti-- Blocks fetched from the chain (live and historical)
blocks (
hash TEXT PRIMARY KEY,
height INTEGER,
timestamp BIGINT,
miner TEXT,
size INTEGER,
tx_count INTEGER,
prev_hash TEXT,
is_orphan BOOLEAN DEFAULT false,
raw_data JSONB,
created_at TIMESTAMPTZ
)
-- Detected chain reorganization events
reorg_events (
id UUID PRIMARY KEY,
detected_at TIMESTAMPTZ,
height INTEGER,
canonical_hash TEXT,
competing_hash TEXT,
depth INTEGER,
resolved_at TIMESTAMPTZ,
miner_a TEXT,
miner_b TEXT,
status TEXT, -- 'detected' | 'resolved'
raw_data JSONB
)Browser (React + D3)
│
├── REST /api/* ──────────────┐
└── WebSocket /socket.io │
▼
Express Server
│
┌─────────────┼──────────────┐
▼ ▼ ▼
Mempool.space PostgreSQL Puppeteer
API (live + (blocks + (PDF
historical) reorg_events) export)
│
Reorg Poller
(30s interval)
kaalvti/
├── client/
│ └── src/
│ ├── components/
│ │ ├── ChainGraph.jsx D3 directed graph
│ │ ├── EventSidebar.jsx Live reorg event feed
│ │ ├── BlockDrawer.jsx Right-side block detail panel
│ │ ├── DateJump.jsx Date picker / historical navigation
│ │ ├── ReorgToast.jsx Live WebSocket notification
│ │ └── ComplianceButton.jsx PDF export trigger
│ ├── pages/
│ │ ├── Home.jsx Main visualizer page
│ │ ├── EventPage.jsx /event/:id permanent URL
│ │ └── DocsPage.jsx /docs API reference page
│ └── lib/
│ ├── api.js Fetch wrappers for all endpoints
│ └── d3-chain.js D3 tree layout and render logic
├── server/
│ ├── index.js Express + Socket.io entry point
│ ├── db.js PostgreSQL pool
│ ├── poller.js 30s reorg detection loop
│ ├── schema.sql Database schema
│ └── routes/
│ ├── chain.js /api/chain/* endpoints
│ ├── events.js /api/events endpoints
│ └── export.js /api/events/:id/export.pdf
├── .env.example
└── README.md