Skip to main content
Volmarket is four pieces with four homes. They deploy independently.

Programs → Solana devnet

anchor build produces signal_markets/target/idl/signal_markets.json. Note the deployed program id and set it as PROGRAM_ID for the keeper. Deploy mock_validator as well (see its README) and point TXLINE_PROGRAM_ID at it. The program accepts exactly two validator addresses for resolve_market: the real TxLINE txoracle (6pW64gN1…) and mock_validator (FPnwSSp2…). Which one is used depends on two keeper settings that must agree — see selecting a path. To settle through the real validator:
  1. Set MOCK_VALIDATOR=false on the keeper, so it fetches a real proof instead of a stub.
  2. Point TXLINE_PROGRAM_ID at the txoracle address — this is already the devnet default, so leaving it blank also works.
  3. Raise the resolve transaction’s compute budget — verification costs ~234k CU.
To run the mock path, set TXLINE_PROGRAM_ID explicitly to the mock_validator address and leave MOCK_VALIDATOR=true. Leaving TXLINE_PROGRAM_ID blank here would send a stub proof to the real validator, which rejects it.

Frontend → Vercel

Point a Vercel project at the repository. The root vercel.json already sets the build:
  • Build command: cd frontend-react && npm install && npm run build
  • Output directory: frontend-react/dist
Set the app’s VITE_* environment variables in the Vercel project settings: RPC URL, USDC mint, Privy app id, fund endpoint, and fee recipient. These are client-side and non-secret. Vite bakes them in at build time, so redeploy after changing one.

Keeper → always-on host

A persistent Node process. Deploy it somewhere that keeps a process running.
The keeper carries a self-contained signal_markets.idl.json, so it runs without the signal_markets/ workspace beside it — IDL_PATH defaults to ./signal_markets.idl.json.

Environment

Set these on the host, starting from keeper/.env.example: For two-sided markets to pay out, the keeper must be running and funded: SOL for transaction fees, plus USDC if BOOTSTRAP_LIQUIDITY_USDC > 0, which it spends seeding empty pools.

Container hosts

Ready-made artifacts live in keeper/: Dockerfile, .dockerignore, .railwayignore, and fly.toml.
Create a new service with root directory keeper/, set the environment variables, and deploy. Running railway up from keeper/ also works.
IPv6 DNS on container hosts. Some container hosts have broken IPv6 egress while Node 24 resolves DNS IPv6-first. Every RPC fetch then fails with a bare TypeError: fetch failed, which crash-loops the keeper on startup.src/index.ts forces IPv4-first via setDefaultResultOrder("ipv4first"). Keep NODE_OPTIONS=--dns-result-order=ipv4first set as well.
RPC rate limits. If the RPC endpoint returns 429s, the keeper’s crash guards absorb them and let its retry timers resume the work rather than restarting. A restart would replay the full startup burst — market scan, board seed, bootstrap deposits, resolve and claim — against the same throttled endpoint, which earns the next 429. Use a dedicated RPC endpoint for anything beyond light testing.

Secrets

  • The keeper’s signing key is set only through the host’s secret manager. Never commit it, and never bake it into an image.
  • Never commit .env — only .env.example, with placeholders — or any Solana keypair (*id.json, .fee-wallet.json, .treasury.json). .gitignore covers these.
  • Never commit an RPC URL containing an API key. Config defaults in the repository are public endpoints only; supply your own key through environment variables on the host.
  • The frontend holds no secrets — only public RPC URLs, program ids, and mints.
A leaked keeper key lets someone submit resolutions as you, wasting fees. A leaked wallet key can drain funds. If either is exposed, rotate immediately.