Cardano TypeScript node / relay — Bun runtime
Gerolamo is a lightweight, modular Cardano node/relay in TypeScript (Bun).
It is HarmonicLabs software — not TxPipe / @txpipe/gerolamo.
| Layer | What you get |
|---|---|
| N2N | Handshake, ChainSync, BlockFetch → SQLite |
| HTTP | Blocks, UTxOs, health/metrics, Mini-Blockfrost subset, tx submit |
| N2C | Optional node.socket (LocalChainSync, LocalTxSubmit, minimal LSQ, TxMonitor) |
| Bootstrap | Mithril via external mithril-client + chunk apply / batch hydrate |
Honest limits: soft body path available; full Praos leadership / governance not in scope. Mini-BF is a subset. LSQ is minimal. Soft hydrate ≠ consensus proof.
git clone https://github.com/HarmonicLabs/gerolamo.git
cd gerolamo
bun install # installs the node, the desktop app and the dashboard (one command)
bun run preflight # tells you if anything on the machine is missing
# Live preprod node
bun src/index.ts start-gerolamo --network preprod
# Desktop Control Center (Electrobun). First launch downloads the Electrobun
# Linux runtime (~150 MB) from GitHub; after that it starts offline.
bun run ui:dev
# Mainnet
bun src/index.ts start-gerolamo --network mainnetRequirements: Bun ≥ 1.1 and, for the desktop app on Linux,
WebKitGTK 4.1 + GTK 3 (bun run preflight prints the exact apt/dnf/pacman
line for your distro). Everything else is in the repo: the HarmonicLabs
libraries that are not on npm yet ship as tarballs in vendor/, so no sibling
checkouts are needed. bun run test runs the node and desktop suites.
| Surface | Default |
|---|---|
| HTTP API | http://127.0.0.1:3030 |
| DB | ./.live/test.db (override with --db-path or config dbPath) |
| Entry | bun src/index.ts start-gerolamo |
Every setting is a flag and a config.json key; Gerolamo reads no environment variables. The full list is in docs/CONFIG.md. A flag beats config.json, which beats the default; --set key.path=value sets any config key.
| Flag | config.json | Purpose |
|---|---|---|
--network |
networkName |
preprod (default) | preview | mainnet |
--config <file> |
- | Instance config.json, merged over the network's bundled one |
--db-path <file> |
dbPath |
SQLite path |
--port <n> |
port |
HTTP port (default 3030) |
--n2c-socket <path> |
n2cSocketPath |
Ouroboros N2C unix socket path |
--no-n2c |
n2c.enabled |
Disable N2C |
Config: src/config/{preprod|mainnet}/config.json
Topology: src/config/{network}/topology.json
Note: config unixSocket = HTTP over unix — not N2C. Use --n2c-socket for Ouroboros.
INST="$HOME/.local/share/myapp/gerolamo/preprod-1"
mkdir -p "$INST"/{data,logs}
bun src/index.ts start-gerolamo --network preprod \
--db-path "$INST/data/gerolamo.db" \
--port 3030 \
--n2c-socket "$INST/node.socket"One writer per DB. Unique port per live instance.
curl -s http://127.0.0.1:3030/health | jq .
curl -s http://127.0.0.1:3030/metrics | jq .
curl -s http://127.0.0.1:3030/governor | jq .
open http://127.0.0.1:3030/stats # HTML dashboard (or curl -s …/stats | head)
curl -s http://127.0.0.1:3030/block/<slot-or-hash>
curl -s "http://127.0.0.1:3030/utxo/<txhash>:<index>"
curl -s http://127.0.0.1:3030/api/v0/
curl -s http://127.0.0.1:3030/api/v0/blocks/latest | jq .
curl -X POST http://127.0.0.1:3030/txsubmit --data-binary @tx.cbor \
-H "Content-Type: application/cbor"| Method | Path | Notes |
|---|---|---|
| GET | /health /healthz |
{ healthy, network, port, uptimeSec } |
| GET | /metrics |
tipSlot, utxoCount, epoch, epochNonce, peers, flags |
| GET | /governor /api/v0/governor |
Peer governor snapshot { cold, warm, hot, total, hotKeys, targets } |
| GET | /stats /dashboard |
HTML dashboard (local only) — tip, UTxO, epoch, peer tiers; WS-first via /ws/stats, 5s poll fallback |
| WS | /ws/stats |
Bun WebSocket stream: hello + tip/peers/metrics/governor topics; client ops subscribe/unsubscribe/ping |
| GET | /block/{slot|hash} |
Raw CBOR hex |
| GET | /utxo/{txhash:index} |
UTxO JSON if unspent |
| POST | /txsubmit |
Raw CBOR → mempool / hot peers |
| GET/POST | /api/v0/* |
Mini-Blockfrost subset v0.2.0 |
Mini-BF v0.2.0: health, epochs/latest (+ params if stored), blocks (+ /txs), address UTxOs + transactions, tx by hash, tx UTxOs (unspent only), mempool snapshot, tx submit (returns body hash).
Tx/address history needs tx_index (empty until backfill or forward index). Not full Blockfrost. See docs/gerolamo-vs-dolos-gap.md.
# Ops stream (Bun WS)
# browser: open /stats — or:
# bun -e 'const ws=new WebSocket("ws://127.0.0.1:3040/ws/stats"); ws.onmessage=e=>console.log(e.data)'
# Mini-BF listing
curl -s http://127.0.0.1:3040/api/v0/ | jq '{version,endpoints}'
# Off-hot-path index backfill (never dual-write soak batch.db while hydrate runs)
bun scripts/backfill-tx-index.mjs --db .live/test.db --limit 200
# refuse soak unless hydrate stopped: --force-batchYes — peer tip is carried with each header on the N2N ChainSync mini-protocol.
- Hot peer streams
MsgRollForward= header + tip (point the peer claims is chain head). - Gerolamo parses the header (
headerParser/handleRollForward). - BlockFetch pulls the body when needed.
- Soft body validation (optional) → apply → SQLite
MAX(slot)rises. - HTTP
/metrics//stats/ Mini-BFblocks/latestread that DB tip.
So tip is not HTTP-only. Frozen tipSlot + hot = 0 means stalled (no header pipeline), not “at tip.”
# Local tip + peer tiers
curl -s http://127.0.0.1:3030/metrics | jq '{tip:.tipSlot, peers:.peers, utxo:.utxoCount, epoch}'
curl -s http://127.0.0.1:3030/governor | jq '{hot, warm, cold, hotKeys, warmKeys, coldSample, targets}'
# HTML dashboard (peer bars + metrics panels, no external APIs)
open http://127.0.0.1:3030/statsFrozen tipSlot + hot = 0 = stalled (no ChainSync header pipeline).
# Read-only watcher for a live node log + DB (does not write chain DBs)
bun scripts/node-watch.mjs --log /tmp/gerolamo-live-test.log --db .live/test.db --http 3040 --port 3041
curl -s http://127.0.0.1:3041/health | jq .
curl -s http://127.0.0.1:3041/errors | jq .
curl -s 'http://127.0.0.1:3041/tail?n=40' | jq .| Method | Path | Notes |
|---|---|---|
| GET | http://…:3041/health |
mon verdict, toHexCrashes, tip |
| GET | http://…:3041/errors |
classified log counts + samples |
| GET | http://…:3041/tail?n=N |
last N log lines |
| GET | http://…:3041/ |
full JSON snapshot |
Agents / integrators: start with agent.md — spawn contract, Lab RPC map, density path, N2C notes, definition of done.
| Plane | Job | Owner |
|---|---|---|
| Data | tip, blocks, UTxO, Mini-BF, submit | Gerolamo HTTP (+ optional N2C) |
| Control | detect, spawn, stop, PID, instance dirs, log files | Host app |
The Lab already implements control:
- Service:
TheLab/src/bun/gerolamoService.ts - Layout:
~/.local/share/thelab/gerolamo/<id>/ - Spawn:
bun src/index.ts start-gerolamowith--db-path+--port - Health:
GET /health· Logs: instancelogs/daemon.log
Recipe for any host:
- Point at this repo + Bun
- Instance dir + unique DB + unique port
- Spawn child process (cwd = repo)
- Poll
/healththen/metricsfor tip - Consume Mini-BF / block / N2C
- Stop with SIGTERM
Host apps should not rely on pgrep + ad-hoc log paths alone.
| Today | Next (recommended) |
|---|---|
GET /health + GET /metrics |
GET /api/v0/status — one dashboard blob |
Host tails daemon.log |
Optional GET /api/v0/logs?tail=N (bounded) |
Keep start/stop in the host. Gerolamo stays data + self-status.
Rebuild soft state from local immutable chunks (no live peers required):
# Fast path (separate DB — never dual-write a live node DB)
bun scripts/batch-hydrate.mjs --wipe --from 0 --progress 25 \
--db .hydrate/batch.db \
--chunks snapshots/preprod/db/immutable
bun scripts/batch-watch.mjs --onceDocs: docs/hydration.md.
Optional Mithril download (external binary; Gerolamo does not reimplement cert crypto):
bun src/index.ts mithril-bootstrap --network preprod \
--download-dir ./snapshots/preprodAncillary ledger UTxO extract is blocked (A2 CBOR adapter) — density = chunks.
Research: docs/mithril-native-client-research.md.
Optional. Gerolamo does not create a node.socket on every start.
| Enable when | How |
|---|---|
| Flag | --n2c-socket ./ledger/node.socket (config n2cSocketPath) |
| Config | n2c.enabled: true + n2c.socketPath (path alone is not enough) |
| Disable | --no-n2c (or omit path) |
bun src/index.ts start-gerolamo --network preprod \
--n2c-socket ./ledger/node.socketHosts: Handshake, LocalChainSync, LocalTxSubmit, LocalTxMonitor, minimal LocalStateQuery.
Client pattern (Lab): TheLab/src/bun/dolos/n2cClient.ts — Multiplexer node-to-client.
Plan: docs/N2C_IMPLEMENTATION_PLAN.md.
Note: config unixSocket = HTTP over unix — not N2C. Use --n2c-socket for Ouroboros.
| Command | Purpose |
|---|---|
start-gerolamo |
Live node |
read-raw-chunks <dir> |
Apply immutable chunks |
mithril-bootstrap |
Download/verify via mithril-client + optional apply |
load-ancillary |
Ancillary path (extract blocked) |
bun src/index.ts --helpbun src/index.ts start-gerolamo
│
├─ init SQLite (--db-path / config dbPath)
├─ peerBlockServer :3030 ── HTTP + Mini-BF
├─ peerManager N2N ── ChainSync / BlockFetch → DB
└─ N2CServer (opt) unix ── LocalChainSync / Tx / LSQ*
| Area | Location |
|---|---|
| Entry / CLI | src/index.ts, src/cli.ts |
| Network start | src/network/index.ts |
| HTTP | src/network/peerBlockServer.ts |
| Mini-BF | src/api/miniBlockfrost.ts |
| Peers | src/network/PeerClient.ts, peerManager.ts |
| N2C | src/network/n2c/ |
| DB / apply | src/db.ts, src/consensus/ |
| Agent embed guide | agent.md |
| Doc | Topic |
|---|---|
agent.md |
Embed Gerolamo (agents + Lab) |
docs/hydration.md |
Batch soft density |
docs/gerolamo-vs-dolos-gap.md |
vs Dolos / Blockfrost |
docs/mithril-native-client-research.md |
Native Mithril options |
docs/N2C_IMPLEMENTATION_PLAN.md |
N2C phases |
docs/GEROLAMINO.md |
Gerolamino (browser extension node): what Gerolamo serves it over N2N-over-WebSocket |
docs/HARMONIC_LIBS_RELEASE.md |
Release review of the HarmonicLabs libraries (cbor, plutus-data, costmodels, cardano-ledger-ts, kes, buildooor, miniprotocols): changes, breaking changes, tests, publish order |
CIP/ |
CIP drafts: Node-to-Node over WebSocket (browser nodes, service announcement, light query, discovery) |
bunx tsc --noEmit
bun src/index.ts start-gerolamo --network preprod
# optional inspect
bun --inspect src/index.ts start-gerolamo --network preprodLogs: config logs.logDirectory (often ./logs/…) JSONL when enabled.
Do not commit ledger/, .hydrate/, or secrets.
Branch for Lab work: The-Lab.
Some HarmonicLabs libraries are installed from tarballs in vendor/ rather than npm.
Four of them are conformance builds: cardano-ledger-ts, cbor, plutus-data and
cardano-costmodels-ts, each built from a branch named conformance-fixes in its own
repository. They make Gerolamo's decoders match cardano-ledger exactly, for pragma-org's
node-diversity-conformance scenarios
(CBOR decoding 9 200 / 9 200, ledger rules 277 / 277, UPLC 1 059 / 1 059). Gerolamo's own root
cbor stays 1.6.6; package.json overrides give the conformance builds only to the 2.x
consumers. What changed, how they are wired and how to refresh them: vendor/README.md.
Harnesses for the scenarios: scripts/ndc/, results and remaining items: docs/parity/ndc-*.md.
Have: N2N sync, SQLite soft ledger, HTTP + Mini-BF subset, N2C scaffold, batch hydrate, pure-TS KES.
Next for data-node parity: tx/address indexes → richer Mini-BF; canonical /api/v0/status (+ optional logs); Lab polish; Mithril WASM verify spike; LSQ breadth only if product needs it.
Built by Harmonic Labs.