@solana/errors 6.3.1 → 6.3.2-canary-20260313112147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ import { SolanaErrorCode } from './codes';
2
+ import { SolanaErrorContext } from './context';
3
+ import { SolanaError } from './error';
4
+ import { safeCaptureStackTrace } from './stack-trace';
5
+
6
+ type Config = Readonly<{
7
+ /**
8
+ * Oh, hello. You might wonder what in tarnation is going on here. Allow us to explain.
9
+ *
10
+ * One of the goals of `@solana/errors` is to allow errors that are not interesting to your
11
+ * application to shake out of your app bundle in production. This means that we must never
12
+ * export large hardcoded maps of error codes/messages.
13
+ *
14
+ * Unfortunately, where instruction and transaction errors from the RPC are concerned, we have
15
+ * no choice but to keep a map between the RPC `rpcEnumError` enum name and its corresponding
16
+ * `SolanaError` code. In the interest of implementing that map in as few bytes of source code
17
+ * as possible, we do the following:
18
+ *
19
+ * 1. Reserve a block of sequential error codes for the enum in question
20
+ * 2. Hardcode the list of enum names in that same order
21
+ * 3. Match the enum error name from the RPC with its index in that list, and reconstruct the
22
+ * `SolanaError` code by adding the `errorCodeBaseOffset` to that index
23
+ */
24
+ errorCodeBaseOffset: number;
25
+ getErrorContext: (
26
+ errorCode: SolanaErrorCode,
27
+ rpcErrorName: string,
28
+ rpcErrorContext?: unknown,
29
+ ) => SolanaErrorContext[SolanaErrorCode];
30
+ orderedErrorNames: string[];
31
+ rpcEnumError: string | { [key: string]: unknown };
32
+ }>;
33
+
34
+ export function getSolanaErrorFromRpcError(
35
+ { errorCodeBaseOffset, getErrorContext, orderedErrorNames, rpcEnumError }: Config,
36
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
37
+ constructorOpt: Function,
38
+ ): SolanaError {
39
+ let rpcErrorName;
40
+ let rpcErrorContext;
41
+ if (typeof rpcEnumError === 'string') {
42
+ rpcErrorName = rpcEnumError;
43
+ } else {
44
+ rpcErrorName = Object.keys(rpcEnumError)[0];
45
+ rpcErrorContext = rpcEnumError[rpcErrorName];
46
+ }
47
+ const codeOffset = orderedErrorNames.indexOf(rpcErrorName);
48
+ const errorCode = (errorCodeBaseOffset + codeOffset) as SolanaErrorCode;
49
+ const errorContext = getErrorContext(errorCode, rpcErrorName, rpcErrorContext);
50
+ const err = new SolanaError(errorCode, errorContext);
51
+ safeCaptureStackTrace(err, constructorOpt);
52
+ return err;
53
+ }
@@ -0,0 +1,47 @@
1
+ import {
2
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE,
3
+ SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT,
4
+ SolanaErrorCode,
5
+ } from './codes';
6
+ import { isSolanaError } from './error';
7
+
8
+ /**
9
+ * Extracts the underlying cause from a simulation-related error.
10
+ *
11
+ * When a transaction simulation fails, the error is often wrapped in a
12
+ * simulation-specific {@link SolanaError}. This function unwraps such errors
13
+ * by returning the `cause` property, giving you access to the actual error
14
+ * that triggered the simulation failure.
15
+ *
16
+ * If the provided error is not a simulation-related error, it is returned unchanged.
17
+ *
18
+ * The following error codes are considered simulation errors:
19
+ * - {@link SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE}
20
+ * - {@link SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT}
21
+ *
22
+ * @param error - The error to unwrap.
23
+ * @return The underlying cause if the error is a simulation error, otherwise the original error.
24
+ *
25
+ * @example
26
+ * Unwrapping a preflight failure to access the root cause.
27
+ * ```ts
28
+ * import { unwrapSimulationError } from '@solana/errors';
29
+ *
30
+ * try {
31
+ * await sendTransaction(signedTransaction);
32
+ * } catch (e) {
33
+ * const cause = unwrapSimulationError(e);
34
+ * console.log('Send transaction failed due to:', cause);
35
+ * }
36
+ * ```
37
+ */
38
+ export function unwrapSimulationError(error: unknown): unknown {
39
+ const simulationCodes: SolanaErrorCode[] = [
40
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE,
41
+ SOLANA_ERROR__TRANSACTION__FAILED_WHEN_SIMULATING_TO_ESTIMATE_COMPUTE_LIMIT,
42
+ ];
43
+ if (isSolanaError(error) && !!error.cause && simulationCodes.includes(error.context.__code)) {
44
+ return error.cause;
45
+ }
46
+ return error;
47
+ }
@@ -0,0 +1,5 @@
1
+ export function safeCaptureStackTrace(...args: Parameters<typeof Error.captureStackTrace>): void {
2
+ if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') {
3
+ Error.captureStackTrace(...args);
4
+ }
5
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ SOLANA_ERROR__TRANSACTION_ERROR__DUPLICATE_INSTRUCTION,
3
+ SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_RENT,
4
+ SOLANA_ERROR__TRANSACTION_ERROR__PROGRAM_EXECUTION_TEMPORARILY_RESTRICTED,
5
+ SOLANA_ERROR__TRANSACTION_ERROR__UNKNOWN,
6
+ } from './codes';
7
+ import { SolanaError } from './error';
8
+ import { getSolanaErrorFromInstructionError } from './instruction-error';
9
+ import { getSolanaErrorFromRpcError } from './rpc-enum-errors';
10
+
11
+ /**
12
+ * How to add an error when an entry is added to the RPC `TransactionError` enum:
13
+ *
14
+ * 1. Follow the instructions in `./codes.ts` to add a corresponding Solana error code
15
+ * 2. Add the `TransactionError` enum name in the same order as it appears in `./codes.ts`
16
+ * 3. Add the new error name/code mapping to `./__tests__/transaction-error-test.ts`
17
+ */
18
+ const ORDERED_ERROR_NAMES = [
19
+ // Keep synced with RPC source: https://github.com/anza-xyz/agave/blob/master/sdk/src/transaction/error.rs
20
+ // If this list ever gets too large, consider implementing a compression strategy like this:
21
+ // https://gist.github.com/steveluscher/aaa7cbbb5433b1197983908a40860c47
22
+ 'AccountInUse',
23
+ 'AccountLoadedTwice',
24
+ 'AccountNotFound',
25
+ 'ProgramAccountNotFound',
26
+ 'InsufficientFundsForFee',
27
+ 'InvalidAccountForFee',
28
+ 'AlreadyProcessed',
29
+ 'BlockhashNotFound',
30
+ // `InstructionError` intentionally omitted; delegated to `getSolanaErrorFromInstructionError`
31
+ 'CallChainTooDeep',
32
+ 'MissingSignatureForFee',
33
+ 'InvalidAccountIndex',
34
+ 'SignatureFailure',
35
+ 'InvalidProgramForExecution',
36
+ 'SanitizeFailure',
37
+ 'ClusterMaintenance',
38
+ 'AccountBorrowOutstanding',
39
+ 'WouldExceedMaxBlockCostLimit',
40
+ 'UnsupportedVersion',
41
+ 'InvalidWritableAccount',
42
+ 'WouldExceedMaxAccountCostLimit',
43
+ 'WouldExceedAccountDataBlockLimit',
44
+ 'TooManyAccountLocks',
45
+ 'AddressLookupTableNotFound',
46
+ 'InvalidAddressLookupTableOwner',
47
+ 'InvalidAddressLookupTableData',
48
+ 'InvalidAddressLookupTableIndex',
49
+ 'InvalidRentPayingAccount',
50
+ 'WouldExceedMaxVoteCostLimit',
51
+ 'WouldExceedAccountDataTotalLimit',
52
+ 'DuplicateInstruction',
53
+ 'InsufficientFundsForRent',
54
+ 'MaxLoadedAccountsDataSizeExceeded',
55
+ 'InvalidLoadedAccountsDataSizeLimit',
56
+ 'ResanitizationNeeded',
57
+ 'ProgramExecutionTemporarilyRestricted',
58
+ 'UnbalancedTransaction',
59
+ ];
60
+
61
+ export function getSolanaErrorFromTransactionError(transactionError: string | { [key: string]: unknown }): SolanaError {
62
+ if (typeof transactionError === 'object' && 'InstructionError' in transactionError) {
63
+ return getSolanaErrorFromInstructionError(
64
+ ...(transactionError.InstructionError as Parameters<typeof getSolanaErrorFromInstructionError>),
65
+ );
66
+ }
67
+ return getSolanaErrorFromRpcError(
68
+ {
69
+ errorCodeBaseOffset: 7050001,
70
+ getErrorContext(errorCode, rpcErrorName, rpcErrorContext) {
71
+ if (errorCode === SOLANA_ERROR__TRANSACTION_ERROR__UNKNOWN) {
72
+ return {
73
+ errorName: rpcErrorName,
74
+ ...(rpcErrorContext !== undefined ? { transactionErrorContext: rpcErrorContext } : null),
75
+ };
76
+ } else if (errorCode === SOLANA_ERROR__TRANSACTION_ERROR__DUPLICATE_INSTRUCTION) {
77
+ return {
78
+ index: Number(rpcErrorContext as bigint | number),
79
+ };
80
+ } else if (
81
+ errorCode === SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_RENT ||
82
+ errorCode === SOLANA_ERROR__TRANSACTION_ERROR__PROGRAM_EXECUTION_TEMPORARILY_RESTRICTED
83
+ ) {
84
+ return {
85
+ accountIndex: Number((rpcErrorContext as { account_index: bigint | number }).account_index),
86
+ };
87
+ }
88
+ },
89
+ orderedErrorNames: ORDERED_ERROR_NAMES,
90
+ rpcEnumError: transactionError,
91
+ },
92
+ getSolanaErrorFromTransactionError,
93
+ );
94
+ }