fix(chat): Solana and Base wallets get mixed up in the connect UI - #25
Merged
Conversation
added 2 commits
August 25, 2026 23:00
Reported from production: Solana and Base get mixed up in the wallet UI. Root cause is the tie-break in use-wallet.ts. It resolved the active chain as `evmConnected ? "evm" : solanaConnected ? "solana" : null` — EVM wins, unconditionally. Nothing ever enforced the "one active wallet" invariant its own comment described: connectSolana never disconnected the EVM wallet, and connectEvm never disconnected the Solana one. Both stacks auto-reconnect on mount. So for anyone who had ever connected MetaMask, wagmi restored it, and connecting Phantom afterwards did nothing visible: the pill kept showing Base, their EVM address and their Base balance, and payments kept routing to blockrun.ai. The user had explicitly chosen Solana and the UI silently said otherwise. The stated rationale — "EVM wins the tie because it is the payable chain" — also stopped being true when Solana payments shipped. Now: - connectEvm/connectSolana each disconnect the other, but only after the wallet says yes, so a declined prompt leaves the previous wallet alone. - The chain the user last deliberately connected on is persisted, and it decides the tie if both are somehow connected — a re-authorization from inside the extension can no longer override a deliberate choice. - Switching chains clears the server session up front. Sign-in is best-effort, so without this a declined SIWS left the previous wallet's cookie serving its history next to the new wallet's address. Also renames two flags that now lie: `onBase` in the connected pill and `isOnBase` from useUsdcBalance are both true for a connected Solana wallet. They mean "on the network we can transact on", and a name that says Base invites the exact mix-up being reported.
…rong chain Two follow-ups to the tie-break fix. Solana is now the preferred chain. It leads the network chooser, and it wins the tie when both wallets are connected and the user has not chosen — which is reached only when both auto-reconnected on their own, where the tie should fall to the chain we want people on rather than to whichever extension happened to restore itself. It is also the cheaper rail: Base adds a $0.001 transaction fee per call that Solana's facilitator absorbs. The second half of the reported Base/Solana mix-up was a warm-up race. wagmi has settled by first paint while the Kit plugin is still restoring a persisted wallet, so a returning Phantom user watched the pill resolve to their EVM wallet and only then flip to Phantom. `isReady` existed for exactly this and was plumbed all the way to useAuth without a single consumer. Now, while Solana is still warming AND Solana is the remembered choice, the facade reports "not connected yet" instead of the other chain, and ConnectWallet holds its existing loading shell until isReady — so the window is rendered as neither disconnected nor wrong. The remembered chain also moved from an effect to the first client render, since it is needed the moment the warm-up resolves rather than one commit later; hydration is unaffected because it only changes the outcome when both wallets are connected, which cannot be true on the first render. Verified in a browser that the connect button still settles to enabled — gating on isReady would otherwise be a good way to disable it forever.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported from production: Solana and Base get confused in the wallet UI. Two separate causes, both in the wallet facade.
1. An EVM wallet silently masked a connected Solana one
The active chain resolved as
evmConnected ? "evm" : solanaConnected ? "solana" : null— EVM wins, unconditionally. And nothing ever enforced the "one active wallet" invariant that the same comment described:connectSolananever disconnected the EVM wallet,connectEvmnever disconnected the Solana one.Both stacks auto-reconnect on mount. So for anyone who had ever connected MetaMask, wagmi restored it, and connecting Phantom afterwards did nothing visible: the pill kept showing Base, the EVM address and the Base balance, and payments kept routing to
blockrun.ai. The user explicitly chose Solana and the UI said otherwise.The stated rationale — "EVM wins the tie because it is the payable chain" — also stopped being true when Solana payments shipped.
connectEvm/connectSolananow disconnect each other, but only after the wallet says yes, so a declined prompt leaves the previous wallet alone.2. The pill flashed the wrong chain on every reload
wagmi has settled by first paint while the Kit plugin is still restoring a persisted wallet. So a returning Phantom user watched the pill resolve to their EVM wallet and only then flip to Phantom.
isReadyexisted for precisely this, and was plumbed all the way through touseAuthwithout a single consumer. Now, while Solana is still warming and Solana is the remembered choice, the facade reports "not connected yet" rather than the other chain, andConnectWalletholds its existing loading shell untilisReady.The remembered chain also moved from an effect to the first client render — it is needed the moment the warm-up resolves, not a commit later. Hydration is unaffected: it only changes the outcome when both wallets are connected, which cannot be true on the first render.
Solana is now the default
It leads the network chooser and wins the untouched tie. That case is only reached when both wallets auto-reconnected without the user picking either, where the tie should fall to the chain we want people on. It is also the cheaper rail — Base adds a $0.001/call transaction fee that Solana's facilitator absorbs.
Also
Renames two flags that now lie:
onBasein the connected pill andisOnBasefromuseUsdcBalanceare both true for a connected Solana wallet. They mean "on the network we can transact on", and a name that says Base invites the exact mix-up being reported.Verified
isReadywould otherwise be an excellent way to disable it forever), chooser lists Solana first, Solana → wallet-list flow intact, no console errors.typecheck/eslint src/buildclean.Not covered
The real two-wallet case still needs a human with both MetaMask and Phantom installed: connect one, connect the other, confirm the pill, balance and payment routing all follow the wallet you just approved, and that history does not leak across the switch.