/** * Borrowed from explorer.solana.com code written by @jstarry. */ import type { TransactionError } from "@solana/web3.js"; /** * Solana error messages built-in. */ export const BUILTIN_SOLANA_ERROR_MESSAGES: Map = new Map([ ["GenericError", "generic instruction error"], ["InvalidArgument", "invalid program argument"], ["InvalidInstructionData", "invalid instruction data"], ["InvalidAccountData", "invalid account data for instruction"], ["AccountDataTooSmall", "account data too small for instruction"], ["InsufficientFunds", "insufficient funds for instruction"], ["IncorrectProgramId", "incorrect program id for instruction"], ["MissingRequiredSignature", "missing required signature for instruction"], [ "AccountAlreadyInitialized", "instruction requires an uninitialized account", ], ["UninitializedAccount", "instruction requires an initialized account"], [ "UnbalancedInstruction", "sum of account balances before and after instruction do not match", ], ["ModifiedProgramId", "instruction modified the program id of an account"], [ "ExternalAccountLamportSpend", "instruction spent from the balance of an account it does not own", ], [ "ExternalAccountDataModified", "instruction modified data of an account it does not own", ], [ "ReadonlyLamportChange", "instruction changed the balance of a read-only account", ], ["ReadonlyDataModified", "instruction modified data of a read-only account"], ["DuplicateAccountIndex", "instruction contains duplicate accounts"], ["ExecutableModified", "instruction changed executable bit of an account"], ["RentEpochModified", "instruction modified rent epoch of an account"], ["NotEnoughAccountKeys", "insufficient account keys for instruction"], ["AccountDataSizeChanged", "non-system instruction changed account size"], ["AccountNotExecutable", "instruction expected an executable account"], [ "AccountBorrowFailed", "instruction tries to borrow reference for an account which is already borrowed", ], [ "AccountBorrowOutstanding", "instruction left account with an outstanding borrowed reference", ], [ "DuplicateAccountOutOfSync", "instruction modifications of multiply-passed account differ", ], ["Custom", "custom program error: {0}"], ["InvalidError", "program returned invalid error code"], ["ExecutableDataModified", "instruction changed executable accounts data"], [ "ExecutableLamportChange", "instruction changed the balance of a executable account", ], ["ExecutableAccountNotRentExempt", "executable accounts must be rent exempt"], ["UnsupportedProgramId", "Unsupported program id"], ["CallDepth", "Cross-program invocation call depth too deep"], ["MissingAccount", "An account required by the instruction is missing"], [ "ReentrancyNotAllowed", "Cross-program invocation reentrancy not allowed for this instruction", ], [ "MaxSeedLengthExceeded", "Length of the seed is too long for address generation", ], ["InvalidSeeds", "Provided seeds do not result in a valid address"], ["InvalidRealloc", "Failed to reallocate account data"], ["ComputationalBudgetExceeded", "Computational budget exceeded"], [ "PrivilegeEscalation", "Cross-program invocation with unauthorized signer or writable account", ], [ "ProgramEnvironmentSetupFailure", "Failed to create program execution environment", ], ["ProgramFailedToComplete", "Program failed to complete"], ["ProgramFailedToCompile", "Program failed to compile"], ["Immutable", "Account is immutable"], ["IncorrectAuthority", "Incorrect authority provided"], ["BorshIoError", "Failed to serialize or deserialize account data: {0}"], [ "AccountNotRentExempt", "An account does not have enough lamports to be rent-exempt", ], ["InvalidAccountOwner", "Invalid account owner"], ["ArithmeticOverflow", "Program arithmetic overflowed"], ["UnsupportedSysvar", "Unsupported sysvar"], ["IllegalOwner", "Provided owner is not allowed"], ]); export type ProgramError = { index: number; message: string; }; export function getTransactionInstructionError( error?: TransactionError | null ): ProgramError | undefined { if (!error) { return; } if (typeof error === "object" && "InstructionError" in error) { const innerError = error["InstructionError"]; const index = innerError[0] as number; const instructionError = innerError[1]; return { index, message: getInstructionError(instructionError), }; } } function getInstructionError(error: unknown): string { let out; let value: string | null = null; if (typeof error === "string") { const message = BUILTIN_SOLANA_ERROR_MESSAGES.get(error); if (message) { return message; } } else if ("Custom" in (error as Record)) { out = BUILTIN_SOLANA_ERROR_MESSAGES.get("Custom"); value = (error as { Custom: string })["Custom"]; } else if ("BorshIoError" in (error as Record)) { out = BUILTIN_SOLANA_ERROR_MESSAGES.get("BorshIoError"); value = (error as { BorshIoError: string })["BorshIoError"]; } if (out && value) { return out.replace("{0}", value); } return "Unknown instruction error"; }