# Building two-player token games on arcade.fun

**Proposed integration contract, 2026-10-25. Not a live feature.** The APIs,
room service and escrow below are not implemented. There is no `arcade.match`
SDK API. The draft configuration is not read by the uploader.

Start at the [creator handbook](../developers.html). Ship a free game today,
keep simulation separate from presentation, and prepare a competitive adapter.

## Player experience and economics

1. Choose free practice or a two-human-player token match.
2. Pick an exact stake tier in the game's token. Queue or invite a specific
   opponent. Queueing does not move tokens.
3. Both players accept identical terms in platform UI and fund the reserved
   match through their wallets. Show network and mint, not just a ticker.
4. Start after both stakes are finalized and both players are ready.
5. The authoritative server decides the result from inputs and pinned rules.
6. A win burns **5% of the combined pool** and pays **95% to the winner**.
   A draw or void returns each deposited stake in full, with **no burn**.
7. Show a receipt with result, pool, burn, payout/refund and transaction.

100 tokens each -> 200 pool -> 10 burned -> 190 to the winner. The winner's
net gain is 90; the loser loses their 100 stake. SOL network fees/account rent
are separately disclosed. There is no platform/creator cut from the pool and no
automatic rematch charge. Market trading fees are a separate mechanism.

## Available now

- Reviewed, versioned browser builds through [Add your game](../make.html).
- SDK voice/camera readings, `ready`, `on`, `score`, `gameOver`, token information,
  wallet connection and optional creator purchases through `arcade.pay`.
