> ## Documentation Index
> Fetch the complete documentation index at: https://hedgeem-api.qeetoto.com/llms.txt
> Use this file to discover all available pages before exploring further.

# F102 — Two-Balance Wallet

> Casino-style dual balance: account wallet vs seat chips. Implemented in HEDGE-187.

# F102 — Two-Balance Wallet System

|            |                             |
| ---------- | --------------------------- |
| **Phase**  | 5 — Platform                |
| **Jira**   | HEDGE-187                   |
| **Status** | TypeScript ✅ · UMA ⬜ · V7 ⬜ |

***

## Business Rule

Every registered player holds two separate balances:

| Balance             | Location                        | Description                                                                                       |
| ------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------- |
| **Account balance** | `wallets.balance` (Supabase)    | Money held by the platform. Shown in the lobby header and cashier page. Persists across sessions. |
| **Seat balance**    | `game_table_state.seat_balance` | Chips in front of the player at a specific table. Exists only while seated.                       |

**Flow:**

1. Player sits at a table → buy-in amount deducted from account, credited as seat chips
2. Player tops up during a session → amount deducted from account, added to seat chips
3. Player presses **Back to Lobby** → all remaining seat chips automatically credited back to account
4. If player loses all chips mid-session → can top up from account without leaving the table

**Welcome balance:** New players receive a **£1,000** welcome balance on first login (Google OAuth).

***

## Technical Design

### Database

```sql theme={null}
-- Account balance — persists across sessions
public.wallets.balance          bigint  -- pence, non-negative
public.wallets.welcome_balance_granted  boolean  -- prevents double-crediting

-- Seat balance — per (table, player) while in a game
public.game_table_state.seat_balance   bigint  -- pence, non-negative
```

### API endpoints

| Endpoint                     | Effect on balances                                                                                                                       |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/players/:playerId` | Returns `accountBalance` from `wallets.balance`. Auto-creates wallet with £1,000 on first login.                                         |
| `POST /api/tables/:id/sit`   | Deducts `buyInAmount` from wallet. Sets `seat_balance`. Returns NACK if wallet insufficient.                                             |
| `POST /api/tables/:id/topup` | Deducts `amount` from wallet. Adds to `seat_balance`. Returns NACK if wallet insufficient. Response always includes `newAccountBalance`. |
| `POST /api/tables/:id/leave` | Credits `seat_balance` → wallet. Resets game state to `STATUS_START`. Idempotent — safe if seat is already empty.                        |

### Client integration (game-shell.html)

The compliance bar's **Lobby** button is intercepted:

```
click → POST /api/tables/:tableId/leave (auth'd only)
      → update localStorage session.balance = newAccountBalance * 100
      → navigate to /lobby.html
```

The `tableId` is extracted from the game iframe URL (`?tableId=N`). If the player is anonymous or no `tableId` is present, navigation proceeds immediately without calling leave.

### localStorage session shape

```json theme={null}
{
  "playerId": "uuid",
  "displayName": "Simon",
  "balance": 100000,
  "access_token": "...",
  "role": "BASIC_USER"
}
```

`balance` is stored in **pence** (integer). `£1,000 = 100000`. Updated after leave and after `fetchPlayerProfile()`.

***

## Acceptance Criteria

| #    | Criterion                                                         | Test                                                                      |
| ---- | ----------------------------------------------------------------- | ------------------------------------------------------------------------- |
| AC1  | After OAuth login, lobby header shows account balance from server | `GET /api/players/:id` returns `accountBalance: 1000.00` for new user     |
| AC2  | Sitting at a table deducts buy-in from account balance            | `wallets.balance` reduced by `buyInAmount * 100` after `/sit`             |
| AC3  | Sitting with more than available balance returns NACK             | `/sit` with `buyInAmount > wallet` → `{ acknowledgement: "NACK" }`        |
| AC4  | Pressing Lobby button calls leave before navigating               | `game-shell.html` network log shows `POST /leave`                         |
| AC5  | Account balance updated in localStorage after leave               | `localStorage.hedgeem_session.balance` reflects `newAccountBalance * 100` |
| AC6  | Lobby header balance refreshes after return from game             | `renderHeader()` reads updated session on load                            |
| AC7  | Top-up deducts from account and adds to seat                      | `wallets.balance -= amount`; `seat_balance += amount`                     |
| AC8  | Top-up with insufficient wallet returns NACK                      | `/topup` with `amount > wallet` → `{ acknowledgement: "NACK" }`           |
| AC9  | New player receives £1,000 welcome balance                        | First `GET /api/players/:id` → `accountBalance: 1000.00`                  |
| AC10 | Leave with empty seat returns `creditsReturned: 0`                | `/leave` when `seat_balance = 0` → `{ creditsReturned: 0 }`               |

***

## Version Parity

| Version           | Status | Notes                                                                     |
| ----------------- | ------ | ------------------------------------------------------------------------- |
| TypeScript (v6)   | ✅      | Full implementation: server endpoints + game-shell leave hook (HEDGE-187) |
| JavaScript (v4)   | N/A    | No server integration; standalone client only                             |
| UMA (v5)          | ⬜      | Not implemented                                                           |
| V7 (Horse Racing) | ⬜      | Not implemented                                                           |

***

## Related

* `POST /api/tables/{tableId}/sit` — buy in to a table
* `POST /api/tables/{tableId}/leave` — cash out chips back to wallet
* `POST /api/tables/{tableId}/topup` — top up chips during a session
* `GET /api/players/{playerId}` — read account balance
* [F016 — Credits wallet](../betting/F016-credits-wallet) — in-game chip balance display
* HEDGE-187 · HEDGE-190 (lobby / cashier page)
