Modern, responsive block explorer for FairCoin. Vite + React SPA frontend with an Express API server (run with Bun) that talks JSON-RPC to a FairCoin node and caches responses in MongoDB. Real-time updates are pushed over WebSocket.
WebSocket (WS /api/ws) pushes change notifications (new blocks, mempool updates, network stats, transaction confirmations). Canonical blockchain data is always loaded over HTTP (GET /api/*) via React Query: the client invalidates (and optionally paints) caches on push, then refetches the full API shape. When the socket is down, live hooks fall back to a 30s HTTP poll. In local dev, Vite proxies /api with ws: true so the browser can upgrade /api/ws to the API server.
- Frontend: Vite, React 19, TypeScript, TanStack Query, Tailwind CSS 4, Bloom UI (7.7.0 + local pane-motion candidate), react-router
- Backend: Express 5 (Bun runtime), WebSocket (
ws), MongoDB cache,@fairco.in/rpc-client - i18n: 8 languages (en, es, fr, de, ru, zh, ja, ko)
The dashboard follows Bloom's Marketing and Finance Storybook templates:
AppShell with scroll="document", AppShellHeader, independent cards,
StatCards, and inset DataTable panels with native sorting and pagination.
A single BloomProvider owns theme, locale, scroll restoration and Bloom’s
shared services. The native ErrorBoundary wraps the app. The provider keeps
the faircoin recipe in light and dark mode, with one persisted theme key and
matching first-paint colors. Components
are imported directly from Bloom; the old local UI adapter layer is removed.
Every screen lives in a Bloom ContentPanel from its first render. The workspace
uses contentMaxWidth="none"; on desktop, the main panel fills the available
width until a block or transaction opens beside it. AppShellSplitPanes owns the
separated surfaces and resizing, with paneScroll={false} so the document owns
vertical scrolling. Mobile shows one pane at a time.
transition="slide" lets Bloom animate the actual pane positions and retain an
outgoing pane until it exits. Desktop opens space beside the main panel; mobile
slides between screens. Reduced-motion preferences settle the layout immediately.
useAppShellPaneActive() suspends global search shortcuts in outgoing panes.
The pane-transition API is a local Bloom candidate, not a published release.
package.json and bun.lock pin its package under vendor/, so installation
works without the sibling Bloom checkout. vendor/bloom-pane-motion.patch
contains the library source changes against published Bloom 7.7.0 (commit
4cab8be8). Replace the local dependency with a registry version once that API
is released upstream.
Detail links preserve the originating screen, including Home, charts and address
details. Bloom's ScrollRestorationProvider and useScrollRestoration restore
document scroll. Filters and pagination live in the URL, and direct detail URLs
use the corresponding list as their origin.
/charts uses explicit stretching rows like the Storybook dashboards, stacking
cards according to their available panel width, including while a detail is open. It includes all 21 Bloom chart-card families, grouped
by network, distribution, observed activity and market, with a paginated source table.
The annual MostActiveDaysCard opens in a Bloom Dialog: its built-in calendar
scroll stays outside the dashboard document flow. Dashboard panels have no
vertical scroll containers or viewport-height constraints.
Network history is sampled on mainnet; testnet shows an explicit empty state.
WFAIR market history is independent of that network switch. No demo series are
shipped. Activity totals refer to distinct tip blocks observed by the sampler,
not every block or transaction in the chain.
The Vite React Native Web setup and unlayered Tailwind utilities are required: React Native Web injects an unlayered View reset; layered utilities would lose its grid/flex overrides and move panel content out of view. The production CommonJS configuration also converts Reanimated's optional RNW DOM-compiler imports, as documented in Bloom's motion guide; otherwise web enter animations can leave the compiled app invisible.
# 1) Install deps
bun install --frozen-lockfile
# 2) Configure environment
cp .env.example .env
# edit .env with your RPC and MongoDB credentials
# 3) Run the API server (port 8080)
npm run server # or: npm run dev:server (watch mode, requires Bun)
# 4) Run the frontend dev server (port 5180, proxies /api to :8080)
npm run devTo use real production data while developing the UI, put
VITE_API_TARGET=https://explorer.fairco.in in .env.local and restart Vite.
The dev proxy forwards HTTP and WebSocket requests to production; the local
API server is unnecessary in this mode. Remove the override to use :8080 again.
For production, build the SPA and let the API server serve it:
npm run build # outputs dist/
npm run server # serves dist/ + API + WebSocket on :8080MongoDB in production. The cache DB is a plain local MongoDB — point
MONGODB_URIatmongodb://localhost:27017/faircoin-explorer. On thefcexplorerhost it runs as a Docker container:docker run -d --name mongo --restart=always -p 127.0.0.1:27017:27017 -v mongo-exp:/data/db mongo:4.4. Usemongo:4.4on hosts whose kernel/CPU trips MongoDB 5+/8 (the box needs AVX for 5+, and Mongo 8 refuses kernel ≥6.19). The cache self-populates from the node's RPC on demand, so a fresh empty DB recovers on its own. Do not use a DigitalOcean managed cluster — that dependency was removed after the DO account was lost.
| Script | Description |
|---|---|
npm run dev |
Vite dev server (frontend, port 5180) |
npm run dev:server |
API server in watch mode (Bun, port 8080) |
npm run server |
API server (Bun) |
npm run build |
Typecheck + production build of the SPA |
npm run typecheck |
Typecheck frontend and server |
npm run sync-db |
Full blockchain sync into MongoDB (optional, Bun) |
npm run check-db |
MongoDB health/index check (Bun) |
# RPC Configuration (server-side only; never exposed to the browser)
FAIRCOIN_RPC_USER=fair
FAIRCOIN_RPC_PASS=change_me
FAIRCOIN_RPC_HOST=127.0.0.1
FAIRCOIN_RPC_PORT=46373
FAIRCOIN_RPC_SCHEME=http
# MongoDB
MONGODB_URI=mongodb://localhost:27017/faircoin-explorer
# WebSocket / realtime monitor
WEBSOCKET_ENABLED=true
WEBSOCKET_NETWORKS=mainnet # comma-separated; add testnet if you run a testnet node
BLOCKCHAIN_POLL_INTERVAL=4000 # block/mempool poll (ms); default 4s
WEBSOCKET_HEARTBEAT_INTERVAL=30000
WEBSOCKET_MAX_CONNECTIONS_PER_IP=5
WEBSOCKET_MAX_PAYLOAD_BYTES=65536
# Public site URL (MCP links, etc.). The JSON API allows any browser origin via CORS.
PUBLIC_BASE_URL=https://explorer.fairco.inSecurity: RPC credentials are only read server-side; all RPC calls are proxied through the API. The /api surface is rate-limited (a global limiter on /api, plus stricter limits on /api/search, /api/transaction, /api/address, /api/tx/broadcast, and the public /mcp endpoint, which can drive daemon-backed wallet tools). Expensive lookups are bounded: /api/price/history samples long windows in memory from a bounded, two-tier-pruned series, and /api/search on an address returns balance only (it no longer triggers an unbounded address-txid scan, so txCount is 0 there — use the address pages for full history). /api/peers is redacted (no raw peer addresses or topology).
Behind a reverse proxy (e.g. nginx/Cloudflare), set TRUSTED_PROXY_CIDRS to the proxy's IP/CIDR(s) so the rate limiter keys on the real client IP — X-Forwarded-For is trusted only from those peers, never from arbitrary clients. Leave it blank when the API is reached directly.
The Express server exposes a read-only JSON API under /api:
GET /api/blocks,/api/block/:hashOrHeight,/api/blockcountGET /api/transactions(paginated recent txs from mempool + recent blocks)GET /api/transaction/:txid,POST /api/tx/broadcastGET /api/address/:address,/api/address/:address/txs?page=&limit=,/api/address/:address/utxos(requires a node withaddressindexfor full data)GET /api/mempool,/api/masternodes,/api/peers,/api/stats,/api/network-info,/api/mining-infoGET /api/search?q=(block height/hash, txid, or address)GET /api/validate-address?address=,/api/fee-estimateGET /api/price,/api/price/history,/api/stats/historyGET /api/bridge/reserves(proxied WFAIR bridge reserves)WS /api/ws— push of chain changes (new-block,block-count,mempool-updatewith top-N txs,transaction-confirmed,network-stats). Canonical data still comes from HTTP/api/*; the socket tells the client when to refetch.
The explorer also speaks the Model Context Protocol, so AI assistants (Claude, ChatGPT, Cursor, …) can query the FairCoin blockchain directly.
- Endpoint:
https://explorer.fairco.in/mcp - Transport: Streamable HTTP, stateless (a fresh server is built per request — no session store).
POSTcarries the JSON-RPC request;GET/DELETEreturn405(there is no SSE stream or session to address). - Access: read-only. No API key required.
Every blockchain tool accepts an optional network argument ("mainnet" by default; "testnet" is also supported).
| Tool | Purpose |
|---|---|
search |
Resolve a block height, block hash, txid, or address into linkable { id, title, url } results (ChatGPT deep-research contract). |
fetch |
Given an id from search (e.g. tx:<hash>), return the full record as { id, title, text, url, metadata }. |
get_network_stats |
Height, difficulty, supply, connections, mempool size, masternode count, PoW/PoS phase. |
get_latest_blocks |
Most recent blocks (limit 1–50, default 10). |
get_block |
A full block by height or hash. |
get_transaction |
A full transaction by txid (with resolved prevouts and live confirmations). |
get_address |
Balance/summary for an address (full history needs a node with addressindex; degrades gracefully). |
get_mempool |
Current mempool size and recent pending transactions. |
get_masternodes |
Masternode list (by rank) plus aggregate stats. |
get_price |
Live FAIR price (via the WFAIR/USDC pool on Base). |
get_supply |
Circulating/max supply, block reward, percent mined. |
create_wallet |
Generate a new FairCoin keypair and return { address, privateKey (WIF) }. Non-custodial — the server stores nothing. |
get_balance |
Confirmed + unconfirmed balance and UTXO count for an address (needs a node with addressindex). |
send |
Send a FAIR amount from the address controlled by your privateKey (WIF) to toAddress. Returns { txid, amount, fee }. |
sweep |
Send the entire balance of your privateKey's address to toAddress (minus fee). Returns { txid, amount, fee }. |
The wallet tools let AI agents receive and spend FAIR autonomously on both mainnet and testnet, with a strictly non-custodial design:
create_walletgenerates a keypair in-process and returns the address and private key (WIF) once. The server keeps no copy — no database, no file, no in-memory cache. The agent is the sole holder of the key.get_balancereads an address balance + UTXOs from the node.send/sweepaccept the agent's WIF as a parameter, derive the from-address, build the raw transaction, and hand the key to the node'ssignrawtransaction(which signs without importing the key to the wallet) before broadcasting. The key is used transiently and never persisted.
Only key generation is done in-process (using the audited @noble/curves secp256k1 implementation); all transaction construction, signing, and broadcasting are delegated to the FairCoin node (createrawtransaction / signrawtransaction / sendrawtransaction) so FairCoin's exact transaction and sighash format is always correct. send and sweep use a flat 0.001 FAIR network fee; change below the dust threshold (0.0001 FAIR) is dropped into the fee rather than creating an uneconomical output.
Security caveat — the key is the holder's responsibility. The private key is returned to and supplied by the caller. The server never logs, echoes, or stores it, and it cannot recover a lost key. Anyone holding the key controls the funds. Store it securely.
send/sweepandget_balancerequire a FairCoin node started with-addressindex=1.
- Claude (Desktop/Code): add a custom connector / MCP server with URL
https://explorer.fairco.in/mcp(transport: HTTP/Streamable HTTP). - ChatGPT (deep research / connectors): add a connector pointing at the same URL. The required
searchandfetchtools are implemented, so it works out of the box. - Cursor / other clients: configure a Streamable HTTP MCP server with the same URL.
- Dashboard with live blocks, mempool, price and network stats
- Global recent transactions feed, charts/analytics, and tools (broadcast, address validator, API docs)
- Block, transaction and address pages (with paginated address history when the node has
addressindex) - Masternode list and reward stats, peers, network status
- Universal search (height, hash, txid, address)
- MongoDB-backed RPC caching with single-flight de-duplication and self-healing TTLs
- Real-time WebSocket updates
- Dark/light theme, 8 languages, responsive layout
- Realtime model: the server polls the FairCoin RPC (default every 4s) and pushes change events on
WS /api/ws. Clients should treat HTTP/api/*as the source of truth and use the socket to invalidate/refetch. - Address balances/history require a FairCoin node with
addressindex=1; without it the explorer degrades gracefully to validation-only data. - The MongoDB cache populates on demand;
bun run sync-db(full historical sync) is optional.