Skip to content

Fix signMessage to pass the wallet's own account to the solana:signMessage feature - #415

Open
mcintyre94 wants to merge 1 commit into
mainfrom
fix-sign-message-account-identity
Open

Fix signMessage to pass the wallet's own account to the solana:signMessage feature#415
mcintyre94 wants to merge 1 commit into
mainfrom
fix-sign-message-account-identity

Conversation

@mcintyre94

Copy link
Copy Markdown
Member

Previously the wallet store passed the UiWalletAccount handle, which is a copy created by the wallet-ui registry.

This meant that if the wallet had a reference to the connected account, then input.account !== account

I found that Salmon wallet (open source) does this: https://github.com/Salmon-HQ/salmon-wallet-frontend/blob/f1291314e58ced515d630ffcc9f129883093b881/apps/extension/src/wallet-standard/wallet.ts#L330

We now use getWalletAccountForUiWalletAccount to get the original wallet account handle back, and pass that to the wallet feature. This is the same thing Kit does when creating the transaction signer: https://github.com/anza-xyz/kit/blob/2b841a6f795823993a9935b4da8246382dbc1694/packages/wallet-account-signer/src/wallet-account-transaction-signer.ts#L65

Note that currently this only affects the signMessage function. Transaction signing goes through the signer API (correct already), and connect/signIn don't pass the account to the wallet. This was found while testing the new signOffchainMessage which requires the same fix, but that's added in a separate PR.

The tests are also updated to make the mocked UI registry use a distinct account object from the one in the mock wallets, to properly exercise this code.

…nMessage` feature

The wallet store passed the `UiWalletAccount` handle as the `account` input when calling the wallet's `solana:signMessage` feature. That handle is a copy created by the wallet-ui registry, never the wallet's own `WalletAccount` object, so wallets that validate the input account by reference (e.g. `if (account !== this.#account) throw`) rejected every request. The store now dematerializes the handle with `getWalletAccountForUiWalletAccount` before invoking the feature — the same thing `@solana/wallet-account-signer` does for the transaction-signing path. The test harness previously shared account references between the mock raw wallet and its `UiWallet`, which made this bug undetectable; the mocks now keep them distinct, matching the real registry, and a regression test asserts the feature receives the wallet's own account by identity.
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 09e8d0e

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

This PR includes changesets to release 1 package
Name Type
@solana/kit-plugin-wallet 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

@mcintyre94

Copy link
Copy Markdown
Member Author

@trevor-cortex

@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 can't formally approve on this repo).

Summary

This PR fixes signMessage to pass the wallet's own WalletAccount object to the solana:signMessage feature instead of the UiWalletAccount handle, using getWalletAccountForUiWalletAccount to dematerialize the handle. This matters for wallets like Salmon that compare input.account by reference against their own account object. The test setup is also hardened so mock raw wallets hold distinct account objects from the UI handles, and a new test asserts the identity contract directly.

Verification

I cross-checked this against Kit's createTransactionSignerFromWalletAccount, which performs exactly the same dematerialization before invoking the raw solana:signTransaction feature. With this change, the message-signing and transaction-signing paths now treat the account boundary consistently. The mock getWalletAccountForUiWalletAccount (resolve owning wallet → find account by address → throw if absent) mirrors the real registry's semantics faithfully.

Things to note

  • Test-setup change is a repo-wide hardening, not just scaffolding for the new test. createMockRawWallet now copies accounts into distinct objects, so every existing test would catch a place where the store leaks a handle/raw identity assumption. The suite passing green after this change is itself meaningful coverage — nice touch.
  • Small behavioral shift in the stale-account race (noted inline, non-blocking): if the active account is deauthorized between the state read and the call, the failure now surfaces as a registry WalletStandardError rather than whatever the wallet would have done. This matches the transaction-signer path, so it's consistent.
  • Changeset is a patch bump for a bug fix, which matches the repo's changeset policy.

For subsequent reviewers

The upcoming signOffchainMessage PR mentioned in the description will need the same treatment — worth confirming it reuses this exact pattern (per-call dematerialization, not cached at connect time) when it lands.

Comment on lines +714 to +718
// Dematerialize the UiWalletAccount handle into the wallet's own
// WalletAccount object: the handle is a copy made by the wallet-ui
// registry, and wallets may compare the input account by reference
// against their own account.
const walletAccount = getWalletAccountForUiWalletAccount(account);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-blocking, two observations worth recording:

  1. New failure mode in the stale-account race. If the active account is deauthorized after the state read but before the standard:events change handler reconciles, getWalletAccountForUiWalletAccount throws a registry WalletStandardError (account not found) instead of letting the wallet decide what to do with a stale handle. That's consistent with what createSignerFromWalletAccount already does on the transaction path, so I think it's the right behavior — just noting it's a change.

  2. Resolving per call (rather than caching at connect time) is the right choice — the wallet's underlying account object can be regenerated on change events, so this always hands the wallet its freshest account reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants