import type { TransactionMessage, TransactionMessageWithFeePayer } from '@solana/transaction-messages'; import type { TransactionPlan } from './transaction-plan'; import { SingleTransactionPlanResult, type TransactionPlanResult, type TransactionPlanResultContext, type TransactionPlanResultContextWithSignature } from './transaction-plan-result'; /** * Executes a transaction plan and returns the execution results. * * This function traverses the transaction plan tree, executing each transaction * message and collecting results that mirror the structure of the original plan. * * The zero-argument spelling defaults `TContext` to {@link TransactionPlanResultContextWithSignature}, * so its results guarantee a `context.signature` on every successful single result — but a different * `TContext` may drop that guarantee entirely. * * @typeParam TContext - The type of the context object that may be passed along with results. * @param transactionPlan - The transaction plan to execute. * @param config - Optional configuration object that can include an `AbortSignal` to cancel execution. * @return A promise that resolves to the execution results. * * @see {@link TransactionPlan} * @see {@link TransactionPlanResult} * @see {@link createTransactionPlanExecutor} */ export type TransactionPlanExecutor = (transactionPlan: TransactionPlan, config?: { abortSignal?: AbortSignal; }) => Promise>; type ExecuteTransactionMessage = (context: Partial, transactionMessage: TransactionMessage & TransactionMessageWithFeePayer, config?: { abortSignal?: AbortSignal; }) => Promise; /** * Configuration object for creating a new transaction plan executor. * * @see {@link createTransactionPlanExecutor} * @see {@link createTransactionPlanExecutorWithConcurrentLeaves} */ export type TransactionPlanExecutorConfig = { /** * Called whenever a transaction message must be executed. * * It should return the context that the successful result must carry — every property * `TContext` promises, which by default includes a `signature`. */ executeTransactionMessage: ExecuteTransactionMessage; }; /** * Creates a new transaction plan executor based on the provided configuration. * * The executor will traverse the provided `TransactionPlan` sequentially or in parallel, * executing each transaction message using the `executeTransactionMessage` function. * * The `executeTransactionMessage` callback receives a mutable context object as its first * argument, which can be used to incrementally store useful data as execution progresses * (e.g. the latest version of the transaction message after setting its lifetime, the * compiled and signed transaction, the transaction signature, or any custom properties). * This context is included in the resulting {@link SingleTransactionPlanResult} regardless * of the outcome. This means that if an error is thrown at any point in the callback, any * attributes already saved to the context will still be available in the plan result, which * can be useful for debugging failures or building recovery plans. * * The callback then returns the context a successful result should carry, as a complete * `TContext`. The executor writes nothing to it on the callback's behalf — notably, it does not * derive a `signature` from a stored transaction. Producing `signature` is therefore the callback's * job, and an executor that produces transactions its fee payer has not signed can simply leave the * property out and declare a `TContext` that does not require it. * * Requiring that return value is what keeps `TContext` honest: a callback that declares a context * with a required `signature` and never produces one fails to compile, rather than yielding a * result whose `context.signature` is typed but `undefined` at runtime. Note that the mutable * context cannot itself be returned — every property on it is optional, so it does not satisfy * `TContext`. Return an object built from the values you have instead: * * ```ts * executeTransactionMessage: async (context, message) => { * const transaction = await signTransactionMessageWithSigners(message); * context.transaction = transaction; // Recorded now, in case the next step throws. * const signature = getSignatureFromTransaction(transaction); * await sendAndConfirmTransaction(transaction, { commitment: 'confirmed' }); * return { signature, transaction }; * } * ``` * * The two channels serve different outcomes. Mutating the context makes a value available to a * *failed* result; returning it makes a value available to a *successful* one. On success the two * are merged, with the returned value taking precedence, so a property stored on the context but * omitted from the return value is still reported. * * `TContext` is the only thing that says what a context contains — the executor adds nothing of its * own on top, in either direction. It defaults to {@link TransactionPlanResultContextWithSignature}, * which is why the zero-type-argument spelling hands the callback the familiar `message`, * `transaction` and `signature` properties and guarantees a `context.signature` on every successful * result. Supply a different `TContext` and you get exactly that instead, so intersect one of the * base context types in if you want those properties alongside your own: * * ```ts * createTransactionPlanExecutor(config); * ``` * * Note the asymmetry between the callback's two context types. A fresh context is created for * every single transaction plan, so on entry it is empty and *every* property of `TContext` is * optional on the parameter — populating them is the callback's job, not a guarantee the executor * makes. The value it returns is a complete `TContext`, which is what lets the * `TransactionPlanExecutor` this factory returns report a successful result's context as fully * populated. Declare the properties you intend to produce as an explicit type argument to this * function; a callback cannot annotate its own context parameter with required properties, because * none of them are present when it is called. * * - If that function is successful, the executor will return a successful `TransactionPlanResult` * for that message, carrying the context the callback returned merged over the one it mutated. * - If that function throws an error, the executor will stop processing and cancel all * remaining transaction messages in the plan. The context accumulated up to the point of * failure is preserved in the resulting {@link FailedSingleTransactionPlanResult}. * - If the `abortSignal` is triggered, the executor will immediately stop processing the plan and * return a `TransactionPlanResult` with the status set to `canceled`. * * @param config - Configuration object containing the transaction message executor function. * @return A {@link TransactionPlanExecutor} function that can execute transaction plans. * * @throws {@link SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN} * if any transaction in the plan fails to execute. The error context contains a * `transactionPlanResult` property with the partial results up to the point of failure. * @throws {@link SOLANA_ERROR__INSTRUCTION_PLANS__NON_DIVISIBLE_TRANSACTION_PLANS_NOT_SUPPORTED} * if the transaction plan contains non-divisible sequential plans, which are not * supported by this executor. * * @example * ```ts * const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions }); * * const transactionPlanExecutor = createTransactionPlanExecutor({ * executeTransactionMessage: async (context, message) => { * const transaction = await signTransactionMessageWithSigners(message); * context.transaction = transaction; * const signature = getSignatureFromTransaction(transaction); * await sendAndConfirmTransaction(transaction, { commitment: 'confirmed' }); * return { signature, transaction }; * } * }); * ``` * * @see {@link TransactionPlanExecutorConfig} */ export declare function createTransactionPlanExecutor(config: TransactionPlanExecutorConfig): TransactionPlanExecutor; /** * Creates a transaction plan executor that processes every leaf concurrently. * * It takes the same configuration as {@link createTransactionPlanExecutor} and its * `executeTransactionMessage` callback follows the same contract: it receives a fresh mutable * context object for every leaf and returns the complete `TContext` a successful result must * carry. On success the two are merged with the returned value taking precedence; on a throw * the context accumulated so far is preserved in the failed result. * * The difference is the traversal. This executor preserves the input plan's nesting, order, and * divisibility in the returned result, but it does not enforce the execution dependencies * expressed by sequential plans: every leaf callback is started immediately, without a concurrency * limit. This makes it suitable for operations such as signing or serializing transactions that * can be performed independently. For the same reason, a callback that throws does not cancel the * other leaves — each one runs to completion — and non-divisible sequential plans are supported. * * The optional abort signal is forwarded to every leaf callback and enforced by the executor. * When the signal is already aborted, leaves are canceled without invoking their callbacks. When * it is aborted during execution, the executor stops waiting for active callbacks and records * failed results carrying the abort reason and the context accumulated so far — a value the * callback resolves with afterwards is discarded. * * After all leaves settle, a plan containing failed or canceled results throws a failed execution * error containing the complete result tree. * * @typeParam TContext - The context carried by each single transaction plan result. * @param config - Configuration object containing the transaction message executor function. * @return A {@link TransactionPlanExecutor} that processes all transaction plan leaves concurrently. * @throws {@link SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN} * if any leaf callback throws, any leaf is canceled, or the abort signal fires. The error * context contains a `transactionPlanResult` property with the complete result tree. * @throws {@link SOLANA_ERROR__INVARIANT_VIOLATION__INVALID_TRANSACTION_PLAN_KIND} if the plan has an unknown kind. * * @example * Signing every transaction in a plan concurrently. * ```ts * const executor = createTransactionPlanExecutorWithConcurrentLeaves<{ transaction: Transaction }>({ * executeTransactionMessage: async (_context, message) => { * const transaction = await signTransactionMessageWithSigners(message); * return { transaction }; * }, * }); * const result = await executor(transactionPlan); * ``` * * @see {@link TransactionPlanExecutorConfig} * @see {@link createTransactionPlanExecutor} */ export declare function createTransactionPlanExecutorWithConcurrentLeaves(config: TransactionPlanExecutorConfig): TransactionPlanExecutor; /** * Wraps a transaction plan execution promise to return a * {@link TransactionPlanResult} even on execution failure. * * When a transaction plan executor throws a * {@link SOLANA_ERROR__INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN} * error, this helper catches it and returns the `TransactionPlanResult` * from the error context instead of throwing. * * This allows us to handle the result of an execution in a single unified way * instead of using try/catch and examine the `TransactionPlanResult` in both * success and failure cases. * * Any other errors are re-thrown as normal. * * @typeParam TContext - The type of the context object attached to the results. Any context is * accepted, since this helper never reads from it. * @param promise - A promise returned by a transaction plan executor. * @return A promise that resolves to the transaction plan result, even if some transactions failed. * * @example * Handling failures using a single result object: * ```ts * const result = await passthroughFailedTransactionPlanExecution( * transactionPlanExecutor(transactionPlan) * ); * * const summary = summarizeTransactionPlanResult(result); * if (summary.successful) { * console.log('All transactions executed successfully'); * } else { * console.log(`${summary.successfulTransactions.length} succeeded`); * console.log(`${summary.failedTransactions.length} failed`); * console.log(`${summary.canceledTransactions.length} canceled`); * } * ``` * * @see {@link TransactionPlanResult} * @see {@link createTransactionPlanExecutor} * @see {@link summarizeTransactionPlanResult} */ export declare function passthroughFailedTransactionPlanExecution(promise: Promise>): Promise>; export declare function passthroughFailedTransactionPlanExecution(promise: Promise>): Promise>; export {}; //# sourceMappingURL=transaction-plan-executor.d.ts.map