@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/src/error.ts ADDED
@@ -0,0 +1,148 @@
1
+ import { SolanaErrorCode, SolanaErrorCodeWithCause, SolanaErrorCodeWithDeprecatedCause } from './codes';
2
+ import { SolanaErrorContext } from './context';
3
+ import { getErrorMessage } from './message-formatter';
4
+
5
+ /**
6
+ * A variant of {@link SolanaError} where the `cause` property is deprecated.
7
+ *
8
+ * This type is returned by {@link isSolanaError} when checking for error codes in
9
+ * {@link SolanaErrorCodeWithDeprecatedCause}. Accessing `cause` on these errors will show
10
+ * a deprecation warning in IDEs that support JSDoc `@deprecated` tags.
11
+ */
12
+ export interface SolanaErrorWithDeprecatedCause<
13
+ TErrorCode extends SolanaErrorCodeWithDeprecatedCause = SolanaErrorCodeWithDeprecatedCause,
14
+ > extends Omit<SolanaError<TErrorCode>, 'cause'> {
15
+ /**
16
+ * @deprecated The `cause` property is deprecated for this error code.
17
+ * Use the error's `context` property instead to access relevant error information.
18
+ */
19
+ readonly cause?: unknown;
20
+ }
21
+
22
+ /**
23
+ * A type guard that returns `true` if the input is a {@link SolanaError}, optionally with a
24
+ * particular error code.
25
+ *
26
+ * When the `code` argument is supplied and the input is a {@link SolanaError}, TypeScript will
27
+ * refine the error's {@link SolanaError#context | `context`} property to the type associated with
28
+ * that error code. You can use that context to render useful error messages, or to make
29
+ * context-aware decisions that help your application to recover from the error.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import {
34
+ * SOLANA_ERROR__TRANSACTION__MISSING_SIGNATURE,
35
+ * SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING,
36
+ * isSolanaError,
37
+ * } from '@solana/errors';
38
+ * import { assertIsFullySignedTransaction, getSignatureFromTransaction } from '@solana/transactions';
39
+ *
40
+ * try {
41
+ * const transactionSignature = getSignatureFromTransaction(tx);
42
+ * assertIsFullySignedTransaction(tx);
43
+ * /* ... *\/
44
+ * } catch (e) {
45
+ * if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING)) {
46
+ * displayError(
47
+ * "We can't send this transaction without signatures for these addresses:\n- %s",
48
+ * // The type of the `context` object is now refined to contain `addresses`.
49
+ * e.context.addresses.join('\n- '),
50
+ * );
51
+ * return;
52
+ * } else if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING)) {
53
+ * if (!tx.feePayer) {
54
+ * displayError('Choose a fee payer for this transaction before sending it');
55
+ * } else {
56
+ * displayError('The fee payer still needs to sign for this transaction');
57
+ * }
58
+ * return;
59
+ * }
60
+ * throw e;
61
+ * }
62
+ * ```
63
+ */
64
+ export function isSolanaError<TErrorCode extends SolanaErrorCodeWithDeprecatedCause>(
65
+ e: unknown,
66
+ code: TErrorCode,
67
+ ): e is SolanaErrorWithDeprecatedCause<TErrorCode>;
68
+ export function isSolanaError<TErrorCode extends SolanaErrorCode>(
69
+ e: unknown,
70
+ code?: TErrorCode,
71
+ ): e is SolanaError<TErrorCode>;
72
+ export function isSolanaError<TErrorCode extends SolanaErrorCode>(
73
+ e: unknown,
74
+ /**
75
+ * When supplied, this function will require that the input is a {@link SolanaError} _and_ that
76
+ * its error code is exactly this value.
77
+ */
78
+ code?: TErrorCode,
79
+ ): e is SolanaError<TErrorCode> {
80
+ const isSolanaError = e instanceof Error && e.name === 'SolanaError';
81
+ if (isSolanaError) {
82
+ if (code !== undefined) {
83
+ return (e as SolanaError<TErrorCode>).context.__code === code;
84
+ }
85
+ return true;
86
+ }
87
+ return false;
88
+ }
89
+
90
+ type SolanaErrorCodedContext = {
91
+ [P in SolanaErrorCode]: Readonly<{
92
+ __code: P;
93
+ }> &
94
+ (SolanaErrorContext[P] extends undefined ? object : SolanaErrorContext[P]);
95
+ };
96
+
97
+ /**
98
+ * Encapsulates an error's stacktrace, a Solana-specific numeric code that indicates what went
99
+ * wrong, and optional context if the type of error indicated by the code supports it.
100
+ */
101
+ export class SolanaError<TErrorCode extends SolanaErrorCode = SolanaErrorCode> extends Error {
102
+ /**
103
+ * Indicates the root cause of this {@link SolanaError}, if any.
104
+ *
105
+ * For example, a transaction error might have an instruction error as its root cause. In this
106
+ * case, you will be able to access the instruction error on the transaction error as `cause`.
107
+ */
108
+ readonly cause?: TErrorCode extends SolanaErrorCodeWithCause ? SolanaError : unknown = this.cause;
109
+ /**
110
+ * Contains context that can assist in understanding or recovering from a {@link SolanaError}.
111
+ */
112
+ readonly context: SolanaErrorCodedContext[TErrorCode];
113
+ constructor(
114
+ ...[code, contextAndErrorOptions]: SolanaErrorContext[TErrorCode] extends undefined
115
+ ? [code: TErrorCode, errorOptions?: ErrorOptions | undefined]
116
+ : [code: TErrorCode, contextAndErrorOptions: SolanaErrorContext[TErrorCode] & (ErrorOptions | undefined)]
117
+ ) {
118
+ let context: SolanaErrorContext[TErrorCode] | undefined;
119
+ let errorOptions: ErrorOptions | undefined;
120
+ if (contextAndErrorOptions) {
121
+ Object.entries(Object.getOwnPropertyDescriptors(contextAndErrorOptions)).forEach(([name, descriptor]) => {
122
+ // If the `ErrorOptions` type ever changes, update this code.
123
+ if (name === 'cause') {
124
+ errorOptions = { cause: descriptor.value };
125
+ } else {
126
+ if (context === undefined) {
127
+ context = {
128
+ __code: code,
129
+ } as unknown as SolanaErrorContext[TErrorCode];
130
+ }
131
+ Object.defineProperty(context, name, descriptor);
132
+ }
133
+ });
134
+ }
135
+ const message = getErrorMessage(code, context);
136
+ super(message, errorOptions);
137
+ this.context = Object.freeze(
138
+ context === undefined
139
+ ? {
140
+ __code: code,
141
+ }
142
+ : context,
143
+ ) as SolanaErrorCodedContext[TErrorCode];
144
+ // This is necessary so that `isSolanaError()` can identify a `SolanaError` without having
145
+ // to import the class for use in an `instanceof` check.
146
+ this.name = 'SolanaError';
147
+ }
148
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * This package brings together every error message across all Solana JavaScript modules.
3
+ *
4
+ * # Reading error messages
5
+ *
6
+ * ## In development mode
7
+ *
8
+ * When your bundler sets the constant `__DEV__` to `true`, every error message will be included in
9
+ * the bundle. As such, you will be able to read them in plain language wherever they appear.
10
+ *
11
+ * > [!WARNING]
12
+ * > The size of your JavaScript bundle will increase significantly with the inclusion of every
13
+ * > error message in development mode. Be sure to build your bundle with `__DEV__` set to `false`
14
+ * > when you go to production.
15
+ *
16
+ * ## In production mode
17
+ *
18
+ * When your bundler sets the constant `__DEV__` to `false`, error messages will be stripped from
19
+ * the bundle to save space. Only the error code will appear when an error is encountered. Follow
20
+ * the instructions in the error message to convert the error code back to the human-readable error
21
+ * message.
22
+ *
23
+ * For instance, to recover the error text for the error with code `123`:
24
+ *
25
+ * ```package-install
26
+ * npx @solana/errors decode -- 123
27
+ * ```
28
+ *
29
+ * > [!IMPORTANT]
30
+ * > The string representation of a {@link SolanaError} should not be shown to users. Developers
31
+ * > should use {@link isSolanaError} to distinguish the type of a thrown error, then show a custom,
32
+ * > localized error message appropriate for their application's audience. Custom error messages
33
+ * > should use the error's {@link SolanaError#context | `context`} if it would help the reader
34
+ * > understand what happened and/or what to do next.
35
+ *
36
+ * # Adding a new error
37
+ *
38
+ * 1. Add a new exported error code constant to `src/codes.ts`.
39
+ * 2. Add that new constant to the {@link SolanaErrorCode} union in `src/codes.ts`.
40
+ * 3. If you would like the new error to encapsulate context about the error itself (eg. the public
41
+ * keys for which a transaction is missing signatures) define the shape of that context in
42
+ * `src/context.ts`.
43
+ * 4. Add the error's message to `src/messages.ts`. Any context values that you defined above will
44
+ * be interpolated into the message wherever you write `$key`, where `key` is the index of a
45
+ * value in the context (eg. ``'Missing a signature for account `$address`'``).
46
+ * 5. Publish a new version of `@solana/errors`.
47
+ * 6. Bump the version of `@solana/errors` in the package from which the error is thrown.
48
+ *
49
+ * # Removing an error message
50
+ *
51
+ * - Don't remove errors.
52
+ * - Don't change the meaning of an error message.
53
+ * - Don't change or reorder error codes.
54
+ * - Don't change or remove members of an error's context.
55
+ *
56
+ * When an older client throws an error, we want to make sure that they can always decode the error.
57
+ * If you make any of the changes above, old clients will, by definition, not have received your
58
+ * changes. This could make the errors that they throw impossible to decode going forward.
59
+ *
60
+ * # Catching errors
61
+ *
62
+ * See {@link isSolanaError} for an example of how to handle a caught {@link SolanaError}.
63
+ *
64
+ * @packageDocumentation
65
+ */
66
+ export * from './codes';
67
+ export * from './error';
68
+ export * from './instruction-error';
69
+ export * from './json-rpc-error';
70
+ export * from './simulation-errors';
71
+ export * from './stack-trace';
72
+ export * from './transaction-error';
@@ -0,0 +1,96 @@
1
+ import { SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM, SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN } from './codes';
2
+ import { SolanaError } from './error';
3
+ import { getSolanaErrorFromRpcError } from './rpc-enum-errors';
4
+
5
+ const ORDERED_ERROR_NAMES = [
6
+ // Keep synced with RPC source: https://github.com/anza-xyz/solana-sdk/blob/master/instruction-error/src/lib.rs
7
+ // If this list ever gets too large, consider implementing a compression strategy like this:
8
+ // https://gist.github.com/steveluscher/aaa7cbbb5433b1197983908a40860c47
9
+ 'GenericError',
10
+ 'InvalidArgument',
11
+ 'InvalidInstructionData',
12
+ 'InvalidAccountData',
13
+ 'AccountDataTooSmall',
14
+ 'InsufficientFunds',
15
+ 'IncorrectProgramId',
16
+ 'MissingRequiredSignature',
17
+ 'AccountAlreadyInitialized',
18
+ 'UninitializedAccount',
19
+ 'UnbalancedInstruction',
20
+ 'ModifiedProgramId',
21
+ 'ExternalAccountLamportSpend',
22
+ 'ExternalAccountDataModified',
23
+ 'ReadonlyLamportChange',
24
+ 'ReadonlyDataModified',
25
+ 'DuplicateAccountIndex',
26
+ 'ExecutableModified',
27
+ 'RentEpochModified',
28
+ 'NotEnoughAccountKeys',
29
+ 'AccountDataSizeChanged',
30
+ 'AccountNotExecutable',
31
+ 'AccountBorrowFailed',
32
+ 'AccountBorrowOutstanding',
33
+ 'DuplicateAccountOutOfSync',
34
+ 'Custom',
35
+ 'InvalidError',
36
+ 'ExecutableDataModified',
37
+ 'ExecutableLamportChange',
38
+ 'ExecutableAccountNotRentExempt',
39
+ 'UnsupportedProgramId',
40
+ 'CallDepth',
41
+ 'MissingAccount',
42
+ 'ReentrancyNotAllowed',
43
+ 'MaxSeedLengthExceeded',
44
+ 'InvalidSeeds',
45
+ 'InvalidRealloc',
46
+ 'ComputationalBudgetExceeded',
47
+ 'PrivilegeEscalation',
48
+ 'ProgramEnvironmentSetupFailure',
49
+ 'ProgramFailedToComplete',
50
+ 'ProgramFailedToCompile',
51
+ 'Immutable',
52
+ 'IncorrectAuthority',
53
+ 'BorshIoError',
54
+ 'AccountNotRentExempt',
55
+ 'InvalidAccountOwner',
56
+ 'ArithmeticOverflow',
57
+ 'UnsupportedSysvar',
58
+ 'IllegalOwner',
59
+ 'MaxAccountsDataAllocationsExceeded',
60
+ 'MaxAccountsExceeded',
61
+ 'MaxInstructionTraceLengthExceeded',
62
+ 'BuiltinProgramsMustConsumeComputeUnits',
63
+ ];
64
+
65
+ export function getSolanaErrorFromInstructionError(
66
+ /**
67
+ * The index of the instruction inside the transaction.
68
+ */
69
+ index: bigint | number,
70
+ instructionError: string | { [key: string]: unknown },
71
+ ): SolanaError {
72
+ const numberIndex = Number(index);
73
+ return getSolanaErrorFromRpcError(
74
+ {
75
+ errorCodeBaseOffset: 4615001,
76
+ getErrorContext(errorCode, rpcErrorName, rpcErrorContext) {
77
+ if (errorCode === SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN) {
78
+ return {
79
+ errorName: rpcErrorName,
80
+ index: numberIndex,
81
+ ...(rpcErrorContext !== undefined ? { instructionErrorContext: rpcErrorContext } : null),
82
+ };
83
+ } else if (errorCode === SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM) {
84
+ return {
85
+ code: Number(rpcErrorContext as bigint | number),
86
+ index: numberIndex,
87
+ };
88
+ }
89
+ return { index: numberIndex };
90
+ },
91
+ orderedErrorNames: ORDERED_ERROR_NAMES,
92
+ rpcEnumError: instructionError,
93
+ },
94
+ getSolanaErrorFromInstructionError,
95
+ );
96
+ }
@@ -0,0 +1,162 @@
1
+ import {
2
+ SOLANA_ERROR__JSON_RPC__INTERNAL_ERROR,
3
+ SOLANA_ERROR__JSON_RPC__INVALID_PARAMS,
4
+ SOLANA_ERROR__JSON_RPC__INVALID_REQUEST,
5
+ SOLANA_ERROR__JSON_RPC__METHOD_NOT_FOUND,
6
+ SOLANA_ERROR__JSON_RPC__PARSE_ERROR,
7
+ SOLANA_ERROR__JSON_RPC__SCAN_ERROR,
8
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_CLEANED_UP,
9
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_NOT_AVAILABLE,
10
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET,
11
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX,
12
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED,
13
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE,
14
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SLOT_SKIPPED,
15
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE,
16
+ SOLANA_ERROR__JSON_RPC__SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION,
17
+ SOLANA_ERROR__MALFORMED_JSON_RPC_ERROR,
18
+ SolanaErrorCode,
19
+ } from './codes';
20
+ import { SolanaErrorContext } from './context';
21
+ import { SolanaError } from './error';
22
+ import { safeCaptureStackTrace } from './stack-trace';
23
+ import { getSolanaErrorFromTransactionError } from './transaction-error';
24
+
25
+ interface RpcErrorResponse {
26
+ code: bigint | number;
27
+ data?: unknown;
28
+ message: string;
29
+ }
30
+
31
+ type TransactionError = string | { [key: string]: unknown };
32
+
33
+ /**
34
+ * Keep in sync with https://github.com/anza-xyz/agave/blob/master/rpc-client-types/src/response.rs
35
+ * @hidden
36
+ */
37
+ export interface RpcSimulateTransactionResult {
38
+ accounts:
39
+ | ({
40
+ data:
41
+ | string // LegacyBinary
42
+ | {
43
+ // Json
44
+ parsed: unknown;
45
+ program: string;
46
+ space: bigint;
47
+ }
48
+ // Binary
49
+ | [encodedBytes: string, encoding: 'base58' | 'base64' | 'base64+zstd' | 'binary' | 'jsonParsed'];
50
+ executable: boolean;
51
+ lamports: bigint;
52
+ owner: string;
53
+ rentEpoch: bigint;
54
+ space?: bigint;
55
+ } | null)[]
56
+ | null;
57
+ err: TransactionError | null;
58
+ // Enabled by `enable_cpi_recording`
59
+ innerInstructions?:
60
+ | {
61
+ index: number;
62
+ instructions: (
63
+ | {
64
+ // Compiled
65
+ accounts: number[];
66
+ data: string;
67
+ programIdIndex: number;
68
+ stackHeight?: number;
69
+ }
70
+ | {
71
+ // Parsed
72
+ parsed: unknown;
73
+ program: string;
74
+ programId: string;
75
+ stackHeight?: number;
76
+ }
77
+ | {
78
+ // PartiallyDecoded
79
+ accounts: string[];
80
+ data: string;
81
+ programId: string;
82
+ stackHeight?: number;
83
+ }
84
+ )[];
85
+ }[]
86
+ | null;
87
+ loadedAccountsDataSize: number | null;
88
+ logs: string[] | null;
89
+ replacementBlockhash: string | null;
90
+ returnData: {
91
+ data: [string, 'base64'];
92
+ programId: string;
93
+ } | null;
94
+ unitsConsumed: bigint | null;
95
+ }
96
+
97
+ export function getSolanaErrorFromJsonRpcError(putativeErrorResponse: unknown): SolanaError {
98
+ let out: SolanaError;
99
+ if (isRpcErrorResponse(putativeErrorResponse)) {
100
+ const { code: rawCode, data, message } = putativeErrorResponse;
101
+ const code = Number(rawCode);
102
+ if (code === SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE) {
103
+ const { err, ...preflightErrorContext } = data as RpcSimulateTransactionResult;
104
+ const causeObject = err ? { cause: getSolanaErrorFromTransactionError(err) } : null;
105
+ out = new SolanaError(SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE, {
106
+ ...preflightErrorContext,
107
+ ...causeObject,
108
+ });
109
+ } else {
110
+ let errorContext;
111
+ switch (code) {
112
+ case SOLANA_ERROR__JSON_RPC__INTERNAL_ERROR:
113
+ case SOLANA_ERROR__JSON_RPC__INVALID_PARAMS:
114
+ case SOLANA_ERROR__JSON_RPC__INVALID_REQUEST:
115
+ case SOLANA_ERROR__JSON_RPC__METHOD_NOT_FOUND:
116
+ case SOLANA_ERROR__JSON_RPC__PARSE_ERROR:
117
+ case SOLANA_ERROR__JSON_RPC__SCAN_ERROR:
118
+ case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_CLEANED_UP:
119
+ case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_NOT_AVAILABLE:
120
+ case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET:
121
+ case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX:
122
+ case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED:
123
+ case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SLOT_SKIPPED:
124
+ case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE:
125
+ case SOLANA_ERROR__JSON_RPC__SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION:
126
+ // The server supplies no structured data, but rather a pre-formatted message. Put
127
+ // the server message in `context` so as not to completely lose the data. The long
128
+ // term fix for this is to add data to the server responses and modify the
129
+ // messages in `@solana/errors` to be actual format strings.
130
+ errorContext = { __serverMessage: message };
131
+ break;
132
+ default:
133
+ if (typeof data === 'object' && !Array.isArray(data)) {
134
+ errorContext = data;
135
+ }
136
+ }
137
+ out = new SolanaError(code as SolanaErrorCode, errorContext as SolanaErrorContext[SolanaErrorCode]);
138
+ }
139
+ } else {
140
+ const message =
141
+ typeof putativeErrorResponse === 'object' &&
142
+ putativeErrorResponse !== null &&
143
+ 'message' in putativeErrorResponse &&
144
+ typeof putativeErrorResponse.message === 'string'
145
+ ? putativeErrorResponse.message
146
+ : 'Malformed JSON-RPC error with no message attribute';
147
+ out = new SolanaError(SOLANA_ERROR__MALFORMED_JSON_RPC_ERROR, { error: putativeErrorResponse, message });
148
+ }
149
+ safeCaptureStackTrace(out, getSolanaErrorFromJsonRpcError);
150
+ return out;
151
+ }
152
+
153
+ function isRpcErrorResponse(value: unknown): value is RpcErrorResponse {
154
+ return (
155
+ typeof value === 'object' &&
156
+ value !== null &&
157
+ 'code' in value &&
158
+ 'message' in value &&
159
+ (typeof value.code === 'number' || typeof value.code === 'bigint') &&
160
+ typeof value.message === 'string'
161
+ );
162
+ }
@@ -0,0 +1,115 @@
1
+ import { SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN, SolanaErrorCode } from './codes';
2
+ import { encodeContextObject } from './context';
3
+ import { SolanaErrorMessages } from './messages';
4
+
5
+ const INSTRUCTION_ERROR_RANGE_SIZE = 1000;
6
+
7
+ const enum StateType {
8
+ EscapeSequence,
9
+ Text,
10
+ Variable,
11
+ }
12
+ type State = Readonly<{
13
+ [START_INDEX]: number;
14
+ [TYPE]: StateType;
15
+ }>;
16
+ const START_INDEX = 'i';
17
+ const TYPE = 't';
18
+
19
+ export function getHumanReadableErrorMessage<TErrorCode extends SolanaErrorCode>(
20
+ code: TErrorCode,
21
+ context: object = {},
22
+ ): string {
23
+ const messageFormatString = SolanaErrorMessages[code];
24
+ if (messageFormatString.length === 0) {
25
+ return '';
26
+ }
27
+ let state: State;
28
+ function commitStateUpTo(endIndex?: number) {
29
+ if (state[TYPE] === StateType.Variable) {
30
+ const variableName = messageFormatString.slice(state[START_INDEX] + 1, endIndex);
31
+
32
+ fragments.push(
33
+ variableName in context
34
+ ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
35
+ `${context[variableName as keyof typeof context]}`
36
+ : `$${variableName}`,
37
+ );
38
+ } else if (state[TYPE] === StateType.Text) {
39
+ fragments.push(messageFormatString.slice(state[START_INDEX], endIndex));
40
+ }
41
+ }
42
+ const fragments: string[] = [];
43
+ messageFormatString.split('').forEach((char, ii) => {
44
+ if (ii === 0) {
45
+ state = {
46
+ [START_INDEX]: 0,
47
+ [TYPE]:
48
+ messageFormatString[0] === '\\'
49
+ ? StateType.EscapeSequence
50
+ : messageFormatString[0] === '$'
51
+ ? StateType.Variable
52
+ : StateType.Text,
53
+ };
54
+ return;
55
+ }
56
+ let nextState;
57
+ switch (state[TYPE]) {
58
+ case StateType.EscapeSequence:
59
+ nextState = { [START_INDEX]: ii, [TYPE]: StateType.Text };
60
+ break;
61
+ case StateType.Text:
62
+ if (char === '\\') {
63
+ nextState = { [START_INDEX]: ii, [TYPE]: StateType.EscapeSequence };
64
+ } else if (char === '$') {
65
+ nextState = { [START_INDEX]: ii, [TYPE]: StateType.Variable };
66
+ }
67
+ break;
68
+ case StateType.Variable:
69
+ if (char === '\\') {
70
+ nextState = { [START_INDEX]: ii, [TYPE]: StateType.EscapeSequence };
71
+ } else if (char === '$') {
72
+ nextState = { [START_INDEX]: ii, [TYPE]: StateType.Variable };
73
+ } else if (!char.match(/\w/)) {
74
+ nextState = { [START_INDEX]: ii, [TYPE]: StateType.Text };
75
+ }
76
+ break;
77
+ }
78
+ if (nextState) {
79
+ if (state !== nextState) {
80
+ commitStateUpTo(ii);
81
+ }
82
+ state = nextState;
83
+ }
84
+ });
85
+ commitStateUpTo();
86
+ let message = fragments.join('');
87
+ if (
88
+ code >= SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN &&
89
+ code < SOLANA_ERROR__INSTRUCTION_ERROR__UNKNOWN + INSTRUCTION_ERROR_RANGE_SIZE &&
90
+ 'index' in context
91
+ ) {
92
+ message += ` (instruction #${(context as { index: number }).index + 1})`;
93
+ }
94
+ return message;
95
+ }
96
+
97
+ export function getErrorMessage<TErrorCode extends SolanaErrorCode>(
98
+ code: TErrorCode,
99
+ context: Record<string, unknown> = {},
100
+ ): string {
101
+ if (__DEV__) {
102
+ return getHumanReadableErrorMessage(code, context);
103
+ } else {
104
+ let decodingAdviceMessage = `Solana error #${code}; Decode this error by running \`npx @solana/errors decode -- ${code}`;
105
+ if (Object.keys(context).length) {
106
+ /**
107
+ * DANGER: Be sure that the shell command is escaped in such a way that makes it
108
+ * impossible for someone to craft malicious context values that would result in
109
+ * an exploit against anyone who bindly copy/pastes it into their terminal.
110
+ */
111
+ decodingAdviceMessage += ` '${encodeContextObject(context)}'`;
112
+ }
113
+ return `${decodingAdviceMessage}\``;
114
+ }
115
+ }