Run it yourself
Run the whole stack on your own machine, with no outside network: a local database, a local chain and the app.
Start a local environment
You need Docker, Foundry, Node.js 24 with pnpm, and OpenSSL.
infra/local/up.shThe script:
- starts the project's Postgres container and creates a database called
linvesther_local, separate from any other database on that server; - starts a local chain (Anvil) and deploys the registry contracts to it. The chain's state is saved in
infra/local/.state, so it survives a restart; - writes
.env.localwith the addresses, fresh secrets and a signing key for your instance. It is never committed.
Then start the API and the web app in two terminals, and open the address it prints.
set -a; source .env.local; set +a; pnpm --filter @linvestherzk/api start
pnpm --filter @linvestherzk/web devBuild the worker once with cargo build -p binance-worker --manifest-path services/Cargo.toml. Add --no-default-features if you do not need zero-knowledge proofs, which require the RISC Zero toolchain.
infra/local/down.sh stops the chain, and infra/local/up.sh --reset forgets the local chain and database and starts over.
What is kept
| State | Where | Note |
|---|---|---|
| Sign-ins | Postgres, for 30 days | Signed out when the session expires or you sign out. |
| Identities and passkeys | Postgres | Without them, nobody could sign in again after a restart. |
| Published claims | Postgres | A link someone shared keeps working until the claim expires. |
| Profile settings | Postgres | Privacy mode never switches itself off. |
| Connected accounts and history | The worker's Postgres tables | Credentials are stored encrypted. |
| Ownership and registration | The chain | Public, and rebuilt from events if the database is lost. |
All of this needs DATABASE_URL. Without it the API starts anyway, warns, and keeps sign-ins, identities and claims in memory, so they are lost when it restarts. Sign-in challenges are always in memory, so run a single API instance.
Your instance and trust
Your instance signs the figures it reads with its own key, so anything it proves is self-attested for everyone else: nobody has a reason to trust a key they have not chosen to trust. That is by design. It makes your own numbers fully usable to you, and it keeps a self-run instance from passing for anyone else's.
Your instance reports its collector at /public/collector. Someone who trusts you can add that fingerprint to their own list. See Verify a proof.
Beyond your machine
- Passkeys need HTTPS on a real domain. Set
WEBAUTHN_RP_IDandWEBAUTHN_ORIGINto it; the session cookie is then markedSecure. - Set
CORS_ORIGINSto the origins of your web app. A state-changing request from any other origin is refused. - Point
CHAIN_RPC_URLand the contract addresses at the network you deploy to, and setCHAIN_DEPLOY_BLOCKso the indexer does not scan from the start of the chain. - Behind a reverse proxy or tunnel, set
TRUST_PROXY_HOPSto the number of proxies in front of the API (usually1). Rate limits are per client address, and without this every client looks like the proxy and shares one allowance. Leave it unset when the API is reachable directly. - Keep the database on a private network. The default password in the repository is public; the local environment binds Postgres to this machine only, and yours should be reachable only by the API and worker.
- Serve the web app with
next buildandnext start, not the development server. - Set your own
POSTGRES_PASSWORD. Secrets can also be mounted as files (BINANCE_WORKER_ENCRYPTION_KEY_FILE,BINANCE_WORKER_A0_SIGNING_KEY_FILE, mode 600), which keeps them out of the process environment.RELAY_MAX_PER_HOURcaps the gas you will fund. - Keep
BINANCE_WORKER_ENCRYPTION_KEYandBINANCE_WORKER_A0_SIGNING_KEYsafe. The first protects stored credentials; losing the second changes your collector identity.