Important
Demo Project — This is a demonstration project showcasing the Qubic.Net framework and Qubic Core Bob JSON-RPC interface. It is not meant for production use. Use it as a reference for building your own Qubic applications with the Qubic.Net NuGet packages.
Tip
Check the current version here: https://explorer-demo.qubic.tools
A blockchain explorer for Qubic that indexes data from a Bob node via the Qubic.Bob library, stores it in ClickHouse for efficient analytics, and provides a web interface for exploring ticks, transactions, transfers, and addresses.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Bob Node │────>│ Indexer │────>│ ClickHouse │<────│ API Server │
│ (Qubic.Bob) │ │ (.NET 10) │ │ │ │ (.NET 10) │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
│
v
┌─────────────────┐
│ Frontend │
│ (Nuxt 3) │
└─────────────────┘
- Tick Browser: View all ticks with transaction and log counts
- Transaction Explorer: Search and view transaction details with associated logs
- Transfer Tracking: Browse all transfers (QU and assets) with filtering
- Address View: See address balance, incoming/outgoing transactions and transfers
- Global Search: Search by tick number, transaction hash, or address
- Real-time Updates: Live updates via SignalR when new ticks arrive
- Analytics: Network statistics, epoch stats, holder distribution, miner flow tracking
- Configurable Indexing: Start from any tick or resume from last indexed
- Self-Initializing: Database schema is created automatically on first startup
qubic-explorer/
├── src/
│ ├── QubicExplorer.Indexer/ # Indexer service (subscribes to Bob node via Qubic.Bob)
│ ├── QubicExplorer.Api/ # REST API + SignalR hub
│ └── QubicExplorer.Shared/ # Shared models, DTOs, and schema definitions
├── frontend/ # Nuxt 3 web application
├── docker/
│ ├── clickhouse/ # ClickHouse schema (fallback)
│ └── nginx/ # Reverse proxy config
├── docker-compose.yml # Full stack deployment
├── docker-compose.override.yml # Development overrides
└── README.md
# Start all services
docker compose up -d
# View logs
docker compose logs -f
# Stop all services
docker compose down
# Full reset (removes database volume)
docker compose down -vThe explorer will be available at:
- Frontend: http://localhost (via nginx reverse proxy)
- API: http://localhost:5000 (direct)
- API Docs (Swagger): http://localhost:5000/swagger (development only)
- ClickHouse: http://localhost:8123 (development only)
Common per-deployment settings live in a .env file at the repo root. Copy
the template and edit as needed:
cp .env.example .envSupported keys:
| Key | Default | Purpose |
|---|---|---|
BOB_URL |
https://bobnet.qubic.li |
Bob HTTP endpoint shared by indexer, analytics, api |
CLICKHOUSE_USER |
default |
Applied both to the ClickHouse container and to every consumer |
CLICKHOUSE_PASSWORD |
(empty) | Same |
ADMIN_API_KEY |
(empty) | Required for /api/admin/*. Empty = admin routes return 503 |
SEQ_URL / SEQ_API_KEY |
(empty) | Optional Seq structured logging |
INDEXER_START_TICK |
0 |
Bootstrap tick on a fresh DB; ignored after first run |
INDEXER_START_FROM_LATEST |
false |
Alternative to INDEXER_START_TICK — start from live chain head |
PRUNER_DRY_RUN |
true |
Set to false to actually drop old data |
USE_LOCAL_PACKAGES |
false |
Docker build only — see next section |
Both docker-compose.yml (build-from-source) and docker-compose.prebuilt.yml
(prebuilt images) read from the same .env, so switching between the two
doesn't require re-configuring anything.
By default every build restores packages from nuget.org only, so anyone
cloning the repo can docker compose build without extra setup.
Maintainers working against pre-release / not-yet-published Qubic.* packages
can drop the .nupkg files into src/local-packages/ and opt in to that
source with an env var:
# One-off
USE_LOCAL_PACKAGES=true docker compose build
# Or persist in a .env file next to docker-compose.yml
echo 'USE_LOCAL_PACKAGES=true' >> .env
docker compose buildUnder the hood the Dockerfiles ship two configs, src/NuGet.config
(public) and src/NuGet.local.config (public + local). The build-arg
USE_LOCAL_PACKAGES=true swaps NuGet.local.config in as the effective
NuGet.config. When the arg is unset or false, the local one is deleted
before restore — even a populated local-packages/ folder is ignored.
- .NET 10 SDK
- Node.js 20+
- Docker (for ClickHouse)
docker compose up -d clickhousecd src/QubicExplorer.Indexer
dotnet runcd src/QubicExplorer.Api
dotnet runcd frontend
npm install
npm run devEdit src/QubicExplorer.Indexer/appsettings.json or use environment variables:
| Setting | Environment Variable | Default | Description |
|---|---|---|---|
| Bob:Nodes:0 | Bob__Nodes__0 |
http://localhost:21841 |
Bob node URL |
| Bob:ReconnectDelayMs | Bob__ReconnectDelayMs |
5000 |
Reconnect delay (ms) |
| Bob:MaxReconnectDelayMs | Bob__MaxReconnectDelayMs |
60000 |
Max reconnect delay (ms) |
| ClickHouse:Host | ClickHouse__Host |
localhost |
ClickHouse host |
| ClickHouse:Port | ClickHouse__Port |
8123 |
ClickHouse HTTP port |
| ClickHouse:Database | ClickHouse__Database |
qubic |
ClickHouse database name |
| Indexer:StartTick | Indexer__StartTick |
0 |
Starting tick number |
| Indexer:StartFromLatest | Indexer__StartFromLatest |
false |
Start from latest tick |
| Indexer:ResumeFromLastTick | Indexer__ResumeFromLastTick |
true |
Resume from last indexed |
| Indexer:BatchSize | Indexer__BatchSize |
1000 |
Batch size for inserts |
| Indexer:FlushIntervalMs | Indexer__FlushIntervalMs |
1000 |
Flush interval (ms) |
Edit src/QubicExplorer.Api/appsettings.json or use environment variables:
| Setting | Environment Variable | Default | Description |
|---|---|---|---|
| Bob:Nodes:0 | Bob__Nodes__0 |
http://localhost:21841 |
Bob node URL |
| ClickHouse:Host | ClickHouse__Host |
localhost |
ClickHouse host |
| ClickHouse:Port | ClickHouse__Port |
8123 |
ClickHouse HTTP port |
| ClickHouse:Database | ClickHouse__Database |
qubic |
ClickHouse database name |
| AddressLabels:BundleUrl | AddressLabels__BundleUrl |
https://static.qubic.org/... |
Address label bundle URL |
| Environment Variable | Default | Description |
|---|---|---|
NUXT_API_URL |
http://api:8080 |
Server-side API URL (SSR, internal) |
NUXT_PUBLIC_API_URL |
(empty) | Client-side API URL (empty = relative, via nginx) |
GET /api/ticks— List ticks (paginated)GET /api/ticks/{tickNumber}— Get tick details with transactions
GET /api/transactions— List transactions (paginated, filterable by address)GET /api/transactions/{hash}— Get transaction details with logs
GET /api/transfers— List transfers (paginated, filterable by address/type/direction)
GET /api/address/{address}— Get address summaryGET /api/address/{address}/transactions— Get address transactionsGET /api/address/{address}/transfers— Get address transfers
GET /api/search?q={query}— Search by tick, hash, or address
GET /api/stats— Get network statisticsGET /api/stats/chart/tx-volume?period={day|week|month}— Get volume chart data
- Hub URL:
/hubs/live - Methods:
SubscribeToTicks(),SubscribeToAddress(address) - Events:
OnNewTick,OnNewTransaction,OnAddressUpdate
This project uses the following Qubic.Net NuGet packages:
| Package | Description |
|---|---|
| Qubic.Bob | Bob JSON-RPC client with WebSocket subscriptions and multi-node failover |
| Qubic.Crypto | K12 hashing, FourQ, SchnorrQ signatures, identity verification |
The ClickHouse schema is automatically created on application startup. It includes:
- ReplacingMergeTree tables for ticks, transactions, logs, and deduplication
- Bloom filter indexes for fast address lookups
- Materialized views for pre-ag 709B gregated statistics (daily volumes, epoch stats, etc.)
- Analytics tables for holder distribution, miner flow tracking, and network stats history
See docker/clickhouse/init.sql for the full schema reference.
- Backend: .NET 10, ASP.NET Core, SignalR
- Bob Client: Qubic.Bob (WebSocket + HTTP RPC)
- Database: ClickHouse
- Frontend: Vue 3, Nuxt 3, TypeScript, Tailwind CSS
- Deployment: Docker, Docker Compose, Nginx
The Qubic.Bob NuGet package provides a typed WebSocket client for Bob's JSON-RPC interface — managed subscriptions (tick stream, new ticks), RPC query methods, multi-node failover, and automatic reconnection. The explorer uses BobWebSocketClient for all communication with the Qubic network, keeping the application code focused on indexing and serving data rather than connection management.
ASP.NET Core is a natural fit for the API layer: built-in dependency injection, controller patterns, and SignalR for real-time WebSocket push to the frontend — all with high throughput and low allocation overhead.
A blockchain explorer is fundamentally an analytics workload: append-heavy writes (new ticks, transactions, logs arriving in order), heavy aggregation reads (volume charts, epoch statistics, address summaries), and sparse point lookups (find a transaction by hash). ClickHouse is purpose-built for this pattern:
- Columnar storage compresses blockchain data extremely well (addresses, amounts, and timestamps stored in separate columns) and makes aggregation queries over millions of rows fast.
- MergeTree engine family handles the append-only write pattern natively.
ReplacingMergeTreegives upsert semantics for deduplication when the indexer re-processes a tick range.SummingMergeTreeandAggregatingMergeTreepower materialized views that pre-aggregate statistics at insert time — no batch jobs needed. - Bloom filter indexes on address columns give fast point lookups without the overhead of a B-tree index on high-cardinality string columns.
Compared to PostgreSQL, ClickHouse handles the "scan millions of rows for charts and stats" queries orders of magnitude faster while still serving point lookups. The trade-off (no transactions, eventual consistency on merges) is acceptable for a read-heavy explorer.
Nuxt provides server-side rendering out of the box, which matters for an explorer: when a user shares a link to a transaction or address, the page should render with data on first load (SEO, social previews, perceived performance). The Composition API keeps component logic clean as complexity grows (70+ API methods, real-time subscriptions, contract input decoding). Tailwind CSS avoids the overhead of a component library while keeping the UI consistent.
The full stack (ClickHouse, Indexer, API, Frontend, Nginx) runs with a single docker compose up. Nginx acts as the single entry point — routing /api/* to the backend and everything else to the frontend, handling WebSocket upgrades for SignalR, and providing gzip compression. This keeps the deployment topology simple and the frontend free from CORS complexity.