> For the complete documentation index, see [llms.txt](https://docs.fast.poker/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fast.poker/for-operators-dealer-service/architecture.md).

# Architecture

The Dealer Service is the packaged operator binary that moves tables forward. It embeds the local dashboard, dashboard API server, and crank runtime in a single executable. It does three jobs in parallel: **discover** new tables, **drive** active tables forward, and **sweep** rewards and cashouts.

## Process layout

| Runtime file                | Purpose                                                                            |
| --------------------------- | ---------------------------------------------------------------------------------- |
| `.env`                      | Dashboard host, dashboard login, RPC endpoints, state directory, and keypair path. |
| `data/keypair.json`         | Dealer wallet when generated or imported by `./dealer init`.                       |
| `data/auth.json`            | Local dashboard login record.                                                      |
| `data/api-token`            | Per-install dashboard API token.                                                   |
| `data/crank-config.json`    | Operator configuration, re-read every 5 seconds.                                   |
| `data/crank-control.json`   | Operator control commands, polled every 5 seconds.                                 |
| `data/crank-heartbeat.json` | Liveness, tracked table count, processing count, and recent runtime state.         |
| `data/crank-metrics.json`   | Aggregated transaction counts, fees, and label stats.                              |
| `data/.crank.pid`           | Current crank process ID.                                                          |

Set `DEALER_HOME` to move the state directory away from `data/`.

## The three loops

### 1. Discovery (every 30 seconds)

`discoverNewTables()` calls `getProgramAccounts` on Solana L1 with a discriminator + size filter for `Table` accounts. New tables are added to the in-memory tracking map. The dealer also runs `sweepL1SngTables()` to promote full, undelegated SNG tables into the TEE by setting up permissions and delegations.

LaserStream gRPC is an optional accelerator: if a Helius API key is configured, table account changes stream in real time and the dealer needs less polling.

### 2. Game loop (every 3 seconds, per tracked table)

For each tracked table the dealer:

1. Reads current phase from the TEE (or L1 for undelegated tables).
2. Looks up the action for that phase in the [phase action map](/for-operators-dealer-service/phase-actions.md).
3. Submits the required transaction(s).
4. Records aggregate counters in `data/crank-metrics.json`.

For a 6-max/9-max SNG in `Waiting`, the action lookup checks the persistent `SngDuelState` before starting a normal hand. An eligible or active scheduled duel owns the lane until it resolves. At tournament completion, the dealer returns the per-game `SngSettlementRecord` and sidecar to L1, pays from the record, completes jackpot/reward cleanup, proves the table clean, resets it, and only then closes the transient record.

A WebSocket subscription is held on Table and Seat PDAs so the loop can react faster than the 3-second tick when an event arrives.

### 3. Sweeps (every 60 seconds)

Rake distribution, operator reward sync, and operator claim flows run on a longer cadence. See [Reward claim flow](/for-operators-dealer-service/reward-claim.md).

## Multi-instance coordination

A single box runs every loop itself and needs no coordination. When you run more than one instance (a fleet), they coordinate purely through a local **shard map** file plus a per-instance stand-down guard, so no two instances crank the same table. There is no network broker and nothing to host. On-chain programs remain the authority for whether a submitted instruction is valid, so the coordination layer only avoids wasted duplicate fees, never custody or gameplay.

See [Fleet and sharding setup](/for-operators-dealer-service/fleet-setup.md) for sizing and running a fleet.

## State management

| State                      | Storage                               | Lost on restart |
| -------------------------- | ------------------------------------- | --------------- |
| Tracked tables             | `Map<string, TableState>` in memory   | Yes             |
| Per-table processing locks | `Set<string>` in memory               | Yes             |
| Backoff / blocklist        | In-memory maps                        | Yes             |
| Turn timers                | In-memory timeouts                    | Yes             |
| Config                     | `.env` and `data/crank-config.json`   | No              |
| Dashboard auth             | `data/auth.json` and `data/api-token` | No              |
| Metrics                    | `data/crank-metrics.json` on disk     | No              |
| Heartbeat                  | `data/crank-heartbeat.json` on disk   | No              |

> Restart is mostly safe. The next discovery sweep re-adopts active tables from L1, and L1 is the system of record. The cost is a short pause before the dealer fully resumes.

## L1, ER, and TEE boundaries

* **L1** is the source of truth: tables, seats, vaults, reward accumulators, and cashouts all live here. The dealer pays fees from the operator wallet or configured `l1_payer_keypair_path`.
* **ER (Ephemeral Rollup)** is where active gameplay runs. Tables are *delegated* to the ER by the dealer before play; they are *undelegated* back to L1 after a Sit-and-Go completes or a cash seat leaves.
* **TEE** is where hole cards are sealed. The dealer calls `tee_deal` and `tee_reveal` here. The dealer cannot read hole cards either; it only signs the transactions that ask the TEE to deal or reveal.

For record-enabled SNGs, `SngSettlementRecord` is delegated with the game. For 6-max/9-max, `SngDuelState` is delegated as well. `EmissionCtrl` and `PoolIdle` stay governed on L1 and are passed to the funding/settlement lane.

The dealer signs everything with **two payers**:

| Payer           | Used for         | Notes                                                                                                                                       |
| --------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Ephemeral payer | TEE transactions | A fresh `Keypair.generate()` per TX. TEE is gasless, so this never needs SOL.                                                               |
| L1 payer        | L1 transactions  | Loaded from `l1_payer_keypair_path` when configured; otherwise the operator wallet pays rent, cashouts, rake distribution, and reward sync. |

Some TEE transactions (notably `settle_hand`) require both signers because the Magic program enforces that the escrow authority must sign the TX.

## Hot-reload and control

| File                      | Purpose                   | Edit while running?                                                                                     |
| ------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------- |
| `data/crank-config.json`  | Operator configuration    | Yes. Re-read every 5s. Process identity, RPC endpoints, and filter changes should be restarted cleanly. |
| `data/crank-control.json` | Operator control commands | Yes. Re-read every 5s. `{"action":"stop"}` exits 0. `{"action":"restart"}` exits 75.                    |

For managed deployments, use the packaged service commands:

```bash
./dealer service install
./dealer service restart
./dealer service stop
```

## What the dealer does *not* do

* It does not see hole cards. Only the TEE validator does.
* It does not generate randomness. Entropy comes from the TEE and `request_vrf_randomness`.
* It does not have privileged authority. Almost every instruction it submits is permissionless; the L1 program checks each one independently.
* It does not hold player funds. Vaults are on-chain PDAs.

## Critical operational rules

1. **Run multiple dealers as a fleet, or give them disjoint table sets.** Overlapping dealers without coordination can spend extra fees racing the same operation. Run them as a fleet (shared shard map + stand-down guard), use `table_whitelist`, or both.
2. **Don't bundle 10+ accounts in a CAU.** MagicBlock silently drops bundles with 10 or more accounts. Always chunk to <= 4 seats per CAU.
3. **TEE auth tokens rotate every \~50 minutes.** The dealer handles this automatically through the current TEE resolver path.
