# Changelog
Track Casino SDK releases, breaking changes, and unreleased work. Check what changed before you move your game to another version.
:::note[Current SDK checkout]
- `@chain-protocol/casino-sdk@0.7.0`
This guidance reflects this checkout and may include unreleased changes.
[Casino changelog](/sdk/casino/changelog/)
:::
Versions follow semver from `0.5.0`, and each heading carries the release date. In `0.x` a minor
bump is breaking and a patch is not. Releases before `0.5.0` used date versions, `YYYY.MM.DD-N`.
## 0.7.0 (2026-10-28)
Released in lockstep with `@chain-protocol/pvp-sdk` and `@chain-protocol/cli` 0.7.0.
### Added
- Optional logical guest windows via `openWindow`, callable only after the guest advertises the exact target. Public exports include `WINDOW_TARGETS`, `supportsWindowTarget`, `WindowTarget`, `WindowCapabilities` and `WindowOpenResult`. Legacy `openPanel` behavior is unchanged.
- **`SteeringDeployer` creates a graph title's Steering contract at a predictable address.**
`deploySteering(initCode)` runs `CREATE2` with a zero salt and emits
`SteeringDeployed(steering, initCodeHash)`; `steeringAddress(initCodeHash)` predicts it. The salt
is not bound to the sender, so a build can name the address before it knows who sends the
deploy, and titles with the same init code share one Steering contract.
- `steeringRefusal({ maxIndependentDraws, configurationChunks, nodeChunks })` returns why derive or
`GraphTitleDeployer` would refuse a packed title under a Steering contract (a ladder, or a table
bundle), or null. `deployGraphGame` throws the same reason as before.
- A graph deployment's `steering` takes `constructorArgs`, with the same `{{diamond}}` and
`{{token}}` placeholders as a contract deployment. `defineDeployment` defaults it to `[]`, and the
local node passes it to the Steering contract's constructor.
- `defineDeployment` takes an optional `frontend` for every kind: `directory`, `framework`
(`vite`, `nextjs` or `tanstack-start`), `installCommand`, `buildCommand` and `outputDirectory`.
It tells the platform where the frontend lives and how it builds; without it the platform
detects the framework.
### Fixed
- **Slot and graph titles name only chunks their own deployer created.** `SlotTitleDeployer` and
`GraphTitleDeployer` record every pointer `deployChunk` creates in `isChunk`, and refuse any other
pointer with `SlotTitleDeployer__NotAChunk(pointer)` or `GraphTitleDeployer__NotAChunk(pointer)`.
A contract that only looks like a chunk could otherwise change its code after the deployer
derived the title's RTP from it. The deploy helpers already upload through the deployer they
create the title with. The fix ships as a new version of both deployers in
`slotGameDeployerArtifact` and `graphGameDeployerArtifact`.
- **Graph titles: a chance cannot reach a stake it never escrowed.** Derive now refuses a chance
any of whose outcomes holds a stake other than the chance's own, with
`GraphDerive__ChanceStakeMismatch(id)`, as the compiler always has. A decision holds its action
0's stake. A forfeit at a decision pays the default value less any part of that stake the session
has not escrowed, as a settlement does. Engines and titles are immutable, so the fix ships as a
new `GraphTitleDeployer`: `graphGameDeployerArtifact` carries it, and titles from the previous
deployer keep the previous engine. Inline derive costs about 2% more gas and batched derive
under 0.5% more; the inline-scale graph still derives in one transaction.
- `GraphNode`'s `chance` kind takes an optional `stake`, the stake a packed chance records.
`unpackGraph` sets it only where it differs from the stake the chance's first outcome holds,
which the compiler never packs, and `deriveGraph` refuses such a chance. `graphGameAbi` lists
the new error.
### Changed
- **Breaking:** the player's contract is called the Smart Vault again. `HostSnapshotV1` reports
it as `wallet.smartVaultAddress` and `balances.smartVaultBalance`, replacing
`smartWalletAddress` and `smartWalletBalance`. The docs, the simulator and the examples use the
new names.
- **Breaking:** every creation call names its game developer. `SlotTitleDeployer.deployTitle` and
`deployThresholdTitle`, `GraphTitleDeployer.deployTitle` and `deployBatchedTitle` and
`GameBytecodeDeployer.createGame` take `address developer` before `authorshipId`, and the
registry's `registerEngineTitle` and `registerBytecodeGame` take it after `creator`. Anyone
may name any developer, since the name only directs the game dev fee. A zero developer reverts
with `GameRegistryFacet__ZeroDeveloper` (`LocalGameRegistry__ZeroDeveloper` on the simulator),
and with an authorship the named developer must be its writer, else
`Authorship__DeveloperMismatch(authorshipId, developer)`. `underivedTitle` carries `developer`
after `creator`. The SDK's `deployTableGame`, `deployThresholdGame`, `deploySlotGame`,
`deployGraphGame` and `createBatchedGraphGame` take an optional `developer` and name the
wallet client's account without one. Against a deployer from before this change the wrappers
simulate the new call first and, when the deployer has no function for it, send the call
without a developer, whose registry makes the sender the developer; that fallback goes once
every registered deployer is redeployed.
- **Breaking:** casino randomness settles whenever it lands. A word delivered after the deadline
reaches `onRandomness` like any other, instead of refunding the stake. The randomness timeout is
now 45 seconds and cannot be set lower, so `cancelStuckRandomness` refunds only after that.
`LocalCasinoHost.RANDOMNESS_TIMEOUT_SECONDS` is 45. See ADR 0026.
- The Title names listed under `0.6.0-canary.3` are no longer deprecated. They stay as aliases of
the Game names, and the deployed results' `title` stays beside `game`.
## 0.6.0 (2026-10-20)
`0.6.0` publishes the `0.6.0` canaries under the `latest` dist-tag, together with the changes of
`0.5.2`. Coming from `0.5.x`, read the three canary entries below: they list every breaking change
and every rename.
### Documentation
- **Engines first.** Getting Started and the overview tell game devs to write a game for the slot
engine or the graph engine whenever it fits: the chain checks the math when it deploys, the
review covers the frontend only, and a game at or below the approval ceiling is whitelisted when
it deploys. An own `ICasinoGameV2` contract is for games neither engine can express.
- **A Graph Engine guide**: writing a title, checking its figures, playing it locally, submitting
it, the frontend, fair ladders, table bundles, Steering contracts and deploy costs. Its example,
Climb, is a tower and a scratch card with a full frontend: `bun run start:graph` in a checkout
plays it on the SDK simulator.
- The Slot Engine page covers the Game names, the browser-safe `slot-engine/threshold` subpath and
how to deploy a threshold Game.
## 0.6.0-canary.3 (2026-10-20)
### Added
- **A browser-safe quote of a plain graph configuration.** `quoteConfiguration(table, configuration,
wager)` from `@chain-protocol/casino-sdk/graph-engine` returns, bit for bit, what the engine's
`quoteCaps`, `quoteRiskParams` and `onSessionStart` reserve give `encodePlainGameData(configuration)`:
`maxEscrowStake`, `maxReservedProfit`, `openingReservedProfit`, `maxPayout`, `probabilityWad`,
`expectedPayout` and `bodyVarianceScaled` (the type `GraphQuote`). `graphQuoteTable(compiled,
{ steered })` builds its `GraphQuoteTable` from a compiled Game; store its figures and quote in the
browser without deriving there. A Game deployed with a Steering contract is `steered` and reserves
its whole cap when a session opens. Out-of-range configurations throw a `RangeError`.
- **`@chain-protocol/casino-sdk/slot-engine/threshold`**, a browser-safe subpath with no Node
imports: `thresholdGameData`, `decodeThresholdGameData`, `rollResult`, `rollThreshold`,
`decodeThresholdGameState`, `quoteThreshold`, `thresholdRtpWadRange`,
`thresholdGameInputsHash`, `ThresholdTargetOutOfRangeError` and the types. `quoteThreshold` now
also returns `maxEscrowStake`, `maxReservedProfit` and `openingReservedProfit`, so it answers
what `quoteCaps`, `quoteRiskParams` and the opening reserve give.
- **`@chain-protocol/casino-sdk/local-host`**, for host-path tests on EDR or any dev chain:
`deployLocalCasinoHost`, `whitelistOnLocalHost`, `decodeCasinoSession`, and the ABIs and bytecode
of `LocalCasinoHost`, `LocalTestToken` and `TestRandomnessRouter`, whose `fulfill(requestId,
word)` delivers the word a test picks.
- **Slot threshold Games from a TS module.** `deployment.ts` takes
`{ kind: 'threshold', name, title }`, where `title` names a module whose default export lists
the Game's `ThresholdConfiguration`s by index. `chain dev casino` deploys it through the
simulator's `SlotTitleDeployer`.
- **Game IDs** (ADR 0025, proposed). The simulator's registry gives each registered game the next
Game ID from 1, with `gameIdOf`, `gameOfId`, `gameCount` and a `GameIdAssigned` event, and
`/__local-contracts.json` lists every game with its `gameId`. The production diamond gains the
same once the security council cuts the `GameRegistryFacet`.
- `runCasinoProperties` and `runCasinoConformance` (`./conformance`) take `{ address }` of a Game
already on the chain as well as runtime code, so they run an engine clone whose implementation
is there.
### Changed
- `quoteBundle` returns `openingReservedProfit` too, the whole cap, and its result is a
`GraphQuote`.
- `thresholdGameData` throws a `RangeError` (still an `Error`) for an index or target out of range.
- **Smaller guests.** The engine and deployer ABIs are marked pure and tree-shake away: a guest
that walks and encodes a graph Game drops from 46.3 KB to 18.8 KB minified (12.5 KB to 6.2 KB
gzip).
- **Faster derive.** A Game's max net is derived once, not per configuration: Dice's 9,799
configurations compile in 0.2 s instead of 2.6 s, with the same figures.
- `chain dev casino` watches every module a Game module imports, workspace packages outside the
project included, and redeploys when one changes. Installed packages under `node_modules` stay
unwatched.
- `chain dev casino` leaves its reference `CoinflipGame` out of the game picker when the project's
Game registers under that name.
- `LocalCasinoHost`'s bytecode changes for the Game IDs. Read it from `./local-host` rather than a
copy.
### Deprecated
- **Title is now Game in the TypeScript API** (original-games spec U2). Every old
name below still works and is the same value, and goes in `0.7.0`. Deployed results carry the
address as `game` beside the deprecated `title`. The Solidity contracts, their functions, events
and errors, the `deployment.ts` field `title` and the simulator's contract keys
(`slotTitleDeployer`, `graphTitleDeployer`) keep their names.
| Subpath | Old name | New name |
| ------------------ | ---------------------------- | --------------------------- |
| `graph-engine` | `compileGraphTitle` | `compileGraphGame` |
| `graph-engine` | `GraphTitleDefinition` | `GraphGameDefinition` |
| `graph-engine` | `CompiledGraphTitle` | `CompiledGraphGame` |
| `graph-engine` | `deployableTitle` | `deployableGame` |
| `graph-engine` | `deployGraphTitle` | `deployGraphGame` |
| `graph-engine` | `DeployGraphTitleParams` | `DeployGraphGameParams` |
| `graph-engine` | `DeployedGraphTitle` | `DeployedGraphGame` |
| `graph-engine` | `createBatchedGraphTitle` | `createBatchedGraphGame` |
| `graph-engine` | `deriveGraphTitle` | `deriveGraphGame` |
| `graph-engine` | `graphTitleInputsHash` | `graphGameInputsHash` |
| `graph-engine` | `graphTitleAbi` | `graphGameAbi` |
| `graph-engine` | `graphTitleDeployerAbi` | `graphGameDeployerAbi` |
| `graph-engine` | `graphTitleDeployerArtifact` | `graphGameDeployerArtifact` |
| `graph-engine` | `MAX_TITLE_CHUNKS` | `MAX_GAME_CHUNKS` |
| `slot-engine` | `slotTitleAbi` | `slotGameAbi` |
| `slot-engine` | `slotTitleDeployerAbi` | `slotGameDeployerAbi` |
| `slot-engine` | `slotTitleDeployerArtifact` | `slotGameDeployerArtifact` |
| `slot-engine` | `readTitle` | `readTableGame` |
| `slot-engine` | `readThresholdTitle` | `readThresholdGame` |
| `slot-engine` | `deployTitle` | `deployTableGame` |
| `slot-engine` | `DeployTitleParams` | `DeployTableGameParams` |
| `slot-engine` | `DeployedTitle` | `DeployedSlotGame` |
| `slot-engine` | `deployThresholdTitle` | `deployThresholdGame` |
| `slot-engine` | `DeployThresholdTitleParams` | `DeployThresholdGameParams` |
| `slot-engine` | `deploySlotTitle` | `deploySlotGame` |
| `slot-engine` | `DeploySlotTitleParams` | `DeploySlotGameParams` |
| `slot-engine` | `compileSlotTitleFile` | `compileSlotGameFile` |
| `slot-engine` | `compileTitleFile` | `compileTableGameFile` |
| `slot-engine` | `TitleDefinition` | `SlotGameDefinition` |
| `slot-engine` | `CompiledTitle` | `CompiledTableGame` |
| `slot-engine` | `ThresholdTitle` | `ThresholdGame` |
| `slot-engine` | `SlotTitle` | `SlotGame` |
| both slot subpaths | `thresholdTitleInputsHash` | `thresholdGameInputsHash` |
### Compatibility
- Nothing on chain changes for Games: engine titles deployed with canary.1 or canary.2 keep working,
and these quotes match them.
- Code that compares a whole `quoteThreshold` or `quoteBundle` result, or spreads it into an array,
sees the added fields.
- The old Title names compile with a deprecation warning. Move to the new names before `0.7.0`.
## 0.6.0-canary.2 (2026-10-20)
### Added
- **Committed ladder rounds** (ADR 0023). Game data
`0x03 ‖ uint16 configuration ‖ uint8 roundCount ‖ roundCount × uint8 option ‖ tail` opens a
session at a fair-ladder root that plays every listed option in one randomness callback and
settles. Auto-bet can now drive a ladder such as Hilo. `@chain-protocol/casino-sdk/graph-engine`
adds `encodeCommittedLadderGameData`, `decodeCommittedLadderGameData`, `checkCommittedLadder`,
`walkCommittedLadder`, `committedLadderReserve`, `InvalidCommittedRoundsError` and the type
`CommittedLadderRounds`. A configuration takes at most its `committedRounds` options, which
derive sets from the callback gas and the ladder's widest option: 93 for Hilo, 16 for options of
256 outcomes. More rounds, none, or a root that is not a ladder revert
`GraphEngine__InvalidCommittedRounds`.
- **`raw.actions`** on session items: every `actionData` submitted to the session, in step order,
as `SubmittedAction { step, actionData, transactionHash? }`. `step` is the step the action
produced, so a randomness request that step made has it as its `nonce`. The list includes the
action that settled the session and is empty before the first action. A multi-step game
rebuilds a session from `randomnessRequests` and `actions` after a reload or on another device.
A host that does not report actions omits the field, so treat `undefined` as unknown.
- `LocalCasinoHost`, and the production casino facet, emit
`CasinoSessionActionSubmitted(uint256 indexed sessionId, uint32 indexed step, bytes actionData)`
for every player action, before the step's `CasinoSessionAdvanced` or `CasinoSessionSettled`.
The simulator reports `raw.actions` from it.
### Changed
- **Breaking:** graph titles need the new `GraphTitleDeployer`. `ConfigurationFigures`, which
`configurationFigures` and `derive` return, ends with `uint8 committedRounds`, and
`DerivedGraph`'s configurations carry it. Titles of the canary.1 engine reject tag `0x03` with
`GraphEngine__InvalidGameData`. Redeploy a game, and regenerate any artefact that stores derived
figures.
## 0.6.0-canary.1 (2026-10-19)
### Changed
- **Breaking:** `SlotTitleDeployer` and `GraphTitleDeployer` take the diamond's game registry
(`IGameRegistry`) as their only constructor argument and register every title on it with the
creator, an authorship id, the title inputs hash and the lowest and highest RTP of its bet
configurations. `deployTitle`, `deployThresholdTitle` and `deployBatchedTitle` take an
`authorshipId`, zero for none, and the SDK's deploy functions an optional `authorshipId`. The
deployers' RTP band, `setRtpBand` and owner are gone: they still refuse an RTP of 100% or more,
and the diamond checks the RTP band. `titleAuthor` is now `titleCreator`, and
`GraphTitleDeployer.pendingTitleAuthor` is now `underivedTitle`, and
`GraphTitleDeployer__NotPending` is now `GraphTitleDeployer__NotDeriving`.
- **Breaking:** casino deadlines are block timestamps. `CasinoSession.deadlineBlock` is now
`deadline`, a session has 30 seconds to receive its randomness and 86,400 seconds (24 hours) to
act, and `LocalCasinoHost` exposes `ACTION_TIMEOUT_SECONDS` and `RANDOMNESS_TIMEOUT_SECONDS`.
The conformance suite expires a session with `advanceTime`.
- **Breaking:** randomness delivered after its deadline no longer reaches `onRandomness`. The host
cancels the session and refunds the stake, as `cancelStuckRandomness` does, and reports a zero
randomness in `CasinoSessionSettled`.
- **Breaking:** a game gets one random value per request. A step reached from `onRandomness` may
not move to `WAITING_RANDOMNESS`. The casino facet reverts the callback with
`CasinoGameFacet__RandomnessRequestedFromCallback`, and once the randomness deadline passes
`cancelStuckRandomness` refunds the session's whole remaining escrow. Derive further values from the one you
received.
- `openSession` and `submitAction` are `payable`. The call whose step requests randomness carries
its fee, and a call whose step requests none gets its ETH back.
- The simulator pays for randomness the same way. `LocalCasinoHost` starts with an empty router
balance, the simulator page attaches the host's `requiredPayment` to every open and action, and
a step reached from `onRandomness` that requests randomness fails delivery with
`LocalCasinoHost__RandomnessRequestedFromCallback`.
## 0.5.2 (2026-10-20)
### Added
- Optional Host capability discovery on guest connections and `playAccess` opt-in for `requestPlayAccess()`. Casino gains the access intent; PvP keeps its existing optional method and legacy callers. An acknowledgment means the existing Host UI was accepted, never that the player is authenticated or ready. Pending duplicate requests coalesce, and connection destruction invalidates pending/queued requests. Simulator requests record explicit fixture intent without automatically changing readiness. See "Requesting play access" in the README.
### Changed
- **Breaking:** casino deadlines are block timestamps. `CasinoSession.deadlineBlock` is now
`deadline`, a session has 30 seconds to receive its randomness and 86,400 seconds (24 hours) to
act, and `LocalCasinoHost` exposes `ACTION_TIMEOUT_SECONDS` and `RANDOMNESS_TIMEOUT_SECONDS`.
The conformance suite expires a session with `advanceTime`.
- **Breaking:** randomness delivered after its deadline no longer reaches `onRandomness`. The host
cancels the session and refunds the stake, as `cancelStuckRandomness` does, and reports a zero
randomness in `CasinoSessionSettled`.
- **Breaking:** a game gets one random value per request. A step reached from `onRandomness` may
not move to `WAITING_RANDOMNESS`. The casino facet reverts the callback with
`CasinoGameFacet__RandomnessRequestedFromCallback`, and once the randomness deadline passes
`cancelStuckRandomness` refunds the session's whole remaining escrow. Derive further values from the one you
received.
- **Breaking:** a step reached from `onRandomness` may not change the escrow. The casino facet
reverts the callback with `CasinoGameFacet__EscrowChangedFromCallback`, which replaces
`CasinoGameFacet__EscrowIncreaseNotAllowed`, and once the randomness deadline passes
`cancelStuckRandomness` refunds the session's whole remaining escrow. Return escrow from
`onPlayerAction` instead. The simulator refuses it with
`LocalCasinoHost__EscrowChangedFromCallback`.
- `openSession` and `submitAction` are `payable`. The call whose step requests randomness carries
its fee, and a call whose step requests none gets its ETH back.
- The simulator pays for randomness the same way. `LocalCasinoHost` starts with an empty router
balance, the simulator page attaches the host's `requiredPayment` to every open and action, and
a step reached from `onRandomness` that requests randomness fails delivery with
`LocalCasinoHost__RandomnessRequestedFromCallback`.
## 0.5.1 (2026-10-17)
### Added
- **Guest panels.** A game can declare panels the host may open from its toolbar:
`connectGameToHost(methods, { capabilities: { panels: [...] } })` with an `openPanel({ panel })`
method. Casino panels are `info`, `history` and `audio`.
The host reads the declaration from `connection.capabilities` on the connection
`connectHostToGame` returns. A game that declares nothing keeps working as before:
the host gets an empty capability set and shows no panel controls. See "Host capabilities and
panels" in the README.
### Changed
- The bridge runs on penpal 8, which fixes listener and timer leaks on failed or destroyed
handshakes. A host on penpal 8 still connects to games built on SDK `0.5.0` with penpal 7.
## 0.5.0 (2026-10-15)
### Added
- **`sdkVersion` in the snapshot.** `HostSnapshot.sdkVersion` is the version of the SDK the
host was built with, for example `0.5.0` or `0.6.0-canary.3`. The production host and the
simulator set it from the SDK's `package.json` when they are built.
- **`hostSupports(snapshot, version)`** answers whether the host's SDK is at `version` or newer. It
compares major, minor and patch and ignores the prerelease tag, so a canary host answers as the
version it previews. It answers `false` for a snapshot without `sdkVersion`, which comes from a
host older than `0.5.0`.
### Changed
- **Breaking:** the package is `@chain-protocol/casino-sdk`. It was `@chain/casino-sdk`.
- **Breaking:** `apiVersion` is removed from the snapshot and from `game.manifest.json`.
`schemaVersion` stays as the manifest file's format version. A manifest that still carries
`apiVersion: 1` validates, and the validated manifest leaves it out.
- **Breaking:** the TypeScript type names lose their version suffixes, with no aliases left
behind. The Solidity names, such as `ICasinoGameV2`, keep theirs.
| Before | After |
| --------------------------------- | ------------------------------- |
| `CasinoGameManifestV1` | `CasinoGameManifest` |
| `HostSnapshotV1` | `HostSnapshot` |
| `HostApiV1` | `HostApi` |
| `GuestApiV1` | `GuestApi` |
| `RandomnessRequestV1` | `RandomnessRequest` |
| `RandomnessRequestEchoV1` | `RandomnessRequestEcho` |
| `RandomnessRequestVerificationV1` | `RandomnessRequestVerification` |
| `RandomnessVerificationV1` | `RandomnessVerification` |
| `VrfVerificationChecksV1` | `VrfVerificationChecks` |
- The slot engine is part of the SDK. Import its library from
`@chain-protocol/casino-sdk/slot-engine` and its contracts from
`@chain-protocol/casino-sdk/slot-engine/contracts/*`. Its title commands move to the `chain`
CLI: `chain slots ` from `@chain-protocol/cli` replaces `bun run cli -- ` in
`slot-engine/`, and the `slot-engine` bin is gone. From the SDK root, `slots:e2e`,
`slots:example` and `slots:test` replace `e2e:simulator`, `example:generate` and `test`.
- **Breaking:** the game interface and the session codec moved from `simulator/contracts/` to
`contracts/`. Import them as `@chain-protocol/casino-sdk/contracts/ICasinoGameV2.sol`,
`.../CasinoSession.sol` and `.../CasinoSessionCodec.sol`.
- The package publishes to npm as ESM with type declarations, MIT, for Node 22+ and Bun 1.4.2+.
It publishes `.`, `./guest`, `./contracts/*`, `./slot-engine` and `./slot-engine/contracts/*`
for games, and `./host` and `./conformance` as internal subpaths outside semver.
`./scripts/*` is gone.
- Versions follow semver, in lockstep across both SDKs and the `chain` CLI. Breaking releases
publish as `X.Y.Z-canary.N` under the npm `canary` tag before production switches.
### Removed
- The SDK ZIP on the docs site and the `shadcn add @chain/casino-sdk` registry item. Install
`@chain-protocol/casino-sdk` from npm.
### Compatibility
- Replace `@chain/casino-sdk` with `@chain-protocol/casino-sdk` in your imports and `package.json`.
- Rename the types in the table above.
- Import `ICasinoGameV2.sol` from `@chain-protocol/casino-sdk/contracts/` in Hardhat, or remap
`@chain-protocol/=node_modules/@chain-protocol/` in Foundry.
- Stop reading `snapshot.apiVersion`. To branch on the host, call
`hostSupports(snapshot, '0.6.0')`; never parse `sdkVersion` yourself.
- Remove `apiVersion` from your `game.manifest.json`. A manifest that keeps it still validates.
## 2026.10.11-1
### Added
- **`hostApi.getMaxWager({ gameData })`** on `HostApiV1`. A game asks the host for its maximum
wager and passes the game data it is about to open the session with. The answer is a
`MaxWagerResult`: `{ kind: 'limit', maxWager }`, `{ kind: 'no-limit' }` or
`{ kind: 'unknown' }`. Without game data (`hostApi.getMaxWager()`) the host answers for empty
game data, the game's base configuration. The production host reads the vault's maximum
wager for the game it serves and answers `unknown` when it cannot be worked out.
- **"Max-wager answer" in the simulator's setup panel.** The harness answers `getMaxWager` with a
fixed cap in whole tokens, 1000 by default, or with `no-limit` or `unknown`.
### Changed
- **Breaking:** `computeMaxWager` is removed. A vault's bet-size cap follows from the game's
quotes and the vault's state, so a game can no longer compute it from a multiplier.
- **Breaking:** the snapshot's `casino` block is removed, with its four fields
`availableLiquidity`, `maxBetRiskBps`, `maxAllowedReservedProfit` and `maxBetAmount`.
- **Breaking:** `maxWager` in `MaxWagerResult` is a decimal string in token base units, like the
`wager` of `openSession`. It was a `bigint`.
- **Breaking for hosts:** `getMaxWager` is a required method of `HostApiV1`.
- The coinflip and Lucky Reels examples ask the host for their maximum wager. The coinflip
example passes the game data of the selected pick.
- **A game's quotes set its fees and its bet-size cap.** `openSession` computes every fee of a
session from its edge, `wager − expectedPayout`, and works the largest wager it accepts out of the
four `quoteRiskParams` figures and `maxReservedProfit`. The game dev fee is credited when the
session settles or is forfeited, whether the player won or lost. A cancelled session pays no
fee. `CONTRACT_CONSTRAINTS.md` has a new section, "What your quotes decide", with what each
figure must mean, the fee split and the bet-size cap.
- **Quotes are checked against guardrails** at whitelisting and at every `openSession`. A quote
outside the RTP band, with `maxPayout` below `expectedPayout`, with a body variance of `0` while
outcomes below `maxPayout` pay, or with a plain variance that is not positive reverts with
`QuoteFiguresLib__RtpOutsideBand`, `QuoteFiguresLib__MaxPayoutBelowExpectedPayout`,
`QuoteFiguresLib__BodyVarianceMissing` or `QuoteFiguresLib__VarianceNotPositive`.
- **A wager above its bet-size cap reverts** with `CasinoGameFacet__WagerAboveRiskBudget`,
`CasinoGameFacet__WagerAboveBreakEvenRule` or `CasinoGameFacet__NetWinAboveSingleWinCap`. These
replace `CasinoGameFacet__BetRiskExceedsLimit`.
- **The reserve reads `expectedPayout` and holds the exact count of jackpots.** It expects the
vault to keep a session's LP income and nothing else of its edge, and for a heavy-tail game it
holds the smallest number of jackpots that covers 99.99% of outcomes, one or two more than
before. `SLOTS_RISK_AND_RESERVES.md` has the rule and a worked example.
- **Breaking for hosts that decode session bytes:** the fixed part of the encoded session grew
from 255 to 295 bytes. The session's edge and the three rates its fees are computed from
follow `operator`.
- Docs corrected: `StepResult` has no `outcome` field, the default randomness timeout is 15
blocks, and `quoteCaps` returns a session's largest stake and net win, not a wager range.
### Compatibility
- Replace `computeMaxWager(snapshot, { maxMultiplierX })` with
`await hostApi.getMaxWager({ gameData })`, where `gameData` is what you pass to `openSession`.
A game with one bet configuration calls `hostApi.getMaxWager()`.
- Read the limit as `answer.kind === 'limit' ? BigInt(answer.maxWager) : undefined`.
- The call is asynchronous and the answer belongs to one bet configuration. Ask again when the
player changes the configuration, and on an interval. The coinflip example's
`src/lib/useMaxWager.ts` is a hook you can copy.
- Delete every read of `snapshot.casino`. A check of your reserved profit against
`maxAllowedReservedProfit` becomes `wager > BigInt(answer.maxWager)`.
- Treat `unknown`, and a rejected call, as "fall back to your own limits", never as unlimited.
- `ICasinoGameV2` is unchanged, so a game compiles and deploys as before. Check its quotes
against "What each return must mean" in `CONTRACT_CONSTRAINTS.md`: `expectedPayout` under the
player's best policy, `probabilityWad` for exactly `maxPayout`, body variance in wei² × 1e18.
`openSession` reverts for a game whose quote fails a guardrail.
- A host that decodes `CasinoSessionAdvanced.session` itself must update its codec; snapshots
from the previous layout no longer decode.
## 2026.10.02-2
### Changed
- **The simulator's chUSD has six decimals**, like USDC on mainnet. The local node funds the
player and the vault in six-decimal amounts.
- **The coinflip and Lucky Reels examples fall back to USDC and six decimals** when the
snapshot's `token` carries no symbol or decimals. The guide's snippets fall back to 6, and the
slot engine's simulator end-to-end run sizes its wagers with the token's decimals.
### Compatibility
- Parse and format amounts with `snapshot.token.decimals`. A game that assumes 18 decimals shows
amounts a trillion times too small.
## 2026.10.02-1
### Changed
- **The player's contract is called the Smart Wallet.** `HostSnapshotV1` reports it as
`wallet.smartWalletAddress` and `balances.smartWalletBalance`; the docs, the simulator and the
coinflip example use the new names.
### Compatibility
- Games that read the player's contract address or balance from the snapshot must switch to the
new field names.
## 2026.09.18-1
### Added
- **Slot engine add-on** in `slot-engine/`: ship a slot as a settlement table on one shared engine
contract, with no per-title Solidity. `bun run start:slots` runs the local stack with the Lucky
Reels example. See `SLOT_ENGINE.md`.
- **Stake Engine importer.** `bun run cli -- import-stake` converts a Stake Engine math export
into a slot engine title, keeping each mode's RTP and a sample of its books for the frontend.
See `MIGRATING_FROM_STAKE_ENGINE.md`.
### Changed
- The SDK's `start` scripts run workspace members through `scripts/run-in.ts`, which uses the
package manager that started them, so `bun`, `npm`, `pnpm` and `yarn` all work. A
`pnpm-workspace.yaml` ships alongside `package.json`'s `workspaces`.
### Compatibility
- Nothing changes for existing games; the add-on is a separate workspace.
## 2026.09.17-2
### Added
- **Operators (not open yet).** `CasinoGameFacet` gained
`openSession(game, vault, wager, gameData, operator)`, which attributes a session to the frontend
that served it. Operator registration is closed for now: every session resolves to the protocol's
default operator, and a missing or unregistered code never reverts. See §7.1 of
`CHAIN_WTF_CASINO_GAMES.md`.
- The simulator's `LocalCasinoHost` mirrors the overload, with permissionless `setOperator` /
`setDefaultOperator` stand-ins for the council-controlled registry.
### Compatibility
- Games and guests need no change: the four-argument `openSession` still works and the operator
never reaches `SessionContext` or the iframe.
- The encoded session grew from 235 to 255 fixed bytes (`operator` sits after the risk flags). A
host that decodes `CasinoSessionAdvanced.session` itself must update its codec; snapshots from
the previous layout no longer decode.
## 2026.09.17-1
### Changed
- **`getRandomnessVerification`** follows the current Verify Network router. The router now keeps
only the id of an open request and stores nothing after fulfillment, so the host verifies from
the fulfillment transaction alone: the `RandomnessFulfilled` log, the request echoed in the
calldata (hashed back to `requestId`, whose derivation gained the locked `gasPrice` and a service
tag), and the enclave signature, whose EIP-712 domain is now version `2`. The signing key is
recovered from that signature — nodes register under the address of their key — and the ECVRF
proof is checked under it; no router view is read.
- **`checks`** are now the four that remain meaningful: `vrfProofValid`,
`vrfBetaMatchesRandomness`, `enclaveSignatureValid`, `signerMatchesFulfiller`. `valid` requires
all four.
- **`request`** carries the router's current request fields: `consumer`, `sequence`, `fulfiller`,
`assignedAt`, `fee`, `gasPrice`, `callbackGasLimit`, `callbackData`.
- The simulator's bundled `local-verify-network` deploys the current router and fulfills with an
RFC 9381 prover on `@noble/curves`; `getRandomnessVerification` runs the same checks against
it as production.
### Compatibility
- Removed from verification results: the `fulfillmentCommitted` and `fastVerifyComponentsMatch`
checks, the `artifacts.uPoint` / `artifacts.vComponents` hints, and `fulfilledAt`. The echoed
request's `requester`, `payment` and `clientData` are now `consumer`, `fee` and `callbackData`.
Verify views that render checks by name should drop the two removed rows; `valid` keeps its
meaning.
## 2026.09.15-1
### Added
- **`SessionPhase`** is now exported from the package root and `./guest`. It mirrors the
Solidity enum, so games can compare `phase === SessionPhase.SETTLED` instead of hardcoding
`3`. `sessions.items[].phaseName` is typed as the matching `SessionPhaseName` union.
- **Payout cap** section in `CONTRACT_CONSTRAINTS.md`, covering two traps that revert large
wins with `InvalidPayout`: a payout computed separately from the reserve and ending up a few
base units above it, and releasing reserved profit on the settling step.
### Changed
- **Breaking:** `computeMaxWager` returns a `MaxWagerResult` instead of `bigint | undefined`:
`{ kind: 'limit', maxWager }`, `{ kind: 'no-limit' }` (limits are published but none binds
this bet: ≤1x multiplier and no `maxBetAmount`), or `{ kind: 'unknown' }` (the host publishes
nothing to derive a limit from). Previously the last two both returned `undefined`.
- The game interface moved from `solidity/ICasinoGameV2.sol` to
`simulator/contracts/ICasinoGameV2.sol`, so drop-in games import it as
`./ICasinoGameV2.sol`. The local node also resolves any other import path ending in
`ICasinoGameV2.sol` to that file, so a contract can keep its own repo's import path.
- `CONTRACT_CONSTRAINTS.md` no longer says `openSession` calls `onSessionStart` twice. The
facet has called it once since the stateless-session release.
### Compatibility
- Update `computeMaxWager` callers: `result.kind === 'limit' ? result.maxWager : undefined`
restores the old value. Contracts are unaffected.
- Games importing `../../solidity/ICasinoGameV2.sol` keep compiling in the simulator. The
import falls back to the new file by name.
## 2026.09.05-1
### Changed
- **Stateless casino sessions.** The facet no longer stores sessions: it keeps
`keccak256(encodedSession)` per id, emits the encoded snapshot in
`CasinoSessionAdvanced(sessionId, step, requestId, randomness, session)` on every non-terminal
step, and takes it back as calldata — `openSession(game, vault, wager, gameData)`,
`submitAction(encodedSession, actionData)`, `cancelStuckRandomness(encodedSession)`,
`forfeitExpiredSession(s)(bytes…)`. `CasinoSessionOpened` shrank to
`(sessionId, game, player, vault, wager)` and `CasinoSessionSettled` now carries the fulfilling
`randomness` instead of `gameName`; `CasinoSessionPhaseAdvanced`, `CasinoSessionEscrowUpdated`,
`CasinoSessionRandomnessRequested` and `CasinoSessionRandomnessFulfilled` are gone. The
game-facing bridge is unchanged — the host resolves the snapshot bytes itself.
- **Casino randomness provider.** Like PvP, the casino facet requests through a swappable
`IRandomnessProvider` (`CasinoConfigFacet.setRandomnessProvider` / `getRandomnessProvider`,
event `RandomnessProviderUpdated`) and receives `onCasinoRandomnessFulfilled(requestId,
randomness, clientData)` from it. The production provider is
`VerifyNetworkCasinoRandomnessProvider`, a stateless adapter that echoes the session snapshot
through the router's `clientData`; it holds the prepaid VRF balance instead of the diamond.
- **`randomnessRequests[].nonce`** is now the session step that made the request (strictly
increasing, not consecutive: a coinflip's only request is `"1"`).
- **`getRandomnessVerification`** verifies against the redesigned Verify Network router, which
stores only `keccak256(proofCommitment ‖ fulfilledAt)` per request: artifacts come from the
fulfillment transaction (the `RandomnessFulfilled` log and the request echoed in the calldata).
`artifacts.uPoint` / `artifacts.vComponents` are now optional derived hints; new fields
`request`, `fulfilledAt`; new check **`fulfillmentCommitted`** (the router still commits to this
proof — a successful challenge clears it). `valid` requires all six checks.
- The simulator's `LocalCasinoHost` mirrors the stateless facet (`simulator/contracts/casino/`
holds the session struct + codec) and its `getRandomnessVerification` now runs the same checks
as production against the local router.
### Deprecated
- **`randomnessRequestData`** on `HostApiV1.openSession` / `submitAction` is ignored; the facet
requests randomness itself. Omit it.
## 2026.08.30-1
### Added
- **`sessions.items[].stake`** in `HostSnapshotV1`: the session's total escrowed stake — the
wager plus mid-session increases (e.g. a blackjack double/split), sourced from the
`CasinoSessionEscrowUpdated` event. Updates flow through the flashblock layer, so an increase
reaches the snapshot without waiting for the indexed feed. Games no longer need to
reconstruct total escrow from their own `gameState`.
### Compatibility
- The field is optional — feature-detect it. It is absent on hosts that predate this release
and on sessions from before `CasinoSessionEscrowUpdated` existed; fall back to `wager` (or
your own reconstruction). On the live platform it stays absent until the facet upgrade that
emits the event ships; the simulator publishes it already.
## 2026.08.29-1
### Added
- **Forfeit cash-out quote — `ICasinoGameV2.quoteForfeitPayout(ctx)`.** New view on the game
interface returning the session's current cash-out value (stake + accrued winnings) decoded
from `ctx.gameState`. When a session is abandoned past `actionDeadlineBlock`, forfeiture now
pays the player `min(quote, escrowedStake + reservedProfit)` **minus a 10% cut**
(`FORFEIT_WINNINGS_CUT_BPS = 1000`) instead of confiscating the whole stake. The facet calls
it defensively (gas-capped `staticcall`, exactly 32 return bytes); any revert, malformed
return, or absence of the method pays `0` — the pre-change behavior.
- **Who should quote:** only games with a true anytime cash-out whose value is fully determined
by already-revealed state (mines-style). Games whose mid-round value depends on unresolved
randomness or hidden state (blackjack-style) **must return `0`** — any concrete quote there is
an adverse-selection exploit (players abandon exactly when their real continuation value is
below the quote). See `CHAIN_WTF_CASINO_GAMES.md` § `quoteForfeitPayout` semantics.
- The simulator's `LocalCasinoHost` mirrors the facet logic, so the forfeit payout path is
testable locally.
### Compatibility
- Backwards compatible: already-deployed games lack the method and keep paying `0` on forfeit.
The method is part of `ICasinoGameV2`, so newly compiled games must implement it — returning
`0` unconditionally is a valid implementation (and the required one for blackjack-style
games). On-chain activation on the live platform awaits the facet upgrade; the simulator
enforces the new behavior already.
## 2026.08.08-1
### Added
- **Live platform bet limits.** The Chain.wtf host (and the simulator) now populate the
snapshot's **`casino`** block on every refresh: `availableLiquidity`, `maxBetRiskBps`,
`maxAllowedReservedProfit` (the per-bet reserved-profit cap `openSession` enforces via
`CasinoGameFacet__BetRiskExceedsLimit`), and the new **`casino.maxBetAmount`** — the absolute
per-game wager ceiling when the platform configures one (unset or `'0'` = none).
- **`computeMaxWager(snapshot, { maxMultiplierX })`** guest helper (exported from the package
root and `./guest`): converts the `casino` block into the largest wager the facet accepts for
a game whose worst-case payout is `wager * maxMultiplierX`, accounting for `maxBetAmount`.
Rounds conservatively (never returns a wager the facet would still reject) and returns
`undefined` on hosts that don't publish limits. Use it to clamp bet inputs so players can't
submit bets the chain will bounce; games with `gameData`-dependent multipliers should pass the
multiplier of the current selection, or invert their exact `quoteCaps` math against
`maxAllowedReservedProfit` (the bundled coinflip example shows both the exact gate and the
helper-driven "Max" clamp).
### Compatibility
- Additive and optional — existing games keep working. An absent `casino` block means "limits
unknown" (older host), not "unlimited".
## 2026.08.03-1
### Added
- **`simulator/`** — a fully standalone local test setup, needing nothing outside the SDK
package. The package is now an npm workspace: one `npm install` at the SDK root covers the
simulator, the bundled VRF node and the example game, and one `npm start` runs the whole
local stack (chain + VRF node + casino deployment + harness + example). Individually,
`npm run local-node` boots a chain (spawns anvil if none is running), deploys the real
Verify Network VRF router + fulfilling node (via the bundled `local-verify-network`), and
deploys a minimal casino: a freely mintable test token, `LocalCasinoHost` — a stand-in for the
production `CasinoGameFacet` that runs the full `ICasinoGameV2` lifecycle and emits
byte-identical events — and the real `CoinflipGame` contract (vendored compiled artifact) so
the bundled example works out of the box. `npm run dev` starts the harness (Vite + React +
Tailwind): it mounts any game iframe by URL and drives it through the same bridge as the
Chain.wtf host, replicating the production host's behavior so games face the real integration
quirks: optimistic `pending:` session rows, a near-instant flashblock push layer plus a lagged
(tunable) indexed session feed, atomic settled flips with monotonic results, the game-steered
balance ledger, and the stuck-randomness cancel path. The collapsible setup panel can also
force `wallet.status` to exercise non-ready game states. See `simulator/README.md`.
### Compatibility
- Tooling only — no bridge/type changes. `getRandomnessVerification` is not implemented by the
harness (the call rejects, as it may on any host that doesn't support it).
## 2026.07.26-1
### Added
- Added transaction hashes to session items so games can render real block-explorer `/tx/` links
instead of falling back to the game contract's address page:
- **`raw.openTransactionHash`** — the transaction that opened the session (the bet). The
Chain.wtf host populates it on the optimistic row as soon as the relayer confirms, so it is
available mid-round, before the indexer projection catches up.
- **`raw.settleTransactionHash`** — the transaction that settled the session; absent until
settled.
- **`raw.randomnessRequests[].transactionHash`** — the VRF fulfillment transaction, per request;
absent until that request is fulfilled. Because the `getRandomnessVerification` result extends
the request shape, the same hash also appears on each verification entry.
- Explorer links prove a transaction exists, not that the randomness is fair — keep the client-side
`getRandomnessVerification` verdict as the primary fairness signal and treat `/tx/` links as
secondary. See `RANDOMNESS_VERIFICATION.md`.
### Compatibility
- Additive and optional: older hosts omit all three fields, and sessions projected before this
version have no hashes (only a re-indexed environment backfills them). Keep an address-link (or
no-link) fallback when a hash is absent.
## 2026.07.20-1
### Added
- Added **`raw.randomnessRequests`** to session items — every VRF request of the session in
request order (`{ nonce, requestId, randomness?, fulfilled }`). Multi-step games (blackjack,
pvp-dice) see one entry per step; single-shot games see exactly one. `raw.randomness` is now
populated with the first fulfilled word.
- Added optional **`HostApiV1.getRandomnessVerification({ sessionId })`** — the host reads the
ECVRF fulfillment artifacts from the Verify Network router and verifies them client-side
(proof, output, EIP-712 enclave signature, signer identity). Returns per-request verdicts plus
the raw artifacts so the result can be re-verified independently. See
`RANDOMNESS_VERIFICATION.md` for the full contract and rendering rules.
### Compatibility
- Additive and optional: feature-detect `host.getRandomnessVerification` before calling; older
hosts also omit `raw.randomnessRequests`. On environments without Verify Network (local dev)
the method resolves with `supported: false`.
## 2026.07.19-1
### Added
- Added optional **`HostSnapshotV1.ui.viewport.availableHeight`** — the height in px from the top
of the game's iframe to the bottom edge of the visible screen, before the user scrolls. Games
that want their primary action (e.g. the bet button) to land exactly at the screen edge should
size the content above it to `availableHeight` minus the action's own height. The value is
measured against the small viewport (`svh` semantics), so it stays stable while scrolling
collapses mobile browser chrome and only changes on real resizes or orientation changes.
- CSS viewport units cannot replace this value: the host grows the iframe to fit the game's
reported content height, so inside the iframe `100vh` equals the full content height, not the
visible screen.
### Compatibility
- Additive and optional: games running against older hosts simply receive no `ui.viewport` and
should fall back to their existing layout.
## 2026.07.18-1
### Added
- Added optional **`HostSnapshotV1.token.iconUrl`** — an absolute URL of the wager token's icon so
games can render it next to balances and wagers.
### Compatibility
- Additive and optional: games running against older hosts simply receive no `iconUrl` and should
fall back to a text symbol.
## 2026.07.03-1
### Added
- Added optional **`HostApiV1.reportContentSize({ minHeight })`** so casino iframe games can report
their current required content height to the host after the bridge connects.
- Added **`reportGameContentSize(hostApi)`** and **`observeGameContentSize(hostApi)`** helpers in
`@chain/casino-sdk/guest`. Games can use the observer to report initial height and subsequent
layout changes without wiring their own `ResizeObserver`.
### Changed
- Removed static **`presentation.minHeight`** from casino game manifest validation and examples.
Height is now a runtime concern reported by the child iframe when `capabilities.resize` is
supported.
### Compatibility
- Dynamic sizing is additive: older games that do not call `reportContentSize` continue to render
using the host's existing frame fallback.
- `reportContentSize` is optional on the host API so games can safely run against older hosts by
checking for the method or using `observeGameContentSize`, which no-ops when unsupported.
## 2026.07.02-1
### Added
- **`HostApiV1.revealOutcome({ sessionId })`** (required) — games **must** call this when their
result presentation has finished. Sessions settle on-chain long before a result animation
completes (bonus buys can run past a minute), so from the moment a bet is placed until the
game reveals the outcome, the Chain.wtf host **clamps its balance displays** (navbar, Smart
Wallet modal, and the `balances.smartWalletBalance` pushed in the snapshot): the balance may move down
(wager debit, extra escrow pulls) but never up. Guarding from bet placement — rather than
subtracting the payout once it is known — means even a settlement that lands on-chain within a
block can never flash the result. This is the reverse of an optimistic update: the payout is
final, only its display is delayed. The guard exists only while the game is mounted —
refreshing the page or navigating away from the game shows the real balance immediately.
- Only **credits** are withheld: the wager debit still shows the moment a session opens, and a
lost session moves no balance at settlement (the host releases the guard itself on a lost
round), so games only need to reveal after wins — calling it after losses is a harmless no-op.
### Breaking
- `revealOutcome` is a **mandatory** part of the game contract: a game that never calls it after
a win leaves the host's displayed balance stale until the player leaves or reloads the game
page. Update existing games to call `hostApi.revealOutcome({ sessionId })` at the end of their
win presentation.
## 2026.06.18-1
### Added
- **`CasinoGameFacet` events now carry opaque game bytes**, so hosts can populate
`HostSnapshotV1.sessions.items[].raw` from the session indexer without supplementary on-chain
reads:
- `CasinoSessionOpened` — added `bytes gameData` (the wager-time payload passed to
`openSession`).
- `CasinoSessionPhaseAdvanced` — added `bytes gameState` (post-step opaque state after each
facet transition, including the first step after open).
- `CasinoSessionSettled` — added `bytes gameState` (final opaque state at settlement).
- Documented that the **casino_sessions projection** ingests these fields and exposes them on each
session row; the Chain.wtf host maps them to `raw.gameData` / `raw.gameState` in the Penpal
snapshot pushed via `guestApi.setState`.
### Changed
- **Host snapshot freshness:** integrators should treat indexer session rows as the source of truth
for `raw.gameData` and `raw.gameState`. On-chain `getSession(sessionId)` is optional for
recovery or debugging — it is no longer required on every phase advance to keep the iframe in
sync.
### Compatibility
- The new event fields are additive. Indexers and hosts that already merge projection rows into
`HostSnapshotV1` gain automatic opaque-state updates; games that decode `raw.gameState` from the
snapshot require no bridge API changes.