Skip to content

Stop writing to the execution context in createTransactionPlanExecutor - #1913

Open
mcintyre94 wants to merge 1 commit into
tx-result-context-typesfrom
tx-executor-context-only
Open

Stop writing to the execution context in createTransactionPlanExecutor#1913
mcintyre94 wants to merge 1 commit into
tx-result-context-typesfrom
tx-executor-context-only

Conversation

@mcintyre94

@mcintyre94 mcintyre94 commented Aug 12, 2026

Copy link
Copy Markdown
Member

This PR follows from #1910

With this PR we no longer write anything to the context in createTransactionPlanExecutor. Just like the new underlying types, the caller is now in complete control of the context.

executeTransactionMessage is now Promise<TContext>. Previously it returned a Transaction, which we added (along with a derived context) to signature. As with the previous PR, now we allow the executor to completely control what TContext is and to return it. We no longer make any changes to what the executor returns.

Note that an alternative design considered was to return Promise<void>, but this loses type safety. Our success case is typed with TContext, but nothing in the type system forces an executor to write what it promises to, to context. The mutable context is only for failed/cancelled cases, which are typed Partial<TContext>. This would be a less dramatic breaking change/migration, but I think the type safety is more valuable.

This refactor means that we no longer derive the signature from a returned transaction, and we also drop the PR that refactored that to improve safety. If a signature is not on TContext then it won't be available in context, and if it is on TContext then the executor must return it itself. Similarly, the catch handler no longer derives a signature from the transaction in context.

Supersedes: #1894

Supersedes: #1906

Note that if we decide this is the correct approach, I will open a PR that makes it possible to return TContext but still allows signature and transaction (ideally deprecated). This will make migration easier, and in particular I'll update our plugins to return context before we move to v8.

@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2187418

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 48 packages
Name Type
@solana/instruction-plans Major
@solana/kit Major
@solana/plugin-interfaces Major
@solana/program-client-core Major
@solana/react Major
@solana/accounts Major
@solana/addresses Major
@solana/assertions Major
@solana/codecs-core Major
@solana/codecs-data-structures Major
@solana/codecs-numbers Major
@solana/codecs-strings Major
@solana/codecs Major
@solana/compat Major
@solana/errors Major
@solana/fast-stable-stringify Major
@solana/fixed-points Major
@solana/functional Major
@solana/instructions Major
@solana/keys Major
@solana/nominal-types Major
@solana/offchain-messages Major
@solana/options Major
@solana/plugin-core Major
@solana/programs Major
@solana/promises Major
@solana/rpc-api Major
@solana/rpc-graphql Major
@solana/rpc-parsed-types Major
@solana/rpc-spec-types Major
@solana/rpc-spec Major
@solana/rpc-subscriptions-api Major
@solana/rpc-subscriptions-channel-websocket Major
@solana/rpc-subscriptions-spec Major
@solana/rpc-subscriptions Major
@solana/rpc-transformers Major
@solana/rpc-transport-http Major
@solana/rpc-types Major
@solana/rpc Major
@solana/signers Major
@solana/subscribable Major
@solana/sysvars Major
@solana/transaction-confirmation Major
@solana/transaction-introspection Major
@solana/transaction-messages Major
@solana/transactions Major
@solana/wallet-account-signer Major
@solana/webcrypto-ed25519-polyfill Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@bundlemon

bundlemon Bot commented Aug 12, 2026

Copy link
Copy Markdown

BundleMon

