ShainShain/docs
Shain/Docs/Error reference
Engine

Error reference

Every numeric error the program can emit, what triggers it, and what an SDK caller should do about it.

Error codes

Anchor offsets custom error codes by 6000. The values below match the ordering in programs/shain/src/error.rs.

CodeVariantTriggerRecoverable?
6000UnauthorizedSession owner mismatch constraintNo
6001InsufficientHoldingBalance below min_holdingYes — top up
6002SessionStillActiveDouble-open or close-too-earlyYes — wait
6003SessionExpiredgated_action after expires_atYes — reopen
6004SessionNotFoundSession PDA uninitializedYes — call start_session
6005InvalidSessionDurationInit duration outside 60s..30dNo — redeploy
6006TokenAccountOwnerMismatchProvided ATA owner ≠ signerNo — wrong account
6007TokenAccountMintMismatchProvided ATA mint ≠ SHAIN mintNo — wrong account
6008OverflowCounter arithmetic overflowNo — will not happen in practice
6009CastErrorNumeric cast failureNo
6010TreasuryAtaMismatchTreasury ATA does not match expected derivationNo

SDK mapping

The TypeScript SDK normalises these into typed ShainErrorReason values via ANCHOR_ERROR_REASONS:

sdk/src/errors.tstypescript
export type ShainErrorReason =
  | "HolderBalanceTooLow"
  | "SessionAlreadyActive"
  | "SessionExpired"
  | "SessionNotFound"
  | "Unauthorized"
  | "InvalidParameter"
  | "ProgramIdMismatch"
  | "RpcError"
  | "Timeout"
  | "Unknown";

export const ANCHOR_ERROR_REASONS: Record<number, ShainErrorReason> = {
  6000: "Unauthorized",
  6001: "HolderBalanceTooLow",
  6002: "SessionAlreadyActive",
  6003: "SessionExpired",
  6004: "SessionNotFound",
  6005: "InvalidParameter",
};

Call ShainSdkError.wrap(err) to normalise any thrown error (RPC, network, Anchor) into the typed form for branching.

Handling errors in a client

handle.tstypescript
try {
  await client.startSession({ shainMint, userTokenAccount: userAta });
} catch (raw) {
  const err = ShainSdkError.wrap(raw);

  switch (err.reason) {
    case "HolderBalanceTooLow":
      return notify("Add more $SHAIN before opening a session.");
    case "SessionAlreadyActive":
      return refreshSnapshot(); // already open — just show the state
    case "RpcError":
    case "Timeout":
      return retryWithBackoff();
    default:
      return notify(err.message);
  }
}

Token / system errors you may also see

Shain does not wrap these; they bubble up from CPI or the runtime:

  • TokenAccountNotInitialized — the holder's $SHAIN ATA does not exist yet. Create it before start_session.
  • InsufficientFundsForRent — the signer does not have enough SOL to fund the session PDA. Top up.
  • TransactionExpired / BlockhashNotFound — standard retry territory.