diff --git a/.changeset/pretty-clubs-press.md b/.changeset/pretty-clubs-press.md new file mode 100644 index 000000000..204fc8616 --- /dev/null +++ b/.changeset/pretty-clubs-press.md @@ -0,0 +1,5 @@ +--- +'@solana/errors': minor +--- + +Mark the `cause` deprecated for `SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN` error diff --git a/packages/errors/src/__typetests__/error-typetest.ts b/packages/errors/src/__typetests__/error-typetest.ts index 7773e6dfa..e80c372ab 100644 --- a/packages/errors/src/__typetests__/error-typetest.ts +++ b/packages/errors/src/__typetests__/error-typetest.ts @@ -1,7 +1,7 @@ import * as SolanaErrorCodeModule from '../codes'; import { SolanaErrorCode, SolanaErrorCodeWithCause } from '../codes'; import { SolanaErrorContext } from '../context'; -import { isSolanaError, SolanaError } from '../error'; +import { isSolanaError, SolanaError, SolanaErrorWithDeprecatedCause } from '../error'; const { SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING, SOLANA_ERROR__TRANSACTION__SIGNATURES_MISSING } = SolanaErrorCodeModule; @@ -66,3 +66,19 @@ null as unknown as SolanaErrorContext satisfies { // Special errors have a nested `cause` property that is an optional `SolanaError` const errorWithCause = null as unknown as SolanaError; errorWithCause.cause satisfies SolanaError | undefined; + +// Errors that have a deprecated `cause` property should satisfy `SolanaErrorWithDeprecatedCause` +// when narrowed with `isSolanaError` +{ + const e = null as unknown; + if (isSolanaError(e)) { + // @ts-expect-error Not all SolanaErrors have deprecated cause + e satisfies SolanaErrorWithDeprecatedCause; + } + // This one does + if (isSolanaError(e, SolanaErrorCodeModule.SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN)) { + e satisfies SolanaErrorWithDeprecatedCause; + // context still works + e.context.transactionPlanResult satisfies unknown; + } +} diff --git a/packages/errors/src/codes.ts b/packages/errors/src/codes.ts index fc686d2c5..2e102622b 100644 --- a/packages/errors/src/codes.ts +++ b/packages/errors/src/codes.ts @@ -624,3 +624,10 @@ export type SolanaErrorCode = * `cause`. */ export type SolanaErrorCodeWithCause = typeof SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE; + +/** + * Errors of this type have a deprecated `cause` property. Consumers should use the error's + * `context` instead to access relevant error information. + */ +export type SolanaErrorCodeWithDeprecatedCause = + typeof SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN; diff --git a/packages/errors/src/error.ts b/packages/errors/src/error.ts index f7a4ef2d5..f169cc610 100644 --- a/packages/errors/src/error.ts +++ b/packages/errors/src/error.ts @@ -1,7 +1,24 @@ -import { SolanaErrorCode, SolanaErrorCodeWithCause } from './codes'; +import { SolanaErrorCode, SolanaErrorCodeWithCause, SolanaErrorCodeWithDeprecatedCause } from './codes'; import { SolanaErrorContext } from './context'; import { getErrorMessage } from './message-formatter'; +/** + * A variant of {@link SolanaError} where the `cause` property is deprecated. + * + * This type is returned by {@link isSolanaError} when checking for error codes in + * {@link SolanaErrorCodeWithDeprecatedCause}. Accessing `cause` on these errors will show + * a deprecation warning in IDEs that support JSDoc `@deprecated` tags. + */ +export interface SolanaErrorWithDeprecatedCause< + TErrorCode extends SolanaErrorCodeWithDeprecatedCause = SolanaErrorCodeWithDeprecatedCause, +> extends Omit, 'cause'> { + /** + * @deprecated The `cause` property is deprecated for this error code. + * Use the error's `context` property instead to access relevant error information. + */ + readonly cause?: unknown; +} + /** * A type guard that returns `true` if the input is a {@link SolanaError}, optionally with a * particular error code. @@ -44,6 +61,14 @@ import { getErrorMessage } from './message-formatter'; * } * ``` */ +export function isSolanaError( + e: unknown, + code: TErrorCode, +): e is SolanaErrorWithDeprecatedCause; +export function isSolanaError( + e: unknown, + code?: TErrorCode, +): e is SolanaError; export function isSolanaError( e: unknown, /** diff --git a/packages/errors/src/messages.ts b/packages/errors/src/messages.ts index c1b3f4274..f0168ba13 100644 --- a/packages/errors/src/messages.ts +++ b/packages/errors/src/messages.ts @@ -433,7 +433,7 @@ export const SolanaErrorMessages: Readonly<{ [SOLANA_ERROR__INSTRUCTION_PLANS__NON_DIVISIBLE_TRANSACTION_PLANS_NOT_SUPPORTED]: 'This transaction plan executor does not support non-divisible sequential plans. To support them, you may create your own executor such that multi-transaction atomicity is preserved — e.g. by targetting RPCs that support transaction bundles.', [SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN]: - 'The provided transaction plan failed to execute. See the `transactionPlanResult` attribute and the `cause` error for more details.', + 'The provided transaction plan failed to execute. See the `transactionPlanResult` attribute for more details. Note that the `cause` property is deprecated, and a future version will not set it.', [SOLANA_ERROR__INSTRUCTION_PLANS__MESSAGE_CANNOT_ACCOMMODATE_PLAN]: 'The provided message has insufficient capacity to accommodate the next instruction(s) in this plan. Expected at least $numBytesRequired free byte(s), got $numFreeBytes byte(s).', [SOLANA_ERROR__INVARIANT_VIOLATION__INVALID_TRANSACTION_PLAN_KIND]: 'Invalid transaction plan kind: $kind.',