Skip to main content

Setup

Get the project

git clone git@github.com:GambaLabs/slotbattle-demo-casino.git
cd slotbattle-demo-casino
npm install

Configure

cp .env.example .env
SLOTBATTLE_BASE_URL=http://127.0.0.1:8070
SLOTBATTLE_API_KEY=sbk_0a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9
SLOTBATTLE_WEBHOOK_SECRET=a-secret-you-also-set-in-the-console
SLOTBATTLE_WS_BASE_URL=
DEMO_GAME_ID=sweet-bonanza
DEMO_CREATOR_GAME_URL=

DEMO_CREATOR_GAME_URL is optional. Leave it empty and the demo mints a session for the creator's seat per battle. Set it when you have one session to reuse every time, or when the mint is not available to you. See First battle.

None of those names carries a VITE_ or NEXT_PUBLIC_ prefix, so none of them reaches the client bundle.

The rule this tutorial is built around

The API key is a server-to-server credential covering your entire casino. Anything holding it can open battles, seat bots, and read every battle you have.

It must never reach a browser: not in a bundle, not in a prop, not in a window.__CONFIG__. Every SlotBattle call in this project goes through a server-side route handler.

The API client enforces that at import time:

lib/slotbattle.ts
if (typeof window !== 'undefined') {
throw new Error(
'lib/slotbattle must never be imported into client code — it reads the API key.',
)
}

Importing this module from a client component fails at that import rather than shipping a credential to the browser.

Run it

npm run dev # http://127.0.0.1:5174

The lobby page renders the catalog. Games on screen confirm three things at once: the base URL, the key and the allowlist.

If the catalog is empty

No games allowed for this casino yet.

That is configuration, not an error. An empty allowlist returns an empty catalog; it does not mean "all games".

Allow the game in the console's game screen if you have access, or ask your host to do it.

The change is normally visible at once, but the allowlist is cached for 30 seconds and the invalidation can be missed on the way in. If the catalog is still empty right after your host confirms the change, wait up to 30 seconds before debugging anything else. This is the most commonly misdiagnosed SlotBattle symptom.

Other things that can go wrong here

SymptomCause
unauthorizedWrong key, or revoked. A lost key cannot be recovered, so issue a new one
forbidden: missing scope: ...The key is missing a scope, named in the message. demo:mint is the one that keys issued earlier lack
Cannot reach SlotBattleSLOTBATTLE_BASE_URL wrong, or the instance is down. Try curl $BASE/health

The shape of the project

app/
page.tsx lobby: catalog, open a battle, open lobbies
_components/
open-battle-form.tsx client — opens a battle, then seats bots
battles/[id]/
page.tsx server — fetches the snapshot
_components/live-battle.tsx client — WebSocket + HLS
api/
games/route.ts ─┐
battles/route.ts │ server-side proxy: the ONLY place
battles/[id]/route.ts │ the API key is ever read
battles/[id]/bot-seats/ │
battles/[id]/viewer-token/ ─┘
webhooks/slotbattle/route.ts signature-verified result
lib/
slotbattle.ts typed API client (server only)
webhook-signature.ts HMAC verification
settlements.ts where a real casino would credit a wallet

Two boundaries are visible in that tree: lib/slotbattle.ts is imported only by app/api/* and server components, and client components only ever call /api/* on our own origin.

First battle