# App-only & offline

The single most important thing to understand about the SDK:

> **Coins only work inside the Gamma Games app.** That's where the host injects the
> real, server-authoritative bridge. Outside the app — your own website, another
> portal, a plain dev browser — the SDK is **inert** by default.

## What "inert" means

When the game runs outside the Gamma Games app and the [dev mock](./dev-mock.md)
is not enabled:

| Call | Result outside the app |
|---|---|
| `getCoins()` | `0` |
| `spendCoins(...)` | `{ok: false, error: 'no_transport'}` |
| `earnCoins(...)` | `{ok: false, error: 'no_transport'}` |
| `getCoinValue()` | default value (display constant) |
| `getPlayer()` | `null` |
| `gamma.storage.*` | **works** — falls back to `localStorage` |

Nothing can fake or spend real coins outside the app, by design.

## Always gate on `isInApp`

After `gamma.ready()`, check `gamma.isInApp`:

```js
await gamma.ready();

if (gamma.isInApp) {
  const res = await gamma.spendCoins(50, 'extra_life');
  if (res.ok) grantExtraLife();
} else {
  // Running outside Gamma Games — use your own offline behaviour
  // (e.g. let the player continue for free, or hide the coin shop).
}
```

`gamma.storage` keeps working everywhere, so progress/saves are unaffected — only
the coin economy is app-gated.

## Why it's built this way

- The coin **balance lives on the server**; the game can only *ask* to spend, and
  the server validates against the authenticated user.
- The host supplies the user identity — the game never sends it and can't spoof it.
- Outside the app there's no authenticated session, so there's nothing to spend.

See [Limits & security](../limits-and-security.md) for the full model.
