import type { Idl } from '@project-serum/anchor'; import type { PublicKey, SendTransactionError } from '@solana/web3.js'; import { BLAZE_ADDRESS, BLAZE_IDL } from './programs/blockasset-blaze/constants'; export type ErrorCode = { code: string; message: string; }; export const NATIVE_ERRORS: ErrorCode[] = [ { code: 'Blockhash not found', message: 'Blockhash not found. Transaction may or may not have gone through' }, { code: 'Transaction was not confirmed in', message: 'Transaction timed out waiting on confirmation from Solana. It may or may not have gone through' }, // token program errors { code: '0x1', message: 'Insufficient funds. User does not have enough balance of token to complete the transaction' }, { code: '0x4', message: 'Invalid owner. The user is likely not mint authority of this token.' }, { code: '91', message: 'Token is not ellgible for original receipts' }, // anchor errors { code: '100', message: 'InstructionMissing: 8 byte instruction identifier not provided' }, { code: '101', message: 'InstructionFallbackNotFound: Fallback functions are not supported' }, { code: '102', message: 'InstructionDidNotDeserialize: The program could not deserialize the given instruction' }, { code: '103', message: 'InstructionDidNotSerialize: The program could not serialize the given instruction' }, { code: '1000', message: 'IdlInstructionStub: The program was compiled without idl instructions' }, { code: '1001', message: 'IdlInstructionInvalidProgram: Invalid program given to the IDL instruction' }, { code: '2000', message: 'ConstraintMut: A mut constraint was violated' }, { code: '2001', message: 'ConstraintHasOne: A has one constraint was violated' }, { code: '2002', message: 'ConstraintSigner: A signer constraint as violated' }, { code: '2003', message: 'ConstraintRaw: A raw constraint was violated' }, { code: '2004', message: 'ConstraintOwner: An owner constraint was violated' }, { code: '2005', message: 'ConstraintRentExempt: A rent exemption constraint was violated' }, { code: '2006', message: 'ConstraintSeeds: A seeds constraint was violated' }, { code: '2007', message: 'ConstraintExecutable: An executable constraint was violated' }, { code: '2008', message: 'ConstraintState: A state constraint was violated' }, { code: '2009', message: 'ConstraintAssociated: An associated constraint was violated' }, { code: '2010', message: 'ConstraintAssociatedInit: An associated init constraint was violated' }, { code: '2011', message: 'ConstraintClose: A close constraint was violated' }, { code: '2012', message: 'ConstraintAddress: An address constraint was violated' }, { code: '2013', message: 'ConstraintZero: Expected zero account discriminant' }, { code: '2014', message: 'ConstraintTokenMint: A token mint constraint was violated' }, { code: '2015', message: 'ConstraintTokenOwner: A token owner constraint was violated' }, { code: '2016', message: 'ConstraintMintMintAuthority: A mint mint authority constraint was violated' }, { code: '2017', message: 'ConstraintMintFreezeAuthority: A mint freeze authority constraint was violated' }, { code: '2018', message: 'ConstraintMintDecimals: A mint decimals constraint was violated' }, { code: '2019', message: 'ConstraintSpace: A space constraint was violated' }, { code: '3000', message: 'AccountDiscriminatorAlreadySet: The account discriminator was already set on this account' }, { code: '3001', message: 'AccountDiscriminatorNotFound: No 8 byte discriminator was found on the account' }, { code: '3002', message: 'AccountDiscriminatorMismatch: 8 byte discriminator did not match what was expected' }, { code: '3003', message: 'AccountDidNotDeserialize: Failed to deserialize the account' }, { code: '3004', message: 'AccountDidNotSerialize: Failed to serialize the account' }, { code: '3005', message: 'AccountNotEnoughKeys: Not enough account keys given to the instruction' }, { code: '3006', message: 'AccountNotMutable: The given account is not mutable' }, { code: '3007', message: 'AccountNotProgramOwned: The given account is not owned by the executing program' }, { code: '3008', message: 'InvalidProgramId: Program ID was not as expected' }, { code: '3009', message: 'InvalidProgramExecutable: Program account is not executable' }, { code: '3010', message: 'AccountNotSigner: The given account did not sign' }, { code: '3011', message: 'AccountNotSystemOwned: The given account is not owned by the system program' }, { code: '3012', message: 'AccountNotInitialized: The program expected this account to be already initialized' }, { code: '3013', message: 'AccountNotProgramData: The given account is not a program data account' }, { code: '3014', message: 'AccountNotAssociatedTokenAccount: The given account is not the associated token account' }, { code: '4000', message: 'StateInvalidAddress: The given state account does not have the correct address' }, { code: '5000', message: 'Deprecated: The API being used is deprecated and should no longer be used' } ].reverse(); export type ErrorOptions = { /** ProgramIdls in priority order */ programIdls?: { idl: Idl; programId: PublicKey }[]; /** Additional errors by code */ additionalErrors?: ErrorCode[]; }; export const handleError = ( e: unknown, fallBackMessage = 'Transaction failed', // programIdls in priority order options: ErrorOptions = { programIdls: [{ programId: BLAZE_ADDRESS, idl: BLAZE_IDL }], additionalErrors: NATIVE_ERRORS } ): string => { const programIdls = options.programIdls ?? []; const additionalErrors = options.additionalErrors ?? []; const hex = (e as SendTransactionError)?.message?.split(' ').at(-1); const dec = parseInt(hex || '', 16); const logs = (e as SendTransactionError)?.logs ?? [ (e as SendTransactionError)?.message ] ?? [(e as Error).toString()] ?? []; const matchedErrors: { programMatch?: boolean; errorMatch?: string }[] = [ ...[ ...programIdls.map(({ idl, programId }) => ({ // match program on any log that includes programId and 'failed' programMatch: logs?.some( l => l?.includes(programId.toString()) && l.includes('failed') ), // match error with decimal errorMatch: idl.errors?.find(err => err.code === dec)?.msg })), { // match native error with decimal errorMatch: additionalErrors.find(err => err.code === dec.toString()) ?.message } ], ...[ { programMatch: false, errorMatch: additionalErrors.find( err => // message includes error (e as SendTransactionError)?.message?.includes(err.code) || // toString includes error (e as Error).toString().includes(err.code) || // any log includes error (e as SendTransactionError)?.logs?.some(l => l.toString().includes(err.code) ) )?.message }, ...programIdls.map(({ idl, programId }) => ({ // match program on any log that includes programId and 'failed' programMatch: logs?.some( l => l?.includes(programId.toString()) && l.includes('failed') ), errorMatch: idl.errors?.find( err => // message includes error (e as SendTransactionError)?.message?.includes( err.code.toString() ) || // toString includes error (e as Error).toString().includes(err.code.toString()) || // any log includes error (e as SendTransactionError)?.logs?.some(l => l.toString().includes(err.code.toString()) ) )?.msg })) ] ]; return ( matchedErrors.find(e => e.programMatch && e.errorMatch)?.errorMatch || matchedErrors.find(e => e.errorMatch)?.errorMatch || fallBackMessage ); };