Skip to content

Conversation

@mcintyre94
Copy link
Member

@mcintyre94 mcintyre94 commented Sep 30, 2025

Problem

Currently this signer only returns what is required for the signer interface, a Transaction. However, per #891 this interface needs to be changed to return a Transaction & TransactionWithLifetime, ie augmented with a lifetimeConstraint field.

Summary of Changes

The signer now returns a lifetime, by decoding the returned messageBytes and comparing the returned lifetimeToken with the existing lifetime of the input transaction.

  • If the input transaction does not have a lifetime, then we create one for the signed transaction using Add a function to extract the lifetime from a CompiledTransactionMessage #918. Otherwise:
  • If the input transaction and signed transaction have identical message bytes, then we return the existing lifetime. Ie if the wallet returns the transaction unchanged.
  • If the message bytes differ, but the lifetimeToken of the signed transaction matches the existing lifetime (either blockhash or nonce field), then we return the existing lifetime. Ie if the wallet modifies the transaction but not its lifetime.
  • If the lifetime token of the signed transaction differs from that of the input transaction, then we create a new lifetime for the signed transaction using Add a function to extract the lifetime from a CompiledTransactionMessage #918.

This pre-emptively makes useWalletAccountTransactionSigner comply with a stricter interface for TransactionModifyingSigner that will require returning Transaction & TransactionWithLifetime.

@changeset-bot
Copy link

changeset-bot bot commented Sep 30, 2025

🦋 Changeset detected

Latest commit: 9895e9d

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

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

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
Copy link

bundlemon bot commented Sep 30, 2025

BundleMon

Files updated (3)
Status Path Size Limits
react/dist/index.browser.mjs
2.29KB (+307B +15.1%) -
react/dist/index.native.mjs
2.28KB (+305B +15%) -
react/dist/index.node.mjs
2.28KB (+304B +14.95%) -
Unchanged files (127)
Status Path Size Limits
@solana/kit production bundle
kit/dist/index.production.min.js
35.91KB -
rpc-graphql/dist/index.browser.mjs
18.82KB -
rpc-graphql/dist/index.native.mjs
18.81KB -
rpc-graphql/dist/index.node.mjs
18.81KB -
errors/dist/index.node.mjs
15.52KB -
errors/dist/index.browser.mjs
15.5KB -
errors/dist/index.native.mjs
15.5KB -
transaction-messages/dist/index.browser.mjs
7.25KB -
transaction-messages/dist/index.native.mjs
7.25KB -
transaction-messages/dist/index.node.mjs
7.25KB -
codecs-data-structures/dist/index.browser.mjs
4.69KB -
codecs-data-structures/dist/index.native.mjs
4.69KB -
codecs-data-structures/dist/index.node.mjs
4.69KB -
webcrypto-ed25519-polyfill/dist/index.node.mj
s
3.6KB -
webcrypto-ed25519-polyfill/dist/index.browser
.mjs
3.59KB -
webcrypto-ed25519-polyfill/dist/index.native.
mjs
3.57KB -
instruction-plans/dist/index.browser.mjs
3.42KB -
instruction-plans/dist/index.native.mjs
3.42KB -
instruction-plans/dist/index.node.mjs
3.41KB -
rpc-subscriptions/dist/index.browser.mjs
3.37KB -
rpc-subscriptions/dist/index.node.mjs
3.34KB -
rpc-subscriptions/dist/index.native.mjs
3.31KB -
codecs-core/dist/index.browser.mjs
3.31KB -
codecs-core/dist/index.native.mjs
3.31KB -
codecs-core/dist/index.node.mjs
3.31KB -
addresses/dist/index.browser.mjs
2.93KB -
rpc-transformers/dist/index.browser.mjs
2.93KB -
rpc-transformers/dist/index.native.mjs
2.93KB -
addresses/dist/index.native.mjs
2.93KB -
addresses/dist/index.node.mjs
2.93KB -
rpc-transformers/dist/index.node.mjs
2.93KB -
transactions/dist/index.browser.mjs
2.71KB -
transactions/dist/index.native.mjs
2.71KB -
transactions/dist/index.node.mjs
2.71KB -
signers/dist/index.browser.mjs
2.63KB -
signers/dist/index.native.mjs
2.63KB -
signers/dist/index.node.mjs
2.62KB -
codecs-strings/dist/index.browser.mjs
2.53KB -
codecs-strings/dist/index.node.mjs
2.48KB -
codecs-strings/dist/index.native.mjs
2.45KB -
transaction-confirmation/dist/index.node.mjs
2.41KB -
transaction-confirmation/dist/index.native.mj
s
2.36KB -
transaction-confirmation/dist/index.browser.m
js
2.35KB -
sysvars/dist/index.browser.mjs
2.35KB -
sysvars/dist/index.native.mjs
2.34KB -
sysvars/dist/index.node.mjs
2.34KB -
rpc-subscriptions-spec/dist/index.node.mjs
2.18KB -
rpc-subscriptions-spec/dist/index.native.mjs
2.13KB -
rpc-subscriptions-spec/dist/index.browser.mjs
2.13KB -
keys/dist/index.browser.mjs
2.08KB -
keys/dist/index.native.mjs
2.08KB -
keys/dist/index.node.mjs
2.08KB -
codecs-numbers/dist/index.native.mjs
2.01KB -
codecs-numbers/dist/index.browser.mjs
2.01KB -
codecs-numbers/dist/index.node.mjs
2.01KB -
rpc/dist/index.node.mjs
1.95KB -
rpc-transport-http/dist/index.browser.mjs
1.91KB -
rpc-transport-http/dist/index.native.mjs
1.9KB -
rpc/dist/index.native.mjs
1.8KB -
subscribable/dist/index.node.mjs
1.8KB -
rpc/dist/index.browser.mjs
1.8KB -
subscribable/dist/index.native.mjs
1.75KB -
subscribable/dist/index.browser.mjs
1.74KB -
rpc-transport-http/dist/index.node.mjs
1.72KB -
kit/dist/index.browser.mjs
1.68KB -
kit/dist/index.native.mjs
1.68KB -
kit/dist/index.node.mjs
1.67KB -
rpc-types/dist/index.browser.mjs
1.53KB -
rpc-types/dist/index.native.mjs
1.53KB -
rpc-types/dist/index.node.mjs
1.53KB -
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 -
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.13KB -
accounts/dist/index.native.mjs
1.12KB -
accounts/dist/index.node.mjs
1.12KB -
rpc-api/dist/index.browser.mjs
976B -
rpc-api/dist/index.native.mjs
975B -
rpc-api/dist/index.node.mjs
973B -
compat/dist/index.browser.mjs
969B -
compat/dist/index.native.mjs
968B -
compat/dist/index.node.mjs
966B -
rpc-spec-types/dist/index.browser.mjs
962B -
rpc-spec-types/dist/index.native.mjs
961B -
rpc-spec-types/dist/index.node.mjs
959B -
rpc-subscriptions-api/dist/index.native.mjs
870B -
rpc-subscriptions-api/dist/index.node.mjs
869B -
rpc-subscriptions-api/dist/index.browser.mjs
868B -
rpc-spec/dist/index.browser.mjs
852B -
rpc-spec/dist/index.native.mjs
851B -
rpc-spec/dist/index.node.mjs
850B -
promises/dist/index.browser.mjs
799B -
promises/dist/index.native.mjs
798B -
promises/dist/index.node.mjs
797B -
assertions/dist/index.browser.mjs
783B -
instructions/dist/index.browser.mjs
769B -
instructions/dist/index.native.mjs
768B -
instructions/dist/index.node.mjs
767B -
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 -
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
137B -
codecs/dist/index.native.mjs
136B -
codecs/dist/index.node.mjs
134B -
event-target-impl/dist/index.browser.mjs
133B -
ws-impl/dist/index.node.mjs
131B -
text-encoding-impl/dist/index.browser.mjs
122B -
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 +916B +0.25%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@mcintyre94 mcintyre94 marked this pull request as ready for review September 30, 2025 15:26
@github-actions
Copy link
Contributor

