Follow PvP SDK releases and the contract, simulator, and bridge changes that affect your game. Check breaking changes before updating your integration.
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
Section titled “Unreleased”- 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 emitsPvpDeveloperRakeSet(game, rakeBps), andgetPvpDeveloperRake(game)returns(set, rakeBps).LocalPvpHostapplies the same rules, reverting withLocalPvpHost__NotDeveloperRakeSetter,LocalPvpHost__DeveloperRakeWindowClosed,LocalPvpHost__DeveloperRakeAlreadySetorLocalPvpHost__InvalidDeveloperRakeBps. - The local node’s
createGame(initCode, { developer, rakeBps })sendsrakeBpsas the developer rake right after the creation call. Breaking for callers that passeddeveloperas the second argument.
0.7.0 (2026-10-28)
Section titled “0.7.0 (2026-10-28)”Released in lockstep with @chain-protocol/casino-sdk and @chain-protocol/cli 0.7.0.
- Optional logical guest windows via
openWindow, callable only after the guest advertises the exact target. Public exports includeWINDOW_TARGETS,supportsWindowTarget,WindowTarget,WindowCapabilitiesandWindowOpenResult. LegacyopenPanelbehavior is unchanged. defineDeploymenttakes an optionalfrontend:directory,framework(vite,nextjsortanstack-start),installCommand,buildCommandandoutputDirectory. It tells the platform where the frontend lives and how it builds; without it the platform detects the framework.
Changed
Section titled “Changed”- The player’s contract is called the Smart Vault again. The docs and the simulator’s host
dependencies use the new name:
getSmartWalletBalanceis nowgetSmartVaultBalance. - Breaking: the PvP bytecode deployer’s creation call names the game developer, as
createGame(initCode, salt, developer, authorshipId), in the protocol’sGameBytecodeDeployerand the simulator’sLocalGameBytecodeDeployer. 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, orLocalPvpHostreverts withAuthorship__DeveloperMismatch. A zero developer reverts withLocalPvpHost__ZeroDeveloper.GameRegisteredis unchanged. The simulator still creates every game from dev account #0 and names that account as its developer.
0.6.0 (2026-10-20)
Section titled “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)
Section titled “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)
Section titled “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)
Section titled “0.6.0-canary.1 (2026-10-19)”Changed
Section titled “Changed”- Breaking: a game gets one random value per request. A step reached from
onRandomnessmay not move toWAITING_RANDOMNESS. The PvP facet reverts the fulfilment withPvpGameFacet__RandomnessRequestedFromCallback. Derive further values from the one you received. startLobbyandsubmitLobbyActionarepayable. 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.
LocalPvpHoststarts with an empty router balance, the simulator page attaches the host’srequiredPaymentto every start and move, and a fulfilment whose step requests randomness again reverts withLocalPvpHost__RandomnessRequestedFromCallback. - Breaking:
runPvpPropertiesfrom@chain-protocol/pvp-sdk/conformancefailshooksDoNotRevertwhen 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 failsgame-verify. - Breaking:
PvpLobbyOpenedno longer carriesstring gameName, in production and inLocalPvpHost. The chain keeps no game names, so its topic changed. Read a lobby’s game by itsgameaddress.
0.5.2 (2026-10-20)
Section titled “0.5.2 (2026-10-20)”- Optional Host capability discovery on guest connections and
playAccessopt-in forrequestPlayAccess(). 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
Section titled “Changed”- Breaking: a game gets one random value per request. A step reached from
onRandomnessmay not move toWAITING_RANDOMNESS. The PvP facet reverts the fulfilment withPvpGameFacet__RandomnessRequestedFromCallback. Derive further values from the one you received. startLobbyandsubmitLobbyActionarepayable. 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.
LocalPvpHoststarts with an empty router balance, the simulator page attaches the host’srequiredPaymentto every start and move, and a fulfilment whose step requests randomness again reverts withLocalPvpHost__RandomnessRequestedFromCallback. - Breaking:
runPvpPropertiesfrom@chain-protocol/pvp-sdk/conformancefailshooksDoNotRevertwhen 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 failsgame-verify.
0.5.1 (2026-10-17)
Section titled “0.5.1 (2026-10-17)”- Guest panels. A game can declare panels the host may open from its toolbar:
connectGameToHost(methods, { capabilities: { panels: [...] } })with anopenPanel({ panel })method. PvP panels areinfoandhistory. The host reads the declaration fromconnection.capabilitieson the connectionconnectHostToGamereturns. 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
Section titled “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.0with penpal 7.
0.5.0 (2026-10-15)
Section titled “0.5.0 (2026-10-15)”sdkVersionin the snapshot.PvpHostSnapshot.sdkVersionis the version of the SDK the host was built with, for example0.5.0or0.6.0-canary.3. The production host and the simulator set it from the SDK’spackage.jsonwhen they are built.hostSupports(snapshot, version)answers whether the host’s SDK is atversionor newer. It compares major, minor and patch and ignores the prerelease tag, so a canary host answers as the version it previews. It answersfalsefor a snapshot withoutsdkVersion, which comes from a host older than0.5.0.
Changed
Section titled “Changed”-
Breaking: the package is
@chain-protocol/pvp-sdk. It was@chain/pvp-sdk. -
Breaking:
apiVersionis removed from the snapshot and fromgame.manifest.json.schemaVersionstays as the manifest file’s format version. A manifest that still carriesapiVersion: 2validates, 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 PvpGameManifestV2PvpGameManifestPvpHostSnapshotV2PvpHostSnapshotPvpHostApiV2PvpHostApiPvpGuestApiV2PvpGuestApiPvpViewerAccessV2PvpViewerAccessPvpHostMetadataV2PvpHostMetadataPvpLobbyMetadataV2PvpLobbyMetadataPvpPlayerMetadataV2PvpPlayerMetadataPvpRoomMetadataV2PvpRoomMetadataPvpRoomParticipantMetadataV2PvpRoomParticipantMetadataRandomnessRequestV2RandomnessRequestRandomnessRequestEchoV2RandomnessRequestEchoRandomnessRequestVerificationV2RandomnessRequestVerificationRandomnessVerificationV2RandomnessVerificationVrfVerificationChecksV2VrfVerificationChecks -
Breaking:
IPvpGameV2.solandPvpLiveness.solmoved fromsimulator/contracts/tocontracts/. Import them as@chain-protocol/pvp-sdk/contracts/IPvpGameV2.soland.../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,./spectatorand./contracts/*for games, and./hostand./conformanceas internal subpaths outside semver. -
Versions follow semver, in lockstep across both SDKs and the
chainCLI. Breaking releases publish asX.Y.Z-canary.Nunder the npmcanarytag before production switches.
Removed
Section titled “Removed”- The SDK ZIP on the docs site and the
shadcn add @chain/pvp-sdkregistry item. Install@chain-protocol/pvp-sdkfrom npm.
Compatibility
Section titled “Compatibility”- Replace
@chain/pvp-sdkwith@chain-protocol/pvp-sdkin your imports andpackage.json. - Import
IPvpGameV2.solandPvpLiveness.solfrom@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, callhostSupports(snapshot, '0.6.0'); never parsesdkVersionyourself. - Remove
apiVersionfrom yourgame.manifest.json. A manifest that keeps it still validates.
2026.10.02-2
Section titled “2026.10.02-2”Changed
Section titled “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
LocalPvpHostdefaults 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
Section titled “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
Section titled “2026.10.02-1”Changed
Section titled “Changed”- The player’s contract is called the Smart Wallet. The docs and the simulator’s host
dependencies use the new name:
getVaultBalanceis nowgetSmartWalletBalance.
Compatibility
Section titled “Compatibility”- No game-facing API change.
2026.10.01-2
Section titled “2026.10.01-2”Changed
Section titled “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.
cancelLobbyresolves as before;lobby.viewer.refundedis then true, so read it before offering that player a refund claim.
Compatibility
Section titled “Compatibility”- No API change. A game that already handles
PVP_CALL_UNAFFORDABLEand readslobby.viewer.refundedneeds no code.
2026.10.01-1
Section titled “2026.10.01-1”- Rake reserve figures on the snapshot.
LobbySnapshotgains optionalreserveRemaining(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) andreserveSpent(what it has paid for the open-and-enter, entries and cancel), both in the asset’s base units.LobbySettlementgains optionalreserveSpentbesidetankSpent: the fee recipients splitfeeAmount - tankSpent - reserveSpent, and winners still sharepot - feeAmount. See §4 and §6.5 ofCHAIN_WTF_PVP_GAMES.md. lobbyNetRefund(lobby, stake). WhatclaimRefundpays a participant of a cancelled lobby: the stake less its share, by stake, oftankSpentplusreserveSpent, 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.
LocalPvpHostrepays the sender ofopenAndEnterLobby,enterLobbyandcancelLobbyfrom the reserve at the simulator’s gas charge, in full or not at all, emitsPvpLobbyReservePaid, refunds net of tank and reserve spend, and splits the rake less both at resolution.getLobbyTankends withreserveRemainingandreserveSpent, like the diamond’s lobby view. See “Rake reserve” inLOCAL_SIMULATOR.md.
Changed
Section titled “Changed”PvpLobbyReservePaid(lobbyId, payee, gasUsed, gasCharge, reserveRemaining)reports each reserve payment, after the paid call’s own entry or cancellation in the same transaction.PvpLobbyResolvedis unchanged and still ends withtankSpentonly; 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
Section titled “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
getLobbyTankwith its own ABI must take the two trailing fields.
2026.09.30-3
Section titled “2026.09.30-3”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 ofCHAIN_WTF_PVP_GAMES.md.- The simulator mirrors the gas tank.
LocalPvpHosthas 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 withPVP_CALL_UNAFFORDABLE. The snapshot now carriestankRemaining/tankSpentandsettlement.tankSpent. The rake, tank share, gas charge and sponsored minimum are adjustable from the setup panel and the local node’s environment; seeLOCAL_SIMULATOR.md.
Changed
Section titled “Changed”LocalPvpHostevents follow production.PvpLobbyOpenedends withuint16 tankShareBps,PvpLobbyResolvedends withuint256 tankSpentand no longer carriesfeeRecipient, andPvpLobbyTankPaidreports 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
Section titled “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
Section titled “2026.09.30-2”- Creation with a first entry.
PvpHostApiV2.createLobbyaccepts an optionalentry: { stake, entryData? }. With it the host opens the lobby and makes the creator’s entry in one transaction (the diamond’sopenAndEnterLobby) and resolves withcontributionIdbesidelobbyId. The entry obeys the same rules asenterLobby(game policy, invitee list); a rejected entry fails the whole call and leaves no lobby open. See §6.1 ofCHAIN_WTF_PVP_GAMES.md. - The bundled examples (
examples/bones,examples/rps) create and enter in one call; the simulator’sLocalPvpHostgained the same entry point.
Changed
Section titled “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
Section titled “Compatibility”- Additive:
entryis optional andcontributionIdis only present when it was passed. Guests that do not passentrysee the same result shape as before.
2026.09.30-1
Section titled “2026.09.30-1”- Gas tank figures on the snapshot.
LobbySnapshotgains optionaltankRemaining(what the lobby’s gas tank can still pay for participants’ moves; present while the lobby is in play) andtankSpent(what it has paid so far), both in the asset’s base units.LobbySettlementgains optionaltankSpentbesidefeeAmount, which stays the gross rake: winners sharepot - feeAmount, the fee recipients splitfeeAmount - tankSpent. See §4 and §6.5 ofCHAIN_WTF_PVP_GAMES.md. - The figures are informational. The relayer decides whether a move is sponsored; never gate a
move on
tankRemaining.
Changed
Section titled “Changed”- Two lobby events carry the tank.
PvpLobbyOpenedends with a newuint16 tankShareBps(the share of the rake that funds the lobby’s tank, fixed when the lobby opens) andPvpLobbyResolvedends with a newuint256 tankSpent. Their topic hashes change; every earlier field keeps its position.PvpLobbyTankPaid(lobbyId, payee, gasUsed, gasCharge, tankRemaining)reports each tank payment.
Compatibility
Section titled “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
Section titled “2026.09.17-2”- Operators (not open yet).
PvpGameFacetgainedopenLobby(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 emitsPvpLobbyOperatorAttributed(lobbyId, operator)and exposesgetLobbyOperator(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 ofCHAIN_WTF_PVP_GAMES.md. - The simulator’s
LocalPvpHostmirrors the overload, event and view. It pays the room operatoroperatorRakeShareBps(default 5%) of the rake, with permissionlesssetOperator,setDefaultOperatorandsetOperatorRakeShareBpsstand-ins for council configuration.
Compatibility
Section titled “Compatibility”- Games and guests need no change: the five-argument
openLobbystill works, the operator never reachesLobbyContextor the iframe, and the distributable pot is unchanged.
2026.09.17-1
Section titled “2026.09.17-1”Changed
Section titled “Changed”lobby.defaultStakein 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’sdecimals(parseUnits(defaultStake, asset.decimals)); the RPS example does this instate/stake.ts.getRandomnessVerificationfollows 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: theRandomnessFulfilledlog, the request echoed in the calldata (hashed back torequestId), and the enclave signature (EIP-712 domain version2). 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.checksarevrfProofValid,vrfBetaMatchesRandomness,enclaveSignatureValid,signerMatchesFulfiller;validrequires all four. Newrequestfield: the echoed router request (consumer,sequence,fulfiller,assignedAt,fee,gasPrice,callbackGasLimit,callbackData).
Compatibility
Section titled “Compatibility”- Manifests written with base-unit
defaultStakevalues 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
fastVerifyComponentsMatchcheck and theartifacts.uPoint/artifacts.vComponentshints. Verify views that render checks by name should drop that row;validkeeps its meaning.
npm installat the SDK root no longer fails withEBADDEVENGINES:package.jsondeclares npm as the dev package manager, matching the documented install path.npm run local-nodecrashed withverifyNetwork.depositLocalVrfClientBalance is not a function. The bundledlocal-verify-networkrenamed that helper todepositLocalVrfBalance(with anaccountargument) 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 failsnpm run check-typesinsimulator/instead of surfacing at runtime.- Every randomness step in the simulator reverted with
0x87c9fc34(Proxy__ImplementationIsNotContract()from the bundled router).LocalPvpHost.solstill called the previous router’srequestRandomness()and exposed a two-argumentonRandomnessFulfilled. It now callsrequestRandomness(bytes callbackData)with empty data and implements the three-argument callback, matching the production PvP randomness adapter andLocalCasinoHost.src/local-node/artifacts.tsis regenerated.
2026.09.16-4
Section titled “2026.09.16-4”Changed
Section titled “Changed”- The Solidity sources moved from
solidity/tosimulator/contracts/:IPvpGameV2.solandPvpLiveness.solsit next to the harness contracts, the reference games insimulator/contracts/examples/. Drop-in games import the interface as./IPvpGameV2.sol.
Compatibility
Section titled “Compatibility”- Games importing
../../solidity/IPvpGameV2.solor../../solidity/PvpLiveness.solkeep compiling in the simulator. Any import path ending in either file name resolves to the shared file. Contracts are unaffected.
2026.09.16-3
Section titled “2026.09.16-3”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=roomskeeps 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. SeeLOCAL_SIMULATOR.md.
- 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
joinLobbyaction 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
Section titled “2026.09.16-2”- 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 }withreasonone ofdisconnected,setup-required,session-key-mismatch,chain-mismatch,host-policy— read throughgetViewerAccess(snapshot), which derives it fromwallet.statuson older hosts. While spectating the host rejects every write withPVP_SPECTATOR_READ_ONLY(isSpectatorReadOnlyError) and, when it can lead the viewer to play, exposes the optionalhostApi.requestPlayAccess()that opens its sign-in or wallet-setup flow. Games declarecapabilities.spectate: truein the manifest; the Chain.wtf host shows a sign-in placeholder instead of the game to spectators of a game that does not. SeeCHAIN_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.roomIdinto any phase, so the host’s Watch and Replay actions open the lobby. simulator/sendsaccess, 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 toready. 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
Section titled “Changed”- A write attempted while the viewer cannot play now rejects with
PVP_SPECTATOR_READ_ONLYinstead ofPVP_WALLET_NOT_READY; the latter remains for aplayviewer 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
Section titled “Compatibility”access,requestPlayAccessandcapabilities.spectateare all optional. Games that ignore them behave as before for players; addcapabilities.spectateonly once the game renders without a wallet, because the host hides the iframe from spectators otherwise.
2026.09.16-1
Section titled “2026.09.16-1”- Liveness standard (optional).
solidity/PvpLiveness.soldefinesIPvpLiveness(ERC-165 id0xae5c86f9), aliveness(phase, config, gameState)view returning the lobby’s pending waits plus a capability bitmask, and four reservedactionDatapayloads (abi.encodePacked(tag, bytes32 subject)):SKIP0x021033a8,FORFEIT0xe04af859,RESIGN0x1fbe2408,ADVANCE0x46c0809d. 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 abstractPvpLivenessbase handles detection, phase gating, payload parsing and wait resolution;@chain/pvp-sdk/livenessexports the tags, ABI,encodeLivenessActionandgetDueWaits. SeePVP_CONTRACT_CONSTRAINTS.md. solidity/examples/RockPaperScissorsGame.sol— the simultaneous reference game: fixed stake, commit and reveal windows ofphaseSecondseach, first towinsNeededrounds 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_waitsOfso 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.
BonesGameimplements the standard:FORFEITtwo turn lengths after the deadline andRESIGNfrom 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
Section titled “Changed”BonesGameSKIPmoved fromabi.encode(uint8(2), uint8(0))to the reserved liveness payload;lastMove.kindis now 0 = ROLL, 1 = PLACE, 2 = FORFEIT, 3 = RESIGN.
Compatibility
Section titled “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
Section titled “2026.09.15-1”Changed
Section titled “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,getLobbyParticipantsandgetLobbyContributionsreflect a move roughly a block before the indexed feed delivers it.createLobbyandenterLobbyresolve 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. Seedocs/LOCAL_SIMULATOR.md.
Compatibility
Section titled “Compatibility”- No bridge or type changes. A lobby can now appear in
lobbies.itemsbefore the indexed feed has it, but games must keep tracking thelobbyIdreturned bycreateLobbyuntil it appears there — the pushed log is an accelerator, not a guarantee.
2026.09.14-1
Section titled “2026.09.14-1”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 minimalPvpGameFacetstand-in emitting byte-identical events — the referenceBonesGameandJackpotGame, 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. Seedocs/LOCAL_SIMULATOR.md.solidity/examples/BonesGame.sol— Chain Bones (Knucklebones) replacesPointDuelGameas the fixed-stake reference game: 1v1, per-roll VRF, turn deadlines with a permissionlessSKIP, 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:3200and wired intonpm startat the SDK root.
Removed
Section titled “Removed”PointDuelGame.soland theexamples/point-duelguest.
2026.09.11-1
Section titled “2026.09.11-1”-
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
Section titled “Compatibility”- Additive and optional: older hosts omit both fields, and games must treat absence as unknown.
2026.09.03-1
Section titled “2026.09.03-1”- Added optional
inviteesto thePvpHostApiV2.createLobbyinput. A non-empty list makes the lobby invite-only: the protocol rejectsenterLobbyfrom 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
inviteestoLobbySnapshot— present only on invite-only lobbies; absent on public lobbies and on older hosts. Guests derive “am I invited” by comparing againstsnapshot.wallet.address. - No manifest schema change: a game whose UI creates invite-only lobbies should declare
lobby.access: 'game-defined'.
Compatibility
Section titled “Compatibility”- The
createLobbyinput change is not additive-optional for hosts (the underlyingopenLobbygained a parameter), but it ships before the first PvP V2 deployment. Games running against older hosts must omitinvitees.
2026.08.31-1
Section titled “2026.08.31-1”-
Added per-player claim state to
LobbyParticipant(so it appears onlobby.viewerand on rows fromgetLobbyParticipants):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
claimWinningstransaction it already sent — the settlement alone never said whether the share was collected.
Compatibility
Section titled “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
Section titled “2026.08.30-1”- 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.claimPayoutdeclaring 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
Section titled “Compatibility”- Additive and optional: feature-detect
host.claimPayoutbefore calling — older hosts omit it, and manifests withoutcapabilities.claimPayoutremain valid.
2026.08.20-1
Section titled “2026.08.20-1”Changed
Section titled “Changed”- Breaking (contract interface):
PvpStepResultnow returnsaddress[] recipientson 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 viagetClaim. The oldImmediatePayoutamounts arrays andMAX_IMMEDIATE_RECIPIENTSare 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 emitsPvpLobbyPayoutDeferredwhen this happens. ImmediatePayoutSnapshotis now{ winner, amount, deferred? }.
2026.07.26-1
Section titled “2026.07.26-1”- 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 thegetRandomnessVerificationresult 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
getRandomnessVerificationverdict as the primary fairness signal and treat/tx/links as secondary. SeeRANDOMNESS_VERIFICATION.md.
Compatibility
Section titled “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
Section titled “2026.07.20-1”- Added
raw.randomnessRequeststo 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.requestIdkeep 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. SeeRANDOMNESS_VERIFICATION.mdfor the full contract and rendering rules.
Compatibility
Section titled “Compatibility”- Additive and optional: feature-detect
host.getRandomnessVerificationbefore calling; older hosts also omitraw.randomnessRequests. On environments without Verify Network (local dev) the method resolves withsupported: false.
2026.07.19-1
Section titled “2026.07.19-1”- 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 toavailableHeightminus the action’s own height. The value is measured against the small viewport (svhsemantics), 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
100vhequals the full content height, not the visible screen.
Compatibility
Section titled “Compatibility”- Additive and optional: games running against older hosts simply receive no
ui.viewportand should fall back to their existing layout.
2026.07.18-1
Section titled “2026.07.18-1”- Added optional
iconUrltoPvpAssetBalanceandLobbySnapshot.asset— an absolute URL of the token’s icon so games can render it next to balances and stakes.
Compatibility
Section titled “Compatibility”- Additive and optional: games running against older hosts simply receive no
iconUrland should fall back to a text symbol.
2026.07.10-1
Section titled “2026.07.10-1”Breaking
Section titled “Breaking”- Replaced API v1’s fixed buy-in, unique seat, creator privilege,
uint8player cap, and fullplayers[]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
IPvpGameV1withIPvpGameV2and added a scheduled weighted Jackpot reference game.
2026.07.03-1
Section titled “2026.07.03-1”- 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)andobserveGameContentSize(hostApi)helpers in@chain/pvp-sdk/guest. Games can use the observer to report initial height and subsequent layout changes without wiring their ownResizeObserver.
Changed
Section titled “Changed”- Removed static
presentation.minHeightfrom PvP game manifest validation and examples. Height is now a runtime concern reported by the child iframe whencapabilities.resizeis supported.
Compatibility
Section titled “Compatibility”- Dynamic sizing is additive: older games that do not call
reportContentSizecontinue to render using the host’s existing frame fallback. reportContentSizeis optional on the host API so games can safely run against older hosts by checking for the method or usingobserveGameContentSize, which no-ops when unsupported.
2026.06.28-1
Section titled “2026.06.28-1”- Added host-to-game metadata support to
PvpHostSnapshotV1. - Added
PvpHostSnapshotV1.metadatafor parent-app context shared with the iframe, including:viewerprofile metadata for the connected viewer.roommetadata for room id, slug, title, invite URL, and participants/spectators.customJSON-serializable host data for presentation-only features.
- Added
LobbySnapshot.metadatafor lobby/table labels, invite URLs, and custom presentation data. - Added
LobbyPlayer.metadatafor seated-player profile data such asuserId,username,displayName,avatarUrl, andprofileUrl. - Added JSON-safe metadata helper types:
PvpMetadataPrimitivePvpMetadataValuePvpMetadataBag
- Added structured metadata types:
PvpPlayerMetadataV1PvpRoomParticipantMetadataV1PvpRoomMetadataV1PvpHostMetadataV1PvpLobbyMetadataV1
- Re-exported the metadata types from
@chain/pvp-sdk/hostand@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
Section titled “Compatibility”- The metadata fields are optional and backward-compatible with existing hosts and games.