> 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/errors-troubleshooting.md).

# Errors and troubleshooting

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

**Read-only: decode program errors against the IDL. Nothing signs.**

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

## Purpose

This page helps you read FastPoker program errors and recover from common operational failures. It groups the on-chain error codes by area, explains what each group means, and then covers the runtime pitfalls that produce confusing failures: stale delegated accounts, RPC throttling, blocked reads, missing private accounts, turn-order rejections, undelegated tables, disabled claim paths, and indexer lag.

The program defines 199 custom error codes starting at `6000`. This page summarizes the groups and the codes you are most likely to hit. The full list lives in the IDL `errors` array.

## When to use / who signs

Use this page when a transaction reverts with a custom program error, when a read returns `null` for an account you expected, or when an RPC call returns an HTTP error. No signing happens here. The example below only decodes an error code against the IDL, so it is read-only.

## Inputs and constants

| Input                 | Value                                             |
| --------------------- | ------------------------------------------------- |
| Program ID            | `PokerXYdXL2SKNnfGbv1WE7vJHipTpNsfZbZeVvoJLn`     |
| Custom error base     | `6000` (Anchor offset)                            |
| Error source of truth | IDL `errors` array in `target/idl/fastpoker.json` |

Anchor reports custom errors as a number. Subtract `6000` to get the index into the IDL `errors` array, or match the number directly against the `code` field. Codes `6000` and above are program errors. Lower numbers and named DLP/proxy strings come from the runtime, the delegation program, or the TEE proxy, not from FastPoker.

## On-chain error groups

The full code, name, and message for every error are in the IDL. The tables below summarize the groups and call out the codes you will see most often during integration.

### Table and seat lifecycle (`6000` to `6016`, `6068`)

| Code   | Name                    | Meaning                                   |
| ------ | ----------------------- | ----------------------------------------- |
| `6000` | `TableFull`             | No open seat at the table.                |
| `6002` | `TableNotWaiting`       | Table is not in the Waiting phase.        |
| `6003` | `TableAlreadyDelegated` | Table is already delegated to the rollup. |
| `6004` | `TableNotDelegated`     | Table is on L1, not delegated.            |
| `6011` | `PlayerAlreadySeated`   | The wallet already holds a seat.          |
| `6012` | `SeatOccupied`          | The target seat index is taken.           |
| `6015` | `NotSeatOwner`          | Signer does not own the seat.             |
| `6016` | `InvalidSeatNumber`     | Seat index out of range for this table.   |

### Gameplay actions (`6017` to `6032`, `6072`)

| Code   | Name                    | Meaning                                            |
| ------ | ----------------------- | -------------------------------------------------- |
| `6021` | `InvalidActionForPhase` | The action is wrong for the current betting phase. |
| `6022` | `NotPlayersTurn`        | It is not this player's turn to act.               |
| `6023` | `InvalidBetAmount`      | Bet does not satisfy the size rules.               |
| `6025` | `CannotCheck`           | A bet is outstanding, so check is illegal.         |
| `6026` | `NothingToCall`         | No bet to call.                                    |
| `6027` | `RaiseTooSmall`         | Raise is below the minimum increment.              |

See the full list `6017` to `6032` for sit-out, all-in, and fold edge cases.

### Authority and delegation (`6033` to `6038`, `6100` to `6105`)

| Code   | Name                      | Meaning                                                          |
| ------ | ------------------------- | ---------------------------------------------------------------- |
| `6033` | `Unauthorized`            | Signer is not authorized for this action.                        |
| `6036` | `DelegationFailed`        | Account delegation to the rollup failed.                         |
| `6037` | `UndelegationFailed`      | Undelegation back to L1 failed.                                  |
| `6100` | `SeatsNotInitialized`     | Cannot delegate until all seats are initialized.                 |
| `6101` | `SetupIncomplete`         | Seats, permissions, and deck state must exist before delegation. |
| `6102` | `InvalidValidator`        | Delegation target is not the expected TEE validator.             |
| `6105` | `CommitCooldownNotPassed` | The table can only be committed once per 60 minutes.             |

### Cards, deal, and reveal (`6039` to `6044`, `6048`, `6106` to `6113`, `6130`)

| Code   | Name                      | Meaning                                                          |
| ------ | ------------------------- | ---------------------------------------------------------------- |
| `6039` | `InvalidVRFProof`         | TEE proof did not verify.                                        |
| `6043` | `InvalidSeatCardsAccount` | SeatCards data is empty or invalid (delegation may have failed). |
| `6048` | `InvalidCardReveal`       | Reveal did not match the prior commitment.                       |
| `6106` | `InvalidEntropy`          | Entropy key is all zeros.                                        |
| `6109` | `BlindsNotPosted`         | Cannot deal before blinds are posted.                            |
| `6130` | `CardsNotVisible`         | SeatCards permission is not set, so players cannot see cards.    |

