Fix signMessage to pass the wallet's own account to the solana:signMessage feature - #415
Fix signMessage to pass the wallet's own account to the solana:signMessage feature#415mcintyre94 wants to merge 1 commit into
signMessage to pass the wallet's own account to the solana:signMessage feature#415Conversation
…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 detectedLatest commit: 09e8d0e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
trevor-cortex
left a comment
There was a problem hiding this comment.
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.
createMockRawWalletnow 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
WalletStandardErrorrather than whatever the wallet would have done. This matches the transaction-signer path, so it's consistent. - Changeset is a
patchbump 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.
| // 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); |
There was a problem hiding this comment.
Non-blocking, two observations worth recording:
-
New failure mode in the stale-account race. If the active account is deauthorized after the
stateread but before thestandard:eventschange handler reconciles,getWalletAccountForUiWalletAccountthrows a registryWalletStandardError(account not found) instead of letting the wallet decide what to do with a stale handle. That's consistent with whatcreateSignerFromWalletAccountalready does on the transaction path, so I think it's the right behavior — just noting it's a change. -
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.
Previously the wallet store passed the
UiWalletAccounthandle, 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 !== accountI 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
getWalletAccountForUiWalletAccountto 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#L65Note that currently this only affects the
signMessagefunction. 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 newsignOffchainMessagewhich 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.