Go from an empty directory to a coinflip round on your machine, then make the game your own. No account, backend access, or testnet funds required.
This page takes you from an empty directory to a coinflip round played on your machine. Then you replace the coinflip game and frontend with your own. You need no Chain.wtf account, no backend access and no testnet funds.
You need Node.js 22 or newer, or Bun 1.4.2 or newer. Nothing else: the SDK simulator brings its own chain, VRF node and Solidity compiler.
What you are building
Section titled “What you are building”A Chain.wtf casino game is two deliverables plus a small metadata file:
- The game’s rules, which settle bets against the house liquidity pool. Write them for one of the two engines, or as your own Solidity contract. See Choose how to write your game.
- A static web frontend (any framework, or none) that the Chain.wtf app embeds as a
sandboxed iframe. It contains no wallet code. It talks to the host page through the
@chain-protocol/casino-sdkbridge, and the host signs everything. - A
game.manifest.jsonserved next to your frontend, describing the game to the host.
The flow of a bet: the player configures a wager in your UI, your UI asks the host to open a session, and the host signs the transaction. The game contract requests verifiable randomness (VRF) and settles the outcome. Your UI animates the result and reveals the payout.
Choose how to write your game
Section titled “Choose how to write your game”Write your game for the slot engine or the graph engine whenever it fits. An engine is a shared game contract that the chain already trusts. You describe your game’s math in a file or a TypeScript module, and the engine plays it. You write no Solidity.
An engine game reaches players faster:
- Only your frontend is reviewed. The chain derives the RTP, the maximum payout and the risk figures from your game’s math when it deploys, and refuses math that breaks a rule. Nobody reviews or audits a contract, because you ship none.
- It joins the offering right away. A game whose highest RTP is at or below the approval ceiling (98% at genesis) is whitelisted as soon as it deploys. A game above the ceiling waits for the security council.
Pick the engine that fits your game:
| Your game | Write it with |
|---|---|
| One draw settles the bet: slots, scratch cards, Limbo, dice over or under a target. | Slot engine |
| Several steps or player choices: Hilo, a tower climb, blackjack, double-or-nothing, roulette, Plinko. | Graph engine |
| Neither engine can express it. | Your own ICasinoGameV2 contract (Steps 4 and 5) |
Write your own contract only when your game fits neither engine. You then own the contract’s math, its risk quotes and its security. The platform reviews the contract as well as the frontend, and the contract needs an audit. Nothing on chain proves its RTP, so it always waits as a pending game until the security council approves it.
Steps 1 to 3 below are the same for every game. With an engine, follow the engine’s page in place of Steps 4 and 5, then come back for Step 6.
Step 1: create the game
Section titled “Step 1: create the game”Create a game from the coinflip example with the chain command from @chain-protocol/cli:
npx @chain-protocol/cli init casino my-gameWith Bun:
bunx @chain-protocol/cli init casino my-gameExpect Created my-game from the Coinflip example. followed by the next steps. my-game/ holds:
| Path | What it is |
|---|---|
contracts/src/CoinflipGame.sol | The game contract. |
deployment.ts | Names the contract the SDK simulator compiles and deploys. |
src/ | The game frontend (React and Vite). |
public/game.manifest.json | The manifest the host fetches from your frontend’s origin. |
package.json | Depends on @chain-protocol/casino-sdk and @chain-protocol/cli, one version. |
Step 2: install the dependencies
Section titled “Step 2: install the dependencies”cd my-gamenpm installWith Bun:
cd my-gamebun installStep 3: play a round
Section titled “Step 3: play a round”Start the frontend in one terminal:
npm run devWith Bun, run bun run dev. Expect Vite to serve the game at http://localhost:3100/.
Start the SDK simulator in a second terminal:
npm run simulatorWith Bun, run bun run simulator. The script runs
chain dev casino --frontend-url http://localhost:3100. It starts the dev chain, the VRF node and
a minimal casino host, compiles and deploys the contract deployment.ts names, and serves the
simulator page. Expect these lines when it is ready:
[simulator] Deployed CoinflipGame at 0x…Play your game at http://127.0.0.1:3300/?game=http%3A%2F%2Flocalhost%3A3100&rpc=…Press Ctrl-C to stop.Open the URL it prints. The simulator page mounts your frontend in its iframe, and the player account
holds 1,000,000 test chUSD. Pick Heads or Tails and press BET. Expect the coin to land
within a few seconds, the result to show in the top-right rail, and session 1 SETTLED in the
log under the frame.
The SDK simulator behaves like the production host: optimistic sessions, a lagging indexer feed and balance guarding, against a real VRF node on your machine. Everything you do from here on replaces the coinflip contract and UI with your own.
Step 4: write your game contract
Section titled “Step 4: write your game contract”Skip this step and Step 5 if your game uses an engine. The Slot Engine and Graph Engine pages show how to write and play an engine game.
Implement ICasinoGameV2. Import it
from the SDK, as contracts/src/CoinflipGame.sol does:
import { ICasinoGameV2 } from '@chain-protocol/casino-sdk/contracts/ICasinoGameV2.sol';The functions that matter most:
| Function | What it does |
|---|---|
onSessionStart | Validates the bet (gameData), sets up session state, usually requests randomness. |
onRandomness | Receives the VRF result, computes the outcome, settles (or advances a multi-step game). |
onPlayerAction | Optional. Mid-session moves for multi-action games (hit or stand, reveal a mine, cash out). |
quoteCaps | The most stake and the largest net win one session of a bet configuration can reach. |
quoteRiskParams | Expected payout, maximum payout, its probability and the body variance of a bet. |
Your quotes set your fees and your maximum wager. openSession computes every session’s
fees from the edge you quote (wager − expectedPayout), and works out the largest wager it accepts
from what quoteRiskParams returns. Get these right before anything else:
expectedPayoutis RTP × wager, under the player’s best policy when your game has choices.probabilityWadis the probability of exactlymaxPayout, not of any win.bodyVarianceScaledis the variance of the payout with the top tier excluded, in wei² × 1e18.
A quote that fails a guardrail reverts openSession. What each figure must mean, the errors, the
fee split and the bet-size cap are in
Contract Constraints → What your quotes decide.
Two shapes cover almost every game:
- Instant (coinflip, dice, slots):
onSessionStartrequests randomness,onRandomnesssettles. One transaction from the player’s point of view. - Multi-action (blackjack, mines): the session stays open across several
onPlayerActioncalls, each optionally requesting more randomness.
Both patterns, with full contract examples, are in Building Casino Games §2.3.
Two rules worth knowing before you write a line of code:
- Deriving dice or cards from random bytes: use rejection sampling, never
byte % n. See Randomness → Dice. A game gets one random value per request and derives every other value it needs from it;onRandomnessmay not request randomness again. - The facet enforces phase rules, timeouts and payout caps on top of your contract. Read Contract Constraints so your state machine doesn’t fight it. Slots-like games with rare huge payouts also need the tiered reserve model in Slots Risk & Reserves.
Step 5: deploy it in the SDK simulator
Section titled “Step 5: deploy it in the SDK simulator”deployment.ts tells chain dev which contract to compile and deploy:
import type { DeploymentInput } from '@chain-protocol/casino-sdk';
export default { kind: 'casino', contract: { source: 'src/MyGame.sol', name: 'MyGame' },} satisfies DeploymentInput;contract.sourceis relative tocontracts/, andcontract.nameis the contract in that file.- If the constructor takes arguments, list them in
constructorArgs.{{diamond}}and{{token}}stand for the host and the token addresses of the chain you deploy to. - Keep
deployment.tsself-contained: import only types.chain devloads it on its own, and the platform reads the same file when it deploys your submission.
While npm run simulator runs, it watches deployment.ts and every Solidity file the build read.
Save a change and it recompiles, deploys the contract at a fresh address and lists it first in
the simulator page’s game picker. You do not restart anything. A compile error prints in the simulator’s
terminal, and the previous deployment keeps running.
To compile the contract with your own toolchain as well, Hardhat resolves
@chain-protocol/casino-sdk/contracts/… from node_modules. Foundry needs the remapping
@chain-protocol/=node_modules/@chain-protocol/.
Step 6: build the guest UI
Section titled “Step 6: build the guest UI”Start from the example’s src/ or from scratch. The whole bridge is small:
import { connectGameToHost } from '@chain-protocol/casino-sdk/guest';import type { HostApi, HostSnapshot } from '@chain-protocol/casino-sdk/guest';
const connection = connectGameToHost({ setState: async (snapshot: HostSnapshot | null) => { // called on every host update: wallet status, balances, session rows render(snapshot); },});
const hostApi: HostApi = await connection.promise;
// Place a bet: ABI-encode your bet config as gameDataconst { sessionKey } = await hostApi.openSession({ wager: parseUnits('10', snapshot.token.decimals).toString(), gameData: encodeAbiParameters(...),});
// ...watch snapshot.sessions.items for the row with your sessionKey to reach// a terminal phase, decode raw.gameState, play your result animation, then:await hostApi.revealOutcome({ sessionId });The essential guest rules:
- Only enable betting when
snapshot.wallet.status === 'ready'. - Clamp the bet input to what the platform accepts right now:
hostApi.getMaxWager({ gameData })answers the maximum wager for the bet the player selected. Ask again when the player changes the bet, and never compute the maximum yourself. See Building Casino Games §4. - Match your bet to a session row via the returned
sessionKey. The row’sphaseNameturning terminal (SETTLED,FORFEITEDorCANCELLED) is your settle signal. - Always call
revealOutcomewhen your win animation finishes. Until then the host hides the payout from its balance displays so the top bar doesn’t spoil the result. - Call
connection.destroy()on unmount.
The full lifecycle, snapshot reference, and per-game frontend patterns (coinflip, blackjack, mines) are in Building Casino Games §3–6.
If your frontend runs on another port, pass it to the simulator:
npx chain dev casino --frontend-url http://localhost:5173 (bunx chain dev casino … with Bun),
or change the simulator script in package.json. The setup panel lets you raise the indexer
lag, force wallet states and change the maximum-wager answer. See
Local Simulator for the full tour.
Step 7: add the manifest
Section titled “Step 7: add the manifest”Serve a game.manifest.json from the same origin as your game. The example serves it from
public/game.manifest.json; adjust it to your game. The gameId must follow the canonical id
rules, and the host validates presentation and capabilities. Validate yours locally with
validateCasinoGameManifest from the SDK
(details).
Step 8: test the unhappy paths
Section titled “Step 8: test the unhappy paths”Before shipping, use the simulator to make sure your game survives production reality:
- Slow indexer: raise the indexed-feed lag in the setup panel. Your game should ride the optimistic session rows without flicker or duplicate results.
- Wallet not ready: force
disconnectedorsetup-requiredand check your non-ready screens. - Maximum wager: set the setup panel’s max-wager answer to a low
limit, then tounknown. Your bet input should clamp to the limit and fall back to your own limits, never to unlimited. - Refresh mid-round: reload the iframe during an open session. Your UI should recover the session from the snapshot instead of starting blank.
Step 9: ship it
Section titled “Step 9: ship it”Build your frontend with npm run build or bun run build, which writes dist/, and host it at
a stable HTTPS URL with game.manifest.json at the same origin. What else you deliver depends on
how you wrote the game:
- With an engine, submit the repository with its
deployment.tsthrough the developer platform. The platform builds your title, the chain checks its math when it deploys, and the review covers your frontend only. A title at or below the approval ceiling is whitelisted at once. - With your own contract, you also deliver the audited game contract. The review covers the contract and the frontend, and the security council approves the game before it takes bets.
The Chain.wtf maintainers wire the rest: indexer registration and the catalog entry.
Keep up with SDK releases
Section titled “Keep up with SDK releases”The host tells your game its SDK version in snapshot.sdkVersion. A breaking release reaches
staging as a canary before it reaches production, and your game handles both hosts during that
notice period. Read
Building Casino Games → SDK versions and breaking releases.
Where to next
Section titled “Where to next”- Building Casino Games: the complete reference this page summarizes.
- Local Simulator: everything
chain dev casinoruns and replicates. - Contract Constraints: the rules the facet enforces on your contract.