Architecture
System architecture and tech
Closing Bell is a Next.js site plus a Node/TypeScript backend (API, indexer, draw keeper, Telegram) on Postgres. Shared draw math lives in @closing-bell/fairness. Cloud backend deploy target in this repo is Amazon Lightsail in us-west-2.
Frontend
- Next.js App Router marketing and docs site (this repo root).
- Public pages include the ritual home, /docs, and /verify.
- Hosting: AWS Amplify Hosting (GitHub →
next build, custom domainclosingbellonrh.com). PointNEXT_PUBLIC_API_BASEat the public Lightsail API URL. The site proxies that origin at/cb-api. Seedocs/AWS_HOSTING.md. - Launch knobs:
NEXT_PUBLIC_TOKEN_ADDRESS,NEXT_PUBLIC_CHART_URL,NEXT_PUBLIC_DRY_RUN_PAYOUTS.
Backend
- Node / TypeScript service under
backend/. - Read API (examples):
GET /health,/pot,/window/current,/odds,/ladder,/winners. - Indexer: on-chain buys/sells mint and burn tickets.
- Draw keeper: bag lock,
closing-bell-draw-v1, receipt, wipe, optional payout send. - Postgres for durable draws, winners, and ticket state.
Amazon Web Services
Deploy docs in the repo describe Lightsail in region us-west-2 (Oregon):
- Lightsail Container Service named
closing-bell-api(Nano scale in the guide). - Lightsail managed Postgres (example name
closing-bell-db) wired viaDATABASE_URL. - Public HTTP endpoint on port
8787with health check on/health. That URL is the public API the site and Telegram bot talk to. - Image push via Lightsail container registry (or ECR as noted in ops docs). Env and secrets stay in the Lightsail deployment config, not in git.
Telegram bot
Same backend process can announce rings and answer commands when TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are set. Without a token, announces log as [tg:dry].
- Commands:
/pot,/odds,/ladder,/next. - All commands reuse runtime / API math (no second odds formula).
Shared fairness package
packages/fairness exports closing-bell-draw-v1: odds helpers, capped weights, seed, weighted pick, snapshot hash. Backend keeper and /verify import the same package so receipt MATCH cannot drift from keeper math.
Local fixture mode vs cloud
| Mode | What runs | Typical flags |
|---|---|---|
| Local fixture | Docker Compose API + Postgres. Fixture swaps drive tickets. Telegram optional. | FIXTURE_MODE=true, DRY_RUN_PAYOUTS=true |
| Cloud (Lightsail) | Same container image on Lightsail with managed Postgres. Public /health API. | Same knobs until go-live. Set live RPC_URL / token addresses when indexing for real. |
| Go-live payouts | Keeper still picks via formula. GME transfer enabled only when dry-run is off and jackpot keying is configured. | FIXTURE_MODE=false, DRY_RUN_PAYOUTS=false |
Seed material (reference)
material = lowercase(blockhashAtSnapshot) + "|" + windowId + "|" + potBalance seed = keccak256(UTF-8 bytes of material) cursor = seed mod totalCappedWeight # then walk entrants sorted by weight DESC, address ASC