Dark Waters is a turn-based, on-chain naval strategy game on Starknet (Dojo). Players commit hidden fleets, fire with commit-reveal attacks, and resolve hits/misses with Merkle proofs.
Live app: https://dark-waters-m2fn.vercel.app/
- Fog of war: fleets stay hidden until protocol-valid reveals.
- Verifiable fairness: hit/miss is checked against committed board roots.
- Optional staking: STRK/WBTC matches with on-chain settlement.
- Recovery-safe UX: encrypted board secrets + restore flow.
- Replay-safe sync: checkpointed, deduped event indexing.
- Modern game UX: proof rail, profile progression, medals, post-match debrief, audio pack.
Dark Waters is live on Sepolia through the official Denshokan path and is already indexed on Fun Factory.
- Sepolia world:
0x0035a61193deacca08e9438fc102a8fd6c9a8f6d1de392fd61277022793b9a3f dark_waters-Actions:0x0ef4aa6462fc34fcba0a18b49973bc83004757cc59c9940412efddae68b9637- Denshokan game id:
19 - Torii endpoint:
https://api.cartridge.gg/x/dark-waters/torii - Live app:
https://dark-waters-m2fn.vercel.app/ - Fun Factory listing:
https://funfactory.gg/games?network=sepolia
What has been validated onchain:
- official Denshokan registration is complete
configure_denshokanandinitialize_denshokansucceed- Denshokan token mint/select flow works from the frontend
link_sessionandcommit_board_egswork on Sepoliascore(token_id)andgame_over(token_id)return the correct unfinished-game values- Dark Waters appears in the Denshokan
/gamesAPI and on Fun Factory
Known follow-up:
- the current Fun Factory entry is live, but replacing its card image requires a fresh contract
registration because the current Denshokan registry flow only exposes
register_game(...), not a metadata update entrypoint
- Host can now spawn a game without pre-selecting an opponent.
- Spawned matches appear in a shared
Spawnedlog. - Any other player can click
Engageto join and activate that game. - Supports both no-stake and staked open matches.
- Open app, connect wallet.
- On home screen, click
Start / Resume Matchto openOperations. - In Operations:
Host: spawn an open match (optional stake token + amount).Spawned: view open matches waiting for an opponent and clickEngage.My Games: resume an existing match.
- Place ships on your 10x10 board.
- Commit board root on-chain.
- Save/copy the recovery package after commit.
- Fire on target grid.
- Each shot follows protocol:
commit_attackreveal_attack- defender Merkle-based
reveal
- Track real-time state in:
Proof Rail(protocol step status)Combat Intel(accuracy, streak, medals)Commander Profile(XP/rank progression)
- Match ends on fleet destruction, timeout resolution, or stake cancellation path.
- Open
View Debrieffor post-match summary:- XP gain + rank progress
- medals earned
- shot heatmap
- replay timeline
- Open lobby flow:
spawn_open_game()spawn_open_game_with_stake(stake_token, stake_amount)engage_game(game_id)
- Board commit:
commit_board(game_id, merkle_root) - Attack commit-reveal:
commit_attack(game_id, attack_hash)reveal_attack(game_id, x, y, reveal_nonce)
- Defender proof reveal:
reveal(game_id, x, y, cell_nonce, is_ship, proof)
- Create open staked game:
spawn_open_game_with_stake(stake_token, stake_amount) - Opponent locks stake:
lock_stake(game_id) - Settlement occurs on valid game end path.
- Setup timeout safeguards include
cancel_staked_game(game_id)when eligible. - Legacy direct-opponent systems remain available in contract:
spawn_game(opponent)andspawn_game_with_stake(opponent, ...).
Set env vars in dark-waters-layout/.env.local:
NEXT_PUBLIC_SEPOLIA_TORII_URL=https://<your-torii-endpoint>
NEXT_PUBLIC_SEPOLIA_BOT_ADDRESS=0x<bot-account-address>
NEXT_PUBLIC_SEPOLIA_STRK_TOKEN_ADDRESS=0x...
NEXT_PUBLIC_SEPOLIA_WBTC_TOKEN_ADDRESS=0x...Run:
cd dark-waters-layout
npm install
npm run devRun these commands from the project root:
cd dark-waters-layout
npm install --legacy-peer-deps
npm install @supabase/supabase-js --legacy-peer-depsInstall Supabase CLI (official, project-local):
npm install --save-dev supabase
npx supabase --versionNo-install fallback (also official):
npx supabase@latest --versionAuthenticate and link your hosted Supabase project:
cd dark-waters-layout
npx supabase login
npx supabase init
npx supabase link --project-ref <YOUR_SUPABASE_PROJECT_REF>Apply chat schema migration:
cd dark-waters-layout
npx supabase db pushThis applies both:
- initial chat tables
- hardening migration (removes public
chat_messagesread policy and uses API-auth polling)
Required local env in dark-waters-layout/.env.local:
NEXT_PUBLIC_SUPABASE_URL=https://<YOUR_PROJECT_REF>.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<YOUR_ANON_KEY>
SUPABASE_SERVICE_ROLE_KEY=<YOUR_SERVICE_ROLE_KEY>
CHAT_AUTH_SECRET=<RANDOM_LONG_SECRET>Generate a secret quickly:
openssl rand -hex 32After env updates:
cd dark-waters-layout
npm run devUse the backend bot worker to automate the bot account for direct spawn_game(bot) matches.
cd dark-waters-layout
BOT_TORII_URL=https://<your-torii-endpoint> \
npm run bot:runProduction flow (hands-off for players):
- Bootstrap session one time (interactive):
cd dark-waters-layout
cp .env.bot.example .env.bot
set -a && source .env.bot && set +a
npm run bot:bootstrapOpen the printed Controller URL, approve policies, and let the process exit.
If you see EACCES on /var/lib/..., use a user-writable path for:
BOT_SESSION_BASE_PATH(example:${HOME}/.dark-waters-bot/session)BOT_STATE_PATH(example:${HOME}/.dark-waters-bot/bot-state.json)
- Start non-interactive daemon mode:
cd dark-waters-layout
set -a && source .env.bot && set +a
npm run bot:run:prod- Keep it always-on with PM2:
cd dark-waters-layout
set -a && source .env.bot && set +a
pm2 start ecosystem.bot.config.cjs --update-env
pm2 save
pm2 startup- Monitor liveness (only if
BOT_HEALTH_PORTis set to a non-zero port):
curl -s http://127.0.0.1:<BOT_HEALTH_PORT>/healthRequired/important envs:
BOT_TORII_URLBOT_ADDRESS(recommended safety check; must match authorized Controller account)BOT_ALLOW_INTERACTIVE_AUTH=falsein production daemon runsBOT_SESSION_BASE_PATHon persistent disk (do not use ephemeral paths in production)
Optional envs:
BOT_POLL_MS(default4000)BOT_BOARD_SEED(for deterministic fleet generation)BOT_RPC_URL,BOT_WORLD_ADDRESS,BOT_ACTIONS_ADDRESSBOT_SESSION_BASE_PATH(orCARTRIDGE_STORAGE_PATH) for persisted session filesBOT_CHAIN_ID(defaultSN_SEPOLIAchain id)BOT_HEALTH_PORT(default disabled0; set e.g.8787for monitoring)
scarb testDark Waters now targets the official Denshokan production path for Fun Factory listing. The
Actions contract is the minigame surface and should be initialized against the official Sepolia
Denshokan token:
- token:
0x0142712722e62a38f9c40fcc904610e1a14c70125876ecaaf25d803556734467 - registry:
0x040f1ed9880611bb7273bf51fd67123ebbba04c282036e2f81314061f6f9b1a1 - renderer:
0x035d01a7689ade1f5b27e50b07c923812580bb91bd0931042a9a2f8ff07dc7ec - live world:
0x0035a61193deacca08e9438fc102a8fd6c9a8f6d1de392fd61277022793b9a3f - live
Actions:0x0ef4aa6462fc34fcba0a18b49973bc83004757cc59c9940412efddae68b9637 - Fun Factory / Denshokan game id:
19 - Torii:
https://api.cartridge.gg/x/dark-waters/torii
Fresh or rebuilt manifests should leave dojo_init() empty and configure Denshokan explicitly
after migration:
sozo -P sepolia build
bash ./scripts/patch_egs_init_calldata.sh sepolia
sozo -P sepolia migrate --waitFor already-initialized worlds, run the post-upgrade admin flow:
sozo -P sepolia execute dark_waters-Actions configure_denshokan \
0x0142712722e62a38f9c40fcc904610e1a14c70125876ecaaf25d803556734467 1 --wait
sozo -P sepolia execute dark_waters-Actions initialize_denshokan --waitAfter initialization:
- verify
dark_waters-EgsConfigcontains the Denshokan token andis_initialized = 1 - mint or select a Denshokan token through the app
- link the token to a game via
link_session - use
_egsgameplay entrypoints for board commit and combat - verify the game appears in the Denshokan
/gamesAPI and then onhttps://funfactory.gg/?network=sepolia
Current outcome:
- Dark Waters is already registered and indexed on the official Denshokan stack
- the game is visible on Fun Factory with id
19 - the current frontend config should continue to point at the Sepolia world/actions addresses above
- the prepared anchor image metadata will only show up on Fun Factory after a fresh contract registration, not by re-running initialization on the same contract address
The helper session token, helper registry, and custom callback contracts remain in the repo as legacy smoke-test artifacts, but they are not part of the Fun Factory production path anymore. Do not use them for Sepolia listing validation.
docker compose upTerminal 1:
katana --dev --dev.no-feeTerminal 2:
sozo build
sozo migrate
torii --world <WORLD_ADDRESS> --http.cors_origins "*"After on-chain migration, update:
dark-waters-layout/src/config/sepolia-config.tsWORLD_ADDRESSACTIONS_ADDRESSDEPLOYED_BLOCK
Then restart frontend.
Current checked-in Sepolia config values are already set to the live Denshokan deployment above.
.
|- src/ # Dojo world + systems
|- dark-waters-layout/ # Next.js game client
| |- app/
| |- components/
| |- hooks/
| |- public/audio/
| `- src/
|- compose.yaml
|- dojo_dev.toml
`- dojo_sepolia.toml
- Private before reveal: fleet layout, attack intent.
- Public on-chain: commitments, revealed attacks, outcomes, game metadata.
- Local secrets are encrypted at rest in browser storage and support recovery restore.