The prove-it run
One command, twelve checks, run against the live Nimiq chain. Real payouts, real refusals, saved output.
Claims in a threat model are only worth what they are worth when somebody runs them. So every claim Vettai makes about its money path is exercised by one command, against the live Nimiq chain, and the output of each run is saved in the repository.
The command
cd packages/server
npm run proveIt starts the real world in the same process, plays it with a scripted wallet through the real socket, then attacks it: forged signatures, replays, somebody else's quest, all three caps, a short shop payment from the wrong wallet, a flood on the socket, and a payout the treasury has already sent once. Every refusal is printed with the reason the server gave.
The local run is a testnet tool by design. It moves real test NIM out of a real test wallet, so running it never spends the mainnet pool.
There is a second mode that points at a deployment:
npm run prove -- --url https://world-production-4620.up.railway.appNothing runs locally in that mode and no key is needed. The run signs in over the network, plays the deployed world through its own socket, claims, waits for the deployed treasury to pay, and reads that payment back off the chain the deployment says it is on. Against Vettai's own deployment that chain is Nimiq mainnet, and the run pays one real 0.5 NIM quest. The checks that need the host's own database or its configured caps are skipped rather than faked, and the summary counts them as skipped.
Mainnet proof: 0.5 NIM, memo vettai:e47d1142, block 61817025, 3dca159e...d18113
What each of the twelve checks proves
| Check | What it proves |
|---|---|
| 1 | The node and the treasury are the ones we say they are: the network matches, and the private key derives the configured address |
| 2 | A wallet signs in and the server reads its address off the key, not off the request |
| 3 | A forged signature and a replayed challenge are both refused |
| 4 | A scripted wallet plays the real world through the real socket until the hunt quest is genuinely done |
| 5 | The hunt claim is signed once. A replay is refused, and so is another wallet trying to claim it |
| 6 | The treasury pays it on chain, and the payment carries the quest id in its memo |
| 7 | The daily cap holds the claim that would cross it |
| 8 | The third wallet claiming from one network address is held |
| 9 | The pool stops the claim that would spend past it |
| 10 | A real shop payment grants the gear, and a short one from another wallet does not |
| 11 | The socket refuses a flood, a long move vector and nonsense frames |
| 12 | A payout that already has a hash is looked up, never sent again |
Checks 7, 8 and 9 run with the caps turned down, so a single run can actually reach all three.
The last saved run
This is a testnet run, kept as the record of all twelve checks, because five of them need the host's own database and its configured caps. It is the file as it was written, not a summary of it.
vettai prove-it, 2026-09-16T12:39:04.425Z
world http://127.0.0.1:57099, database pglite in memory, node https://rpc.testnet.nimiqwatch.com
caps for this run: daily 0.6 NIM, pool 1.2 NIM, 2 wallets per IP per day
1/12 PASS the node and the treasury are the ones we say they are: TestAlbatross at block 11598689, key derives NQ92YGUBVUV9LX6H36G033C1081VTMDD9152, balance 19.07 NIM
2/12 PASS a wallet signs in and the server reads its address off the key: NQ03EN6YK5MEHGURXNC98G3GTGAJBMTVSUEV signed in, /api/me agrees, nothing named an address but the key
3/12 PASS a forged signature and a replayed challenge are both refused: forged signature 401 "signature does not match message", replayed challenge 401 "nonce used"
4/12 PASS a scripted wallet plays the real world until the hunt quest is done: 5 kills in 19s through the socket, reward 0.5 NIM
5/12 PASS the hunt claim is signed once, and a replay or another wallet is refused: queued with memo vettai:035a98cb, replay 409 "already claimed", another wallet 404
sent claim=bcd243c1-9c16-4ea2-a10c-948e0c045173 to=NQ03EN6YK5MEHGURXNC98G3GTGAJBMTVSUEV hash=eda94221aea512261739002eac94d119bad46fee901c8dee4f05715c33761a89
paid claim=bcd243c1-9c16-4ea2-a10c-948e0c045173 hash=eda94221aea512261739002eac94d119bad46fee901c8dee4f05715c33761a89 block=11598713
6/12 PASS the treasury pays it on chain and the payment carries the quest id: 0.5 NIM to NQ03EN6YK5MEHGURXNC98G3GTGAJBMTVSUEV, memo "vettai:035a98cb", hash eda94221aea512261739002eac94d119bad46fee901c8dee4f05715c33761a89, block 11598713
7/12 PASS the daily cap holds the claim that would cross it: 0.5 NIM already committed today, 0.2 NIM more crosses the 0.6 NIM cap, landmarks and streak both held for "daily cap"
8/12 PASS the third wallet claiming from one address is held: wallets 1 and 2 from 127.0.0.1 were queued, 2 distinct wallets had claimed, wallets 3 and 4 held for "ip cap"
9/12 PASS the pool stops the claim that would spend past it: 0.7 NIM + 0.2 NIM -> queued, 0.9 NIM + 0.2 NIM -> queued, 1.1 NIM + 0.2 NIM -> held; 1.3 NIM is past the 1.2 NIM pool
shop order b5dca953-b06f-43a4-89cb-065d21b8bcea paid with blaster-mk2 hash=84f96c0098166717387b1e24275ad5b586a7396d4791295f284c051fabdc3015 block=11598787
skipped hash=c736e0c04f22916fcc87baa2d1678e2e5d8f8ffd88b05e71902b591fb099bfcc block=11598788: sender mismatch
10/12 PASS a real shop payment grants the gear and a short one from another wallet does not: paid 0.6 NIM with memo vettai:shop:b5dca953, hash 84f96c0098166717387b1e24275ad5b586a7396d4791295f284c051fabdc3015, gear now mk2; 0.01 NIM from the wrong wallet on vettai:shop:c0107617 left the order pending
11/12 PASS the socket refuses a flood, a long move vector and nonsense frames: 40 moves in a second: 10 dropped and 5.99 m covered, under the 6 m/s cap; dx 50 stored as a unit vector; 20 fire frames gave 4 accepted shots on an mk1; three bad frames closed the socket with 1008
still pending claim=b0fe9301-3606-442d-9396-d67db8857395 hash=569354bd431f2e1cae76dcd9c8d1689f479418106ade4610eda09d306d2718c2
sent claim=23322ba4-5c2d-484c-952c-f52091feb55c to=NQ75N8DCMRFP6QDDJDTQ17TERV4Y6P862840 hash=4617b411755ba750371737bb483f5befc2005ed4da56025590eb98c1a34b6a1c
sent claim=8c2113ae-804d-42ef-be65-dbf8d3d22611 to=NQ0520FVBUVK6J7TJGYPR661LVS2GEXT2XPK hash=793c192e75758f74e8ee2f8e95d2b77d8edf951bc072e9ac5fd15d627bfefbe3
paid claim=23322ba4-5c2d-484c-952c-f52091feb55c hash=4617b411755ba750371737bb483f5befc2005ed4da56025590eb98c1a34b6a1c block=11598796
paid claim=8c2113ae-804d-42ef-be65-dbf8d3d22611 hash=793c192e75758f74e8ee2f8e95d2b77d8edf951bc072e9ac5fd15d627bfefbe3 block=11598798
12/12 PASS a payout that already has a hash is looked up, never sent again: 2 payout(s) left in this pass; the claim holding a made up hash stayed "sending" and was not signed again; the treasury fell by 0.4 NIM, exactly the confirmed payouts of this pass
treasury NQ92YGUBVUV9LX6H36G033C1081VTMDD9152 now holds 17.28 NIM
12/12 passed
finished in 129sEvery run is saved under docs/proofs/ in the repository, so the numbers above can be
checked against the chain long after the run finished. The transaction hashes and block
numbers in it are real testnet payments.
What this run does not cover
It proves the money path and the refusals. It does not prove the advisory locks under real contention: the test database runs one statement at a time, so nothing here exercises many Postgres connections holding those locks against each other. That limitation is written down in What we did not fix.