Architecture
Bachelier is a pnpm + turbo monorepo (Node 20+). The on-chain protocol is Clarity; everything off-chain is TypeScript. The web app can run chain-direct (reading state straight from a Stacks node) so a working demo needs no backend.
Data flow
┌─────────────────────────────────────────┐
Stacks (Clarity) │ oracle-adapter.clar ── reads ──► Pyth │
│ │ │
│ ▼ │
bs-math.clar ◄─ used by ─ vault.clar ◄── SIP-010 ── sBTC / USDC │
(fixed-point BS) │ + bcshare-token (vault-gated SIP-010) │
└───────────────┬───────────────┬──────────┘
│ print events │ read-onlys
▼ │
Chainhook ──► indexer (TS) ─────┤
│ writes │
▼ │
Postgres (events_raw,│
rounds, positions, │
snapshots, …) │
▲ │
│ reads │
api (Fastify) ◄──────┘
▲
│ REST
web (React + Vite)
wallet writes ──► Stacks directly
keeper (TS) ── signs ──► start-round / settle-round
Repository layout
| Path | What | Tests |
|---|---|---|
contracts/ | Clarinet project: bs-math, vault, oracle-adapter, bcshare-token, devnet mocks | 78 vitest + clarinet-sdk tests incl. BS vector table, 216-point TS↔Clarity parity grid, full lifecycle both settlement branches, execution-cost gate |
packages/shared/ | BS TS mirror (bs.ts), fixed-point helpers, typed event decoders, network config, zod DTOs | parity exercised by contract + API suites |
db/ | Drizzle schema + migrations + seed | covered via indexer/API suites (PGlite) |
indexer/ | Chainhook webhook consumer → Postgres projections + snapshots; reorg rollback; Stacks-API backfill | 5 acceptance tests (replay, duplicate no-op, rollback/re-apply, resume) |
api/ | Fastify REST: /vault, /vault/history, /rounds, /positions/:addr, /quote, /price, /tx/:txid, /health | 13 tests, zod-validated |
keeper/ | Cron + tick worker: weekly start-round, post-expiry settle-round, devnet price relay, retries/backoff, /health | 7 policy + loop tests |
web/ | React + Vite app: landing + vault dApp, payoff canvas, wallet flows with post-conditions | 25 component/format tests |
All tests pass across the workspace (pnpm test).
On-chain contracts
There are eight Clarity contracts. The vault is the hub; everything else is a dependency it calls. See the Contracts reference for live addresses and the Protocol section for mechanics.
| Contract | Role |
|---|---|
vault | The covered-call vault: deposits, shares, rounds, settlement, exercise |
bs-math | Fixed-point Black–Scholes pricing engine (pure, read-only) |
oracle-adapter | Normalizes a price feed; enforces positivity + staleness |
bcshare-token | Vault-gated SIP-010 share token (the vault's share ledger) |
sip-010-trait | The SIP-010 fungible-token trait |
pyth-mock | Mock price feed (keeper relays a price on testnet/devnet) |
sbtc-token | Mock SIP-010 sBTC (open mint = faucet) |
usdc-token | Mock SIP-010 USDC (open mint = faucet) |
Shared TypeScript core (packages/shared)
The shared package is the seam that keeps off-chain and on-chain in lockstep:
bs.ts— a TypeScript mirror ofbs-math.clar, term-for-term, used as the parity oracle in tests.networks.ts— network-keyed config (contract ids per devnet / testnet / mainnet).configWithDeployer()derives the whole address set from one deployer principal — this is how the web build bakes in live addresses without relying onprocess.env(which Vite strips from the browser bundle).events.ts— typed decoders for the vault'sprintevents.dto.ts— zod schemas shared by the API and web.
Off-chain services
The indexer, API, and keeper are independent TypeScript services (each with a
/health endpoint, Dockerized via docker/service.Dockerfile). They are
optional for a demo — see Services and the
Web app chain-direct mode.