### Cash funds, vault, and rake (`6045`, `6051` to `6056`, `6065`, `6078` to `6096`)

| Code   | Name                    | Meaning                                                               |
| ------ | ----------------------- | --------------------------------------------------------------------- |
| `6056` | `InsufficientFunds`     | Escrow does not hold enough to settle.                                |
| `6083` | `VaultInsufficient`     | Vault has too little SOL for the cashout.                             |
| `6084` | `NonceAlreadyProcessed` | Cashout nonce already consumed (a peer beat you to it).               |
| `6090` | `CashoutNotProcessed`   | Cashout not yet finalized on L1, so the seat cannot clear.            |
| `6094` | `TableCloseTooSoon`     | A cash table cannot close until empty for 24 hours.                   |
| `6095` | `RatholingBuyInTooLow`  | Anti-ratholing: buy-in must match the chips you left with (12h lock). |
| `6096` | `DepositInFlight`       | A pending deposit proof must apply or clean up first.                 |

### SNG pool, queue, and match (`6114` to `6148`, `6162`)

| Code   | Name                    | Meaning                                                         |
| ------ | ----------------------- | --------------------------------------------------------------- |
| `6115` | `PoolAlreadyQueued`     | Wallet is already in the pool queue.                            |
| `6117` | `PoolMatchInProgress`   | A match is already running for this pool.                       |
| `6123` | `CrankOperatorRequired` | SNG table creation needs a CrankOperator PDA.                   |
| `6126` | `WaitingTableExists`    | Reuse the existing Waiting table instead of creating a new one. |
| `6128` | `TableIndexExhausted`   | Pool reached 255 lifetime table creates.                        |
| `6140` | `AlreadySeated`         | This match already seated this player.                          |

### Emission governance (`6152` to `6154`)

| Code   | Name                       | Meaning                                                    |
| ------ | -------------------------- | ---------------------------------------------------------- |
| `6152` | `EmissionAccountsRequired` | `EmissionCtrl` and the matching `PoolIdle` must be passed. |
| `6153` | `InvalidEmissionAccount`   | Emission account owner, PDA, identity, or data is invalid. |
| `6154` | `InvalidEmissionConfig`    | Governed controller parameters fail validation.            |

### Jackpot and settlement (`6166` to `6183`)

| Code   | Name                        | Meaning                                                               |
| ------ | --------------------------- | --------------------------------------------------------------------- |
| `6166` | `JackpotNotInitialized`     | Jackpot global or bucket account is missing.                          |
| `6167` | `JackpotAlreadySettled`     | Settlement already exists for this table hand.                        |
| `6168` | `JackpotNotSettled`         | Settlement must run before prizes distribute.                         |
| `6179` | `SngJackpotCaptureBacklog`  | Capture ring is full of unsettled hands; settle before the next hand. |
| `6180` | `SngJackpotCaptureExpiring` | A captured hand is nearing SlotHashes expiry and must settle first.   |
| `6183` | `MiniUnclaimed`             | SNG Lucky/Mini winnings are not fully claimed.                        |

### Flat Bounty, duel, and settlement record (`6184` to `6198`)

| Code   | Name                       | Meaning                                                                      |
| ------ | -------------------------- | ---------------------------------------------------------------------------- |
| `6184` | `SngDuelStateRequired`     | A 6-max/9-max duel-mode table is missing its sidecar.                        |
| `6185` | `InvalidSngDuelState`      | Sidecar PDA, version, ruleset, table binding, or point invariant is invalid. |
| `6186` | `SngDuelAlreadyPaid`       | This sidecar already paid the current tournament.                            |
| `6187` | `SngDuelCreditInvariant`   | Conserved point/credit accounting failed.                                    |
| `6188` | `SngDuelNotPaid`           | Reset is blocked until Flat Bounty payout completes.                         |
| `6189` | `SngDuelInactive`          | No scheduled duel is active.                                                 |
| `6190` | `SngDuelAlreadyActive`     | A scheduled duel is already active.                                          |
| `6191` | `InvalidSngDuelAction`     | Choice, actor, round, or deadline is invalid.                                |
| `6192` | `SngDuelTimeoutNotReady`   | The 20-second duel deadline has not passed.                                  |
| `6193` | `TableNotClean`            | A reuse/reset cleanliness gate still fails.                                  |
| `6194` | `InvalidSettlementRecord`  | Record PDA, owner, nonce binding, or contents are invalid.                   |
| `6195` | `SettlementRecordRequired` | Record mode cannot use the legacy payout path.                               |
| `6196` | `SettlementNotFinal`       | The record has not reached final payout state.                               |
| `6197` | `SettlementAlreadyPaid`    | This per-game record already credited player balances.                       |
| `6198` | `InsufficientVault`        | The table vault cannot cover the recorded payout.                            |