- The [test bench](../make.html), [starter](starter.html),
  [design brief](game-design-template.md), [draft match template](match-template.json)
  and [exact stake calculator](../developers.html#pool).

**`arcade.pay()` sends purchases to the creator. It is not a stake deposit.** It
has no opponent escrow, refund, winner payout or burn. `arcade.score()` and
`arcade.gameOver()` are untrusted client messages, not proof of a result.
Recorded leaderboard submissions require human review and do not settle wagers.

## A verifiable game architecture

| Layer | Creator supplies | Platform supplies (planned) |
| --- | --- | --- |
| Simulation | Initial state, input schema, validation, fixed-step rules | Authoritative room, clocks, limits, replay |
| Presentation | Graphics, sound, controls, interpolation, accessibility | SDK readings, sandbox, lifecycle |
| Match economy | Requested modes/stake tiers for review | Eligibility, pairing, funding UI, escrow, burn, payouts, receipts |

These are **conceptual functions to implement in your own project**, not an
installed library or current SDK API:

```js
createState({ seed, rules })                  // reproducible initial state
validateInput({ state, seat, input, tick })    // bounded, legal actions
step({ state, inputsBySeat, tick })           // one fixed simulation step
snapshot(state)                              // versioned reconnect state
result(state)                                // null, A wins, B wins or draw
```

Define legal action rates, duration, win/tie rules, seats, timers, disconnect
grace and finality. Use server ticks, not player wall clocks or rendering rate.
Seed randomness on the server after rules are committed, and record replay
evidence. Use integers/fixed point where floating point would diverge. Never
accept a client-supplied winning score or player-selected seed.

Keep hidden information server-side. A seed shared with players must not reveal
future cards/spawns or secret opponent state. Those games need server-only
randomness and an auditable event log. Simultaneous secret-choice games may use
a reviewed commit/reveal design with explicit withholding/timeout rules.

Voice levels, face expressions and hand positions come from the player's browser
and can be fabricated. Server authority cannot prove a human shouted or moved.
Sensor-only competitions need a reviewed validation scheme before real stakes.
Today's Shout-Off and Hand Pong are same-screen games, not online staked rooms.

## Draft match configuration and queues

Download [match-template.json](match-template.json) into your planning folder.
Replace game, build, rules and token placeholders before review. It is not an
automatic deployment manifest. Approval must pin a client build and a trusted
server adapter; an uploaded zip must never run arbitrary server code.

Immutable queue key:

```text
gameId + buildHash + rulesHash + mode + cluster + mint + tokenProgram
+ stakeRaw + region + inputClass
```

Skill and latency windows may widen with waiting time; stake, token, version,
rules and input class may not. Atomically reserve two distinct wallets and allow
one active ticket per wallet. Distinct wallets do not prove distinct people;
staked modes still need reviewed anti-collusion controls. Use a few stake tiers
to avoid fragmenting the player pool. Offer practice/wait/cancel when no human
opponent is available; never silently substitute a bot for stakes.

## Proposed protocol (not available endpoints)

| Proposed request | Purpose | Requirements |
| --- | --- | --- |
| `POST /api/matches/queue` | Enter a queue | Authenticated wallet, accepted terms, idempotency key |
| `DELETE /api/matches/queue/{ticket}` | Leave | Atomic with pairing; return match if already reserved |
| `POST /api/matches/invites` | Private challenge | Intended opponent, exact terms, expiry |
| `GET /api/matches/{id}` | Recover state | Membership checks, private-input redaction |
| `WS /api/matches/{id}/play` | Ready, inputs, snapshots | Short-lived session, sequence/tick validation, rate limits |
| `GET /api/matches/{id}/receipt` | Inspect settlement | Raw amounts, replay digest, transaction signature |

Deposits, settlement and refunds are **on-chain instructions**, not payments
to an HTTP endpoint. The parent platform owns wallet prompts and verifies
network, program, mint, match, amounts and recipients. A signed, single-use,
expiring challenge must prove wallet control, bound to the domain, network,
session and action; connecting a wallet is not authentication.

Proposed events: `matched`, `funding`, `funded`, `ready`, `started`, `snapshot`,
`reconnecting`, `result`, `settlementPending`, `settled`, `refundAvailable`,
`refunded`, `cancelled`, `error`. Each carries match ID, monotonic revision and
protocol version. Input messages carry seat, sequence and target tick. Reject
duplicates, illegal actions, out-of-window input and oversized messages.
Reconnect uses a snapshot/last acknowledged sequence, never a second deposit.

A future versioned parent/frame bridge should expose only approved match actions.
SDK games currently cannot call an external match server under their CSP. Keep
the sandbox; do not give game code wallet access or a settlement key.

## State and recovery

```text
queued -> reserved -> funding -> funded -> ready -> running
running -> verifying -> settlementPending -> settled

queued/reserved (no deposits) -> cancelled
funding/funded/ready (never started) -> refundAvailable -> refunded
running/verifying (unresolved at deadline) -> refundAvailable -> refunded
verified draw/void -> refundAvailable -> refunded
```

A transaction/network failure is retryable state, not a payout/refund. Monetary
completion requires finalized chain state. Exactly one terminal monetary outcome
is allowed. Persist both room and chain state; reconcile after deploy or crash.

| Situation | Required outcome |
| --- | --- |
| No opponent / queue cancelled | No deposit, no burn |
| One stake arrives before funding expiry | Full refund of that deposit; no burn |
| Wallet rejects signing | No play; refund any other deposit at deadline |
| Both funded; ready deadline expires | Full refunds; no burn |
| Brief disconnect after start | Keep room; allow published reconnect grace |
| Player abandons after grace | Server-proven forfeit under accepted rules: 95% payout, 5% burn |
| Platform outage / unverifiable result | Void/full refunds; no burn; never guess a winner |
| Draw | Full refunds; no burn |
| Players disagree with a browser score | Authoritative rules/replay decide; client reports cannot settle |
| Verified result; transaction/RPC fails | Persist result and retry until on-chain deadline; never double-pay |
| No authorized settlement by expiry | Permissionless timeout refund; no burn |
| Duplicate result / stale session or build | Reject; preserve existing match and funds |

Disclose bounded funding, ready, reconnect and settlement deadlines before any
deposit. The template uses 120 s funding, 30 s ready, 15 s reconnect grace and
a 10-minute unresolved deadline from the on-chain start. These are planning
defaults, not guarantees. Max game duration must leave time for verification and
settlement. Settle/refund must share an exclusive on-chain deadline boundary.

## Exact economics and receipts

Cross JSON boundaries with decimal **strings of base units**, not floating-point
numbers or serialized BigInt. For a 6-decimal mint, 100 tokens is `"100000000"`.
Do not use dollar prices or auto-convert to SOL to size an escrow. Require stakes
divisible by 10 base units so the pool is divisible by 20 and the burn is exact.

```js
// Available standalone quote helper; not part of window.arcade.
// Load a local copy of stake-math.js before your script.
const stakeRaw = ArcadeStakeMath.parseAmount('100', 6);
const quote = ArcadeStakeMath.quote(stakeRaw);
// poolRaw: 200000000n; burnRaw: 10000000n; winnerRaw: 190000000n
```

This helper cannot authorize funds. The program independently checks arithmetic,
deposits, accounts and authority. Real burning uses the token program's
[BurnChecked](https://solana.com/docs/tokens/basics/burn-tokens) to reduce supply.
The burn and payout must settle atomically; neither can complete alone.

A receipt identifies network, escrow program, match, approved build/rules, mint,
decimals, players, outcome, pool, burned amount, payout/refunds, signature,
finality and replay digest. Exclude session secrets, personal eligibility data
and hidden in-progress match information.

## Acceptance tests before a staked mode ships

- Identical inputs/seed/rules replay to one result. Fabricated scores, modified
  clients, illegal inputs and unexpected tick rates cannot win.
- Both players accept the same quote. Cancellation races, duplicate messages
  and multiple workers cannot reserve one player twice.
- Reject wrong mint/program/network, reused deposits, wrong signer, self-match,
  unsupported token extensions and altered builds.
- 100 + 100 yields 10 burned and 190 paid; supported small stakes are exact;
  nonrepresentable amounts and u64 overflow are rejected.
- Payout plus burn equals the pool. Draws, voids, partial funding and unresolved
  timeouts burn nothing.
- Duplicate settlement, settle/refund races and replayed results cannot spend
  twice. The verifier cannot change the burn or pay another wallet.
- Tab closure, reconnect, worker restart, deploy and RPC outage preserve state
  and the refund path. Burn/payout failure reverts settlement state changes.
- Receipts stay pending until finality; reconciliation repairs interrupted
  indexing. Refunds remain callable without a working arcade server.
- Test free mode, then devnet end to end, then external security review and the
  platform's required eligibility/jurisdiction review before real tokens.

Creators build the game; reusable platform services should handle pairing,
consent, deposits, verification, burns, payouts and recovery.
