Retro monochrome Snake mini app with:
- instant guest play
- wallet mode (wagmi + viem)
- onchain best-score contract
- leaderboard indexed from onchain events
- lightweight retro sound effects with mute toggle
- Core Snake gameplay loop (grid movement, growth, collisions, speed-up)
- Mobile controls + desktop keyboard controls
- Wallet connect + username + score submit flow
- Leaderboard API and UI states
- Solidity score contract + Hardhat deploy scripts
The app now supports environment-based target chain mode:
mainnetmode: Base Mainnet is target (recommended for production)sepoliamode: Base Sepolia is target (recommended for local testing)
By default:
- production =>
mainnet - development =>
sepolia
You can override with:
NEXT_PUBLIC_APP_NETWORK_MODE=mainnetorsepolia
Sound is implemented with Web Audio API (no heavy audio library, no large files).
- Hook:
lib/sound/useRetroSounds.ts - Sound presets map:
soundMapin that same file - Mute storage key:
SOUND_MUTE_STORAGE_KEYinconfig/sound.ts
Implemented sounds:
- game start
- food collected
- score increase
- button click
- game over
- wallet connect success
- score submit success/error
- leaderboard refresh success/error
- Use the
Sound On / Sound Offtoggle in the game header. - Mute state is saved in
localStorage. - Audio waits for user interaction before playing (browser-safe behavior for desktop/mobile).
Create .env.local from .env.example and fill:
NEXT_PUBLIC_APP_NETWORK_MODE(mainnetorsepolia)NEXT_PUBLIC_BASE_MAINNET_RPC_URLNEXT_PUBLIC_BASE_SEPOLIA_RPC_URLNEXT_PUBLIC_SCORE_CONTRACT_ADDRESS_BASE_MAINNETNEXT_PUBLIC_SCORE_CONTRACT_ADDRESS_BASE_SEPOLIASCORE_EVENTS_FROM_BLOCK_BASE_MAINNET(optional but recommended on mainnet)SCORE_EVENTS_FROM_BLOCK_BASE_SEPOLIA(optional)SCORE_EVENTS_LOOKBACK_BLOCKS(optional fallback, default200000)
BASE_MAINNET_RPC_URLBASE_SEPOLIA_RPC_URLDEPLOYER_PRIVATE_KEYBASESCAN_API_KEY(optional for explorer verification flow later)
npm installPowerShell:
Copy-Item .env.example .env.localStart:
npm run devChecks:
npm run lint
npm run build- Set mainnet deploy env values:
BASE_MAINNET_RPC_URLDEPLOYER_PRIVATE_KEY
- Compile contract:
npm run compile:contracts- Deploy:
npm run deploy:base-mainnet- Copy deployed address into:
NEXT_PUBLIC_SCORE_CONTRACT_ADDRESS_BASE_MAINNET
- Set app mode to mainnet:
NEXT_PUBLIC_APP_NETWORK_MODE=mainnet
- Restart app.
- Set:
NEXT_PUBLIC_APP_NETWORK_MODE=sepoliaNEXT_PUBLIC_BASE_SEPOLIA_RPC_URLNEXT_PUBLIC_SCORE_CONTRACT_ADDRESS_BASE_SEPOLIA
- Optional deploy on Sepolia:
npm run deploy:base-sepolia- Restart app and test wallet + leaderboard safely on testnet.
Guest mode:
- play instantly
- local best score only
Wallet mode:
- connect wallet
- save username
- submit best score onchain
- appear in leaderboard
- Contract keeps only each address best score.
- Global leaderboard is built from
ScoreUpdatedevents (top 20 sorted by score desc, earliest timestamp tie-breaker). - If target-network contract address is missing, UI now shows setup guidance instead of broken behavior.
- Mainnet RPC endpoints often limit log ranges; API now scans in 10k block chunks.
- For best performance and full history, set
SCORE_EVENTS_FROM_BLOCK_BASE_MAINNETto your deployment block.
- Add/edit sound patterns in:
lib/sound/useRetroSounds.ts(soundMap)
- Add new sound IDs in:
config/sound.ts
- App UI:
components/game/SnakeApp.tsx - Sound hook:
lib/sound/useRetroSounds.ts - Sound toggle:
components/game/SoundToggle.tsx - Network mode config:
config/networks.ts - Wallet config:
lib/wallet/wagmiConfig.ts - Leaderboard client/server chain resolution:
lib/contracts/clients.tsapp/api/leaderboard/route.ts