> ## Documentation Index
> Fetch the complete documentation index at: https://docs.upside.win/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing & Examples

Test a local iframe game on Upside, verify the bridge, and debug common integration failures before launch.

# Testing & Examples

Upside includes a local game loader so you can embed a localhost game in the real parent shell before the game is approved for production discovery.



## Local Iframe Testing

The test route is:

```text
https://upside.win/test/games/{BASE64_URL}
```

`{BASE64_URL}` is your game URL encoded as base64.

**Start your game**

```bash
pnpm dev
# game frontend available at http://localhost:3000
```
  **Encode the URL**

```bash
node -e 'console.log(Buffer.from("http://localhost:3000").toString("base64"))'
```
  **Open the Upside test URL**

```text
https://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDozMDAw
```
  **Send ready from the iframe**

Your game should call `window.parent.postMessage({ type: "ready" }, "https://upside.win")` after its message listener is mounted.
  **Play a complete round**

Verify `placeBet`, backend game logic, `processPayout`, parent modal, balance refresh, and match-history refresh.

## Encoding Examples

| Game URL | Base64 | Test URL |
| --- | --- | --- |
| `http://localhost:3000` | `aHR0cDovL2xvY2FsaG9zdDozMDAw` | `https://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDozMDAw` |
| `http://localhost:5000` | `aHR0cDovL2xvY2FsaG9zdDo1MDAw` | `https://upside.win/test/games/aHR0cDovL2xvY2FsaG9zdDo1MDAw` |
| `http://127.0.0.1:3000` | `aHR0cDovLzEyNy4wLjAuMTozMDAw` | `https://upside.win/test/games/aHR0cDovLzEyNy4wLjAuMTozMDAw` |
| `https://example.com/game` | `aHR0cHM6Ly9leGFtcGxlLmNvbS9nYW1l` | `https://upside.win/test/games/aHR0cHM6Ly9leGFtcGxlLmNvbS9nYW1l` |

## Manual Bridge Test

Add temporary logging during local development.

```ts
window.addEventListener("message", event => {
  console.log("[upside message]", event.origin, event.data);
});

window.parent.postMessage({ type: "ready" }, "https://upside.win");
```

You should see a parent message containing `balance`, `token`, and `locale`. If you do not, verify that:

- The iframe route actually loaded your game URL.
- Your game sent `ready` after registering the listener.
- Your browser did not block localhost networking.
- Your message handler is checking `https://upside.win`, not your localhost origin.

## Backend Smoke Test

Before wiring full gameplay, add a temporary authenticated route that calls `checkAuth` and `getBalance`.

```ts
app.post("/api/upside-smoke-test", async c => {
  const b3Jwt = c.req.header("Authorization")?.replace(/^Bearer\s+/i, "");
  if (!b3Jwt) return c.json({ error: "Missing token" }, 401);

  const auth = await upsideGameAction(c.env, b3Jwt, { trigger: "checkAuth" });
  const balance = await upsideGameAction(c.env, b3Jwt, { trigger: "getBalance" });

  return c.json({ auth, balance });
});
```

Call it from your iframe with the token received from Upside:

```ts
await fetch("/api/upside-smoke-test", {
  method: "POST",
  headers: { Authorization: `Bearer ${token}` },
});
```

## Complete Coin Flip Payloads

### placeBet

```json
{
  "trigger": "placeBet",
  "gameType": "coin-flip",
  "sessionId": "coin-flip-2026-05-09T20-15-30Z-01",
  "gameId": "round-01",
  "betAmount": "1000000000000000000"
}
```

### processPayout Win

```json
{
  "trigger": "processPayout",
  "gameType": "coin-flip",
  "sessionId": "coin-flip-2026-05-09T20-15-30Z-01",
  "payoutAmount": "2000000000000000000",
  "result": "win",
  "gameData": {
    "prediction": "heads",
    "result": "heads"
  }
}
```

### processPayout Loss

```json
{
  "trigger": "processPayout",
  "gameType": "coin-flip",
  "sessionId": "coin-flip-2026-05-09T20-15-30Z-01",
  "payoutAmount": "0",
  "result": "loss",
  "gameData": {
    "prediction": "heads",
    "result": "tails"
  }
}
```

## Troubleshooting

**The game route shows Game not found**

Production `/games/{slug}` only works for approved games with an active record and `workerUrl`. Use `/test/games/{BASE64_URL}` until the game is registered.
  **The iframe never receives a token**

Confirm your game posts `{ type: "ready" }` after mounting, listens for messages before posting `ready`, and accepts `event.origin === "https://upside.win"`.
  **Signature is invalid**

Check the exact prehash: ISO timestamp, uppercase `POST`, `/upside/game-actions`, the raw JSON body string, and the same `B3-JWT` header value. Also confirm server time is accurate.
  **Bet amount exceeds maximum**

The service caps a bet at a percentage of the current game pool. Call `getMaxBet` before rendering high-stakes options.
  **Insufficient balance**

Display the balance from the parent snapshot and disable bets above the current balance. Also handle this error server-side because balance can change between render and click.
  **Payout already processed**

Treat this as a reconciliation path, not as a second payout opportunity. Fetch history and return the already settled result to the player.
  **CORS fails in local testing**

If frontend and backend are on different origins, allow your iframe origin. Do not open your backend to arbitrary origins in production.

## Pre-Launch Review

**[Protocol Review](/upside/iframe-protocol)**

Verify the exact parent-child message behavior.
  **[Security Review](/upside/security-and-ops)**

Check auth, idempotency, CORS, logging, and fairness requirements.