> 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/building-on-the-protocol/leaving-and-recovery.md).

# Leaving a table and recovering funds

> Verified against the public fastpoker IDL (PokerXYdXL2SKNnfGbv1WE7vJHipTpNsfZbZeVvoJLn) released 2026-07-17.

**Wallet signs.** Leaving a cash seat is a player action signed by the seated wallet. The cashout that follows is permissionless and lands back in that wallet.

> Beta software on mainnet. Examples can fail. Test with small amounts.

## Purpose

This page covers how a player exits a cash table and gets their stack back. It documents the normal path first: the player signs a leave action, and the protocol returns the stack to the wallet through the standard cashout. It then describes the recovery and admin instructions that exist for edge cases (a wallet that never collected a stranded balance, or a table operator reclaiming a long-abandoned balance). Those recovery instructions are not part of a normal player session.

For the player-facing view of cashouts, top-ups, and prize delivery, see [Top-ups and cashouts](/for-players/topup-cashout.md). This page is the integration-side reference for the same flow.

## When to use / who signs

| Path                  | Who signs                                      | When                                                                          |
| --------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------- |
| Leave a cash seat     | The seated player wallet                       | Normal exit from a cash game.                                                 |
| Cashout settlement    | Permissionless (the Dealer Service, or anyone) | Runs automatically after the leave action.                                    |
| `claim_unclaimed`     | The original player wallet                     | Recovery only: a stranded SPL balance, before the 100-day expiry.             |
| `claim_unclaimed_sol` | Permissionless (anyone)                        | Recovery only: a stranded SOL balance on a cash table.                        |
| `reclaim_expired`     | The table creator wallet                       | Admin only: reclaim a balance after 100 days from the player's last activity. |

Sit-and-Go seats do not leave voluntarily. An SNG buy-in is already escrowed when the player is seated, so the exit paths are elimination, final payout, or a match-wide cancel. Record-enabled tables finalize a per-game `SngSettlementRecord`, then `distribute_prizes_from_record` credits Player `claimable_sol` and unrefined $FP. SOL moves to the wallet only after the player signs `claim_sol_winnings`; Raw $FP becomes Liquid $FP through the normal Claim All/refinement flow. The `SngPool` account tracks queue and match state for each tier (size 150 bytes, `waiting_count` at offset 27, `active_match_set` at offset 83). See [Sit-and-Go](/for-players/sit-and-go.md).

## Inputs and constants

| Name                      | Value                                           |
| ------------------------- | ----------------------------------------------- |
| FastPoker program ID      | `PokerXYdXL2SKNnfGbv1WE7vJHipTpNsfZbZeVvoJLn`   |
| Table PDA seeds           | `["table", table_id: [u8;32]]`                  |
| Seat PDA seeds            | `["seat", table_pubkey, seat_index: u8]`        |
| Player-Table marker seeds | `["player_table", player_pubkey, table_pubkey]` |
| TableVault (SOL) seeds    | `["vault", table_pubkey]`                       |
| UnclaimedBalance seeds    | `["unclaimed", table_pubkey, player_pubkey]`    |
| Leave action code         | `LeaveCashGame`                                 |
| Seat status after leave   | `Leaving` (6)                                   |
| Unclaimed-balance expiry  | 100 days from the player's last activity        |

For full account layouts and offsets, see [State accounts](/architecture/state-accounts.md). Do not hardcode offsets from this page; read them from the account layouts there.

## Steps: the normal path

Leaving a cash table is two stages. The player signs the first. The protocol does the second.

1. The player wallet sends the `LeaveCashGame` action on the active table. This is a `PokerAction` variant of `player_action`, not a separate top-level instruction. It flips the seat status to `Leaving` (6) and snapshots the cashout amount on the seat. It does not move funds and does not close the `player_table` marker.
2. The current hand finishes if one is in progress. A `Leaving` seat does not block the table.
3. The Dealer Service detects the `Leaving` seat between hands and runs `process_cashout_v2` on L1. This is permissionless: it moves the snapshotted stack from the table vault to the player wallet and clears the seat.
4. The player's stack arrives in their wallet. The `player_table` marker is closed so the player can rejoin later.

The cashout transfer is permissionless and the program enforces that the payout goes to the seated player's wallet. The player does not sign the cashout. If no operator is running, anyone can submit the cashout path against the pending receipt. The exact cashout instruction and its accounts are documented in [Top-ups and cashouts](/for-players/topup-cashout.md).

Note on `leave_table`: the IDL exposes a top-level `leave_table` instruction, but it rejects Sit-and-Gos in its account constraints and rejects cash games in its handler. It is a legacy ABI entry, not the cash exit. Use the `LeaveCashGame` action plus `process_cashout_v2` for cash seats.

## Example

This builds and sends the player-signed leave action for a cash seat. It imports the shared helpers from [setup.md](/building-on-the-protocol/setup.md). The `player_action` instruction takes three accounts: `signer`, `table`, `seat`. The instruction data encodes the `PokerAction::LeaveCashGame` enum variant plus a 32-byte `entropy` field. After this action confirms, the permissionless cashout returns the stack to the wallet with no further signing from the player.

