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.
| Code | Variant | Trigger | Recoverable? |
|---|---|---|---|
| 6000 | Unauthorized | Session owner mismatch constraint | No |
| 6001 | InsufficientHolding | Balance below min_holding | Yes — top up |
| 6002 | SessionStillActive | Double-open or close-too-early | Yes — wait |
| 6003 | SessionExpired | gated_action after expires_at | Yes — reopen |
| 6004 | SessionNotFound | Session PDA uninitialized | Yes — call start_session |
| 6005 | InvalidSessionDuration | Init duration outside 60s..30d | No — redeploy |
| 6006 | TokenAccountOwnerMismatch | Provided ATA owner ≠ signer | No — wrong account |
| 6007 | TokenAccountMintMismatch | Provided ATA mint ≠ SHAIN mint | No — wrong account |
| 6008 | Overflow | Counter arithmetic overflow | No — will not happen in practice |
| 6009 | CastError | Numeric cast failure | No |
| 6010 | TreasuryAtaMismatch | Treasury ATA does not match expected derivation | No |
SDK mapping
The TypeScript SDK normalises these into typed ShainErrorReason values via ANCHOR_ERROR_REASONS:
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
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$SHAINATA does not exist yet. Create it beforestart_session.InsufficientFundsForRent— the signer does not have enough SOL to fund the session PDA. Top up.TransactionExpired / BlockhashNotFound— standard retry territory.