import { SOLANA_ERROR__JSON_RPC__INTERNAL_ERROR, SOLANA_ERROR__JSON_RPC__INVALID_PARAMS, SOLANA_ERROR__JSON_RPC__INVALID_REQUEST, SOLANA_ERROR__JSON_RPC__METHOD_NOT_FOUND, SOLANA_ERROR__JSON_RPC__PARSE_ERROR, SOLANA_ERROR__JSON_RPC__SCAN_ERROR, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_CLEANED_UP, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_NOT_AVAILABLE, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_FILTER_TRANSACTION_NOT_FOUND, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SLOT_SKIPPED, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION, SOLANA_ERROR__MALFORMED_JSON_RPC_ERROR, SolanaErrorCode, } from './codes'; import { SolanaErrorContext } from './context'; import { SolanaError } from './error'; import { safeCaptureStackTrace } from './stack-trace'; import { getSolanaErrorFromTransactionError } from './transaction-error'; interface RpcErrorResponse { code: bigint | number; data?: unknown; message: string; } type TransactionError = string | { [key: string]: unknown }; /** * Keep in sync with https://github.com/anza-xyz/agave/blob/master/rpc-client-types/src/response.rs * @hidden */ export interface RpcSimulateTransactionResult { accounts: | ({ data: | string // LegacyBinary | { // Json parsed: unknown; program: string; space: bigint; } // Binary | [encodedBytes: string, encoding: 'base58' | 'base64' | 'base64+zstd' | 'binary' | 'jsonParsed']; executable: boolean; lamports: bigint; owner: string; rentEpoch: bigint; space?: bigint; } | null)[] | null; err: TransactionError | null; fee: bigint | null; // Enabled by `enable_cpi_recording` innerInstructions?: | { index: number; instructions: ( | { // Compiled accounts: number[]; data: string; programIdIndex: number; stackHeight?: number; } | { // Parsed parsed: unknown; program: string; programId: string; stackHeight?: number; } | { // PartiallyDecoded accounts: string[]; data: string; programId: string; stackHeight?: number; } )[]; }[] | null; loadedAccountsDataSize: number | null; loadedAddresses: { readonly: readonly string[]; writable: readonly string[]; } | null; logs: string[] | null; postBalances: bigint[] | null; postTokenBalances: unknown[] | null; preBalances: bigint[] | null; preTokenBalances: unknown[] | null; replacementBlockhash: string | null; returnData: { data: [string, 'base64']; programId: string; } | null; unitsConsumed: bigint | null; } export function getSolanaErrorFromJsonRpcError(putativeErrorResponse: unknown): SolanaError { let out: SolanaError; if (isRpcErrorResponse(putativeErrorResponse)) { const { code: rawCode, data, message } = putativeErrorResponse; const code = Number(rawCode); if (code === SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE) { const { err, ...preflightErrorContext } = data as RpcSimulateTransactionResult; const causeObject = err ? { cause: getSolanaErrorFromTransactionError(err) } : null; out = new SolanaError(SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE, { ...preflightErrorContext, ...causeObject, }); } else { let errorContext; switch (code) { case SOLANA_ERROR__JSON_RPC__INTERNAL_ERROR: case SOLANA_ERROR__JSON_RPC__INVALID_PARAMS: case SOLANA_ERROR__JSON_RPC__INVALID_REQUEST: case SOLANA_ERROR__JSON_RPC__METHOD_NOT_FOUND: case SOLANA_ERROR__JSON_RPC__PARSE_ERROR: case SOLANA_ERROR__JSON_RPC__SCAN_ERROR: case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_CLEANED_UP: case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_NOT_AVAILABLE: case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_FILTER_TRANSACTION_NOT_FOUND: case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SLOT_SKIPPED: case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE: case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: // The server supplies no structured data, but rather a pre-formatted message. Put // the server message in `context` so as not to completely lose the data. The long // term fix for this is to add data to the server responses and modify the // messages in `@solana/errors` to be actual format strings. errorContext = { __serverMessage: message }; break; default: if (typeof data === 'object' && !Array.isArray(data)) { errorContext = data; } } out = new SolanaError(code as SolanaErrorCode, errorContext as SolanaErrorContext[SolanaErrorCode]); } } else { const message = typeof putativeErrorResponse === 'object' && putativeErrorResponse !== null && 'message' in putativeErrorResponse && typeof putativeErrorResponse.message === 'string' ? putativeErrorResponse.message : 'Malformed JSON-RPC error with no message attribute'; out = new SolanaError(SOLANA_ERROR__MALFORMED_JSON_RPC_ERROR, { error: putativeErrorResponse, message }); } safeCaptureStackTrace(out, getSolanaErrorFromJsonRpcError); return out; } function isRpcErrorResponse(value: unknown): value is RpcErrorResponse { return ( typeof value === 'object' && value !== null && 'code' in value && 'message' in value && (typeof value.code === 'number' || typeof value.code === 'bigint') && typeof value.message === 'string' ); }