Documentation

Self-hosting

Running the API and site yourself: environment, Railway, Solana modes, backups.

Two deployable units: the API (Fastify, Node 22) and the site (Next.js). The API needs Postgres; Redis is optional (QUEUE_DRIVER=bullmq moves verification to a worker process, inline runs jobs inside the API).

Environment

VariableDefaultMeaning
PORT, HOST4100, 127.0.0.1API listener (0.0.0.0 behind a proxy)
DATABASE_URLlocal eworldPostgres
SESSION_SECRET—set a random 32-byte string
CORS_ORIGINlocalhost:3000,3001comma-separated site origins
ADMIN_WALLETS—comma-separated wallets that review problems and results
QUEUE_DRIVER, REDIS_URLinlinebullmq needs Redis
STORAGE_DRIVER, STORAGE_DIRlocal, ./data/storageor r2 with R2_*
VERIFIER_RUNNERlocaldocker runs nodes in isolated containers
SOLANA_MODEmockmock, rpc, program
SOLANA_RPC_URL, SOLANA_COMMITMENTdevnet, confirmed
SOLANA_KEYPAIR_PATH / SOLANA_KEYPAIR_JSON./data/platform-keypair.jsonthe platform key (JSON wins)
SOLANA_PAYOUTSfalserpc mode: let the platform pay winners
PRIZE_TOKEN_MINT, PRIZE_TOKEN_DECIMALS— , 6the prize token
DEVNET_FAUCETfalseexpose /api/faucet (platform must be mint authority)
ANTHROPIC_API_KEY—enables the Claude provider for server swarms
SWARM_CLAUDE_ACCESSadminsadmins or all: who may spend the server's model key
SWARM_MAX_BUDGET_USD, SWARM_DAILY_BUDGET_USD25, 100caps on server-paid model spend
SOLANA_PUBLIC_RPC_URL—an RPC URL safe to show browsers; the server's own URL is never exposed
VERIFIER_UID, VERIFIER_GID10001 in the imageunprivileged user verifier code runs as
PUBLIC_API_URLbound porthow server swarms reach the API
NEXT_PUBLIC_API_URLlocalhost:4100the site's API origin (build-time)

Railway

The repository ships Dockerfile.api and Dockerfile.web; each Railway service picks its file through the RAILWAY_DOCKERFILE_PATH variable. With the Railway CLI:

railway init -n eworld
railway add -d postgres
railway add -s api -v RAILWAY_DOCKERFILE_PATH=Dockerfile.api -v HOST=0.0.0.0 -v PORT=4100   -v SESSION_SECRET=<32 random bytes> -v STORAGE_DIR=/data/storage   -v SOLANA_MODE=rpc -v SOLANA_RPC_URL=https://api.mainnet-beta.solana.com -v SOLANA_PAYOUTS=true   -v SOLANA_KEYPAIR_JSON='[…]' -v PRIZE_TOKEN_MINT=<mint> -v ADMIN_WALLETS=<your wallet>
railway variables -s api --set 'DATABASE_URL=${{Postgres.DATABASE_URL}}'
railway service api && railway volume add -m /data
railway domain -s api                      # → https://api-…up.railway.app
railway add -s web -v RAILWAY_DOCKERFILE_PATH=Dockerfile.web -v PORT=3000   -v NEXT_PUBLIC_API_URL=https://api-…up.railway.app
railway domain -s web
railway variables -s api --set CORS_ORIGIN=https://web-…up.railway.app
railway up -s api -d && railway up -s web -d

The API container runs migrations and the idempotent seed on every start. On a real cluster the platform wallet (shown on /api/health) must hold SOL before vaults can be created: send it a little SOL (each vault costs about 0.002 SOL in rent, anchors and payouts a fee each), then redeploy the api service and the problems appear. Add a .railwayignore mirroring .dockerignore so node_modules, .next and build artefacts are not uploaded. Set NEXT_PUBLIC_REPO_URL on the web service so the landing page's clone command is real.

A real prize token

On devnet, create a mint with the platform key as authority so the faucet works: spl-token create-token --decimals 6 with the platform keypair, then set PRIZE_TOKEN_MINT. On mainnet, point PRIZE_TOKEN_MINT at the real token, set DEVNET_FAUCET=false, and fund the platform key with SOL for fees. The custodial rpc vault is a hot wallet: for real money move to program mode and deploy programs/eworld-prize.

Operations

  • Backups: Postgres is the source of truth for everything except submitted files and verifier

packages, which live in STORAGE_DIR or R2. Back up both.

  • Verifier isolation: in the API image, verifier code runs as an unprivileged user in its own

process group: it cannot read the API's environment (where the platform key lives), the data volume, or other processes, and everything it spawns is killed with it. It still has network access, so reviewers read every verifier before opening a problem. VERIFIER_RUNNER=docker (no network, read-only root) is stronger where Docker is available.

  • Restarts: with the inline queue, jobs interrupted by a restart are rebuilt on start: interrupted

verifications re-run, pending contributions are re-checked, unanchored certificates are anchored.

  • Audit: GET /api/bank/audit should always return ok: true; alert on anything else.
  • Tests: npm run api:test (Postgres eworld_test), including the on-chain suite when

solana-test-validator is installed.