createTransactionPlanExecutor

function createTransactionPlanExecutor<TContext>(
    config,
): TransactionPlanExecutor<TContext>;

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 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:

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 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:

createTransactionPlanExecutor<TransactionPlanResultContextWithSignature & { startedAt: number }>(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 FailedSingleTransactionPlanResult.
  • If the abortSignal is triggered, the executor will immediately stop processing the plan and return a TransactionPlanResult with the status set to canceled.

Type Parameters

Parameters

ParameterTypeDescription
configTransactionPlanExecutorConfig<TContext>Configuration object containing the transaction message executor function.

Returns

TransactionPlanExecutor<TContext>

A TransactionPlanExecutor function that can execute transaction plans.

Throws

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

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

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

TransactionPlanExecutorConfig

On this page