# Red 8 Table Service

Deals blackjack for the **Blackjack Table** template of the Red 8 Studio Game Creator, with chips bought in a MooChedda **game token** that the creator chooses (USDC by default, or any token, such as one made with the Reward Service's `npm run create-token`). Like the Reward Service, it runs on your computer or server as a Nodofi relay worker. It connects out to a relay and needs no open ports.

Why a service? With play chips, the oldest player's browser deals the cards. With tokens, that player could see the face-down card or fake a result. So token tables are dealt here: the service alone holds the shoe and the dealer's hidden card, keeps every player's chips, signs everything it says, and keeps the ledger.

```
Game ──table.buyin──▶ relay ──▶ Table Service ──invoice/create──▶ MooChedda
Player pays the invoice in the MooChedda Wallet; the service confirms it with MooChedda and credits the chips
Game ◀── signed table snapshots (room r8t:<service>:<code>) ── the service deals, settles, logs
Game ──table.cashout──▶ Table Service ──signed /token/transfer──▶ the wallet the player named at buy-in
```

## Status

- `npm test` (offline, against a fake MooChedda): rules, sessions, invoice buy-ins, bankroll limits, restarts mid-hand, signed cash-outs, signed replies and snapshots.
- End to end in test mode (a local relay, this service, two games): buying chips, playing alone, a shared table joined by code, a service restart, and both players cashing out, with every player's buy-ins plus winnings equal to their cash-out in the ledger.
- Not yet run against the real MooChedda (real invoices and transfers). Try that with a small token amount before opening tables to players.

## In the game

With **Chips** set to *MooChedda game token*, the start screen offers **Play with <token>**, which opens the chips screen. There the player can:

- enter their wallet address (the first time only; it's where cash-outs go, and the only place they can go),
- **Buy chips** (the MooChedda Wallet opens in a new tab to pay; the chips appear when MooChedda confirms),
- **Play alone**, **Open a shared table** (it gets a 4-letter code), or **Join** a friend's table by its code,
- **Cash out** everything between hands.

At the table, the pause menu has **Chips & cash out**. The session key stays in the browser, so a player's chips survive reloads. Two tabs in the same browser are the same player; use another browser or a private window to play as someone else.

## Set up

1. Node.js 18 or newer, then `npm install` here.
2. `cp .env.example .env`. Leave `TEST_MODE=true` until everything works: in test mode, buy-ins are credited at once, cash-outs are only logged, nothing touches MooChedda, and no wallet is needed.
3. The house wallet (for real tokens): `npm run create-wallet` or `npm run use-wallet -- path/to/wallet.json`. Buy-ins are paid to it and cash-outs are sent from it, so fund it with the table's token: that bankroll covers players' wins.
4. In the studio, open the blackjack game: **Settings → Game tokens (MooChedda)**. Set **Chips** to *MooChedda game token*, choose the token (USDC by default) and the buy-in limits, then **Download table config**. Put the file in `tables/` (one file per game; one service can run many games).
5. `npm start`. It prints its **service address** and house wallet. Enter both in the studio.
6. If the relay charges for access (relay.nodofi.com does), pay on its page, the same as for the Reward Service.

## Players

- **Session key:** the game makes a random key and sends it only to this service. Sessions are stored by `pid = sha256(key)` (first 24 hex characters); the key itself is never stored. Relay messages can claim any sender, so the key, not the sender id, says who is playing.
- **Buy-in:** an invoice in the game's token, paid to the house wallet, which the player pays in the MooChedda Wallet. The service checks pending invoices every 5 seconds (and whenever the game asks), so a paid buy-in is credited even if the player closed the game. Each invoice is credited once. A payment in the wrong token or for less is not credited, and is logged for you to settle by hand.
- **Cash-out:** the whole balance goes, as a transfer signed by the house wallet, to the wallet named at the session's first buy-in, and to no other. So even a stolen session key can't send a player's chips anywhere else. The balance is taken off before sending (so a crash can't pay twice). A refused transfer puts it back, and an unconfirmed one is logged as `sent-unconfirmed`.
- **Tables:** *private* (playing alone; untimed) or *shared* (a 4-letter code to share; a betting window and a clock on each decision). Players who go quiet for two minutes between hands are stood up, and their chips stay in their session.

## The house's limits

- **Bankroll:** before a bet is accepted, the house's free tokens (what the house wallet holds, minus what players are owed) must cover the worst case of every bet in play plus this one: a blackjack's bonus plus every split hand doubled and won, limited by what each player has left. Otherwise the bet is refused.
- **MAX_FLOAT:** the most all players together may hold at the tables.
- **Buy-in limits** per game come from the table config (smallest buy-in, most chips one player may hold).
- **Pacing:** at most 120 requests a minute per sender.

## Safety and records

- **Signed:** every reply is signed by the house wallet over `nonce|reply`, and every table snapshot over `snap|room|seq|snapshot`. Games accept only what the house wallet set in the studio signed.
- **No impostors:** the service's address is its own registered identity, as with the Reward Service. Outside test mode it refuses to run without a registry, because without one anyone could JOIN its address and read players' session keys.
- **Restarts:** balances are written to `sessions.json` after every change. A bet on the table when the service stops goes back to its player at the next start, logged as a void round.
- **Ledger:** `ledger.jsonl` records every buy-in, round (bets, net, the cards), void, and cash-out. Back up `sessions.json` and `ledger.jsonl`: together they are what the house owes each player. `npm run sessions` lists who holds what; `npm run balance` shows the house wallet against what players are owed.
- **One copy only:** it refuses to start if its address already answers. With PM2: `pm2 start ecosystem.config.js` (one instance, fork mode).

## Requests (relay protocol)

| `function` | Input | Answer |
|---|---|---|
| `table.info` | `game?` | `{ testMode, wallet, game \| games }` |
| `table.status` | `session` | `{ pid, token, balance, inPlay, wallet, table, pending }` |
| `table.buyin` | `session, game, wallet, amount, name?, returnUrl?` | `{ invoiceId, payUrl }` or, in test mode, `{ credited, balance }` |
| `table.buyinStatus` | `session, invoiceId` | `{ status, balance }` |
| `table.join` | `session, code?, shared?` | `{ code, room, shared, pid, rules, token }` |
| `table.leave` | `session` | `{ balance }` |
| `table.cashout` | `session` | `{ amount, token, wallet, signature?, confirmed? }` |
| `table.bet` / `table.ready` / `table.act` / `table.ping` | `session, amount? / action?` | nothing unless refused |
| `service.ping` | | `{ name }` |

Amounts are in tokens (two decimals) on the wire and in cents inside the service. Every answer echoes the request's `nonce` and carries `proof: { signer, sig }`.

## Files

| File | What it does |
|---|---|
| `src/service.js` | Relay connection, requests, signed replies, a relay connection per table for its snapshots |
| `src/casino.js` | Games, sessions, buy-ins, tables, the bankroll check, cash-outs, the ledger |
| `src/blackjack.js` | The rules (the same `BJTable` as the game's engine; keep them in step) with a secure shuffle |
| `src/moochedda.js` | The Reward Service's MooChedda module plus invoices and per-token balances |
| `src/identity.js`, `src/relay-access.js` | The same as the Reward Service's |
| `test/run.js` | `npm test` |