Files updated (4)
Status Path Size Limits
@solana/kit production bundle
kit/dist/index.production.min.js
55.97KB (-81B -0.14%) -
instruction-plans/dist/index.browser.mjs
6.94KB (-117B -1.62%) -
instruction-plans/dist/index.native.mjs
6.94KB (-117B -1.62%) -
instruction-plans/dist/index.node.mjs
6.94KB (-118B -1.63%) -
Unchanged files (146)
Status Path Size Limits
errors/dist/index.node.mjs
21.81KB -
errors/dist/index.browser.mjs
21.79KB -
errors/dist/index.native.mjs
21.79KB -
rpc-graphql/dist/index.browser.mjs
18.82KB -
rpc-graphql/dist/index.native.mjs
18.82KB -
rpc-graphql/dist/index.node.mjs
18.82KB -
wallet-account-signer/dist/index.node.mjs
18.5KB -
wallet-account-signer/dist/index.browser.mjs
18.48KB -
wallet-account-signer/dist/index.native.mjs
18.48KB -
transaction-messages/dist/index.browser.mjs
11.34KB -
transaction-messages/dist/index.native.mjs
11.34KB -
transaction-messages/dist/index.node.mjs
11.34KB -
react/dist/index.browser.mjs
5.32KB -
react/dist/index.native.mjs
5.32KB -
react/dist/index.node.mjs
5.32KB -
codecs-data-structures/dist/index.browser.mjs
5.29KB -
codecs-data-structures/dist/index.native.mjs
5.29KB -
codecs-data-structures/dist/index.node.mjs
5.29KB -
offchain-messages/dist/index.browser.mjs
5.25KB -
offchain-messages/dist/index.native.mjs
5.24KB -
offchain-messages/dist/index.node.mjs
5.24KB -
fixed-points/dist/index.browser.mjs
5.08KB -
fixed-points/dist/index.native.mjs
5.07KB -
fixed-points/dist/index.node.mjs
5.07KB -
kit/dist/index.browser.mjs
4.86KB -
kit/dist/index.native.mjs
4.85KB -
kit/dist/index.node.mjs
4.85KB -
transactions/dist/index.browser.mjs
4.07KB -
transactions/dist/index.native.mjs
4.07KB -
transactions/dist/index.node.mjs
4.07KB -
codecs-core/dist/index.browser.mjs
3.62KB -
codecs-core/dist/index.native.mjs
3.62KB -
codecs-core/dist/index.node.mjs
3.62KB -
webcrypto-ed25519-polyfill/dist/index.node.mj
s
3.61KB -
webcrypto-ed25519-polyfill/dist/index.browser
.mjs
3.59KB -
webcrypto-ed25519-polyfill/dist/index.native.
mjs
3.57KB -
rpc-subscriptions/dist/index.browser.mjs
3.37KB -
rpc-subscriptions/dist/index.node.mjs
3.34KB -
rpc-subscriptions/dist/index.native.mjs
3.31KB -
signers/dist/index.browser.mjs
3.26KB -
signers/dist/index.native.mjs
3.26KB -
signers/dist/index.node.mjs
3.26KB -
rpc-transformers/dist/index.browser.mjs
3.18KB -
rpc-transformers/dist/index.native.mjs
3.18KB -
rpc-transformers/dist/index.node.mjs
3.18KB -
subscribable/dist/index.node.mjs
3.13KB -
keys/dist/index.node.mjs
3.06KB -
subscribable/dist/index.native.mjs
3.06KB -
subscribable/dist/index.browser.mjs
3.05KB -
addresses/dist/index.browser.mjs
2.93KB -
addresses/dist/index.native.mjs
2.92KB -
addresses/dist/index.node.mjs
2.92KB -
keys/dist/index.browser.mjs
2.85KB -
keys/dist/index.native.mjs
2.85KB -
transaction-introspection/dist/index.browser.
mjs
2.73KB -
transaction-introspection/dist/index.native.m
js
2.73KB -
transaction-introspection/dist/index.node.mjs
2.73KB -
codecs-strings/dist/index.browser.mjs
2.55KB -
codecs-strings/dist/index.node.mjs
2.51KB -
codecs-strings/dist/index.native.mjs
2.47KB -
transaction-confirmation/dist/index.node.mjs
2.42KB -
transaction-confirmation/dist/index.native.mj
s
2.37KB -
sysvars/dist/index.browser.mjs
2.37KB -
sysvars/dist/index.native.mjs
2.37KB -
transaction-confirmation/dist/index.browser.m
js
2.37KB -
sysvars/dist/index.node.mjs
2.37KB -
rpc-subscriptions-spec/dist/index.node.mjs
2.26KB -
rpc-subscriptions-spec/dist/index.native.mjs
2.21KB -
rpc-subscriptions-spec/dist/index.browser.mjs
2.21KB -
rpc/dist/index.node.mjs
1.95KB -
codecs-numbers/dist/index.browser.mjs
1.95KB -
codecs-numbers/dist/index.native.mjs
1.95KB -
codecs-numbers/dist/index.node.mjs
1.94KB -
rpc-types/dist/index.browser.mjs
1.9KB -
rpc-types/dist/index.native.mjs
1.9KB -
rpc-types/dist/index.node.mjs
1.9KB -
rpc-transport-http/dist/index.browser.mjs
1.89KB -
rpc-transport-http/dist/index.native.mjs
1.89KB -
rpc/dist/index.native.mjs
1.81KB -
rpc/dist/index.browser.mjs
1.8KB -
rpc-transport-http/dist/index.node.mjs
1.71KB -
rpc-spec-types/dist/index.browser.mjs
1.54KB -
rpc-spec-types/dist/index.native.mjs
1.54KB -
rpc-spec-types/dist/index.node.mjs
1.54KB -
rpc-subscriptions-channel-websocket/dist/inde
x.node.mjs
1.33KB -
rpc-subscriptions-channel-websocket/dist/inde
x.native.mjs
1.27KB -
rpc-subscriptions-channel-websocket/dist/inde
x.browser.mjs
1.26KB -
program-client-core/dist/index.browser.mjs
1.21KB -
program-client-core/dist/index.native.mjs
1.21KB -
program-client-core/dist/index.node.mjs
1.21KB -
plugin-core/dist/index.browser.mjs
1.18KB -
plugin-core/dist/index.native.mjs
1.18KB -
plugin-core/dist/index.node.mjs
1.18KB -
options/dist/index.browser.mjs
1.18KB -
options/dist/index.native.mjs
1.18KB -
options/dist/index.node.mjs
1.17KB -
accounts/dist/index.browser.mjs
1.17KB -
accounts/dist/index.native.mjs
1.17KB -
accounts/dist/index.node.mjs
1.16KB -
rpc-api/dist/index.browser.mjs
1.04KB -
rpc-api/dist/index.native.mjs
1.04KB -
rpc-api/dist/index.node.mjs
1.04KB -
compat/dist/index.browser.mjs
969B -
compat/dist/index.native.mjs
968B -
compat/dist/index.node.mjs
966B -
rpc-spec/dist/index.browser.mjs
928B -
rpc-spec/dist/index.native.mjs
928B -
rpc-spec/dist/index.node.mjs
926B -
rpc-subscriptions-api/dist/index.native.mjs
871B -
rpc-subscriptions-api/dist/index.browser.mjs
870B -
rpc-subscriptions-api/dist/index.node.mjs
870B -
promises/dist/index.native.mjs
841B -
promises/dist/index.node.mjs
840B -
promises/dist/index.browser.mjs
839B -
assertions/dist/index.browser.mjs
783B -
instructions/dist/index.browser.mjs
771B -
instructions/dist/index.native.mjs
770B -
instructions/dist/index.node.mjs
768B -
fast-stable-stringify/dist/index.browser.mjs
726B -
fast-stable-stringify/dist/index.native.mjs
725B -
assertions/dist/index.native.mjs
724B -
fast-stable-stringify/dist/index.node.mjs
724B -
assertions/dist/index.node.mjs
723B -
programs/dist/index.browser.mjs
329B -
programs/dist/index.native.mjs
327B -
programs/dist/index.node.mjs
325B -
fs-impl/dist/index.browser.mjs
245B -
event-target-impl/dist/index.node.mjs
230B -
functional/dist/index.browser.mjs
154B -
functional/dist/index.native.mjs
152B -
text-encoding-impl/dist/index.native.mjs
152B -
functional/dist/index.node.mjs
151B -
codecs/dist/index.browser.mjs
145B -
codecs/dist/index.native.mjs
144B -
codecs/dist/index.node.mjs
142B -
event-target-impl/dist/index.browser.mjs
133B -
ws-impl/dist/index.node.mjs
131B -
text-encoding-impl/dist/index.browser.mjs
122B -
fs-impl/dist/index.node.mjs
120B -
text-encoding-impl/dist/index.node.mjs
119B -
ws-impl/dist/index.browser.mjs
113B -
crypto-impl/dist/index.node.mjs
111B -
crypto-impl/dist/index.browser.mjs
109B -
rpc-parsed-types/dist/index.browser.mjs
66B -
rpc-parsed-types/dist/index.native.mjs
65B -
rpc-parsed-types/dist/index.node.mjs
63B -

