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.

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 never run a server. Everything else is platform plumbing you do not write.

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.

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 — outright. One operator flag, GAMES_SAME_ORIGIN_FIRST_PARTY=1, declares every hosted bundle first-party and lifts both refusals, and both public planes run with it: arcade.xaya.io and test-arcade.xaya.io serve every game from their own hostname under /g/<slug>/. On such a plane a bundle can reach the shell's realm, which is why the arcade lists only games whose repository has been read — and why an attached bundle on the playground shares that plane's localStorage with its shell and with every other attached bundle: write under your own storagePrefix and nowhere else.

    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. A game is playable for free without it. Payouts are pooled by stake across the whole arcade: a 10-WCHI win in any game joins one 10-WCHI payout queue and is paid, when it reaches the front, by a later 10-WCHI match in any game — so picking a different game no longer means a different queue. The first three paid matches at a stake and player count nobody has played yet pay their pot to the operator — a one-time fee that gets that stake's payouts started.

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.
  • Submissions are closed during the curated phase. arcade.xaya.io takes no uploads: its submissions service is switched off, Submit is hidden and /api/submissions answers 503. The five first-party games there were registered by the operator. Prove your game on the playground — upload at test-arcade.xaya.io/attach, which wipes every Monday 04:10 UTC — then get in touch in #builders on the Xaya Discord with your repository and your four registration values. /repos says what every repository is and whether you can clone it today, and wager enablement stays a manual operator step.

Two planes, and you should use both

There are two of these stacks, running the same code:

arcade.xaya.iothe real one, in mainnet beta. Polygon, real wallets and real names, five first-party games with WCHI wagering (free / 1 WCHI / 10 WCHI). Curated for now: the moderated submission queue is closed — Submit is hidden and /api/submissions answers 503. Acceptance here is permanent.
test-arcade.xaya.ioa disposable playground on its own forked chain. Pre-funded test wallets, uploads at /attach auto-accept, wiped every Monday around 04:10 UTC and without notice in between, reserves nothing.

The playground runs the same pre-flight, the same bake and the same go-live pipeline — that identity is the entire point of it. So the honest order is: build and test offline for free, point your dev server at the playground, dry-run the real submission there and play a full match, and only then hand the same bytes to the operator (#builders in Discord). It is the difference between finding out your bundle will not boot in ten seconds and finding out after the hand-over.

Where to go next

  1. What you need — the repos, the tooling, and what costs money (almost nothing).
  2. Getting started — clone, run, and point it at the playground.
  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 the playground does and does not prove.
  6. Submitting — the dry run, then the real thing.

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