Sub Rosa does not require a committed .env file. Secrets stay out of git; you inject them where each layer runs.
| Layer | Needs env? | When vars are read |
|---|---|---|
Jury UI (apps/web) |
Optional | Build time (VITE_* baked into static JS) |
| Keeper / appraisal API | Yes (secrets) | Runtime (shell, systemd, Fly/Railway secrets) |
| One-off scripts (deploy, e2e) | Yes | Runtime (inline VAR=… command or CI secrets) |
The demo works from embedded DEMO_TRACE (demo-trace.generated.ts from pnpm agents:e2e). No .env needed.
pnpm install
pnpm web:build
# static output → apps/web/distHost dist/ on Vercel, Netlify, Cloudflare Pages, GitHub Pages, S3, etc.
Build settings (generic):
| Setting | Value |
|---|---|
| Install | pnpm install |
| Build | pnpm web:build |
| Output directory | apps/web/dist |
| Node | 22+ |
Only if you want “Poll live contract” on the deployed site, set these before pnpm web:build in the hosting UI (Vercel → Settings → Environment Variables, etc.).
Copy from apps/web/.env.example:
VITE_RPC_URL=https://soroban-testnet.stellar.org
VITE_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
VITE_CONTRACT_ID=CAPTODBCDEVIK23ALBJBS2TXRTIK47ZA5MBTHYF4XLHG2BK7JPYUCU2Y
VITE_ROUND_ID=1Mainnet example (live poll against mainnet smoke):
VITE_RPC_URL=https://rpc.ankr.com/stellar_soroban
VITE_NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015"
VITE_CONTRACT_ID=CA7KSDEYJEPGZEB2ZROTLUWKQQ6GIRIQNGG6Z745MZ34QHP4UJPWODEX
VITE_ROUND_ID=1Important: Vite only exposes vars prefixed with VITE_. They are public in the browser bundle — never put secret keys here.
cp apps/web/.env.example apps/web/.env.local
# edit VITE_* then:
pnpm web:dev.env.local is gitignored; not required for production.
Runs on a server/VM, not in the static site. No .env file required — export vars in the process manager:
export KEEPER_SECRET="S…"
export ROUND_CONTRACT_ID="C…"
export RPC_URL="https://soroban-testnet.stellar.org"
export NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
export WATCH_POLL_MS=15000
export WATCH_ROUND_IDS=1
pnpm keeper:watchOr one line:
KEEPER_SECRET=S… ROUND_CONTRACT_ID=C… pnpm keeper:watchOn Fly.io / Railway / GitHub Actions: put the same names in Secrets, not in the web build.
See root .env.example for the full keeper variable list.
Scripts read env at invocation — no .env file on disk:
OPERATOR_SECRET=S… pnpm mainnet:deploy
OPERATOR_SECRET=S… \
ROUND_CONTRACT_ID=CA7KSDEY… \
RPC_URL=https://rpc.ankr.com/stellar_soroban \
pnpm mainnet:settleE2E scripts (lifecycle:e2e, agents:e2e) generate ephemeral keys via Stellar CLI — they do not need a root .env either.
Runtime env for pnpm appraisal:start:
| Var | Purpose |
|---|---|
FACILITATOR_SECRET |
Signs/submits x402 settle txs |
PAY_TO / server key |
Receives USDC |
RPC_URL |
Soroban RPC |
PRICE |
Appraisal price (default 0.10) |
PORT |
HTTP port (default 4021) |
Agents point at the public URL via X402_APPRAISAL_URL — not baked into the web UI.
Shipping jury demo only?
→ pnpm web:build, upload dist/, no env
Want live on-chain overlay on the site?
→ set VITE_* in hosting build env, then build
Running keeper 24/7?
→ KEEPER_SECRET + ROUND_CONTRACT_ID on the server (runtime)
Deploying new contract round?
→ OPERATOR_SECRET inline for mainnet:deploy / mainnet:settle
pnpm mainnet:verify # read-only — no secrets
pnpm mainnet:micro # dry-run checklist
MAINNET_CONFIRM=SUB_ROSA_MAINNET OPERATOR_SECRET=S… BIDDER_SECRET=S… \
pnpm mainnet:micro -- --execute # optional micro commit (≤1 XLM escrow)- Never commit
.envwith secrets (already in.gitignore). - Never put
S…secret keys inVITE_*— they end up in public JS. - Rotate any key that was pasted in chat or logs.