# Changelog Follow PvP SDK releases and the contract, simulator, and bridge changes that affect your game. Check breaking changes before updating your integration. :::note[Current SDK checkout] - `@chain-protocol/pvp-sdk@0.7.0` This guidance reflects this checkout and may include unreleased changes. [PvP changelog](/sdk/pvp/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`. ## Unreleased ### Added - A PvP game's developer, or the wallet that sent its creation call, may choose the game's rake once with `setPvpDeveloperRake(game, rakeBps)`, from 100 to 500 bps, within 60 seconds of its registration. The council's per-game figure still overrides it, and the protocol-wide default of 500 bps applies without either. A successful call emits `PvpDeveloperRakeSet(game, rakeBps)`, and `getPvpDeveloperRake(game)` returns `(set, rakeBps)`. `LocalPvpHost` applies the same rules, reverting with `LocalPvpHost__NotDeveloperRakeSetter`, `LocalPvpHost__DeveloperRakeWindowClosed`, `LocalPvpHost__DeveloperRakeAlreadySet` or `LocalPvpHost__InvalidDeveloperRakeBps`. - The local node's `createGame(initCode, { developer, rakeBps })` sends `rakeBps` as the developer rake right after the creation call. **Breaking** for callers that passed `developer` as the second argument. ## 0.7.0 (2026-10-28) Released in lockstep with `@chain-protocol/casino-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. - `defineDeployment` takes an optional `frontend`: `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. ### Changed - The player's contract is called the Smart Vault again. The docs and the simulator's host dependencies use the new name: `getSmartWalletBalance` is now `getSmartVaultBalance`. - **Breaking:** the PvP bytecode deployer's creation call names the game developer, as `createGame(initCode, salt, developer, authorshipId)`, in the protocol's `GameBytecodeDeployer` and the simulator's `LocalGameBytecodeDeployer`. Anyone may name any developer, since the name only directs the game dev fee. An authorship is an optional way for a developer's wallet to authorize a specific creator wallet; a call that consumes one must name the authorship's writer, or `LocalPvpHost` reverts with `Authorship__DeveloperMismatch`. A zero developer reverts with `LocalPvpHost__ZeroDeveloper`. `GameRegistered` is unchanged. The simulator still creates every game from dev account #0 and names that account as its developer. ## 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 `0.6.0-canary.1` below. Released in lockstep with `@chain-protocol/casino-sdk` 0.6.0. ## 0.6.0-canary.3 (2026-10-20) No changes. Released in lockstep with `@chain-protocol/casino-sdk` 0.6.0-canary.3. ## 0.6.0-canary.2 (2026-10-20) No changes. Released in lockstep with `@chain-protocol/casino-sdk` 0.6.0-canary.2. ## 0.6.0-canary.1 (2026-10-19) ### Changed - **Breaking:** a game gets one random value per request. A step reached from `onRandomness` may not move to `WAITING_RANDOMNESS`. The PvP facet reverts the fulfilment with `PvpGameFacet__RandomnessRequestedFromCallback`. Derive further values from the one you received. - `startLobby` and `submitLobbyAction` 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. `LocalPvpHost` starts with an empty router balance, the simulator page attaches the host's `requiredPayment` to every start and move, and a fulfilment whose step requests randomness again reverts with `LocalPvpHost__RandomnessRequestedFromCallback`. - **Breaking:** `runPvpProperties` from `@chain-protocol/pvp-sdk/conformance` fails `hooksDoNotRevert` when a lobby has one actor as its only exit. It plays the started lobby with each seated player silent, and opens the started config again with a never-acting address where the opener's address was. Someone else must still be able to move or cancel the lobby, after waiting out about a day. A game whose start, cancel or every move belongs to one opener-chosen address now fails `game-verify`. - **Breaking:** `PvpLobbyOpened` no longer carries `string gameName`, in production and in `LocalPvpHost`. The chain keeps no game names, so its topic changed. Read a lobby's game by its `game` address. ## 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; omitted Host declarations derive support from the supplied handler, while explicit empty/false declarations disable it. 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:** a game gets one random value per request. A step reached from `onRandomness` may not move to `WAITING_RANDOMNESS`. The PvP facet reverts the fulfilment with `PvpGameFacet__RandomnessRequestedFromCallback`. Derive further values from the one you received. - `startLobby` and `submitLobbyAction` 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. `LocalPvpHost` starts with an empty router balance, the simulator page attaches the host's `requiredPayment` to every start and move, and a fulfilment whose step requests randomness again reverts with `LocalPvpHost__RandomnessRequestedFromCallback`. - **Breaking:** `runPvpProperties` from `@chain-protocol/pvp-sdk/conformance` fails `hooksDoNotRevert` when a lobby has one actor as its only exit. It plays the started lobby with each seated player silent, and opens the started config again with a never-acting address where the opener's address was. Someone else must still be able to move or cancel the lobby, after waiting out about a day. A game whose start, cancel or every move belongs to one opener-chosen address now fails `game-verify`. ## 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. PvP panels are `info` and `history`. 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.** `PvpHostSnapshot.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/pvp-sdk`. It was `@chain/pvp-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: 2` 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 `IPvpGameV2`, keep theirs. | Before | After | | --------------------------------- | ------------------------------- | | `PvpGameManifestV2` | `PvpGameManifest` | | `PvpHostSnapshotV2` | `PvpHostSnapshot` | | `PvpHostApiV2` | `PvpHostApi` | | `PvpGuestApiV2` | `PvpGuestApi` | | `PvpViewerAccessV2` | `PvpViewerAccess` | | `PvpHostMetadataV2` | `PvpHostMetadata` | | `PvpLobbyMetadataV2` | `PvpLobbyMetadata` | | `PvpPlayerMetadataV2` | `PvpPlayerMetadata` | | `PvpRoomMetadataV2` | `PvpRoomMetadata` | | `PvpRoomParticipantMetadataV2` | `PvpRoomParticipantMetadata` | | `RandomnessRequestV2` | `RandomnessRequest` | | `RandomnessRequestEchoV2` | `RandomnessRequestEcho` | | `RandomnessRequestVerificationV2` | `RandomnessRequestVerification` | | `RandomnessVerificationV2` | `RandomnessVerification` | | `VrfVerificationChecksV2` | `VrfVerificationChecks` | - **Breaking:** `IPvpGameV2.sol` and `PvpLiveness.sol` moved from `simulator/contracts/` to `contracts/`. Import them as `@chain-protocol/pvp-sdk/contracts/IPvpGameV2.sol` and `.../PvpLiveness.sol`. - The package publishes to npm as ESM with type declarations, MIT, for Node 22+ and Bun 1.4.2+. It publishes `.`, `./guest`, `./liveness`, `./spectator` and `./contracts/*` for games, and `./host` and `./conformance` as internal subpaths outside semver. - 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/pvp-sdk` registry item. Install `@chain-protocol/pvp-sdk` from npm. ### Compatibility - Replace `@chain/pvp-sdk` with `@chain-protocol/pvp-sdk` in your imports and `package.json`. - Import `IPvpGameV2.sol` and `PvpLiveness.sol` from `@chain-protocol/pvp-sdk/contracts/` in Hardhat, or remap `@chain-protocol/=node_modules/@chain-protocol/` in Foundry. - Rename the types in the table above. - 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.02-2 ### Changed - **The simulator's chUSD has six decimals**, like USDC on mainnet. The local node funds each account and sets the gas charge in six-decimal amounts, and `LocalPvpHost` defaults to a 0.01 chUSD gas charge at six decimals. - **The Bones and RPS examples fall back to USDC and six decimals** when the lobby's asset carries no symbol or decimals. ### Compatibility - Format and parse amounts with the asset's `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.** The docs and the simulator's host dependencies use the new name: `getVaultBalance` is now `getSmartWalletBalance`. ### Compatibility - No game-facing API change. ## 2026.10.01-2 ### Changed - **Entries, the creation with an entry and the cancel follow the rake reserve.** The chain.wtf host and the simulator carry each as a sponsored call when the lobby's rake reserve, after the call's own stake, can pay its gas charge, and as a covered call otherwise. The relayer's sponsored minimum no longer decides them; it still decides claims of winnings and deferred payouts. See §6.3 of `CHAIN_WTF_PVP_GAMES.md`. - **A refund claim is always a covered call**, repaid from the refund itself, so it goes through from an empty balance. The host shows the gas line item. - **Cancel and refund in one call.** A player whose balance cannot cover a covered cancel, and who has a stake in the lobby, cancels and claims their own refund in one covered call. `cancelLobby` resolves as before; `lobby.viewer.refunded` is then true, so read it before offering that player a refund claim. ### Compatibility - No API change. A game that already handles `PVP_CALL_UNAFFORDABLE` and reads `lobby.viewer.refunded` needs no code. ## 2026.10.01-1 ### Added - **Rake reserve figures on the snapshot.** `LobbySnapshot` gains optional `reserveRemaining` (what the lobby's rake reserve, the part of the rake the gas tank does not hold, can still pay for its entries and cancel; present while the lobby is open or in play) and `reserveSpent` (what it has paid for the open-and-enter, entries and cancel), both in the asset's base units. `LobbySettlement` gains optional `reserveSpent` beside `tankSpent`: the fee recipients split `feeAmount - tankSpent - reserveSpent`, and winners still share `pot - feeAmount`. See §4 and §6.5 of `CHAIN_WTF_PVP_GAMES.md`. - **`lobbyNetRefund(lobby, stake)`.** What `claimRefund` pays a participant of a cancelled lobby: the stake less its share, by stake, of `tankSpent` plus `reserveSpent`, rounded up against the participant and never below zero. The Chain Bones example shows it on its refunds to collect. - **The simulator mirrors the rake reserve.** `LocalPvpHost` repays the sender of `openAndEnterLobby`, `enterLobby` and `cancelLobby` from the reserve at the simulator's gas charge, in full or not at all, emits `PvpLobbyReservePaid`, refunds net of tank and reserve spend, and splits the rake less both at resolution. `getLobbyTank` ends with `reserveRemaining` and `reserveSpent`, like the diamond's lobby view. See "Rake reserve" in `LOCAL_SIMULATOR.md`. ### Changed - `PvpLobbyReservePaid(lobbyId, payee, gasUsed, gasCharge, reserveRemaining)` reports each reserve payment, after the paid call's own entry or cancellation in the same transaction. `PvpLobbyResolved` is unchanged and still ends with `tankSpent` only; read reserve spend from the payment events or the lobby view. - A cancelled lobby's refunds are smaller than before by each participant's share of what the reserve spent, on chain.wtf and in the simulator. ### Compatibility - Additive for guests: the snapshot fields are optional and a guest that ignores them reads the same snapshot as before. Hosts that predate them, and lobbies without a gas tank, omit them. - Anything decoding `getLobbyTank` with its own ABI must take the two trailing fields. ## 2026.09.30-3 ### Added - **`PVP_CALL_UNAFFORDABLE`.** The message prefix of the error a write method rejects with when the call would be a covered call and the player's balance cannot pay the stake plus the cover limit. Nothing is sent; the game keeps its state and may retry. Exported from `@chain/pvp-sdk`; the chain.wtf host reports the same code. See §6.3 of `CHAIN_WTF_PVP_GAMES.md`. - **The simulator mirrors the gas tank.** `LocalPvpHost` has the per-game rake (`setPvpGameRake` / `clearPvpGameRake` / `getPvpGameRake`), the tank share (`setPvpTankShare`, 3500 bps by default), the lobby gas tank that pays a participant's moves in full or not at all and credits the move's sender (`getClaimable`, `claim`), refunds net of tank spend (`getLobbyRefund`), the relayer's lobby view (`getLobbyTank`) and the production event shapes. The simulator's host applies the relayer's policy in process: moves are sponsored while the tank can pay their charge, entries at or above the sponsored minimum are sponsored, everything else is a covered call charged to the tab's balance and shown as a gas line item over the frame, and an unaffordable call is refused with `PVP_CALL_UNAFFORDABLE`. The snapshot now carries `tankRemaining` / `tankSpent` and `settlement.tankSpent`. The rake, tank share, gas charge and sponsored minimum are adjustable from the setup panel and the local node's environment; see `LOCAL_SIMULATOR.md`. ### Changed - **`LocalPvpHost` events follow production.** `PvpLobbyOpened` ends with `uint16 tankShareBps`, `PvpLobbyResolved` ends with `uint256 tankSpent` and no longer carries `feeRecipient`, and `PvpLobbyTankPaid` reports each tank payment. Anything decoding the local host's logs with its own ABI must take the new signatures. - The local node deploys the host with the protocol's default rake of 500 bps (was 250) and prices every call at a gas charge of 0.01 chUSD. - The SDK docs describe cancellation refunds as net of tank spend, which they have been since the tank shipped. ### Compatibility - Additive for guests: the error prefix is new, the snapshot fields are the optional ones from `2026.09.30-1`, and a game that ignores them behaves as before. ## 2026.09.30-2 ### Added - **Creation with a first entry.** `PvpHostApiV2.createLobby` accepts an optional `entry: { stake, entryData? }`. With it the host opens the lobby and makes the creator's entry in one transaction (the diamond's `openAndEnterLobby`) and resolves with `contributionId` beside `lobbyId`. The entry obeys the same rules as `enterLobby` (game policy, invitee list); a rejected entry fails the whole call and leaves no lobby open. See §6.1 of `CHAIN_WTF_PVP_GAMES.md`. - The bundled examples (`examples/bones`, `examples/rps`) create and enter in one call; the simulator's `LocalPvpHost` gained the same entry point. ### Changed - **Creating without an entry is a covered call.** The chain.wtf host relays a creation with an entry as it relays an entry (sponsored when the stake reaches the PvP sponsored minimum, else a covered call with the gas line item), and a creation without one always as a covered call paid by the creator's Smart Wallet. Games whose creator plays should pass `entry`; a creation-then-enter pair keeps working but costs the creator its gas. ### Compatibility - Additive: `entry` is optional and `contributionId` is only present when it was passed. Guests that do not pass `entry` see the same result shape as before. ## 2026.09.30-1 ### Added - **Gas tank figures on the snapshot.** `LobbySnapshot` gains optional `tankRemaining` (what the lobby's gas tank can still pay for participants' moves; present while the lobby is in play) and `tankSpent` (what it has paid so far), both in the asset's base units. `LobbySettlement` gains optional `tankSpent` beside `feeAmount`, which stays the gross rake: winners share `pot - feeAmount`, the fee recipients split `feeAmount - tankSpent`. See §4 and §6.5 of `CHAIN_WTF_PVP_GAMES.md`. - The figures are informational. The relayer decides whether a move is sponsored; never gate a move on `tankRemaining`. ### Changed - **Two lobby events carry the tank.** `PvpLobbyOpened` ends with a new `uint16 tankShareBps` (the share of the rake that funds the lobby's tank, fixed when the lobby opens) and `PvpLobbyResolved` ends with a new `uint256 tankSpent`. Their topic hashes change; every earlier field keeps its position. `PvpLobbyTankPaid(lobbyId, payee, gasUsed, gasCharge, tankRemaining)` reports each tank payment. ### Compatibility - Guests need no change: the three fields are optional and additive to snapshot `apiVersion: 2`, and a guest that ignores them reads the same snapshot as before. Hosts that predate them, and lobbies without a tank, omit them. - The chain.wtf host sends the fields. - Anything that decodes diamond logs with its own copy of the ABI must take the two new event signatures. ## 2026.09.17-2 ### Added - **Operators (not open yet).** `PvpGameFacet` gained `openLobby(game, token, config, openData, invitees, operator)`, which attributes a room to the frontend that opened it; that operator earns its share of the lobby's rake. It also emits `PvpLobbyOperatorAttributed(lobbyId, operator)` and exposes `getLobbyOperator(lobbyId)`. Operator registration is closed for now: every lobby resolves to the protocol's default operator, and a missing or unregistered code never reverts. See §9.1 of `CHAIN_WTF_PVP_GAMES.md`. - The simulator's `LocalPvpHost` mirrors the overload, event and view. It pays the room operator `operatorRakeShareBps` (default 5%) of the rake, with permissionless `setOperator`, `setDefaultOperator` and `setOperatorRakeShareBps` stand-ins for council configuration. ### Compatibility - Games and guests need no change: the five-argument `openLobby` still works, the operator never reaches `LobbyContext` or the iframe, and the distributable pot is unchanged. ## 2026.09.17-1 ### Changed - **`lobby.defaultStake`** in the manifest is now a whole-token decimal string (`"1"`, `"0.1"`) rather than base units, so one manifest serves every deployment regardless of the asset's decimals. Games convert it with the live asset's `decimals` (`parseUnits(defaultStake, asset.decimals)`); the RPS example does this in `state/stake.ts`. - **`getRandomnessVerification`** follows the current Verify Network router and matches the casino SDK's verification. The router 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`), and the enclave signature (EIP-712 domain version `2`). The signing key is recovered from that signature and the ECVRF proof is checked under it; no router view is read. The chain.wtf host now implements the method for PvP lobbies, and the simulator runs the same checks against its bundled router. - **`checks`** are `vrfProofValid`, `vrfBetaMatchesRandomness`, `enclaveSignatureValid`, `signerMatchesFulfiller`; `valid` requires all four. New **`request`** field: the echoed router request (`consumer`, `sequence`, `fulfiller`, `assignedAt`, `fee`, `gasPrice`, `callbackGasLimit`, `callbackData`). ### Compatibility - Manifests written with base-unit `defaultStake` values must be converted to whole tokens (`"1000000000000000000"` → `"1"`); a leftover base-unit string would now prefill an enormous stake. The RPS and Bones example manifests are updated. - Removed from verification results: the `fastVerifyComponentsMatch` check and the `artifacts.uPoint` / `artifacts.vComponents` hints. Verify views that render checks by name should drop that row; `valid` keeps its meaning. ### Fixed - `npm install` at the SDK root no longer fails with `EBADDEVENGINES`: `package.json` declares npm as the dev package manager, matching the documented install path. - `npm run local-node` crashed with `verifyNetwork.depositLocalVrfClientBalance is not a function`. The bundled `local-verify-network` renamed that helper to `depositLocalVrfBalance` (with an `account` argument) and the simulator now calls it by its current name. The local-node module type is derived from the bundled package's real exports, so a future rename fails `npm run check-types` in `simulator/` instead of surfacing at runtime. - Every randomness step in the simulator reverted with `0x87c9fc34` (`Proxy__ImplementationIsNotContract()` from the bundled router). `LocalPvpHost.sol` still called the previous router's `requestRandomness()` and exposed a two-argument `onRandomnessFulfilled`. It now calls `requestRandomness(bytes callbackData)` with empty data and implements the three-argument callback, matching the production PvP randomness adapter and `LocalCasinoHost`. `src/local-node/artifacts.ts` is regenerated. ## 2026.09.16-4 ### Changed - The Solidity sources moved from `solidity/` to `simulator/contracts/`: `IPvpGameV2.sol` and `PvpLiveness.sol` sit next to the harness contracts, the reference games in `simulator/contracts/examples/`. Drop-in games import the interface as `./IPvpGameV2.sol`. ### Compatibility - Games importing `../../solidity/IPvpGameV2.sol` or `../../solidity/PvpLiveness.sol` keep compiling in the simulator. Any import path ending in either file name resolves to the shared file. Contracts are unaffected. ## 2026.09.16-3 ### Added - **`simulator/` room browser.** A **Room browser** tab above the frame renders production's PvP landing page from the local host: every game's lobbies railed as Open lobbies / Matches in progress / Recently archived with the production card anatomy and info popover, fed by the same lagged indexed feed the game reads. Clicking a card opens that lobby on the game page (switching the harness to the lobby's game contract when it differs); `?view=rooms` keeps the browser open across reloads. Titles and covers come from the running game's manifest (`assets.coverUrl`), other games fall back to their registered name. See `LOCAL_SIMULATOR.md`. ### Fixed - **Rock Paper Scissors waiting room** offers the open seat to a viewer the host routed into a filling lobby (the lobby route, a room browser card, the simulator's **Join**) — previously only the example's own lobby list could join, so a routed viewer saw "Waiting for opponent" with no way in. Joining now goes through one `joinLobby` action that starts the match when the join fills the lobby, replacing the waiting room's last-entrant auto-start; a full lobby nobody started shows a **Start the match** fallback. ## 2026.09.16-2 ### Added - **Spectator mode (optional).** A viewer can open any lobby without a wallet and watch it. The snapshot gains `access` — `{ mode: 'play' }` or `{ mode: 'spectate', reason, canRequestPlayAccess }` with `reason` one of `disconnected`, `setup-required`, `session-key-mismatch`, `chain-mismatch`, `host-policy` — read through `getViewerAccess(snapshot)`, which derives it from `wallet.status` on older hosts. While spectating the host rejects every write with `PVP_SPECTATOR_READ_ONLY` (`isSpectatorReadOnlyError`) and, when it can lead the viewer to play, exposes the optional `hostApi.requestPlayAccess()` that opens its sign-in or wallet-setup flow. Games declare `capabilities.spectate: true` in the manifest; the Chain.wtf host shows a sign-in placeholder instead of the game to spectators of a game that does not. See `CHAIN_WTF_PVP_GAMES.md` §8. - **Bones and Rock Paper Scissors** implement it: a live-matches list with **Watch**, a recent-matches list with **Replay** that opens a finished lobby cold on its final board and result, seats named by position instead of "you", no write controls, no automatic reveal / claim / start as a watcher, a **Sign in to play** hand-off, and a result screen that names the winning seat. Both follow `metadata.room.roomId` into any phase, so the host's Watch and Replay actions open the lobby. - **`simulator/`** sends `access`, rejects writes while spectating, replicates the production placeholder, and gains a **Spectate by host policy** toggle next to the wallet status override; the sign-in hand-off flips the tab's wallet status to `ready`. The match panel gains a **Past games** tab with a **Replay** action for every finished lobby of the game, and reopening the lobby the tab already has open reloads the frame instead of doing nothing. ### Changed - A write attempted while the viewer cannot play now rejects with `PVP_SPECTATOR_READ_ONLY` instead of `PVP_WALLET_NOT_READY`; the latter remains for a `play` viewer whose relay is not ready. - The Chain.wtf host no longer errors out for a wallet on another chain; it spectates with `reason: 'chain-mismatch'`. ### Compatibility - `access`, `requestPlayAccess` and `capabilities.spectate` are all optional. Games that ignore them behave as before for players; add `capabilities.spectate` only once the game renders without a wallet, because the host hides the iframe from spectators otherwise. ## 2026.09.16-1 ### Added - **Liveness standard (optional).** `solidity/PvpLiveness.sol` defines `IPvpLiveness` (ERC-165 id `0xae5c86f9`), a `liveness(phase, config, gameState)` view returning the lobby's pending **waits** plus a capability bitmask, and four reserved `actionData` payloads (`abi.encodePacked(tag, bytes32 subject)`): `SKIP` `0x021033a8`, `FORFEIT` `0xe04af859`, `RESIGN` `0x1fbe2408`, `ADVANCE` `0x46c0809d`. A wait says who owes a step, until when it can be skipped and when it costs them the match, so turn-based games publish one wait and simultaneous games publish one per player. The abstract `PvpLiveness` base handles detection, phase gating, payload parsing and wait resolution; `@chain/pvp-sdk/liveness` exports the tags, ABI, `encodeLivenessAction` and `getDueWaits`. See `PVP_CONTRACT_CONSTRAINTS.md`. - **`solidity/examples/RockPaperScissorsGame.sol`** — the simultaneous reference game: fixed stake, commit and reveal windows of `phaseSeconds` each, first to `winsNeeded` rounds takes the pot. It has no skip path; a player who misses a window while the other has acted can be forfeited immediately, and a window both players miss ends as a no-contest that each seat claims half of. The local backend deploys and registers it. - **`examples/rps/`** — a guest UI for it, the SDK's first example of a commit–reveal game. The sealed pick lives in browser storage and is written and read back BEFORE the commit is sent, since a commitment whose preimage was never stored is unopenable and loses the round silently. The guest reveals automatically, because the reveal window makes a missed click cost the match, and mirrors the contract's `_waitsOf` so it can offer the right liveness control without an RPC: a forfeit only ever against the opponent, a no-contest only when both seats missed. Served on `:3500`. - **Bones forfeit and resign.** `BonesGame` implements the standard: `FORFEIT` two turn lengths after the deadline and `RESIGN` from either seat hand the whole pot to the other seat, whatever the score. The guest shows a countdown, a **Claim the win** button and a Resign option, and the result screen marks conceded matches. ### Changed - `BonesGame` `SKIP` moved from `abi.encode(uint8(2), uint8(0))` to the reserved liveness payload; `lastMove.kind` is now 0 = ROLL, 1 = PLACE, 2 = FORFEIT, 3 = RESIGN. ### Compatibility - Games that do not implement the standard are unaffected. Redeploy `BonesGame` (the simulator picks the new bytecode up on restart); older Bones guests cannot skip on the new contract. ## 2026.09.15-1 ### Changed - **Flashblock push layer on the host.** The Chain.wtf host now overlays flashblock-pushed lobby events (open, entries, phase advances, randomness, resolve, payouts, claims, refunds — from every participant) onto the indexed lobby feed, so `lobbies.items`, `getLobbyParticipants` and `getLobbyContributions` reflect a move roughly a block before the indexed feed delivers it. `createLobby` and `enterLobby` resolve from the pushed log or the receipt, whichever lands first. The layer is best-effort: without it the host behaves exactly as before. - **`simulator/`** gains the same two-layer timing: a flashblock channel next to the indexed feed, a live **Flashblock lag** slider beside the indexer lag, and the same overlay of the lobby list, roster and ledger. See `docs/LOCAL_SIMULATOR.md`. ### Compatibility - No bridge or type changes. A lobby can now appear in `lobbies.items` before the indexed feed has it, but games must keep tracking the `lobbyId` returned by `createLobby` until it appears there — the pushed log is an accelerator, not a guarantee. ## 2026.09.14-1 ### Added - **`simulator/`** — a fully standalone local test setup, needing nothing outside the SDK: a Vite harness that mounts a game iframe through the production host bridge (strict manifest validation, the lagged indexed lobby feed, per-lobby room routing, browser-side randomness verification) and a one-command local backend (in-memory Hardhat node, `LocalPvpHost` — a minimal `PvpGameFacet` stand-in emitting byte-identical events — the reference `BonesGame` and `JackpotGame`, and a real Verify Network VRF node). Each browser tab plays as its own dev wallet, so a duel is two tabs of the same page. See `docs/LOCAL_SIMULATOR.md`. - **`solidity/examples/BonesGame.sol`** — Chain Bones (Knucklebones) replaces `PointDuelGame` as the fixed-stake reference game: 1v1, per-roll VRF, turn deadlines with a permissionless `SKIP`, immediate winner payout and a claimable 50/50 draw split. - **`examples/bones/`** — the Chain Bones guest UI (create, join + start, roll, place, skip, claim, refund, rematch) with its deterministic rules engine and tests, served on `:3200` and wired into `npm start` at the SDK root. ### Removed - `PointDuelGame.sol` and the `examples/point-duel` guest. ## 2026.09.11-1 ### Added - Added optional cancellation-refund state to **`LobbyParticipant`**: - **`refunded`** — true once the address has collected its cancelled-lobby refund. - **`refundAmount`** — amount already returned to the address. This lets a returning player recover a refund whose first transaction failed after cancellation, without attempting to cancel the lobby again. ### Compatibility - Additive and optional: older hosts omit both fields, and games must treat absence as unknown. ## 2026.09.03-1 ### Added - Added optional **`invitees`** to the `PvpHostApiV2.createLobby` input. A non-empty list makes the lobby invite-only: the protocol rejects `enterLobby` from any address not on the list, before the game's own admission policy runs (AND semantics). The list is immutable after creation, bounded at 64 addresses, and the creator is **not** auto-invited. Claims, refunds, and cancellation are untouched — invitations decide who can put money in, never who can take theirs out. Omit or pass empty for a public lobby (exactly the previous behavior). - Added optional **`invitees`** to `LobbySnapshot` — present only on invite-only lobbies; absent on public lobbies and on older hosts. Guests derive "am I invited" by comparing against `snapshot.wallet.address`. - No manifest schema change: a game whose UI creates invite-only lobbies should declare `lobby.access: 'game-defined'`. ### Compatibility - The `createLobby` input change is not additive-optional for hosts (the underlying `openLobby` gained a parameter), but it ships before the first PvP V2 deployment. Games running against older hosts must omit `invitees`. ## 2026.08.31-1 ### Added - Added per-player claim state to **`LobbyParticipant`** (so it appears on `lobby.viewer` and on rows from `getLobbyParticipants`): - **`claimed`** — true once the address has collected at least one claimable-settlement claim. - **`claimedAmount`** — sum of claims already paid to the address. - **`claimedClaimIds`** — the claim ids already collected; check the specific id when a game issues several claims per player. Claim id semantics are game-defined. This lets a returning player's game skip a `claimWinnings` transaction it already sent — the settlement alone never said whether the share was collected. ### Compatibility - Additive and optional: older hosts omit all three fields, and an absent field means "unknown", not "unclaimed" — a resent claim is safe (it reverts as already collected) but wasted. ## 2026.08.30-1 ### Added - Added optional **`PvpHostApiV2.claimPayout({ lobbyId })`** so games can offer collection of an immediate payout whose settlement-time transfer failed (`settlement.immediate.deferred`). The call is permissionless — it always pays the winner recorded at resolution — and resolves with `{ winner, amount, transactionHash }`. - Added optional manifest capability **`capabilities.claimPayout`** declaring that the game renders its own collect control for parked payouts; hosts may keep their own fallback control when the flag is absent or false. ### Compatibility - Additive and optional: feature-detect `host.claimPayout` before calling — older hosts omit it, and manifests without `capabilities.claimPayout` remain valid. ## 2026.08.20-1 ### Changed - **Breaking (contract interface):** `PvpStepResult` now returns **`address[] recipients`** on resolution and no longer declares a settlement mode; the protocol derives it from the length. Exactly **one** recipient settles IMMEDIATE — that address receives the whole distributable pot. Zero (not enumerable) or several recipients settle CLAIMABLE via `getClaim`. The old `ImmediatePayout` amounts arrays and `MAX_IMMEDIATE_RECIPIENTS` are removed (Point Duel now settles via per-participant claims, or immediately when a single player scores). - The winner transfer no longer reverts resolution on failure: the payout is parked on the lobby and collected via the new permissionless **`claimPayout(lobbyId)`**; the facet emits `PvpLobbyPayoutDeferred` when this happens. - `ImmediatePayoutSnapshot` is now `{ winner, amount, deferred? }`. ## 2026.07.26-1 ### Added - Added transaction hashes to lobby snapshots so games can render real block-explorer `/tx/` links instead of falling back to the game contract's address page: - **`raw.createTransactionHash`** — the transaction that created the lobby. - **`raw.resolveTransactionHash`** — the transaction that resolved the lobby; absent until resolved. - **`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 lobbies 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 lobby snapshots — every VRF request of the lobby in request order (`{ nonce, requestId, randomness?, fulfilled }`). Per-throw games see one entry per throw; `raw.randomness` / `raw.requestId` keep reflecting only the latest request. - Added optional **`PvpHostApiV2.getRandomnessVerification({ lobbyId })`** — 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 **`PvpHostSnapshotV2.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 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 **`iconUrl`** to **`PvpAssetBalance`** and **`LobbySnapshot.asset`** — an absolute URL of the token's icon so games can render it next to balances and stakes. ### Compatibility - Additive and optional: games running against older hosts simply receive no `iconUrl` and should fall back to a text symbol. ## 2026.07.10-1 ### Breaking - Replaced API v1's fixed buy-in, unique seat, creator privilege, `uint8` player cap, and full `players[]` snapshots with the game-policy-oriented API v2 model. - Added explicit game-validated entries (including zero and repeated stakes), game-defined positions, optional lobby keys, a protocol-owned contribution ledger, and paginated reads. - Added immediate settlement for at most ten recipients and game-computed claimable settlement. - Removed the separate Smart Wallet field from PvP types; the submitting address is the participant. - Replaced `IPvpGameV1` with `IPvpGameV2` and added a scheduled weighted Jackpot reference game. ## 2026.07.03-1 ### Added - Added optional **`PvpHostApiV1.reportContentSize({ minHeight })`** so PvP 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/pvp-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 PvP 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.06.28-1 ### Added - Added host-to-game metadata support to `PvpHostSnapshotV1`. - Added `PvpHostSnapshotV1.metadata` for parent-app context shared with the iframe, including: - `viewer` profile metadata for the connected viewer. - `room` metadata for room id, slug, title, invite URL, and participants/spectators. - `custom` JSON-serializable host data for presentation-only features. - Added `LobbySnapshot.metadata` for lobby/table labels, invite URLs, and custom presentation data. - Added `LobbyPlayer.metadata` for seated-player profile data such as `userId`, `username`, `displayName`, `avatarUrl`, and `profileUrl`. - Added JSON-safe metadata helper types: - `PvpMetadataPrimitive` - `PvpMetadataValue` - `PvpMetadataBag` - Added structured metadata types: - `PvpPlayerMetadataV1` - `PvpRoomParticipantMetadataV1` - `PvpRoomMetadataV1` - `PvpHostMetadataV1` - `PvpLobbyMetadataV1` - Re-exported the metadata types from `@chain/pvp-sdk/host` and `@chain/pvp-sdk/guest`, so host and game iframe code can import them from the bridge entrypoints. - Documented common metadata that host apps may pass to games: usernames, display names, avatars, profile URLs, room titles, room/invite links, participants, spectators, lobby labels, and app-specific custom metadata. - Documented that host-provided metadata is display-only and must not be used for authoritative game rules, turn ownership, eligibility, scoring, payouts, or any contract-sensitive logic. ### Compatibility - The metadata fields are optional and backward-compatible with existing hosts and games.