# Coins

Gamma Coins are the player's real, server-authoritative balance. The SDK never
trusts the client: every spend/earn is validated and applied on the server.

## Read the balance

```js
const balance = await gamma.getCoins(); // number (0 outside the app)
```

Cheap to call — read it whenever you need the current value rather than caching.

## Spend coins

```js
const res = await gamma.spendCoins(50, 'extra_life');
if (res.ok) {
  grantExtraLife();           // only now
} else {
  showError(res.error);       // e.g. 'insufficient_coins'
}
```

- `amount` — integer, `1..10000`
- `reason` — lowercase `[a-z0-9_]+`, ≤ 64 chars (e.g. `extra_life`, `power_up_shield`)

Result shape:

```ts
{ ok: boolean; newBalance?: number; spent?: number; balance?: number; error?: string }
```

> **Never grant the reward before the promise resolves with `ok: true`.** The
> operation is atomic and server-authoritative — assume nothing until you get the
> result.

## Earn coins

Award coins for an in-game accomplishment:

```js
const res = await gamma.earnCoins(5, 'checkpoint');
if (res.ok) showToast('+5 coins!');
```

- `amount` — integer, `1..100` per call
- The server enforces a hard cap of **2000 coins earned from all games per user
  per hour**; excess returns `{ok: false, error: 'rate_limited'}`.

Result shape:

```ts
{ ok: boolean; newBalance?: number; earned?: number; error?: string }
```

## Use semantic reasons

The `reason` is stored in the coin audit log. Use descriptive identifiers so spend
patterns can be analysed:

- ✅ `extra_life`, `power_up_shield`, `skin_dragon`, `revive`
- ❌ `buy`, `x`, `item1`

## Outside the app

`getCoins()` returns `0` and `spendCoins`/`earnCoins` return
`{ok: false, error: 'no_transport'}` unless you enable the
[dev mock](./dev-mock.md). Always keep an offline path — see
[App-only & offline](./app-only-and-offline.md).

See also the full [API reference](../api-reference.md) and [error codes](../error-codes.md).
