@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.
- package/dist/cli.mjs +1 -1
- package/package.json +3 -2
- package/src/cli.ts +60 -0
- package/src/codes.ts +682 -0
- package/src/context.ts +862 -0
- package/src/error.ts +148 -0
- package/src/index.ts +72 -0
- package/src/instruction-error.ts +96 -0
- package/src/json-rpc-error.ts +162 -0
- package/src/message-formatter.ts +115 -0
- package/src/messages.ts +791 -0
- package/src/rpc-enum-errors.ts +53 -0
- package/src/simulation-errors.ts +47 -0
- package/src/stack-trace.ts +5 -0
- package/src/transaction-error.ts +94 -0
|
@@ -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,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
|
+
}
|