@solana/errors
Version:
Throw, identify, and decode Solana JavaScript errors
174 lines (168 loc) • 7.42 kB
text/typescript
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'
);
}