From 25c9f3a7576c4348088cec00fbae9961b483f3ca Mon Sep 17 00:00:00 2001 From: Alex Donesky Date: Wed, 4 Feb 2026 14:46:15 -0600 Subject: [PATCH] fix: resolve Solana connect race condition causing WalletNotSelectedError The previous implementation called select() and connect() synchronously, but connect() executed before React state updated from select(), causing WalletNotSelectedError. Now uses an effect to wait for wallet selection before initiating connection. Co-authored-by: Cursor --- playground/browser-playground/CHANGELOG.md | 4 +++ playground/browser-playground/src/App.tsx | 30 ++++++++++++++----- .../src/sdk/SolanaProvider.tsx | 2 -- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/playground/browser-playground/CHANGELOG.md b/playground/browser-playground/CHANGELOG.md index 3b6b21b6..62ccf4d4 100644 --- a/playground/browser-playground/CHANGELOG.md +++ b/playground/browser-playground/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING**: Update connect/disconnect button labels to include connector names ("Connect" → "Connect (Multichain)", "Disconnect" → "Disconnect (Multichain)" / "Disconnect All") for clarity and consistency with other connector buttons +### Fixed + +- Fix Solana connect race condition that caused `WalletNotSelectedError` when extension not installed + ## [0.1.1] ### Added diff --git a/playground/browser-playground/src/App.tsx b/playground/browser-playground/src/App.tsx index f100632c..aabce6a5 100644 --- a/playground/browser-playground/src/App.tsx +++ b/playground/browser-playground/src/App.tsx @@ -71,11 +71,26 @@ function App() { connected: solanaConnected, publicKey: solanaPublicKey, wallets, + wallet: solanaWallet, select, connect: solanaConnect, disconnect: solanaDisconnect, } = useWallet(); + // Track when user has requested a Solana connection + const [solanaPendingConnect, setSolanaPendingConnect] = useState(false); + + // Effect to connect once wallet is selected (avoids race condition between select and connect) + useEffect(() => { + if (solanaPendingConnect && solanaWallet && !solanaConnected) { + solanaConnect().catch((error) => { + console.error('Solana connection error:', error); + }).finally(() => { + setSolanaPendingConnect(false); + }); + } + }, [solanaPendingConnect, solanaWallet, solanaConnected, solanaConnect]); + const handleCheckboxChange = useCallback( (value: string, isChecked: boolean) => { if (isChecked) { @@ -161,22 +176,21 @@ function App() { } }, [customScopes, connectors, wagmiConnectAsync]); - const connectSolana = useCallback(async () => { + const connectSolana = useCallback(() => { // Find the MetaMask wallet in registered wallets const metamaskWallet = wallets.find((w) => w.adapter.name.toLowerCase().includes('metamask connect'), ); if (metamaskWallet) { - try { - select(metamaskWallet.adapter.name); - await solanaConnect(); - } catch (error) { - console.error('Solana connection error:', error); - } + // Select the wallet and set pending connect flag. + // The useEffect will call connect() once the wallet state updates, + // avoiding the race condition between select() and connect(). + select(metamaskWallet.adapter.name); + setSolanaPendingConnect(true); } else { console.error('MetaMask wallet not found in registered wallets'); } - }, [wallets, select, solanaConnect]); + }, [wallets, select]); const isConnected = status === 'connected'; const isDisconnected = diff --git a/playground/browser-playground/src/sdk/SolanaProvider.tsx b/playground/browser-playground/src/sdk/SolanaProvider.tsx index ae94e2f9..a00722e4 100644 --- a/playground/browser-playground/src/sdk/SolanaProvider.tsx +++ b/playground/browser-playground/src/sdk/SolanaProvider.tsx @@ -1,5 +1,4 @@ import { createSolanaClient, type SolanaClient } from '@metamask/connect-solana'; -import { METAMASK_PROD_CHROME_ID } from '@metamask/playground-ui'; import { ConnectionProvider, WalletProvider, @@ -10,7 +9,6 @@ import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'; import type React from 'react'; import { createContext, - useCallback, useContext, useEffect, useMemo,