Build one-draw games with settlement tables or threshold rules, without writing Solidity. Compile the math, connect your frontend, and let the shared slot engine settle the bet.
The slot engine is part of the casino SDK. One shared contract settles every slot title. A title
is a settlement table per bet configuration, deployed as a clone of the engine. A
threshold title holds five numbers per bet configuration instead. You write no
Solidity. For a table title you write a tier list and check it with chain slots. Then you build
the frontend that shows the result.
Prefer an engine to your own contract. A slot-engine title needs no contract audit. The chain derives its RTP and risk figures when it deploys, so the platform review covers your frontend only. A title whose highest RTP is at or below the approval ceiling (98% at genesis) is whitelisted as soon as it deploys and joins the offering right away. If your game has several steps or player choices, use the graph engine. Write your own contract only for a game neither engine can express.
@chain-protocol/casino-sdk/slot-engineholds the library (pickDisplaySeed,parseDisplayMapping, the title compilercompileSlotGameFile, the deploy helpers) and the compiled contracts.@chain-protocol/casino-sdk/slot-engine/thresholdholds the threshold helpers. It has no Node imports, so a frontend can import it.@chain-protocol/casino-sdk/slot-engine/contracts/*holds the contracts as Solidity source. They import@openzeppelin/contracts, an optional peer dependency; install it only if you compile them yourself.chain slotsfrom@chain-protocol/clicompiles, checks, deploys and decodes table titles with the slot engine of the casino SDK your project installed.
From 0.6.0 the TypeScript API calls a title a Game: compileSlotGameFile,
deployTableGame and deployThresholdGame. The old Title names remain as aliases. The Solidity
contracts and the deployment.ts field title keep their names.
Coming from Stake Engine? Follow Migrating from Stake Engine; it converts your existing math export into a title.
Build your own title
Section titled “Build your own title”Run every command below in your game’s project. A project created with
npx @chain-protocol/cli init casino already has the casino SDK and the CLI (see
Getting Started). Otherwise install the same version of both:
npm install @chain-protocol/casino-sdknpm install --save-dev @chain-protocol/cliWith Bun:
bun add @chain-protocol/casino-sdkbun add --dev @chain-protocol/cliThe commands below use npx chain. With Bun, write bunx chain instead.
-
Write a tier list per bet configuration:
prize,weightlines. Prizes are multiples of the session wager, as decimals (72.5) or fractions (1/3); prize0carries the miss weight.prize,weight0,90000.5,90072.6,9200,1 -
Describe the title. The first bet configuration is the base configuration; its RTP is the title RTP.
prizeDenominatoris optional and defaults to the smallest grid that holds every prize.displayMappingis optional.{"name": "My Slot","betConfigurations": [{"name": "base","tierList": "base.csv","displayMapping": "base.display-mapping.json"},{ "name": "bonus", "tierList": "bonus.csv" }]} -
Check the figures. Expect exact RTP as a fraction, hit rate, top prize and chunk count for each bet configuration.
Terminal window npx chain slots compile my-slot/title.json -
Check the display mapping. Expect
0 prizes without seedsfor each configuration; any uncovered prize exits 1.Terminal window npx chain slots check-mapping my-slot/title.json -
Play it locally. Point
deployment.tsin the project’s root at the title:import type { DeploymentInput } from '@chain-protocol/casino-sdk';export default {kind: 'slot',title: 'my-slot/title.json',} satisfies DeploymentInput;Serve your frontend, then start the SDK simulator:
Terminal window npx chain dev casino --frontend-url http://localhost:5173Expect
Deployed My Slot at 0x…and thenPlay your game at …. Open that URL, pick My Slot in the game list and spin. Every session settles through the local VRF node against the real engine bytecode. Save a change to the title or its tier lists and the simulator deploys it again. See Local Simulator. -
Deploy. The account of
SLOT_ENGINE_PRIVATE_KEYuploads the chunks and sends the creation call. Expect the title address and title RTP, then the game developer and the status the title registered with:whitelisted, orpendingabove the approval ceiling (98% at genesis), until the security council approves it. The command fails if the chain derived different figures than the compiler. TheSlotTitleDeployeris part of Chain’s contracts deploy: every contracts deployment records its address asslotTitleDeployerbeside the protocol’s other contracts.Terminal window SLOT_ENGINE_PRIVATE_KEY=0x... npx chain slots deploy my-slot/title.json \--rpc <url> --deployer <slotTitleDeployer address>The command checks
SLOT_ENGINE_PRIVATE_KEYbefore it compiles; without it, it exits 1. The creation call names the game developer, who collects the game dev fee:--developer <address>names one, and without it the command names the signing account. Anyone may name any developer, since the name only directs the fee. An authorship is an optional way for a developer’s wallet to authorize a specific creator wallet: have that wallet write an authorship naming the title inputs hash and the signing account, and pass its id with--authorship <id>together with--developer <that wallet>: the named developer must be the authorship’s writer. -
Submit it through the developer platform with your frontend. The build reads
title.jsonand its tier lists, and compiles no contract. The review covers your frontend only. The platform builds table titles; it does not build threshold titles yet.
Build the frontend
Section titled “Build the frontend”The frontend is a normal casino SDK guest (see Building Casino Games). Three things are specific to slot titles:
- Choose the bet configuration with one byte of
gameData:0x(or0x00) is the base configuration,0x01the second one intitle.json, and so on. - Read the result from the settled session: its
gameStateis 4 bytes holding the prize in units of that configuration’s prize denominator. The payout iswager × prizeUnits / prizeDenominator, rounded down. A session still in flight has an emptygameState; only decode 4-byte states. - Pick what to show with
pickDisplaySeed(mapping, prizeUnits, randomness)from@chain-protocol/casino-sdk/slot-engine. Every client derives the same seed for the same session. The seed is an opaque string your frontend knows how to render, such as reel stops or a book id.
The Lucky Reels example frontend (see the example) does all three.
Threshold titles
Section titled “Threshold titles”A threshold configuration lets the player name a target multiplier. It holds five numbers instead
of a tier list. This title.json is Limbo, which pays any target from 1.01x to 10,000x at 99% RTP:
{ "name": "Limbo", "betConfigurations": [ { "name": "limbo", "threshold": { "prizeDenominator": 100, "totalWeight": "1000000000000000000", "winNumerator": "99000000000000000000", "minTarget": 101, "maxTarget": 1000000 } } ]}- Fields. Call
prizeDenominatorD,totalWeightW andwinNumeratorK. Targets are in prize units over D, so101is 1.01x. A session rollsu = randomness mod Wandresult = floor(K / (u + 1)). It pays its target whenresult >= targetand 0 otherwise. - Numbers. Write a field as a number or as a string of decimal digits. Use a string above 2⁵³,
because JSON numbers lose precision there.
totalWeightmust fit 64 bits,winNumerator128 bits and the others 32 bits. - One kind per title. A bet configuration has either
tierListorthreshold. A title holds only one kind. - RTP. Every target’s RTP lies between
(K - maxTarget + 1) / (D × W)andK / (D × W). The deployer requires the upper end below 100% and registers both ends with the diamond, which checks them against the council’s RTP band. It also requiresD < minTarget <= maxTargetandK >= maxTarget. - Game data is exactly 5 bytes,
uint8 index ‖ uint32 target. Write it withthresholdGameData(index, target)from@chain-protocol/casino-sdk/slot-engine/threshold, the browser-safe subpath, and read it back withdecodeThresholdGameData. Any other length revertsSlotEngine__InvalidGameData, and a target outside[minTarget, maxTarget]revertsSlotEngine__TargetOutOfRange. - Game state is 20 bytes,
uint32 prizeUnits ‖ uint128 result.prizeUnitsis the target on a win and 0 on a miss.resultis the rolled multiplier in prize units, so the frontend can show it.decodeThresholdGameStatereads it, androllResultandrollThresholdreplay a randomness word. - Quote.
quoteThreshold(configuration, wager, target)returns what the title’squoteCaps,quoteRiskParamsandonSessionStartreserve give the session, bit for bit:maxEscrowStake,maxReservedProfit,openingReservedProfit,maxPayout,probabilityWad,expectedPayoutandbodyVarianceScaled. A threshold session reserves its whole cap when it opens. - From a TS module. A Game whose configurations live in TypeScript deploys with
{ kind: 'threshold', name: 'DiceGame', title: 'title.ts' }indeployment.ts. The module’s default export lists the configurations by index, each field abigint:export default [dice, dice] satisfies ThresholdConfiguration[], withimport type { ThresholdConfiguration } from '@chain-protocol/casino-sdk/slot-engine/threshold'. The simulator watches the module and every module it imports, and deploys the Game again when one changes. - Locally,
chain dev casinocreates slot titles through the simulator’s registeredSlotTitleDeployer. The simulator’s game registry checks no RTP band. Limbo at 99% registers pending, and the simulator’s council account approves it. - Deploy. Every
chain slotscommand handles table titles only. To deploy a threshold Game on a live chain, calldeployThresholdGame({ publicClient, walletClient, deployer, configurations, developer, authorshipId })from@chain-protocol/casino-sdk/slot-engine. Its title inputs hash, for an authorship, isthresholdGameInputsHash(configurations). The developer platform does not build threshold titles yet.
Commands
Section titled “Commands”| Command | Does |
|---|---|
chain slots compile <title.json> | Prints each bet configuration’s exact figures. |
chain slots check-mapping <title.json> | Checks the display mappings cover every prize; exits 1 if not. |
SLOT_ENGINE_PRIVATE_KEY=0x… chain slots deploy <title.json> --rpc <url> --deployer <address> | Deploys the title through the SlotTitleDeployer. |
chain slots decode <title address> --rpc <url> [--out <dir>] | Rebuilds tier lists and RTP from a live title’s chain bytes. |
chain slots import-stake <publish_files> --out <dir> [--name <title>] [--seeds-per-prize <n>] | Converts a Stake Engine math export into a title. |
chain dev casino | Runs the SDK simulator with the title deployment.ts names. |
Run them with npx chain … or bunx chain …. npx chain slots <command> --help lists every
flag.
Verify a live title
Section titled “Verify a live title”mkdir decodednpx chain slots decode <title address> --rpc <url> --out decodedExpect quoted figures match the table for each bet configuration, and one tier list CSV per
configuration rebuilt from the chunk bytes. A mismatch exits 1.
Rules the chain enforces
Section titled “Rules the chain enforces”- RTP.
SlotTitleDeployerwalks every chunk and derives total weight, prize sum, top prize and body variance itself. It rejects any table at or above 100% RTP. It then registers the title on the diamond with the lowest and highest RTP of its bet configurations. The diamond refuses a title outside the council’s RTP band. A title whose highest RTP is at or below the approval ceiling is whitelisted at once. The ceiling is 98% at genesis, and the council can change it. A title above it is a pending game until the council approves it. - Game developer. The creation call names the game developer, who collects the game dev fee.
Anyone may name any developer, since the name only directs the fee.
deployTableGameanddeployThresholdGametake it asdeveloperand name the signing account without one. An authorship is an optional way for a developer’s wallet to authorize a specific creator wallet: that wallet writes an authorship for the title inputs hash and the creator, the creator passes its id asauthorshipId, and the named developer must be the authorship’s writer. - Sizes. Winning weights must sum to at most 2³² − 1; the miss weight can use 64 bits. A prize must fit 32 bits of prize units. A chunk holds at most 3,071 prizes; larger tables span several chunks. A title holds at most 255 bet configurations.
- Risk quotes are exact from the table: top-prize probability and body variance round up. See Slots Risk & Reserves for what they feed.
- Fees and maximum wager follow from those quotes, per bet configuration: your game dev fee is a share of each session’s edge, and the largest wager the vault accepts falls as a configuration’s variance rises. See Contract Constraints → What your quotes decide.
Reference
Section titled “Reference”The example: Lucky Reels
Section titled “The example: Lucky Reels”Lucky Reels is a complete title: three reels, three rows, five lines, one roll. It lives in the
Chain repository at packages/casino-sdk/slot-engine/example/, not in the npm package.
game.tsholds the reel strips and paytable. The generator and the frontend share it; the chain never sees it.generate.tsenumerates every reel stop combination intobase.csv(the settlement table) anddisplay-mapping.json(reel stops per prize). Win weights are combination counts; the miss weight is set so the table pays exactly 96%.title.jsonnames the bet configuration, pins its prize denominator and points at both files.frontend/is the guest iframe: it decodes the prize, picks reel stops withpickDisplaySeed, spins to them and reveals the outcome.
To run it from a checkout of the Chain repository, in packages/casino-sdk:
bun installbun run start:slotsOpen http://localhost:3300/?game=http://localhost:3500, pick Lucky Reels in the game list
and spin. bun run slots:e2e plays 25 sessions against the running stack and expects
25 sessions settled and matched the reference sampler.
File formats
Section titled “File formats”- Display mapping.
{ "prizeDenominator": 10, "seeds": { "<prizeUnits>": ["<seed>", …] } }, one per bet configuration. Seed counts are not odds and never feed a tier list; the tier list alone decides what a session pays. - Chunk. A data contract the title’s own deployer created with
deployChunk: oneSTOPbyte, then 8-byte tiers ofuint32 prizeUnits ‖ uint32 cumulativeWeight, prizes strictly ascending.
Measured gas
Section titled “Measured gas”Two-chunk table of 4,534 prizes, production casino facet with the Verify Network router, steady state: about 494k for a miss (307k open, 188k settle) and 515k for a hit. The first round of a title costs about 630k while the facet initialises its per-game risk storage. Deployment is about 7.9M for two chunks plus 1.4M for the title.