### Disabled and guarded paths (`6149` to `6151`, `6178`)

| Code   | Name                     | Meaning                                                                             |
| ------ | ------------------------ | ----------------------------------------------------------------------------------- |
| `6149` | `ProtocolPaused`         | The protocol guard paused this operation.                                           |
| `6178` | `UnclaimedClaimDisabled` | Unclaimed cash-balance claim is disabled; recovery is by admin reconciliation only. |

For arithmetic and account-state guards (`6097` `Overflow`, `6098` `InvalidAccountData`, `6099` `AccountNotInitialized`), check the full IDL list. Note that `6099` is also reused on-chain as a cards-visible guard, so the same code can mean a missing permission delegation in the deal path.

## Operational pitfalls

These failures are not always reported as FastPoker error codes. They come from the runtime, the RPC provider, the delegation program, or the TEE proxy.

### Stale delegated accounts

A writable account that lives on the rollup but is referenced in an L1 transaction (or the reverse) fails. The delegation program reports `InvalidWritableAccount`, and the runtime can report `3007 AccountOwnedByWrongProgram` while an account is in transition between TEE and L1.

Recovery: wait 5 to 15 seconds for absorption, then retry. If the account is supposed to live on L1, undelegate it first, then resubmit on L1. If it is supposed to live on the rollup, re-delegate it. Confirm the account owner before retrying.

### RPC 413 and 429

`429 Too Many Requests` means rate limiting. `413 Payload Too Large` means the request body exceeded the provider limit, which happens on large `getMultipleAccounts` batches or oversized transactions.

Recovery: back off and retry with jitter on `429`. For `413`, shrink the batch. Free and shared RPC pools also reject `getMultipleAccounts` with more than 10 accounts; chunk all multi-account reads into groups of 10 or fewer. Use a keyed RPC endpoint for production read load.

### Free-pool getProgramAccounts blocked

Free and public RPC pools commonly disable or heavily restrict `getProgramAccounts`. A scan that works against a keyed endpoint can return an error or an empty result against a free pool.

Recovery: derive the specific PDAs you need and read them directly instead of scanning. See [setup.md](/building-on-the-protocol/setup.md) for the PDA seed helper. Reserve `getProgramAccounts` for a keyed endpoint or an indexer.

### ER "Blockhash not found"

A transaction built against the rollup can fail with "Blockhash not found" when a cached connection or a stale TEE auth token is used after token rotation. This shows up as a cascade roughly 50 minutes after the last refresh.

Recovery: fetch a fresh blockhash from the correct endpoint for the leg you are sending (L1 vs rollup), and refresh the TEE auth token through the live resolver path rather than a captured one. Restart a long-running client if the failure repeats after rotation.

### Private accounts returning null

SeatCards and other permissioned accounts read as `null` or return `403` for a signer that is not in the account's permission members. The card data is real but scoped.

Recovery: only read private accounts with a signer that is permitted on that account, using scoped TEE authorization. Do not treat `null` here as "account does not exist". See [../04-architecture/permissions.md](/architecture/permissions.md) for the card-access flow.

### Acting out of turn

A player action submitted for a non-actor reverts with `6022 NotPlayersTurn`, and a stale phase read produces `6021 InvalidActionForPhase`. Both are normal under contention because the phase or current player changed between your read and your write.

Recovery: re-read the table state and submit only for the current actor. Build the action transaction immediately before sending, not from a cached snapshot.

### Table not delegated

Rollup-only actions fail with `6004 TableNotDelegated` when the table is still on L1, and L1 actions fail with `6003 TableAlreadyDelegated` when the table has moved to the rollup. Delegation also requires full setup first (`6100`, `6101`).

Recovery: read the table owner and phase to confirm which side it lives on, then route the action to the matching endpoint. Complete seat, permission, and deck-state setup before delegating.

### Disabled claim paths

The unclaimed cash-balance claim path returns `6178 UnclaimedClaimDisabled`. This is intentional while the stranding fix is in place; recovery for those balances is via admin reconciliation. The protocol guard can also pause specific operations with `6149 ProtocolPaused`.

