Playground: disposable chain, test WCHI, wiped without warning
Builder docs

Overview

What the Xaya Arcade is, how the pieces fit together, and the rules of the road for a game.

The Xaya Arcade is a shared platform for provably-fair, real-time games. One game-agnostic host runs the rules for many games at once, so building a game here is not building a blockchain backend: it is writing rules and a UI, and handing over two files.

Relays and peer-to-peer play

We host a relay server for convenience: it forwards signed channel messages between players. It does not decide who wins. Clients verify the messages against the game rules, and disputes can be resolved on-chain.

You can run your own relay or host your own Arcade. The relay addresses are deployment configuration: two players reach each other through the same relay, or through relays linked to forward frames between each other. The referee's and games-host's source is in arcade-platform; Operating lists every component and its repository.

Game channels do not require our relay: their signed messages can also travel over a direct peer-to-peer transport. The current Arcade client uses WebSockets through a relay; direct browser-to-browser play would require an additional transport implementation and is not a selectable mode today.

Skill wagers and payouts

Some games offer optional WCHI stakes on matches against other players. You can lose your stake. Only wager if it is legal where you live and play and you meet the applicable age requirements. Free play is available. The label "skill wager" describes the feature; it is not a determination of its legal status in any jurisdiction.

Stakes are held by the wagering contract. Payouts use a shared queue across games at the same stake and player count. The first three paid matches in a new stake/player-count pool pay their pots to the operator to initialise that pool. A queued payout has no deadline and no on-chain claim: each paid match that starts in the pool pays the payout at the front of the queue, so a win waits until every payout queued ahead of it is paid, which can take several matches, and in a pool nobody plays again it waits indefinitely. Player-to-player describes the competition, not a direct transfer of your opponent's stake or a guarantee of immediate payment.

Wagering capacity is shared at each stake and player count. When all slots are busy, the game disables staking and checks automatically for availability. You can wait, play free, or choose another stake. Waiting tables do not reserve a slot: another match can use it before yours fills. A creator can cancel an unfilled table to refund its stakes, or update its payout snapshot once capacity returns. Availability is checked again before signing, but a transaction can still lose a race and revert, costing gas.

What a game is

A game is exactly two artifacts, and only two:

  • A rules.wasm blob: a freestanding, zero-import WebAssembly reactor that exports the arcade_* ABI. It is the deterministic judge: it decides which moves are legal, whose turn it is, and who won. The chain knows it only by a sha256 of its raw bytes, so "which rules is the chain running?" always has a one-word answer. See The rules blob.
  • A UI bundle: a Next app built on @xayaarcade/sdk, plugged in through a single GameAdapter. The SDK owns the channel protocol, the wallet, the lobby, the relay, and disputes; your game owns its rules, its renderer, and its input. See The SDK.

You reuse the platform services. Build and test locally; the operator hosts approved games.

One consequence of the shared games origin is yours to handle. Games are served from a games origin of their own: a hostname separate from the arcade shell, so nothing your bundle does can reach the shell's page or storage, but every game shares that one origin under a different path. So a root-absolute URL (fetch('/data.json'), <img src="/sprite.png">, url(/bg.png)) resolves to the games origin's root rather than your game, and your localStorage namespace is shared with every other game. Keep asset references relative, and set STORAGE_PREFIX to your own game's key. Make it yours covers both. The public playground gives each game a hostname of its own, so a storage clash may not show there; the arcade's games origin is shared, so set the prefix anyway.

How the pieces fit

browser
  │ loads your UI bundle (served by the games-host)
  ▼
your frontend ──ws──► relay ──► host GSP ──loads──► your rules.wasm
   (SDK)                          │  (the game-agnostic referee)
                                  ▼
                                xayax ──► Polygon
  • Shell (xaya-arcade): the arcade site: login, the game library, the lobby, and the iframe that embeds a game. This is the app you are reading.

  • games-host: serves every registered UI bundle from one games origin, each mounted at its own path /g/<slug>/, and hands each one its endpoints (gspUrl, relayUrl, chainRpcUrl, arcadeOrigin, gameId) via <basePath>/arcade-config.json at serve time. By default that origin is a hostname of its own: the host refuses to start when it equals the shell's, and the shell refuses to frame a bundle served from its own origin, because an iframe on the same origin shares the embedder's realm: its page, wallet plumbing and stored credentials. The public arcade and internal test deployment use separate shell and games origins. GAMES_SAME_ORIGIN_FIRST_PARTY=1 is an optional override for fully trusted bundles; these deployments leave it disabled.

    Two URLs, and they are not the same thing. /g/<slug>/ is where the bundle is mounted: the iframe's src. /play/<slug> is the page a player visits, which is the shell wrapping that frame. You build against the first and link people to the second.

    Your bundle is slug-agnostic: ./scripts/build-export.sh --bundle bakes the fixed placeholder /__arcade_base__ and refuses a caller who pre-sets NEXT_PUBLIC_BASE_PATH to anything else (scripts/build-export.sh: the mistake is cheap before the build and expensive after). The platform rewrites that placeholder to /g/<slug> at registration, so the same bytes can be served under any slug with no rebuild, and a bundle whose index.html carries no placeholder is rejected at pre-flight rather than silently served broken.

  • Host GSP: the referee. It contains no game code; it loads whichever rules.wasm a channel's game type is pinned to and calls the arcade_* ABI.

  • relay: the WebSocket transport players exchange signed off-chain states over.

  • xayax → Polygon: the base chain the channel opens, closes, and settles on.

  • ArcadeWager contract: optional, operator-enabled wagering, with the shared payout queue described above. A game is playable for free without it.

The rules of the road

  • Free play first. Every game is playable with no stake. Wagering is a separate, optional, operator-enabled module: your game code stays wager-free. See Hosting & registration.
  • The shell is the only signer. When your game is embedded in the arcade it holds no wallet. The shell owns the signature and brokers every move to your frontend over a postMessage bridge. Never write wallet integration into a game.
  • Determinism is the whole game. The same blob must produce the same result on native C++, on wasmtime (the host), and in V8 (the browser judge). The template ships a three-way determinism gate you keep. See Testing.
  • Submit through a GitHub issue. Send the tested repository commit, compiled WASM hash, game details and test results. We rebuild, review and test privately before listing a game. Submitting explains the form. Testing on a playground first is optional.

Public arcade and developer playgrounds

arcade.xaya.io is the curated Polygon mainnet beta. It uses real wallets, names and WCHI.

test-arcade.xaya.io is the public playground: a disposable fork with test wallets, where you can attach your own game and play it before you submit (Testing). The substrate it is built on, forked-evm-testing, forks a real chain locally for tests of your own. Both are optional and separate from submitting a game.

Where to go next

  1. What you need: the repos, the tooling, and what costs money (almost nothing).
  2. Getting started: clone, run and test the template.
  3. Make it yours: turn the template into your game.
  4. The rules blob and The SDK: the two things you actually write.
  5. Testing: the ladder, and what a playground does and does not prove.
  6. Submitting: what you send, and the GitHub submission that carries it.

Driving an AI agent? /docs/ai-agent is the literal opening prompt, and /skills hands you the two agent skills: one button, a zip you unzip into .claude/skills/.