```ts
import { Connection, TransactionInstruction, Keypair } from '@solana/web3.js';
import { BorshCoder } from '@coral-xyz/anchor';
import {
  FASTPOKER_PROGRAM_ID,
  FASTPOKER_IDL,
  getTablePda,
  getSeatPda,
  sendAndConfirm,
} from './setup';

async function leaveCashSeat(
  connection: Connection,
  player: Keypair,
  tableId: Uint8Array,
  seatIndex: number,
): Promise<string> {
  const [table] = getTablePda(tableId);
  const [seat] = getSeatPda(table, seatIndex);

  // Encode the player_action data from the IDL. The action is the
  // PokerAction::LeaveCashGame variant; entropy is unused by this variant
  // but the instruction always carries the 32-byte field.
  const coder = new BorshCoder(FASTPOKER_IDL as any);
  const data = coder.instruction.encode('player_action', {
    action: { leaveCashGame: {} },
    entropy: new Array(32).fill(0),
  });

  const ix = new TransactionInstruction({
    programId: FASTPOKER_PROGRAM_ID,
    keys: [
      { pubkey: player.publicKey, isSigner: true, isWritable: false },
      { pubkey: table, isSigner: false, isWritable: true },
      { pubkey: seat, isSigner: false, isWritable: true },
    ],
    data,
  });

  // The player signs only this leave action. The cashout that follows is
  // permissionless and is submitted by the Dealer Service (process_cashout_v2).
  return sendAndConfirm(connection, [ix], [player]);
}
```

When the table is delegated to the ER, send this action to the ER endpoint rather than L1. Hand-report logging may require extra accounts in `remaining_accounts`. Confirm the current account order against the reference client and [State accounts](/architecture/state-accounts.md) before sending.

## Result

After the leave action confirms, the seat reads status `Leaving` (6). Once the cashout runs, the stack is in the player wallet, the seat is cleared, and the `player_table` marker is closed. A typical end-to-end leave plus cashout is on the order of 30 to 60 seconds. The funds are held in the table vault and earmarked for the player until the cashout lands; the delivery can be delayed, but the balance is not stranded.

## Recovery and admin instructions (not a normal player recipe)

These instructions exist for edge cases where a balance was not collected through the normal path. Do not present them as the way to leave a table. Most players and most integrations never call them.

### `claim_unclaimed` (original player, SPL, before expiry)

| Property  | Value                                                                     |
| --------- | ------------------------------------------------------------------------- |
| Who signs | The original player wallet (`unclaimed.player`)                           |
| When      | A stranded SPL token balance exists and the 100-day expiry has not passed |
| Closes    | The `UnclaimedBalance` PDA after paying out                               |

The player signs and the program returns the SPL balance from the table token escrow to the player's token account. The escrow is owned by the table PDA and the transfer signs with table seeds.

### `claim_unclaimed_sol` (permissionless, SOL)

| Property  | Value                                               |
| --------- | --------------------------------------------------- |
| Who signs | Anyone (permissionless)                             |
| When      | A stranded SOL balance exists on a cash table       |
| Argument  | `player_wallet: Pubkey` (the owed wallet)           |
| Incentive | The caller receives the `UnclaimedBalance` PDA rent |

This returns owed SOL to the player wallet from the table vault. It is the SOL counterpart to `claim_unclaimed`, and the Dealer Service calls it after a player is removed from a table. The payout still goes to the owed wallet, not the caller; only the rent refund goes to the caller.

### `reclaim_expired` (table creator, after expiry)

| Property  | Value                                               |
| --------- | --------------------------------------------------- |
| Who signs | The table creator (`table.creator`)                 |
| When      | Only after 100 days from the player's last activity |
| Argument  | `player: Pubkey`                                    |
| Closes    | The `UnclaimedBalance` PDA after reclaim            |

After the expiry window, the table creator can reclaim a long-abandoned balance. This is a creator-only cleanup for balances that no one ever collected. It cannot be used to take a balance that is still within the 100-day window.

## Pitfalls

* Do not use `leave_table` for a cash seat. It rejects cash games and SNGs in its handler. The cash exit is the `LeaveCashGame` action followed by `process_cashout_v2`.
* SNG seats cannot leave voluntarily. The buy-in is already escrowed. Exit is by elimination, record-backed final payout, or match cancel.
* Do not treat tournament completion as a direct wallet transfer. Read Player `claimable_sol` and the unrefined balance, then surface the appropriate signed claim action.
* The cashout is not part of the leave transaction. The player signs the leave; a separate permissionless transaction delivers the stack. Do not block your UI waiting for one transaction to do both.
* A `Leaving` seat must not be overwritten by a later `SitOut` or status change, or the cashout snapshot is lost. Send the leave action once and let the crank finish.
* `claim_unclaimed` is original-player-only and only before 100 days. `reclaim_expired` is creator-only and only after 100 days. The two windows do not overlap.
* Do not derive account offsets from this page. Read them from [State accounts](/architecture/state-accounts.md).

## See also

* [Sitting and deposits](/building-on-the-protocol/sitting-and-deposits.md)
* [Errors and troubleshooting](/building-on-the-protocol/errors-troubleshooting.md)
* [Top-ups and cashouts](/for-players/topup-cashout.md)
* [State accounts](/architecture/state-accounts.md)