github-actions bot commented Sep 30, 2025

Documentation Preview: https://kit-docs-ralhklsip-anza-tech.vercel.app

@mcintyre94 mcintyre94 changed the base branch from transaction-lifetime-construct to graphite-base/919 October 1, 2025 10:15
@mcintyre94 mcintyre94 changed the base branch from graphite-base/919 to transaction-lifetime-construct October 1, 2025 10:35
@mcintyre94 mcintyre94 force-pushed the wallet-hook-lifetime branch from 0c17c5d to 28ae2b1 Compare October 1, 2025 10:36
Copy link
Collaborator

@steveluscher steveluscher left a comment

Choose a reason for hiding this comment

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

Added via Giphy

} catch (e) {
if (isSolanaError(e, SOLANA_ERROR__TRANSACTION__NONCE_ACCOUNT_CANNOT_BE_IN_LOOKUP_TABLE)) {
throw new SolanaError(SOLANA_ERROR__SIGNER__NONCE_ACCOUNT_CANNOT_BE_IN_LOOKUP_TABLE, {
signedTransaction,
Copy link
Collaborator

Choose a reason for hiding this comment

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

So, I presume that you attach the signedTransaction to the error context in the case that the caller wants to say ‘I don't care about the lifetime, just give me the damn signature.’

Tricky. We can't stuff signatures into the error context for fear of logging, but I can't think of another way of getting them out of this callback.

How about we just punt on this case, throw your error, and offer no remediation. To say it back to you, the conditions that would have to stack up for this error to ever get hit are:

  • A proposer would need to be using nonces, AND
  • That nonce account would have to be found only in an address lookup table, AND
  • Someone would have to be using a Wallet Standard wallet to sign that transaction, AND
  • The wallet would have had to have modified the transaction before signing

Other than being a lot of ducks to line up, all in a row, for things to go wrong, I think it's actually pretty unlikely. The primary use case for nonce transactions is to make it possible for multiple signers to participate in a transactions in cases where it will take longer than the expiry of a blockhash to organize all of them. If you have a wallet modifying the transaction message in such a case, your signature will mismatch with the others unless their wallets also made the exact same modification. Unlikely.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep it was just to provide a recovery mechanism when you get that SOLANA_ERROR__TRANSACTION__NONCE_ACCOUNT_CANNOT_BE_IN_LOOKUP_TABLE edge case. I figured that you'd still get the signed transaction in your app, you just wouldn't have a lifetime - but you might be able to recover depending what your app needs.

I agree it's unlikely and that logging the transaction is too dangerous. I'll just drop the custom error here, not worth the risk and - as you say - very unlikely to happen in practice.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay I've removed the specific error and error handling here. It'll just throw SOLANA_ERROR__TRANSACTION__NONCE_ACCOUNT_CANNOT_BE_IN_LOOKUP_TABLE in this annoying edge case now.

address: string;
};
[SOLANA_ERROR__SIGNER__NONCE_ACCOUNT_CANNOT_BE_IN_LOOKUP_TABLE]: {
signedTransaction: Uint8Array;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We have to keep in mind that in production, the error context object gets serialized and logged. We must keep sensitive information out of error context, and this probably qualifies as sensitive enough to exclude.

@mcintyre94 mcintyre94 force-pushed the wallet-hook-lifetime branch from 28ae2b1 to 8a6cc93 Compare October 1, 2025 20:30
@mcintyre94 mcintyre94 force-pushed the transaction-lifetime-construct branch from 525e9ae to 11136da Compare October 1, 2025 20:30
Copy link
Member

@lorisleiva lorisleiva left a comment

Choose a reason for hiding this comment

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

Looks good to me!

@mcintyre94 mcintyre94 force-pushed the transaction-lifetime-construct branch from 11136da to 7171940 Compare October 2, 2025 10:07
@mcintyre94 mcintyre94 force-pushed the wallet-hook-lifetime branch from 8a6cc93 to 3536388 Compare October 2, 2025 10:07
Copy link
Collaborator

@steveluscher steveluscher left a comment

Choose a reason for hiding this comment

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

Added via Giphy

Copy link
Member Author

mcintyre94 commented Oct 3, 2025

Merge activity

  • Oct 3, 10:36 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Oct 3, 10:38 AM UTC: Graphite rebased this pull request as part of a merge.
  • Oct 3, 10:39 AM UTC: @mcintyre94 merged this pull request with Graphite.

@mcintyre94 mcintyre94 changed the base branch from transaction-lifetime-construct to graphite-base/919 October 3, 2025 10:36
@mcintyre94 mcintyre94 changed the base branch from graphite-base/919 to main October 3, 2025 10:36
@mcintyre94 mcintyre94 force-pushed the wallet-hook-lifetime branch from 3536388 to 9895e9d Compare October 3, 2025 10:37
@mcintyre94 mcintyre94 merged commit c87cada into main Oct 3, 2025
13 checks passed
@mcintyre94 mcintyre94 deleted the wallet-hook-lifetime branch October 3, 2025 10:39
mcintyre94 added a commit that referenced this pull request Oct 3, 2025
…#927)

#### Problem

The `TransactionModifyingSigner` was previously typed to return the exact same type `T` as its input transactions. This is incorrect, as we always intended for signers to be able to eg change the lifetime of a transaction. In practice one example of `TransactionModifyingSigner` returns signed transactions from wallet-standard wallets, which can make arbitrary changes to the transaction before signing it.

This also meant that such a signer could not return a `TransactionWithLifetime`, ie a `lifetimeConstraint` field. Casts in signer functions would treat the signer as having returned the expected type, but there was no type safety.

In practice, this meant `useWalletAccountTransactionSigner` and third-party `TransactionModifyingSigner` did not return a `lifetimeConstraint`, but appeared to do so to Typescript. This led to a runtime error when attempting to confirm transactions (#891) 

#### Summary of Changes

This PR changes the type of modifying signers to:
**input**: (Transaction | (Transaction & TransactionWithLifetime))[]
**output**: (Transaction & TransactionWithLifetime & TransactionWithinSizeLimit)[]

Note that an upstream PR (#919) already made `useWalletAccountTransactionSigner` satisfy the `TransactionWithLifetime` part of this interface.

It also changes the return type of `signModifyingAndPartialTransactionSigners` to `Transaction & TransactionWithLifetime & TransactionWithinSizeLimit`, and removes the cast to the expected return type that limited type safety in this function.

Fixes #891
@github-actions
Copy link
Contributor

Because there has been no activity on this PR for 14 days since it was merged, it has been automatically locked. Please open a new issue if it requires a follow up.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 20, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants