Branch structure: See workflow/branching.md. Briefly: work on feature branches; commits directly on dev or master are blocked by hooks. Feature-branch commits run the quick suite, and merges into master are gated by the full suite.
Prerequisites: Node.js 24.x, Docker
npm install
verifier-run automated.build # or npm run buildSee build.md for more details.
After building, you can run:
./scripts/services.sh --start
./scripts/data.sh --seedThat's it. This uses Docker Compose to start a local Hardhat blockchain, deploys the smart contracts, starts IPFS, the Ponder indexer, and the platform API service, then publishes the selected UI domain SPA(s) to the local IPFS gateway.
Which UI bundles get built: local start currently publishes CauseStarter only. The eight legacy ui domains (commonality, lazyGiving, alignment, tally, content-funding, civility, common-sense-majority, conceptspace) each run a full Docker Vite build sequentially and were a major part of --start time. This is a temporary, reversible default — the compose services and source trees are still there.
- Default:
LOCAL_UI_DOMAINS=causestarter(implicit) - Restore every local IPFS SPA:
LOCAL_UI_DOMAINS=all ./scripts/services.sh --start - Subset:
LOCAL_UI_DOMAINS=causestarter,tally ./scripts/services.sh --start
The same env var is read by scripts/deploy-causestarter.sh. The allow-list lives in scripts/ui-domains.mjs (resolveLocalPublishDomains). CauseStarter's dedicated SPA on :8090 is always started and is independent of this list.
A local UI gateway then gives each published IPFS bundle a stable URL such as http://causestarter.localhost:8088/#/. Bookmark http://localhost:8088/admin for links to whatever was published. The latest CIDs, raw IPFS gateway URLs, and stable local URLs are written to ./data/ui-ipfs/<domain>/. You can re-print the stable URLs any time with ./scripts/services.sh --url. After that, run ./scripts/data.sh --seed to populate the chain with fake data. The default is --seed=tiny (5 users, 1 round, 12 statements, no invariant pass). Use --seed=small for the older 10-user / 3-round set.
For a clean local reset, use:
./scripts/data.sh --wipe
./scripts/services.sh --start
./scripts/data.sh --seedThe Anvil container (hardhat-node) persists blocks to data/hardhat/state.json via --state plus a 15s --state-interval. Docker stop is SIGTERM; a small entrypoint (scripts/anvil-docker-entrypoint.sh) forwards that as SIGINT so Anvil dumps instead of dying empty. Recreate the node after changing that compose service (docker compose up -d --force-recreate --no-deps hardhat-node) so the wrapper is mounted.
--wipe removes the saved local chain, IPFS repo, and Ponder indexer database. Do not delete only one of data/hardhat/ or data/ponder/: a reset chain with an old Ponder database can make the UI look empty because the indexer thinks old blocks were already processed. services.sh --start clears Ponder automatically when it sees Ponder data without a saved local chain, and data.sh --seed now errors if the indexer already contains events. If you intentionally want to add another seed run on top of existing data, pass --allow-seed-on-existing-data.
For a richer first-run demo that uses the formal seed-content corpus (excluding proliferation variants) and publishes one-shot Explorer/nudge fixtures without live AI worker calls, run:
./scripts/data.sh --seed=demo--start runs cause-assist, christian-bridge-creator, and the attester
bundle service-host-attesters (implication-attester + content-attester on one
Express listener, :3006). Health: http://localhost:3006/health, and per
service at http://localhost:3006/implication-attester/health. CauseStarter
reaches it through /api/implication-attester — proxied by Vite on :5174 and
by nginx on :8090 — which is what the bridge-cluster editor's "submit pairs to
attester" step calls.
Two local-only wrinkles are worth knowing about:
- content-attester is off by default (
CONTENT_ATTESTER_ENABLED=falseindocker-compose.yml). It requiresALIGNMENT_TOPIC_STATEMENT_CID, which is a published statement CID rather than a deploy artifact, so a fresh chain has none. Because the bundle validates all its services at boot, leaving it on takes the implication-attester down with it. Set that CID andCONTENT_ATTESTER_ENABLED=trueto run it. - Service signer wallets need funding. Compose falls back to prefunded
Hardhat keys, but
docker composealso auto-loads the root.env, and oncescripts/generate-wallets.mjshas run that file holds generated keys with no balance on a local chain. Services then boot, reportdegraded, and fail every on-chain write.--startnow runsnode scripts/fund-local-service-wallets.mjs, which tops up any configured signer below 1 ETH from Hardhat account #0 (idempotent, and refuses to run off chain 31337). Run it by hand after a wipe if an attester reportsdegraded.
No API keys or secrets are needed for local development. The generated root .env and ui/.env are based on the local deployment defaults; use .env.example and ui/.env.example as the reference for the variables that the stack and UI understand. scripts/services.sh owns starting/stopping/status/URL printing for Docker services; scripts/data.sh owns wiping and seeding local chain/IPFS/indexer data.
See deployment.md for testnet/mainnet deployment (which does require secrets).
See:
- Verifier workspace — how to run the checks; DESIGN.md for the testing philosophy and validation-pass runbook