Off-chain services
Three independent TypeScript services support a full deployment. They are
optional for a demo (the web app runs chain-direct), but
power richer history, charts, trailing APY, and keep rounds turning. Each has a
/health endpoint and is Dockerized via docker/service.Dockerfile.
Keeper (keeper/)
A cron + tick worker — the only server-side signer. It can drive the round lifecycle but cannot touch user funds:
- weekly
start-round, - post-expiry
settle-round, - devnet/testnet price relay into
pyth-mock(since the demo uses the mock feed), - retries with backoff.
cd keeper
STACKS_NETWORK=testnet KEEPER_PRIVATE_KEY=<key from .testnet-deployer.json> \
PRICE_RELAY=true pnpm start
The keeper key is isolated in its own process/secret store and is rotatable via
vault.set-keeper. (7 policy + loop tests.)
Indexer (indexer/)
A Chainhook webhook consumer that projects the vault's print events into
Postgres:
- idempotent by
(tx_id, event_index), - reorg rollback — rebuilds projections from the canonical event log,
- Stacks-API backfill for cold starts / resume.
Register the predicate indexer/chainhooks/vault-print.testnet.json (its
contract_identifier is already set to the live vault) with your Chainhook
node, pointed at the indexer's /chainhook/events endpoint. (5 acceptance
tests: replay, duplicate no-op, rollback/re-apply, resume.)
API (api/)
A read-only Fastify REST service (zod-validated IO, rate-limited 300/min):
| Route | Returns |
|---|---|
/vault | current vault state |
/vault/history | snapshots over time |
/rounds | round list + status |
/positions/:addr | a user's positions |
/quote | a live Black–Scholes quote |
/price | current BTC price |
/tx/:txid | a transaction's status |
/health | liveness |
Set VITE_API_URL in the web app to this service's base URL to upgrade from
chain-direct to API-backed data. (13 tests.)
Database (db/)
Drizzle schema + migrations + seed (events_raw, rounds, positions,
snapshots, …). Run with:
pnpm --filter @bachelier/db migrate
pnpm --filter @bachelier/db seed
Running the whole stack
docker compose up postgres indexer api
# keeper drives the rounds:
pnpm --filter @bachelier/keeper dev
See docker-compose.yml and the repo README.md for the full env matrix.