Sub Rosa does not require a committed .env file. Secrets stay out of git; you inject them where each layer runs.
CI runs node scripts/check-env-examples.mjs on the root and web .env.example
files. It rejects duplicate active names within either file and populated
secret-shaped values; keep credential fields empty or use clear placeholders.
Run node --test scripts/check-env-examples.test.mjs for the focused tests.
| 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:watchFor a read-only preflight of one round, no signing secret is required:
KEEPER_DRY_RUN=true ROUND_CONTRACT_ID=C… ROUND_ID=1 pnpm --filter @sub-rosa/keeper startThe command prints a structured JSON summary with the round status, Drand round, bidder/revealed counts, current phase, and next action. It exits without submitting open, reveal, clear, settle, or void transactions.
On 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 |
X402_NETWORK |
stellar:testnet (default) or stellar:pubnet |
NETWORK_PASSPHRASE |
Optional; when set, must match X402_NETWORK |
RPC_URL |
Soroban RPC; required for pubnet |
PAYMENT_ASSET |
SEP-41 contract; defaults to USDC for the selected network |
PRICE |
Appraisal price (default 0.10) |
PORT |
HTTP port (default 4021) |
Minimal local/testnet configuration:
export FACILITATOR_SECRET="S…"
export PAY_TO="G…"
export X402_NETWORK="stellar:testnet"
export NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
pnpm appraisal:startTestnet defaults to the canonical testnet USDC contract, Soroban testnet RPC,
price 0.10, and port 4021. For pubnet, set
X402_NETWORK=stellar:pubnet, the public-network passphrase, and an explicit
RPC_URL; the default payment asset then changes to pubnet USDC.
Configuration errors fail before the HTTP server starts and name the affected variable, for example:
FACILITATOR_SECRET: missing required env var
PRICE: must be a finite number greater than 0
X402_NETWORK: unsupported network "stellar:mainnet"
NETWORK_PASSPHRASE: does not match X402_NETWORK=stellar:testnet
RPC_URL: is required when X402_NETWORK=stellar:pubnet
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:ready -- --strict # consolidated read-only readiness
pnpm mainnet:ready -- --fixture packages/sdk/fixtures/mainnet-readiness.json --strict # CI-safe: recorded deployment, no mainnet RPC
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)
pnpm mainnet:settle # readiness + capped, read-only dry-run
MAINNET_CONFIRM=SUB_ROSA_MAINNET KEEPER_SECRET=S… ROUND_CONTRACT_ID=C… \
pnpm mainnet:settle -- --execute # keeper settle (requires readiness + confirm)pnpm mainnet:ready and pnpm mainnet:verify load the committed manifest
(packages/sdk/mainnet-artifacts.json) and compare the live deployment with it
through the read-only client. Four fields must agree, and each one blocks on its
own:
| manifest field | live value it is compared against |
|---|---|
contractId |
the contract the client is bound to |
networkPassphrase |
the passphrase the RPC reports |
wasmHash |
the executable hash in the contract ledger entry |
tokenContract |
usdc in the deployed GlobalConfig (escrow SAC) |
A field that cannot be read blocks too, and the report names the disagreeing field. Passphrases are printed as a short fingerprint, never in full, so logs stay shareable. The manifest is a committed file on purpose: editing readiness, the manifest, and the verify script in one review is what keeps a green check honest.
Neither command needs a secret key. Balance checks take public keys:
OPERATOR_PUBLIC_KEY, KEEPER_PUBLIC_KEY, BIDDER_PUBLIC_KEY.
- Run
pnpm mainnet:ready -- --strict(no secrets required for baseline checks). - Run
pnpm mainnet:verifyto confirm settled round 1 matches frozen artifacts. - Optional balance review:
pnpm mainnet:ready -- --with-balanceswith funded operator/keeper public keys in env. - For value-moving commands, set
MAINNET_CONFIRM=SUB_ROSA_MAINNETand re-run readiness implicitly via deploy/micro/settle guards. - After settlement, confirm contract native XLM SAC balance is 0 (settle script enforces this).
- 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.
Before attaching terminal transcripts, container logs, or browser exports to an
issue, pull request, or support channel, strip every credential, bearer token,
and pre-reveal secret listed below. Structured logger output from
@sub-rosa/logging automatically scrubs common secret keys (secret, token,
password, privateKey, credential, cookie, authorization, apiKey,
mnemonic, seedPhrase), S… Stellar secret seeds, PEM blocks, Bearer /
Basic headers, and URL credentials to [REDACTED], but raw shell command
lines, unhandled crash dumps, and DevTools HAR files can still include them:
- Keeper logs (
services/keeper): RemoveKEEPER_SECRET,OPERATOR_SECRET, andBIDDER_SECRET(S…Stellar secret seeds), provider-authenticatedRPC_URLorSTELLAR_RPC_URLstrings (basic-auth userinfohttps://user:pass@…or query parameters such as?api_key=…/?token=…), and any unrevealed bidvalueornonceprior to Drand roundR. - Appraisal API logs (
services/appraisal-api): RemoveFACILITATOR_SECRET,Authorization(Bearer/Basic) headers, x402 payment headers (X-Payment/ signed XDR payloads), and provider-keyedRPC_URLendpoints. - SDK & relayer logs (
packages/sdk): RemoveSTELLAR_SECRET_KEY,OZ_CHANNELS_API_KEY, session Ed25519 private keys, round auditor secret keys, and unrevealed bid plaintext (value,nonce) beforeopen_revealsucceeds on-chain. - Browser & DevTools logs (
apps/web): Remove passkey/session key material inlocalStorageorsessionStorage,CookieandAuthorizationheaders in Network HAR exports, wallet seed phrases orS…secret keys, and local unrevealed bid values/nonces before the round entersRevealing.