Recovery: do not retry a disabled claim in a loop. Surface the disabled state to the user and route stranded balances through operator support.

### Indexer lag

An indexer can trail the chain during heavy load or a restart, so pool counts, history, and profiles can be stale or briefly empty. The indexer is a read accelerator, never the source of truth for funds.

Recovery: fall back to direct Solana reads for anything custody-related. Treat an empty or stale indexer response as "unknown", not as "zero". See [indexer-integration.md](/integrations/indexer-integration.md).

## Steps

1. Read the failure category: a FastPoker error code, an HTTP status, a delegation-program string, or a TEE proxy code.
2. For a custom code, subtract `6000` and look up the entry in the IDL `errors` array, or match `code` directly.
3. For a runtime, RPC, delegation, or TEE failure, match the symptom in the pitfalls section above.
4. Apply the matching recovery. For transient delegation and contention failures, re-read state and retry; for guarded paths, stop and route to support.

## Example

This example imports the shared helper from [setup.md](/building-on-the-protocol/setup.md), loads the IDL, and decodes a custom error code into its name and message. It is read-only.

```ts
import { FASTPOKER_IDL } from './setup'; // shared helper from setup.md

// Anchor reports custom errors at base 6000.
const CUSTOM_ERROR_BASE = 6000;

type IdlError = { code: number; name: string; msg?: string };

function decodeFastPokerError(code: number): IdlError | null {
  const errors = (FASTPOKER_IDL as { errors?: IdlError[] }).errors ?? [];
  return errors.find((e) => e.code === code) ?? null;
}

// Pull the custom code out of a thrown Anchor/web3.js error if present.
function customCodeFrom(err: unknown): number | null {
  const anchorCode = (err as { error?: { errorCode?: { number?: number } } })
    ?.error?.errorCode?.number;
  if (typeof anchorCode === 'number') return anchorCode;

  const logs = (err as { logs?: string[] })?.logs ?? [];
  for (const line of logs) {
    const hex = line.match(/custom program error: 0x([0-9a-fA-F]+)/);
    if (hex) return parseInt(hex[1], 16);
    const dec = line.match(/Error Code: \w+\. Error Number: (\d+)/);
    if (dec) return Number(dec[1]);
  }
  return null;
}

function explain(err: unknown): string {
  const code = customCodeFrom(err);
  if (code === null) return 'Not a FastPoker custom program error.';
  if (code < CUSTOM_ERROR_BASE) {
    return `Code ${code} is below ${CUSTOM_ERROR_BASE}: runtime, DLP, or proxy error, not FastPoker.`;
  }
  const found = decodeFastPokerError(code);
  return found
    ? `${found.code} ${found.name}: ${found.msg ?? '(no message)'}`
    : `Unknown FastPoker code ${code}. Check the IDL errors array.`;
}

// Demonstration against known codes.
for (const code of [6022, 6004, 6178, 6195, 6130, 5999]) {
  console.log(explain({ error: { errorCode: { number: code } } }));
}
```

## Result

You can map any FastPoker failure to one of three sources: a custom program error decoded from the IDL, an HTTP or RPC condition, or a delegation/proxy condition. Transient delegation and contention failures clear on a fresh re-read and retry. Guarded and disabled paths do not, and should be surfaced or routed to support rather than retried.

## Pitfalls

* Do not retry blindly. `6022`, `6021`, and `3007` clear with a re-read, but `6178`, `6195`, `6149`, and `6094` do not.
* Do not treat a `null` private-account read or a stale indexer response as proof that funds are zero. Confirm against direct Solana reads.
* Do not scan with `getProgramAccounts` on a free pool, and do not batch more than 10 accounts on a shared RPC. Derive PDAs and chunk reads.
* Remember that `6099` is reused as a cards-visible guard in the deal path, so the same code can mean a missing permission delegation.
* The IDL is the source of truth. If a code is not in the table above, look it up in the `errors` array rather than guessing.

## See also

* [reading-accounts.md](/building-on-the-protocol/reading-accounts.md) - reading and decoding accounts for direct PDA reads.
* [../04-architecture/permissions.md](/architecture/permissions.md) - card access and read scoping.
* [frontend-integration.md](/integrations/frontend-integration.md) - the wallet-signed action and turn-order flow.
* [setup.md](/building-on-the-protocol/setup.md) - the shared helper with program IDs, the IDL, and PDA derivations.
* [Operator troubleshooting](/for-operators-dealer-service/troubleshooting.md) - Dealer Service error meanings and operator actions.
* [State accounts](/architecture/state-accounts.md) - account layouts referenced by these errors.
