import { SOLANA_ERROR__INSTRUCTION_PLANS__EXPECTED_SUCCESSFUL_TRANSACTION_PLAN_RESULT, SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_SINGLE_TRANSACTION_PLAN_RESULT_NOT_FOUND, SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, SolanaError, } from '@solana/errors'; import { Signature } from '@solana/keys'; import { TransactionMessage, TransactionMessageWithFeePayer } from '@solana/transaction-messages'; import { Transaction } from '@solana/transactions'; /** * The result of executing a transaction plan. * * This is structured as a recursive tree of results that mirrors the structure * of the original transaction plan, capturing the execution status at each level. * * Namely, the following result types are supported: * - {@link SingleTransactionPlanResult} - A result for a single transaction message * containing its execution status. * - {@link ParallelTransactionPlanResult} - A result containing other results that * were executed in parallel. * - {@link SequentialTransactionPlanResult} - A result containing other results that * were executed sequentially. It also retains the divisibility property from the * original plan. * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * @typeParam TSingle - The type of single transaction plan results in this tree * * @see {@link SingleTransactionPlanResult} * @see {@link ParallelTransactionPlanResult} * @see {@link SequentialTransactionPlanResult} */ export type TransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, > = | ParallelTransactionPlanResult | SequentialTransactionPlanResult | TSingle; /** * A {@link TransactionPlanResult} where all single transaction results are successful. * * This type represents a transaction plan result tree where every * {@link SingleTransactionPlanResult} has a 'successful' status. It can be used * to ensure that an entire execution completed without any failures or cancellations. * * Note: This is different from {@link SuccessfulSingleTransactionPlanResult} which * represents a single successful transaction, whereas this type represents an entire * plan result tree (which may contain parallel/sequential structures) where all * leaf nodes are successful. * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * * @see {@link isSuccessfulTransactionPlanResult} * @see {@link assertIsSuccessfulTransactionPlanResult} * @see {@link SuccessfulSingleTransactionPlanResult} */ export type SuccessfulTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, > = TransactionPlanResult< TContext, TTransactionMessage, SuccessfulSingleTransactionPlanResult >; /** * The context object associated with a {@link SingleTransactionPlanResult}. * * This type defines the shape of custom context that can be attached to * transaction plan results. It allows arbitrary additional properties that * consumers can use to pass along extra data with their results. * * Note that base context fields such as `message`, `signature`, and `transaction` are not part of * this type. They come from the context a result is parameterised with, which defaults to * {@link TransactionPlanResultContextWithSignature}. Supply a different context to change or drop * those guarantees. * * @see {@link SingleTransactionPlanResult} * @see {@link SuccessfulSingleTransactionPlanResult} * @see {@link FailedSingleTransactionPlanResult} * @see {@link CanceledSingleTransactionPlanResult} * @see {@link TransactionPlanResultContextWithSignature} */ export type TransactionPlanResultContext = { [key: number | string | symbol]: unknown }; /** * A {@link TransactionPlanResultContext} that is guaranteed to include a {@link Signature}. * * This is the default context for every transaction plan result type, which is why a successful * result exposes a required `context.signature` unless a different context is supplied. * * @example * Intersect this type into a custom context to keep the signature guarantee alongside your own * properties. * ```ts * const executor = createTransactionPlanExecutor< * TransactionPlanResultContextWithSignature & { startedAt: number } * >({ * executeTransactionMessage: async (context, message) => { * const startedAt = Date.now(); * context.startedAt = startedAt; * const signature = await sendAndConfirm(message); * return { signature, startedAt }; * }, * }); * ``` * * @see {@link TransactionPlanResultContext} */ export type TransactionPlanResultContextWithSignature = TransactionPlanResultContext & { message?: TransactionMessage & TransactionMessageWithFeePayer; signature: Signature; transaction?: Transaction; }; /** * A result for a sequential transaction plan. * * This represents the execution result of a {@link SequentialTransactionPlan} and * contains child results that were executed sequentially. It also retains the * divisibility property from the original plan. * * You may use the {@link sequentialTransactionPlanResult} and * {@link nonDivisibleSequentialTransactionPlanResult} helpers to create objects of this type. * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * @typeParam TSingle - The type of single transaction plan results in this tree * * @example * ```ts * const result = sequentialTransactionPlanResult([ * singleResultA, * singleResultB, * ]); * result satisfies SequentialTransactionPlanResult; * ``` * * @example * Non-divisible sequential result. * ```ts * const result = nonDivisibleSequentialTransactionPlanResult([ * singleResultA, * singleResultB, * ]); * result satisfies SequentialTransactionPlanResult & { divisible: false }; * ``` * * @see {@link sequentialTransactionPlanResult} * @see {@link nonDivisibleSequentialTransactionPlanResult} */ export type SequentialTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, > = Readonly<{ divisible: boolean; kind: 'sequential'; planType: 'transactionPlanResult'; plans: TransactionPlanResult[]; }>; /** * A result for a parallel transaction plan. * * This represents the execution result of a {@link ParallelTransactionPlan} and * contains child results that were executed in parallel. * * You may use the {@link parallelTransactionPlanResult} helper to create objects of this type. * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * @typeParam TSingle - The type of single transaction plan results in this tree * * @example * ```ts * const result = parallelTransactionPlanResult([ * singleResultA, * singleResultB, * ]); * result satisfies ParallelTransactionPlanResult; * ``` * * @see {@link parallelTransactionPlanResult} */ export type ParallelTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, > = Readonly<{ kind: 'parallel'; planType: 'transactionPlanResult'; plans: TransactionPlanResult[]; }>; /** * A result for a single transaction plan. * * This represents the execution result of a {@link SingleTransactionPlan} and * contains the original transaction message along with its execution status. * * You may use the {@link successfulSingleTransactionPlanResult}, * {@link failedSingleTransactionPlanResult}, or {@link canceledSingleTransactionPlanResult} * helpers to create objects of this type. * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * * @example * Successful result with a signature in its context. * ```ts * const result = successfulSingleTransactionPlanResult( * transactionMessage, * { signature }, * ); * result satisfies SingleTransactionPlanResult; * ``` * * @example * Failed result with an error. * ```ts * const result = failedSingleTransactionPlanResult( * transactionMessage, * new SolanaError(SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_FEE), * ); * result satisfies SingleTransactionPlanResult; * ``` * * @example * Canceled result. * ```ts * const result = canceledSingleTransactionPlanResult(transactionMessage); * result satisfies SingleTransactionPlanResult; * ``` * * @see {@link successfulSingleTransactionPlanResult} * @see {@link failedSingleTransactionPlanResult} * @see {@link canceledSingleTransactionPlanResult} */ export type SingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, > = | CanceledSingleTransactionPlanResult | FailedSingleTransactionPlanResult | SuccessfulSingleTransactionPlanResult; /** * A {@link SingleTransactionPlanResult} with a 'successful' status. * * This type represents a single transaction that was successfully executed. * It includes the original planned message and a context object. That context defaults to * {@link TransactionPlanResultContextWithSignature} — a required transaction {@link Signature}, * and optionally the {@link TransactionMessage} and the full {@link Transaction} object — but a * different `TContext` may drop the signature requirement entirely. * * You may use the {@link successfulSingleTransactionPlanResult} helper to * create objects of this type. * * @typeParam TContext - The type of the context object that may be passed along with the result. * @typeParam TTransactionMessage - The type of the transaction message. * * @example * Creating a successful result from a transaction message and signature. * ```ts * const result = successfulSingleTransactionPlanResult( * transactionMessage, * { signature }, * ); * result satisfies SuccessfulSingleTransactionPlanResult; * result.context.signature; // The transaction signature. * ``` * * @see {@link successfulSingleTransactionPlanResult} * @see {@link isSuccessfulSingleTransactionPlanResult} * @see {@link assertIsSuccessfulSingleTransactionPlanResult} */ export type SuccessfulSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, > = { context: Readonly; kind: 'single'; planType: 'transactionPlanResult'; plannedMessage: TTransactionMessage; status: 'successful'; }; /** * A {@link SingleTransactionPlanResult} with a 'failed' status. * * This type represents a single transaction that failed during execution. * It includes the original planned message, the {@link Error} that caused * the failure, and a context object in which every field is optional — including * {@link TransactionMessage}, {@link Signature}, and {@link Transaction} * fields that may or may not be populated depending on how far execution * progressed before the failure. * * You may use the {@link failedSingleTransactionPlanResult} helper to * create objects of this type. * * @typeParam TContext - The type of the context object that may be passed along with the result. * @typeParam TTransactionMessage - The type of the transaction message. * * @example * Creating a failed result from a transaction message and error. * ```ts * const result = failedSingleTransactionPlanResult( * transactionMessage, * new Error('Transaction simulation failed'), * ); * result satisfies FailedSingleTransactionPlanResult; * result.error; // The error that caused the failure. * ``` * * @see {@link failedSingleTransactionPlanResult} * @see {@link isFailedSingleTransactionPlanResult} * @see {@link assertIsFailedSingleTransactionPlanResult} */ export type FailedSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, > = { context: Readonly>; error: Error; kind: 'single'; planType: 'transactionPlanResult'; plannedMessage: TTransactionMessage; status: 'failed'; }; /** * A {@link SingleTransactionPlanResult} with a 'canceled' status. * * This type represents a single transaction whose execution was canceled * before it could complete. It includes the original planned message and * a context object in which every field is optional — including * {@link TransactionMessage}, {@link Signature}, and {@link Transaction} * fields that may or may not be populated depending on how far execution * progressed before cancellation. * * You may use the {@link canceledSingleTransactionPlanResult} helper to * create objects of this type. * * @typeParam TContext - The type of the context object that may be passed along with the result. * @typeParam TTransactionMessage - The type of the transaction message. * * @example * Creating a canceled result from a transaction message. * ```ts * const result = canceledSingleTransactionPlanResult(transactionMessage); * result satisfies CanceledSingleTransactionPlanResult; * ``` * * @see {@link canceledSingleTransactionPlanResult} * @see {@link isCanceledSingleTransactionPlanResult} * @see {@link assertIsCanceledSingleTransactionPlanResult} */ export type CanceledSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, > = { context: Readonly>; kind: 'single'; planType: 'transactionPlanResult'; plannedMessage: TTransactionMessage; status: 'canceled'; }; /** * Creates a divisible {@link SequentialTransactionPlanResult} from an array of nested results. * * This function creates a sequential result with the `divisible` property set to `true`, * indicating that the nested plans were executed sequentially but could have been * split into separate transactions or batches. * * @typeParam TContext - The type of the context object that may be passed along with results * @param plans - The child results that were executed sequentially * * @example * ```ts * const result = sequentialTransactionPlanResult([ * singleResultA, * singleResultB, * ]); * result satisfies SequentialTransactionPlanResult & { divisible: true }; * ``` * * @see {@link SequentialTransactionPlanResult} */ export function sequentialTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, >(plans: TransactionPlanResult[]): SequentialTransactionPlanResult & { divisible: true } { return Object.freeze({ divisible: true, kind: 'sequential', planType: 'transactionPlanResult', plans }); } /** * Creates a non-divisible {@link SequentialTransactionPlanResult} from an array of nested results. * * This function creates a sequential result with the `divisible` property set to `false`, * indicating that the nested plans were executed sequentially and could not have been * split into separate transactions or batches (e.g., they were executed as a transaction bundle). * * @typeParam TContext - The type of the context object that may be passed along with results * @param plans - The child results that were executed sequentially * * @example * ```ts * const result = nonDivisibleSequentialTransactionPlanResult([ * singleResultA, * singleResultB, * ]); * result satisfies SequentialTransactionPlanResult & { divisible: false }; * ``` * * @see {@link SequentialTransactionPlanResult} */ export function nonDivisibleSequentialTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, >(plans: TransactionPlanResult[]): SequentialTransactionPlanResult & { divisible: false } { return Object.freeze({ divisible: false, kind: 'sequential', planType: 'transactionPlanResult', plans }); } /** * Creates a {@link ParallelTransactionPlanResult} from an array of nested results. * * This function creates a parallel result indicating that the nested plans * were executed in parallel. * * @typeParam TContext - The type of the context object that may be passed along with results * @param plans - The child results that were executed in parallel * * @example * ```ts * const result = parallelTransactionPlanResult([ * singleResultA, * singleResultB, * ]); * result satisfies ParallelTransactionPlanResult; * ``` * * @see {@link ParallelTransactionPlanResult} */ export function parallelTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, >(plans: TransactionPlanResult[]): ParallelTransactionPlanResult { return Object.freeze({ kind: 'parallel', planType: 'transactionPlanResult', plans }); } /** * Creates a successful {@link SingleTransactionPlanResult} from a transaction message and context. * * This function creates a single result with a 'successful' status, indicating that * the transaction was successfully executed. It also includes the original transaction * message and a context object, which becomes the result's `context` as-is, typed as * `TContext`. Passing a context containing a `signature` — the common case — yields the * default {@link TransactionPlanResultContextWithSignature} shape; supply a different * context shape when the transaction has no fee payer signature to report. * * @typeParam TContext - The type of the context object * @typeParam TTransactionMessage - The type of the transaction message * @param plannedMessage - The original transaction message * @param context - The context object to be included with the result, as `TContext` * * @example * ```ts * const result = successfulSingleTransactionPlanResult( * transactionMessage, * { signature }, * ); * result satisfies SingleTransactionPlanResult; * ``` * * @see {@link SingleTransactionPlanResult} */ export function successfulSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plannedMessage: TTransactionMessage, context: TContext, ): SuccessfulSingleTransactionPlanResult { return Object.freeze({ context: Object.freeze({ ...context }), kind: 'single', planType: 'transactionPlanResult', plannedMessage, status: 'successful', }); } /** * Creates a failed {@link SingleTransactionPlanResult} from a transaction message and error. * * This function creates a single result with a 'failed' status, indicating that * the transaction execution failed. It includes the original transaction message * and the error that caused the failure. * * @typeParam TContext - The type of the context object * @typeParam TTransactionMessage - The type of the transaction message * @param plannedMessage - The original transaction message * @param error - The error that caused the transaction to fail * * @example * ```ts * const result = failedSingleTransactionPlanResult( * transactionMessage, * new SolanaError({ * code: 123, * message: 'Transaction simulation failed', * }), * ); * result satisfies SingleTransactionPlanResult; * ``` * * @see {@link SingleTransactionPlanResult} */ export function failedSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plannedMessage: TTransactionMessage, error: Error, context?: Partial, ): FailedSingleTransactionPlanResult { return Object.freeze({ context: Object.freeze({ ...((context ?? {}) as Partial) }), error, kind: 'single', planType: 'transactionPlanResult', plannedMessage, status: 'failed', }); } /** * Creates a canceled {@link SingleTransactionPlanResult} from a transaction message. * * This function creates a single result with a 'canceled' status, indicating that * the transaction execution was canceled. It includes the original transaction message. * * @typeParam TContext - The type of the context object * @typeParam TTransactionMessage - The type of the transaction message * @param plannedMessage - The original transaction message * * @example * ```ts * const result = canceledSingleTransactionPlanResult(transactionMessage); * result satisfies SingleTransactionPlanResult; * ``` * * @see {@link SingleTransactionPlanResult} */ export function canceledSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plannedMessage: TTransactionMessage, context?: Partial, ): CanceledSingleTransactionPlanResult { return Object.freeze({ context: Object.freeze({ ...((context ?? {}) as Partial) }), kind: 'single', planType: 'transactionPlanResult', plannedMessage, status: 'canceled', }); } /** * Checks if the given value is a {@link TransactionPlanResult}. * * This type guard checks the `planType` property to determine if the value * is a transaction plan result. This is useful when you have a value that could be * an {@link InstructionPlan}, {@link TransactionPlan}, or {@link TransactionPlanResult} * and need to narrow the type. * * @param value - The value to check. * @return `true` if the value is a transaction plan result, `false` otherwise. * * @example * ```ts * function processItem(item: InstructionPlan | TransactionPlan | TransactionPlanResult) { * if (isTransactionPlanResult(item)) { * // item is narrowed to TransactionPlanResult * console.log(item.kind); * } * } * ``` * * @see {@link TransactionPlanResult} * @see {@link isInstructionPlan} * @see {@link isTransactionPlan} */ export function isTransactionPlanResult(value: unknown): value is TransactionPlanResult { return ( typeof value === 'object' && value !== null && 'planType' in value && typeof value.planType === 'string' && value.planType === 'transactionPlanResult' ); } /** * Checks if the given transaction plan result is a {@link SingleTransactionPlanResult}. * * @param plan - The transaction plan result to check. * @return `true` if the result is a single transaction plan result, `false` otherwise. * * @example * ```ts * const result: TransactionPlanResult = successfulSingleTransactionPlanResult(message, { signature }); * * if (isSingleTransactionPlanResult(result)) { * console.log(result.status); // TypeScript knows this is a SingleTransactionPlanResult. * } * ``` * * @see {@link SingleTransactionPlanResult} * @see {@link assertIsSingleTransactionPlanResult} */ export function isSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >(plan: TransactionPlanResult): plan is TSingle { return plan.kind === 'single'; } /** * Asserts that the given transaction plan result is a {@link SingleTransactionPlanResult}. * * @param plan - The transaction plan result to assert. * @throws Throws a {@link SolanaError} with code * `SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT` if the result is not a single transaction plan result. * * @example * ```ts * const result: TransactionPlanResult = successfulSingleTransactionPlanResult(message, { signature }); * * assertIsSingleTransactionPlanResult(result); * console.log(result.status); // TypeScript knows this is a SingleTransactionPlanResult. * ``` * * @see {@link SingleTransactionPlanResult} * @see {@link isSingleTransactionPlanResult} */ export function assertIsSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >(plan: TransactionPlanResult): asserts plan is TSingle { if (!isSingleTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind, expectedKind: 'single', transactionPlanResult: plan, }); } } /** * Checks if the given transaction plan result is a successful {@link SingleTransactionPlanResult}. * * @param plan - The transaction plan result to check. * @return `true` if the result is a successful single transaction plan result, `false` otherwise. * * @example * ```ts * const result: TransactionPlanResult = successfulSingleTransactionPlanResult(message, { signature }); * * if (isSuccessfulSingleTransactionPlanResult(result)) { * console.log(result.context.signature); // TypeScript knows this is a successful result. * } * ``` * * @see {@link SuccessfulSingleTransactionPlanResult} * @see {@link assertIsSuccessfulSingleTransactionPlanResult} */ export function isSuccessfulSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plan: TransactionPlanResult, ): plan is SuccessfulSingleTransactionPlanResult { return plan.kind === 'single' && plan.status === 'successful'; } /** * Asserts that the given transaction plan result is a successful {@link SingleTransactionPlanResult}. * * @param plan - The transaction plan result to assert. * @throws Throws a {@link SolanaError} with code * `SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT` if the result is not a successful single transaction plan result. * * @example * ```ts * const result: TransactionPlanResult = successfulSingleTransactionPlanResult(message, { signature }); * * assertIsSuccessfulSingleTransactionPlanResult(result); * console.log(result.context.signature); // TypeScript knows this is a successful result. * ``` * * @see {@link SuccessfulSingleTransactionPlanResult} * @see {@link isSuccessfulSingleTransactionPlanResult} */ export function assertIsSuccessfulSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plan: TransactionPlanResult, ): asserts plan is SuccessfulSingleTransactionPlanResult { if (!isSuccessfulSingleTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind === 'single' ? `${plan.status} single` : plan.kind, expectedKind: 'successful single', transactionPlanResult: plan, }); } } /** * Checks if the given transaction plan result is a failed {@link SingleTransactionPlanResult}. * * @param plan - The transaction plan result to check. * @return `true` if the result is a failed single transaction plan result, `false` otherwise. * * @example * ```ts * const result: TransactionPlanResult = failedSingleTransactionPlanResult(message, error); * * if (isFailedSingleTransactionPlanResult(result)) { * console.log(result.error); // TypeScript knows this is a failed result. * } * ``` * * @see {@link FailedSingleTransactionPlanResult} * @see {@link assertIsFailedSingleTransactionPlanResult} */ export function isFailedSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plan: TransactionPlanResult, ): plan is FailedSingleTransactionPlanResult { return plan.kind === 'single' && plan.status === 'failed'; } /** * Asserts that the given transaction plan result is a failed {@link SingleTransactionPlanResult}. * * @param plan - The transaction plan result to assert. * @throws Throws a {@link SolanaError} with code * `SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT` if the result is not a failed single transaction plan result. * * @example * ```ts * const result: TransactionPlanResult = failedSingleTransactionPlanResult(message, error); * * assertIsFailedSingleTransactionPlanResult(result); * console.log(result.error); // TypeScript knows this is a failed result. * ``` * * @see {@link FailedSingleTransactionPlanResult} * @see {@link isFailedSingleTransactionPlanResult} */ export function assertIsFailedSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plan: TransactionPlanResult, ): asserts plan is FailedSingleTransactionPlanResult { if (!isFailedSingleTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind === 'single' ? `${plan.status} single` : plan.kind, expectedKind: 'failed single', transactionPlanResult: plan, }); } } /** * Checks if the given transaction plan result is a canceled {@link SingleTransactionPlanResult}. * * @param plan - The transaction plan result to check. * @return `true` if the result is a canceled single transaction plan result, `false` otherwise. * * @example * ```ts * const result: TransactionPlanResult = canceledSingleTransactionPlanResult(message); * * if (isCanceledSingleTransactionPlanResult(result)) { * console.log('Transaction was canceled'); // TypeScript knows this is a canceled result. * } * ``` * * @see {@link CanceledSingleTransactionPlanResult} * @see {@link assertIsCanceledSingleTransactionPlanResult} */ export function isCanceledSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plan: TransactionPlanResult, ): plan is CanceledSingleTransactionPlanResult { return plan.kind === 'single' && plan.status === 'canceled'; } /** * Asserts that the given transaction plan result is a canceled {@link SingleTransactionPlanResult}. * * @param plan - The transaction plan result to assert. * @throws Throws a {@link SolanaError} with code * `SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT` if the result is not a canceled single transaction plan result. * * @example * ```ts * const result: TransactionPlanResult = canceledSingleTransactionPlanResult(message); * * assertIsCanceledSingleTransactionPlanResult(result); * console.log('Transaction was canceled'); // TypeScript knows this is a canceled result. * ``` * * @see {@link CanceledSingleTransactionPlanResult} * @see {@link isCanceledSingleTransactionPlanResult} */ export function assertIsCanceledSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plan: TransactionPlanResult, ): asserts plan is CanceledSingleTransactionPlanResult { if (!isCanceledSingleTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind === 'single' ? `${plan.status} single` : plan.kind, expectedKind: 'canceled single', transactionPlanResult: plan, }); } } /** * Checks if the given transaction plan result is a {@link SequentialTransactionPlanResult}. * * @param plan - The transaction plan result to check. * @return `true` if the result is a sequential transaction plan result, `false` otherwise. * * @example * ```ts * const result: TransactionPlanResult = sequentialTransactionPlanResult([resultA, resultB]); * * if (isSequentialTransactionPlanResult(result)) { * console.log(result.divisible); // TypeScript knows this is a SequentialTransactionPlanResult. * } * ``` * * @see {@link SequentialTransactionPlanResult} * @see {@link assertIsSequentialTransactionPlanResult} */ export function isSequentialTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >( plan: TransactionPlanResult, ): plan is SequentialTransactionPlanResult { return plan.kind === 'sequential'; } /** * Asserts that the given transaction plan result is a {@link SequentialTransactionPlanResult}. * * @param plan - The transaction plan result to assert. * @throws Throws a {@link SolanaError} with code * `SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT` if the result is not a sequential transaction plan result. * * @example * ```ts * const result: TransactionPlanResult = sequentialTransactionPlanResult([resultA, resultB]); * * assertIsSequentialTransactionPlanResult(result); * console.log(result.divisible); // TypeScript knows this is a SequentialTransactionPlanResult. * ``` * * @see {@link SequentialTransactionPlanResult} * @see {@link isSequentialTransactionPlanResult} */ export function assertIsSequentialTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >( plan: TransactionPlanResult, ): asserts plan is SequentialTransactionPlanResult { if (!isSequentialTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind, expectedKind: 'sequential', transactionPlanResult: plan, }); } } /** * Checks if the given transaction plan result is a non-divisible {@link SequentialTransactionPlanResult}. * * A non-divisible sequential result indicates that the transactions were executed * atomically — usually in a transaction bundle. * * @param plan - The transaction plan result to check. * @return `true` if the result is a non-divisible sequential transaction plan result, `false` otherwise. * * @example * ```ts * const result: TransactionPlanResult = nonDivisibleSequentialTransactionPlanResult([resultA, resultB]); * * if (isNonDivisibleSequentialTransactionPlanResult(result)) { * // Transactions were executed atomically. * } * ``` * * @see {@link SequentialTransactionPlanResult} * @see {@link assertIsNonDivisibleSequentialTransactionPlanResult} */ export function isNonDivisibleSequentialTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >( plan: TransactionPlanResult, ): plan is SequentialTransactionPlanResult & { divisible: false } { return plan.kind === 'sequential' && plan.divisible === false; } /** * Asserts that the given transaction plan result is a non-divisible {@link SequentialTransactionPlanResult}. * * A non-divisible sequential result indicates that the transactions were executed * atomically — usually in a transaction bundle. * * @param plan - The transaction plan result to assert. * @throws Throws a {@link SolanaError} with code * `SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT` if the result is not a non-divisible sequential transaction plan result. * * @example * ```ts * const result: TransactionPlanResult = nonDivisibleSequentialTransactionPlanResult([resultA, resultB]); * * assertIsNonDivisibleSequentialTransactionPlanResult(result); * // Transactions were executed atomically. * ``` * * @see {@link SequentialTransactionPlanResult} * @see {@link isNonDivisibleSequentialTransactionPlanResult} */ export function assertIsNonDivisibleSequentialTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >( plan: TransactionPlanResult, ): asserts plan is SequentialTransactionPlanResult & { divisible: false } { if (!isNonDivisibleSequentialTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind === 'sequential' ? 'divisible sequential' : plan.kind, expectedKind: 'non-divisible sequential', transactionPlanResult: plan, }); } } /** * Checks if the given transaction plan result is a {@link ParallelTransactionPlanResult}. * * @param plan - The transaction plan result to check. * @return `true` if the result is a parallel transaction plan result, `false` otherwise. * * @example * ```ts * const result: TransactionPlanResult = parallelTransactionPlanResult([resultA, resultB]); * * if (isParallelTransactionPlanResult(result)) { * console.log(result.plans.length); // TypeScript knows this is a ParallelTransactionPlanResult. * } * ``` * * @see {@link ParallelTransactionPlanResult} * @see {@link assertIsParallelTransactionPlanResult} */ export function isParallelTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >( plan: TransactionPlanResult, ): plan is ParallelTransactionPlanResult { return plan.kind === 'parallel'; } /** * Asserts that the given transaction plan result is a {@link ParallelTransactionPlanResult}. * * @param plan - The transaction plan result to assert. * @throws Throws a {@link SolanaError} with code * `SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT` if the result is not a parallel transaction plan result. * * @example * ```ts * const result: TransactionPlanResult = parallelTransactionPlanResult([resultA, resultB]); * * assertIsParallelTransactionPlanResult(result); * console.log(result.plans.length); // TypeScript knows this is a ParallelTransactionPlanResult. * ``` * * @see {@link ParallelTransactionPlanResult} * @see {@link isParallelTransactionPlanResult} */ export function assertIsParallelTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >( plan: TransactionPlanResult, ): asserts plan is ParallelTransactionPlanResult { if (!isParallelTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__UNEXPECTED_TRANSACTION_PLAN_RESULT, { actualKind: plan.kind, expectedKind: 'parallel', transactionPlanResult: plan, }); } } /** * Checks if the given transaction plan result is a {@link SuccessfulTransactionPlanResult}. * * This function verifies that the entire transaction plan result tree contains only * successful single transaction results. It recursively checks all nested results * to ensure every {@link SingleTransactionPlanResult} has a 'successful' status. * * Note: This is different from {@link isSuccessfulSingleTransactionPlanResult} which * checks if a single result is successful. This function checks that the entire * plan result tree (including all nested parallel/sequential structures) contains * only successful transactions. * * @param plan - The transaction plan result to check. * @return `true` if all single transaction results in the tree are successful, `false` otherwise. * * @example * ```ts * const result: TransactionPlanResult = parallelTransactionPlanResult([ * successfulSingleTransactionPlanResult(messageA, { signature: signatureA }), * successfulSingleTransactionPlanResult(messageB, { signature: signatureB }), * ]); * * if (isSuccessfulTransactionPlanResult(result)) { * // All transactions were successful. * result satisfies SuccessfulTransactionPlanResult; * } * ``` * * @see {@link SuccessfulTransactionPlanResult} * @see {@link assertIsSuccessfulTransactionPlanResult} * @see {@link isSuccessfulSingleTransactionPlanResult} */ export function isSuccessfulTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plan: TransactionPlanResult, ): plan is SuccessfulTransactionPlanResult { return everyTransactionPlanResult( plan, r => !isSingleTransactionPlanResult(r) || isSuccessfulSingleTransactionPlanResult(r), ); } /** * Asserts that the given transaction plan result is a {@link SuccessfulTransactionPlanResult}. * * This function verifies that the entire transaction plan result tree contains only * successful single transaction results. It throws if any {@link SingleTransactionPlanResult} * in the tree has a 'failed' or 'canceled' status. * * Note: This is different from {@link assertIsSuccessfulSingleTransactionPlanResult} which * asserts that a single result is successful. This function asserts that the entire * plan result tree (including all nested parallel/sequential structures) contains * only successful transactions. * * @param plan - The transaction plan result to assert. * @throws Throws a {@link SolanaError} with code * `SOLANA_ERROR__INSTRUCTION_PLANS__EXPECTED_SUCCESSFUL_TRANSACTION_PLAN_RESULT` if * any single transaction result in the tree is not successful. * * @example * ```ts * const result: TransactionPlanResult = parallelTransactionPlanResult([ * successfulSingleTransactionPlanResult(messageA, { signature: signatureA }), * successfulSingleTransactionPlanResult(messageB, { signature: signatureB }), * ]); * * assertIsSuccessfulTransactionPlanResult(result); * // All transactions were successful. * result satisfies SuccessfulTransactionPlanResult; * ``` * * @see {@link SuccessfulTransactionPlanResult} * @see {@link isSuccessfulTransactionPlanResult} * @see {@link assertIsSuccessfulSingleTransactionPlanResult} */ export function assertIsSuccessfulTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( plan: TransactionPlanResult, ): asserts plan is SuccessfulTransactionPlanResult { if (!isSuccessfulTransactionPlanResult(plan)) { throw new SolanaError(SOLANA_ERROR__INSTRUCTION_PLANS__EXPECTED_SUCCESSFUL_TRANSACTION_PLAN_RESULT, { transactionPlanResult: plan, }); } } /** * Finds the first transaction plan result in the tree that matches the given predicate. * * This function performs a depth-first search through the transaction plan result tree, * returning the first result that satisfies the predicate. It checks the root result * first, then recursively searches through nested results. * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * @typeParam TSingle - The type of single transaction plan results in this tree * @param transactionPlanResult - The transaction plan result tree to search. * @param predicate - A function that returns `true` for the result to find. * @returns The first matching transaction plan result, or `undefined` if no match is found. * * @example * Finding a failed transaction result. * ```ts * const result = parallelTransactionPlanResult([ * successfulSingleTransactionPlanResult(messageA, { signature: signatureA }), * failedSingleTransactionPlanResult(messageB, error), * ]); * * const failed = findTransactionPlanResult( * result, * (r) => r.kind === 'single' && r.status === 'failed', * ); * // Returns the failed single transaction plan result for messageB. * ``` * * @see {@link TransactionPlanResult} * @see {@link everyTransactionPlanResult} * @see {@link transformTransactionPlanResult} * @see {@link flattenTransactionPlanResult} */ export function findTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >( transactionPlanResult: TransactionPlanResult, predicate: (result: TransactionPlanResult) => boolean, ): TransactionPlanResult | undefined { if (predicate(transactionPlanResult)) { return transactionPlanResult; } if (transactionPlanResult.kind === 'single') { return undefined; } for (const subResult of transactionPlanResult.plans) { const foundResult = findTransactionPlanResult(subResult, predicate); if (foundResult) { return foundResult; } } return undefined; } /** * Retrieves the first failed transaction plan result from a transaction plan result tree. * * This function searches the transaction plan result tree using a depth-first traversal * and returns the first single transaction result with a 'failed' status. If no failed * result is found, it throws a {@link SolanaError}. * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * @param transactionPlanResult - The transaction plan result tree to search. * @return The first failed single transaction plan result. * @throws Throws a {@link SolanaError} with code * `SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_SINGLE_TRANSACTION_PLAN_RESULT_NOT_FOUND` if no * failed transaction plan result is found. The error context contains a non-enumerable * `transactionPlanResult` property for recovery purposes. * * @example * Retrieving the first failed result from a parallel execution. * ```ts * const result = parallelTransactionPlanResult([ * successfulSingleTransactionPlanResult(messageA, { signature: signatureA }), * failedSingleTransactionPlanResult(messageB, error), * failedSingleTransactionPlanResult(messageC, anotherError), * ]); * * const firstFailed = getFirstFailedSingleTransactionPlanResult(result); * // Returns the failed result for messageB. * ``` * * @see {@link FailedSingleTransactionPlanResult} * @see {@link findTransactionPlanResult} */ export function getFirstFailedSingleTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( transactionPlanResult: TransactionPlanResult, ): FailedSingleTransactionPlanResult { const result = findTransactionPlanResult(transactionPlanResult, r => r.kind === 'single' && r.status === 'failed'); if (!result) { // Here we want the `transactionPlanResult` to be available in the error context // so applications can recover but we don't want this object to be // serialized with the error. This is why we set it as a non-enumerable property. const context = {}; Object.defineProperty(context, 'transactionPlanResult', { configurable: false, enumerable: false, value: transactionPlanResult, writable: false, }); throw new SolanaError( SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_SINGLE_TRANSACTION_PLAN_RESULT_NOT_FOUND, context, ); } return result as FailedSingleTransactionPlanResult; } /** * Checks if every transaction plan result in the tree satisfies the given predicate. * * This function performs a depth-first traversal through the transaction plan result tree, * returning `true` only if the predicate returns `true` for every result in the tree * (including the root result and all nested results). * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * @typeParam TSingle - The type of single transaction plan results in this tree * @param transactionPlanResult - The transaction plan result tree to check. * @param predicate - A function that returns `true` if the result satisfies the condition. * @return `true` if every result in the tree satisfies the predicate, `false` otherwise. * * @example * Checking if all transactions were successful. * ```ts * const result = parallelTransactionPlanResult([ * successfulSingleTransactionPlanResult(messageA, { signature: signatureA }), * successfulSingleTransactionPlanResult(messageB, { signature: signatureB }), * ]); * * const allSuccessful = everyTransactionPlanResult( * result, * (r) => r.kind !== 'single' || r.status === 'successful', * ); * // Returns true because all single results are successful. * ``` * * @example * Checking if no transactions were canceled. * ```ts * const result = sequentialTransactionPlanResult([resultA, resultB, resultC]); * * const noCanceled = everyTransactionPlanResult( * result, * (r) => r.kind !== 'single' || r.status !== 'canceled', * ); * ``` * * @see {@link TransactionPlanResult} * @see {@link findTransactionPlanResult} * @see {@link transformTransactionPlanResult} * @see {@link flattenTransactionPlanResult} */ export function everyTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >( transactionPlanResult: TransactionPlanResult, predicate: (plan: TransactionPlanResult) => boolean, ): boolean { if (!predicate(transactionPlanResult)) { return false; } if (transactionPlanResult.kind === 'single') { return true; } return transactionPlanResult.plans.every(p => everyTransactionPlanResult(p, predicate)); } /** * Transforms a transaction plan result tree using a bottom-up approach. * * This function recursively traverses the transaction plan result tree, applying the * transformation function to each result. The transformation is applied bottom-up, * meaning nested results are transformed first, then the parent results receive * the already-transformed children before being transformed themselves. * * All transformed results are frozen using `Object.freeze` to ensure immutability. * * @param transactionPlanResult - The transaction plan result tree to transform. * @param fn - A function that transforms each result and returns a new result. * @return A new transformed transaction plan result tree. * * @example * Converting all canceled results to failed results. * ```ts * const result = parallelTransactionPlanResult([ * successfulSingleTransactionPlanResult(messageA, { signature: signatureA }), * canceledSingleTransactionPlanResult(messageB), * ]); * * const transformed = transformTransactionPlanResult(result, (r) => { * if (r.kind === 'single' && r.status === 'canceled') { * return failedSingleTransactionPlanResult(r.plannedMessage, new Error('Execution canceled')); * } * return r; * }); * ``` * * @see {@link TransactionPlanResult} * @see {@link findTransactionPlanResult} * @see {@link everyTransactionPlanResult} * @see {@link flattenTransactionPlanResult} */ export function transformTransactionPlanResult( transactionPlanResult: TransactionPlanResult, fn: (plan: TransactionPlanResult) => TransactionPlanResult, ): TransactionPlanResult { if (transactionPlanResult.kind === 'single') { return Object.freeze(fn(transactionPlanResult)); } return Object.freeze( fn( Object.freeze({ ...transactionPlanResult, plans: transactionPlanResult.plans.map(p => transformTransactionPlanResult(p, fn)), }), ), ); } /** * Retrieves all individual {@link SingleTransactionPlanResult} instances from a transaction plan result tree. * * This function recursively traverses any nested structure of transaction plan results and extracts * all the single results they contain. It's useful when you need to access all the individual * transaction results, regardless of their organization in the result tree (parallel or sequential). * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * @typeParam TSingle - The type of single transaction plan results in this tree * @param result - The transaction plan result to extract single results from * @returns An array of all single transaction plan results contained in the tree * * @example * ```ts * const result = parallelTransactionPlanResult([ * sequentialTransactionPlanResult([resultA, resultB]), * nonDivisibleSequentialTransactionPlanResult([resultC, resultD]), * resultE, * ]); * * const singleResults = flattenTransactionPlanResult(result); * // Array of `SingleTransactionPlanResult` containing: * // resultA, resultB, resultC, resultD and resultE. * ``` * * @see {@link TransactionPlanResult} * @see {@link findTransactionPlanResult} * @see {@link everyTransactionPlanResult} * @see {@link transformTransactionPlanResult} */ export function flattenTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, TSingle extends SingleTransactionPlanResult = SingleTransactionPlanResult< TContext, TTransactionMessage >, >(result: TransactionPlanResult): TSingle[] { if (result.kind === 'single') { return [result]; } return result.plans.flatMap(flattenTransactionPlanResult); } /** * A summary of a {@link TransactionPlanResult}, categorizing transactions by their execution status. * - `successful`: Indicates whether all transactions were successful (i.e., no failed or canceled transactions). * - `successfulTransactions`: An array of successful transactions, each including its signature. * - `failedTransactions`: An array of failed transactions, each including the error that caused the failure. * - `canceledTransactions`: An array of canceled transactions. */ export type TransactionPlanResultSummary< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, > = Readonly<{ canceledTransactions: CanceledSingleTransactionPlanResult[]; failedTransactions: FailedSingleTransactionPlanResult[]; successful: boolean; successfulTransactions: SuccessfulSingleTransactionPlanResult[]; }>; /** * Summarize a {@link TransactionPlanResult} into a {@link TransactionPlanResultSummary}. * * @typeParam TContext - The type of the context object that may be passed along with results * @typeParam TTransactionMessage - The type of the transaction message * @param result The transaction plan result to summarize * @returns A summary of the transaction plan result */ export function summarizeTransactionPlanResult< TContext extends TransactionPlanResultContext = TransactionPlanResultContextWithSignature, TTransactionMessage extends TransactionMessage & TransactionMessageWithFeePayer = TransactionMessage & TransactionMessageWithFeePayer, >( result: TransactionPlanResult, ): TransactionPlanResultSummary { type Out = TransactionPlanResultSummary; const successfulTransactions: Out['successfulTransactions'] = []; const failedTransactions: Out['failedTransactions'] = []; const canceledTransactions: Out['canceledTransactions'] = []; const flattenedResults = flattenTransactionPlanResult(result); for (const singleResult of flattenedResults) { switch (singleResult.status) { case 'successful': { successfulTransactions.push(singleResult); break; } case 'failed': { failedTransactions.push(singleResult); break; } case 'canceled': { canceledTransactions.push(singleResult); break; } } } return Object.freeze({ canceledTransactions, failedTransactions, successful: failedTransactions.length === 0 && canceledTransactions.length === 0, successfulTransactions, }); }