Running locally
Two processes, one web app, no database to install. Every environment key, and the commands that make the city busy.
The whole thing runs on one machine with no Postgres and no Docker. With DATABASE_URL
left blank the server falls back to an embedded Postgres under packages/server/.data/vettai,
which is what the tests use too.
Get it running
git clone https://github.com/ramakrishnanhulk20/vettai
cd vettai
npm install
cp .env.example packages/server/.envFill in TREASURY_ADDRESS, then start the two halves in two terminals:
cd packages/server && npm run start # the world on port 8788
cd packages/web && npm run dev # the app on port 3004The web app proxies /api and /ws to http://localhost:8788 through its own rewrites, so
from the browser's point of view the game and the server are one origin. That is the same
shape the deployment uses, because inside the Nimiq Pay WebView a cross-origin socket would
need CORS and a second hostname for the wallet to trust.
The treasury is a third terminal, and it is the only one that wants a key:
cd packages/server && npm run treasuryPut TREASURY_PRIVATE_KEY in packages/server/.env.treasury, which only the treasury reads.
The world refuses to boot if it can see that key at all, so the split is enforced rather than
remembered.
The environment
Every key is validated at boot and a bad one fails loudly.
| Key | Used by | Meaning |
|---|---|---|
| PORT | world | HTTP and WebSocket port, 8788 by default |
| DATABASE_URL | both | Postgres URL. Blank runs the embedded database under .data/vettai |
| NIMIQ_RPC_URL | both | https://rpc.testnet.nimiqwatch.com for testnet, https://rpc.nimiqwatch.com for mainnet |
| NIMIQ_NETWORK | both | TestAlbatross or MainAlbatross, and it must match the URL |
| TREASURY_ADDRESS | both | The address that pays players and receives shop payments |
| TREASURY_PRIVATE_KEY | treasury | 64 hex characters, and never on the world |
| POOL_TOTAL_NIM | both | Hard stop on the whole game. 100 in the example file |
| DAILY_CAP_NIM | both | What one wallet can take in a UTC day. 5 in the example file |
| IP_WALLETS_PER_DAY | world | Distinct wallets allowed to claim from one address per UTC day. 2 |
| ALLOWED_ORIGINS | world | Comma list for CORS. Blank means same-origin only, through the web rewrite |
| MAP_SEED | world | The seed the city is built from. vettai-1. Change it and you get a different town |
| IP_SALT | world | Mixed into the hash of a caller's address. A long random string in production |
| TRUST_PROXY | world | Which proxies may say where a caller came from. Blank locally. Never true |
| REWARD_HUNT, REWARD_COURIER, REWARD_LANDMARKS, REWARD_LANDMARKS_REPEAT, REWARD_LANDLORD | both | Quest payouts in NIM. Blank uses the defaults |
| LADDER_PRIZES_NIM | both | The weekly top three, in order. 2,1,0.5 |
| LANDLORD_ENABLED | both | false until somebody turns the landlord quest on |
| LANDLORD_MIN_NIM | both | The stake that finishes the landlord quest. 10 |
| VETTAI_PROCESS | both | src/treasury/index.ts makes the image run the treasury. Unset runs the world |
The other commands
All of them are run from packages/server.
| Command | What it does |
|---|---|
npm test | The whole suite, including the property tests over the simulation |
npm run typecheck | Types only |
npm run prove | The twelve checks. The local run is a testnet tool; npm run prove -- --url <world> points it at the mainnet deployment. See The prove-it run |
npm run bot | A scripted player: signs in, joins the real socket, kills drones with computed aim, dodges bolts and walks the courier route |
npm run seed | Fills the database so the screens have something to read |
npm run db:generate | Regenerates the migrations after a schema change |
npm run db:migrate | Runs them. Both processes also migrate at boot |
The bot is the most useful of these while building. It is better at the game than a person, which is exactly why it exists: it makes an empty city busy, and it is also the attacker the caps are there to bound.
Playing it from a browser
The game needs the Nimiq Pay wallet provider to sign the login challenge, so a plain desktop
tab cannot sign in. For real play the app has to be opened inside Nimiq Pay through a deep
link of the shape https://nimpay.app/miniapps/open/<host>/play, which means the host needs
to be reachable over HTTPS.