# 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.