Self-hosting
Run your own console + backend — environment variables, the API routes, proving artifacts, the relayer, and deployment notes.
The console is a Next.js app you can run yourself. Its frontend is the SDK in the browser; its
backend (/api/v1/*) wraps the open-source route service. This
page is what you need to stand it up.
Environment
The app reads two sets of variables — public ones the browser uses, and server ones the backend
uses. Copy .env.example to .env.local and fill them in.
# ── server (backend API routes; consume @hestia/route) ──
HESTIA_CHAIN=baseSepolia # base | baseSepolia | anvil
HESTIA_RPC_URL=https://sepolia.base.org
HESTIA_POOL_ADDRESS=0x…
HESTIA_REGISTRY_ADDRESS=0x…
HESTIA_RELAYER_KEY=0x… # server-side relayer wallet — NEVER commit a real key
# ── public (browser): deployed addresses + chain ──
NEXT_PUBLIC_HESTIA_CHAIN=baseSepolia
NEXT_PUBLIC_HESTIA_RPC_URL=/api/rpc # same-origin proxy — keeps the provider key server-side
NEXT_PUBLIC_HESTIA_POOL_ADDRESS=0x…
NEXT_PUBLIC_HESTIA_REGISTRY_ADDRESS=0x…
NEXT_PUBLIC_HESTIA_USDC_ADDRESS=0x036CbD53842c5426634e7929541eC2318f3dCF7eDefaults, if unset, target a local chain (anvil at http://127.0.0.1:8545) with zero
addresses — useful for wiring up against a local node before you have a deployment.
Keep the provider key off the client.
NEXT_PUBLIC_*values are inlined into the browser bundle, so never put a keyed RPC URL there. The app ships a same-origin read proxy atapp/api/rpc— setNEXT_PUBLIC_HESTIA_RPC_URL=/api/rpcand the browser's reads are forwarded to the server-sideHESTIA_RPC_URL, which never leaves the server. The proxy allows only read methods; the wallet still broadcasts writes through the user's own provider.
Run it
npm install
cp .env.example .env.local # fill in addresses + relayer key
npm run dev # → http://localhost:3000/appFor production: npm run build then npm run start.
The backend routes
The /api/v1/* routes are thin wrappers over @hestia/route handlers, running on the Node
runtime. They expose only public, indexed state plus the relayer:
| Route | Purpose |
|---|---|
GET /api/v1/health | indexer health |
GET /api/v1/pool/state | root, leaf count, tree depth |
GET /api/v1/tree/proof?leaf= | Merkle proof for a leaf |
GET /api/v1/notes/scan?since= | encrypted note blobs to trial-decrypt |
GET /api/v1/association/status?root= | is an association root approved |
POST /api/v1/relay | submit a client-built proof (relayer pays gas) |
See the API routes reference for shapes. The backend keeps a single in-memory indexer instance reused across requests; the chain is the source of truth, so a restart simply re-syncs.
Proving artifacts
The browser fetches circuit wasm/zkey from /circuits. These files are large (a few MB
- tens of MB per arity) and are not committed to the repo by default. For any deployment you must make them available, by one of:
- committing them (Git LFS recommended) so they ship with the app,
- fetching them at build time into
public/circuits/, or - serving them from a CDN and pointing the artifact URLs there.
If they're missing, client-side proving 404s and send/unshield fail. (Reads, balance, and
shield still work.)
The relayer
POST /api/v1/relay submits a proof with the server's HESTIA_RELAYER_KEY, so a withdrawal's
destination has no gas history. Keep that key:
- funded with gas on the target chain, and
- secret — only in the environment, never in the repo.
The relayer can't alter your transaction (recipient, amount, and fee are bound into the proof), but it does pay gas, so treat it as an operational wallet.
Enabling spends — the association registry
The pool accepts a spend only if its associationRoot is approved on-chain
(registry.isValidRoot(root)). Deposits never touch this gate, so shield works immediately —
but send / unshield revert with InvalidAssociationRoot until the matching root is
published. Two owner/ASP steps bring the registry online:
- Authorize an ASP — the registry owner calls
setASP(asp, true). - Publish the root — that ASP calls
publishRoot(root, uri)for the current association set.
The Console screens its own deposits (labels 0..leafCount-1) as the set, so the root advances
with every deposit. The repo ships an operator script that reproduces that exact set and
publishes its root from the ASP key:
node --env-file=.env.local scripts/publish-association-root.mjsRe-run it after deposits to keep the current root approved. On the live Base-mainnet deployment the deployer is already authorized as the ASP, so operating the gate is just re-running the script (or wiring a small ASP service to do it automatically).
Deployment notes
- Don't hardcode the port.
next starthonors thePORTthe platform injects — leave it to the environment. - Persistence. v0.1 uses the in-memory store, which re-syncs on boot. If you wire the Prisma/Postgres store, run migrations at start time, not build time (build environments often can't reach the database).
- Self-contained build. The app consumes the
@hestia/*packages; once they're resolved (from the registry or vendored locally), the build needs no access to the protocol repo.
With your own console and backend, you control the indexer, the relayer, and the RPC — the trust-minimized story from the route service becomes no third party at all.
