@vtechcom/hexcore
v2.0.0-beta.1
Published
Boot and operate Cardano Hydra Heads — control-plane and web UI in one command
Maintainers
Readme
Hexcore v2
Monorepo for booting and operating Hydra Heads on Cardano — control-plane and web UI in one repository.
v2 is a rewrite of hydra-hexcore focused on one thing: going from
git cloneto a running Hydra Head without a checklist.
What changed from v1
| | v1 | v2 |
|---|---|---|
| Repos | 3 separate (hydra-hexcore, hexcore-ui, hexcore-cli) | 1 monorepo |
| Key material | You generated Hydra + Cardano keys yourself and POSTed them in | Generated server-side on head creation |
| Database | MySQL + Redis, provisioned before first run | Embedded SQLite in the state dir |
| Config | ~30 env vars, unvalidated | zod-validated, works with a single key set |
| Setup failures | Surfaced as stack traces at random later points | pnpm doctor checks everything up front |
| Container names | Derived from DB ids (hexcore-hydra-node-7) | Deterministic (hexcore-h1-n0) |
| Ports | Scan upward from 10005 until free | Deterministic block per head |
| Failed boot | Left orphaned containers behind | Rolls back every container it created |
| Head state | Only "is the container up?" | Real Hydra state probed over WebSocket |
| Types | Duplicated between API and UI | Single zod schema in packages/shared |
Requirements
- Node >= 22
- Docker running and reachable by your user
- Nothing else. The
offlinebackend runs a real multi-node Hydra Head with no chain, no credential and no waiting — a Blockfrost project id (free, https://blockfrost.io) or a syncedcardano-nodesocket is only needed when you want heads on the real chain.
No MySQL. No Redis. No RabbitMQ.
Quick start
npm install -g @vtechcom/hexcore@beta
hexcoreThis is a beta, published under the
betatag rather thanlatest— sonpm i -g @vtechcom/hexcorewithout@betawill not resolve until 2.0.0 is final. The package is scoped; the command it installs is plainhexcore. See CHANGELOG.md for what that means and what is known broken.
Building it from this checkout gives the identical artefact:
pnpm install
node scripts/pack-cli.mjs # builds, packs, and verifies the tarball installs
npm i -g ./vtechcom-hexcore-2.0.0-beta.1.tgzThat starts the control-plane and serves the web UI from the same port, and prints a setup link with a one-time token in it:
Hexcore is not configured yet. Open this link to finish setup:
http://localhost:4000/setup?token=…Open it, answer four questions, and you have a working installation. Nothing to
edit by hand; there is no .env to copy.
The token is not ceremony. Setup has to be reachable without a login, because it is where the login is created — so on any interface but loopback, whoever reaches the port first would otherwise choose the admin password.
Prefer a terminal, or setting up a machine with no browser pointed at it?
hexcore init # the same questions, in the shellEverything the CLI does
hexcore # start the control-plane and the web UI
hexcore init # configure from the terminal
hexcore config # every setting, and which layer it came from
hexcore doctor # check Docker, chain access and permissions
hexcore reset # forget the configuration; heads and keys are kept
hexcore clean # delete every head and its containers; keys are kept
hexcore clean --all # delete everything, including keysWhere configuration comes from
Four layers, highest wins:
- a real environment variable — for containers and CI
- a
.envin the current directory ~/.hexcore/config.json, written by the setup wizard orhexcore init- the built-in defaults
hexcore config prints the value and the layer that supplied it, which is
the only useful answer to "I changed that setting and nothing happened".
From a checkout
pnpm install
pnpm build
pnpm startSame first-run experience — the setup link is printed on boot. pnpm doctor
runs the preflight checks on their own.
On Apple Silicon, read this before creating a head
Every hydra-node image the Hydra core team publishes is amd64-only. On a Mac with an M-series chip the 2.2.0+ image runs under Rosetta, stalls inside a cryptographic warm-up before it installs a logger, and fails as a plain readiness timeout with nothing in the log to explain it.
Hexcore warns about this while you are still choosing, and on macOS/arm64 it
recommends aniadev9/hydra-node:2.3.0-arm64 — the same 2.3.0 source rebuilt for
arm64, which starts natively in 0.47s.
That is a community image on a personal Docker Hub account, not one this
project controls. It is offered because the only upstream image that boots
there is 2.1.x, which carries a double-spend defect. If you would rather not
depend on someone else's registry, rebuild 2.3.0 yourself and push it as
you/hydra-node:2.3.0-arm64 — it resolves to the same profile, with the same
flags and the same warnings. This recommendation goes away the moment upstream
publishes multi-arch images (#7).
Create and start a head
TOKEN=$(curl -s -X POST localhost:4000/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<your password>"}' | jq -r .data.token)
# Creates the head AND generates every key it needs.
curl -X POST localhost:4000/api/heads \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"my-head","nodeCount":3}'The response contains one cardanoAddress per node. Fund each of them with
test ADA (https://docs.cardano.org/cardano-testnets/tools/faucet) — a Hydra
node cannot commit without UTxOs.
curl -X POST localhost:4000/api/heads/1/start -H "Authorization: Bearer $TOKEN"
curl localhost:4000/api/heads/1/status -H "Authorization: Bearer $TOKEN"Layout
apps/
control-plane/ NestJS + Fastify adapter
src/
config.ts zod-validated environment
preflight.ts startup checks (also `pnpm doctor`)
hydra/keys.ts Hydra + Cardano key generation
hydra/head.service.ts head lifecycle: create/start/stop/reset
hydra/ws-client.ts live Hydra state over WebSocket
docker/ container orchestration + rollback
db/ SQLite schema and repositories
controllers/ REST API (Swagger annotated)
web/ Nuxt SPA (ssr:false) — Tailwind v4 + shadcn-vue
packages/
shared/ zod schemas + types used by BOTH sides
docs/ UI specification and architecture notes
sketches/ HTML mockups the UI design was chosen fromWeb UI
Built, and served by the control-plane on the same port when SERVE_WEB=true —
which is what hexcore does. It builds to static files (nuxt generate), so
there is no second process and no second port.
docs/UI_SPEC.md— screens, states, actions, error handlingdocs/UI_ARCHITECTURE.md— stack and implementation notes
State directory
Everything mutable lives under STATE_DIR. Unset, that is ~/.hexcore — unless
a .hexcore already exists in the working directory, which is adopted instead so
an existing checkout keeps the heads and keys it already has.
~/.hexcore/
config.json settings from the wizard (0600)
hexcore.db SQLite
secrets.json jwt secret + admin password hash (0600)
install-id distinguishes this installation's containers
heads/head-1/
keys/n0.hydra.sk 0600, text-envelope format
keys/n0.cardano.sk
persistence/n0/ hydra-node state
protocol-parameters.jsonhexcore config prints the resolved path. Back this up to preserve funded
addresses — the Cardano signing keys exist here and nowhere else. hexcore
clean --all deletes it, which is why that one is CLI-only and asks you to type
your machine's name.
API
| Method | Path | Purpose |
|---|---|---|
| GET | /health | Liveness + Docker reachability (public) |
| POST | /api/auth/login | Get a bearer token (public) |
| GET | /api/heads | List heads with their nodes |
| POST | /api/heads | Create a head, generating all keys |
| GET | /api/heads/:id | Head detail |
| PATCH | /api/heads/:id | Update settings (applied on next start) |
| POST | /api/heads/:id/start | Boot every node, wait until ready |
| POST | /api/heads/:id/stop | Remove containers, keep keys + state |
| POST | /api/heads/:id/restart | Stop then start |
| POST | /api/heads/:id/reset | Wipe persistence (includeKeys for keys too) |
| DELETE | /api/heads/:id | Delete head, containers and state |
| GET | /api/heads/:id/status | Live state probed from the nodes |
| GET | /api/heads/:id/nodes/:index/logs | Tail a node's container log |
| GET | /api/heads/:id/events | Lifecycle audit trail |
Every response is { ok: true, data } or { ok: false, error: { code, message } }.
Troubleshooting
Run pnpm doctor first — it diagnoses most problems with a concrete fix.
DOCKER_UNAVAILABLE — Docker is not running, or your user cannot use the
socket. On Linux: sudo usermod -aG docker $USER, then log out and back in.
CHAIN_PROVIDER_ERROR / Blockfrost 403 — the key does not match
CARDANO_NETWORK. A key starting with preprod only works on preprod.
Node never becomes ready — check its log:
GET /api/heads/:id/nodes/0/logs. The usual cause is an unfunded Cardano
address; the head cannot proceed without UTxOs at each node's address.
Port already in use — change PORT_BASE; each node needs 10 ports.
Development
pnpm dev # control-plane + web in watch mode
pnpm dev:api # control-plane only
pnpm test # unit tests
pnpm doctor # environment diagnosticsLicence
Apache License 2.0. Use it, ship it, fork it, build a product on it — the licence also grants you a patent licence from the contributors, which is the main reason to prefer it over MIT for infrastructure.
Two things it does not cover, because they are not ours to license:
- hydra-node is a separate project (cardano-scaling/hydra, Apache-2.0). Hexcore runs its published container images; it does not vendor or modify them.
aniadev9/hydra-node:2.3.0-arm64, the community rebuild recommended on Apple Silicon, is a third-party image on a personal Docker Hub account. It is a rebuild of upstream's Apache-2.0 source, but this project does not control that registry — see the Apple Silicon section above.