Total files change -433B -0.08%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@mcintyre94
mcintyre94 force-pushed the tx-executor-context-only branch from 8176ffe to a7e859c Compare August 12, 2026 13:49

mcintyre94 commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@mcintyre94
mcintyre94 marked this pull request as ready for review August 12, 2026 13:58

Copy link
Copy Markdown
Member Author

@trevor-cortex

@mcintyre94 mcintyre94 added the major This would require a major version bump label Aug 12, 2026

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: LGTM (submitting as a comment since I don't have approve permissions on this repo)

Summary

This PR completes the inversion started in #1910: createTransactionPlanExecutor no longer writes anything to the execution context. The executeTransactionMessage callback now returns a complete TContext, which becomes the successful result's context (merged over whatever was stored on the mutable context, with the returned value winning). The two signature derivations are gone — the success path no longer calls getSignatureFromTransaction on a returned transaction, and the catch handler no longer derives one from a stored transaction.

This fixes two real bugs for executors producing partially signed transactions (no fee payer signature):

  1. They can now succeed — previously successfulSingleTransactionPlanResultFromTransaction would throw SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING.
  2. A failure no longer has its original error replaced by the signature-derivation throw in the catch handler.

Both are covered by new tests using the new createPartiallySignedTransaction helper, and the removal of the as unknown as TContext assertion on the happy path is a genuine type-safety win — the callback now proves the context is populated instead of the executor taking it on faith.

Things to watch

  • Return-type inference is parameter-sensitivity dependent. A zero-parameter callback gets TContext inferred from its return value, while any callback that declares parameters falls back to the default TransactionPlanResultContextWithSignature. The typetests pin this behavior explicitly (nice), but it's a subtle inference quirk that depends on TypeScript's context-sensitivity rules — worth keeping an eye on across TS version bumps, since a change in inference order would silently alter which executors compile.
  • Promise<void> vs Promise<TContext> trade-off. The PR description's reasoning holds up: void would be a gentler migration but loses the one guarantee that makes TContext on successful results honest. The typetest // Mutating the context does not discharge the obligation to return it captures exactly the failure mode the return type prevents.
  • Docs are consistent. The TSDoc on createTransactionPlanExecutor, the TransactionPlanResultContextWithSignature example, the token-airdrop example, and the changeset all tell the same story, including the "return an object built from the values you have; the mutable context itself doesn't satisfy TContext" caveat. The changeset's migration diff matches the actual new API.
  • Changeset bump. major for @solana/instruction-plans matches the scale of the break (callback return type change); consistent with the majors on the preceding PRs in this series.

Notes for subsequent reviewers

  • The merge in traverseSingle ({ ...context, ...returnedContext }) is the one spot with non-obvious runtime semantics — see the inline note about explicitly-undefined returned properties.
  • successfulSingleTransactionPlanResultFromTransaction still derives signature/transaction and still throws on missing fee payer signatures — that's intentional and called out in the changeset as the opt-in helper for the fully-signed case.
  • The return context as Context; // Never reached casts in the failure-path tests are unreachable by construction (the callback always throws before them) and only exist to satisfy the new return type; they don't weaken what the tests assert.

@mcintyre94

mcintyre94 commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

Note that if we decide this is the correct approach, I will open a PR that makes it possible to return TContext but still allows signature and transaction (ideally deprecated). This will make migration easier, and in particular I'll update our plugins to return context before we move to v8.

@lorisleiva lorisleiva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thank you!

Comment thread packages/instruction-plans/src/transaction-plan-executor.ts Outdated
The `executeTransactionMessage` callback returned a `Signature` or a `Transaction`, and the executor wrote that return value into the context — overwriting anything the callback had already stored under those keys. It also derived `context.signature` by calling `getSignatureFromTransaction` on a returned transaction, and again on any transaction found on the context while handling a failure. That call throws when the fee payer slot is empty, so an executor that deliberately produces partially signed transactions could not succeed, and one that stored such a transaction before failing had its real error replaced by `SOLANA_ERROR__TRANSACTION__FEE_PAYER_SIGNATURE_MISSING` thrown from inside the catch block.

The callback now returns `void` and the context is its only output. Neither derivation survives, which is what makes the partially signed case work: no code path asks a transaction for a signature any more, so an executor declares a `TContext` that does not require one, stores just the transaction, and gets results that honestly report no signature. The previous commit made the types admit that shape; this one makes the runtime match. `BaseTransactionPlanResultContext` goes with those derivations — it described fields the executor wrote on the caller's behalf, and nothing writes them now.

The cost is that a signature is no longer supplied on the caller's behalf. An executor whose context requires one — including the default `TransactionPlanResultContextWithSignature` — must now assign it, and failed results carry only what was stored before the throw. `successfulSingleTransactionPlanResultFromTransaction`, the last helper that derived both on its own, goes too; construct results with `successfulSingleTransactionPlanResult` and pass the context explicitly.

This supersedes #1906, which wrapped the failed-path derivation in a catch-all rather than removing it. That branch's regression test and its partially-signed-transaction fixture are carried over here.
@mcintyre94
mcintyre94 force-pushed the tx-executor-context-only branch from 67cbc21 to 2187418 Compare August 14, 2026 15:56
@mcintyre94
mcintyre94 force-pushed the tx-result-context-types branch from 50426f2 to 1ec4b6f Compare August 14, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

major This would require a major version bump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants