Okay, so most people screw this up big time-they jump straight into npm installing a million packages without thinking about their React version. Boom. Errors everywhere. React 18? Forget it, the current Solana Wallet Adapter doesn't play nice with it yet. Stick to React 17 if you're adding this to an existing project. That's the first thing I tell anyone asking me for help. In my experience, skipping this check wastes like two hours of your life.
Why does this happen? The adapter's built around older React hooks and contexts that clash with 18's concurrent features. Sound familiar? Happened to me last week on a side project. Fixed it by downgrading. Now, let's do this right from scratch so you don't end up rage quitting.
Don't reinvent the wheel. The Solana team has this thing called the dApp Scaffold-it's basically a pre baked app with Wallet Adapter all wired up. Perfect for learning or prototyping.
mkdir my solana dapp then cd my solana dapp.git clone https://github.com/solana labs/dapp scaffold.git . Yeah, that dot at the end pulls it right into your folder.npm install or yarn if that's your jam.npm i @solana/spl token.npm run dev. Hit localhost:3000. Boom, wallet connect button right there.You'll see a clean UI with a WalletMultiButton in the top right. Click it, pick Phantom or whatever, connect. It even has airdrop and sign message buttons pre built. Pretty much instant gratification. I usually test this first before building anything custom.
Look, every Solana dApp you've used-those popups listing Phantom, Solflare, Slope? That's the Wallet Adapter at work. It's a TypeScript library with ready made components and adapters for like 12+ wallets out of the box. No more writing custom connect logic for each one. The community keeps it updated, so it just works.
The thing is, it handles the messy stuff: detecting installed wallets, injecting providers, signing transactions. You just drop in a button and go. But honestly, without understanding the contexts, you'll hit walls later.
Jump to src/contexts/ContextProvider.tsx in the scaffold. This is your control center. It wraps your whole app with providers for connection, wallets, and modals.
clusterApiUrl('devnet'). Swap to mainnet later with 'https://api.mainnet beta.solana.com' or your QuickNode URL.PhantomWalletAdapter(), SolflareWalletAdapter(). Comment one out, refresh-poof, it's gone from the dropdown. Super handy for testing specific wallets.Pro tip: If a wallet vanishes from the list, check your browser extensions. Sometimes Phantom's sneaky and doesn't register right. Reload, approve permissions.
Say no scaffold for you. Fine. New React app, Next.js whatever. Install these exact packages:
npm install --save \ @solana/web3.js \ @solana/wallet adapter base \ @solana/wallet adapter react \ @solana/wallet adapter react ui \ @solana/wallet adapter wallets
That's it for basics. Add @solana mobile/wallet adapter mobile if you want mobile wallet support later. Now, wrap your app.
In your root component (like _app.tsx or App.tsx), set up providers like this:
import { ConnectionProvider, WalletProvider } from '@solana/wallet adapter react';
import { WalletModalProvider } from '@solana/wallet adapter react ui';
import { WalletAdapterNetwork } from '@solana/wallet adapter base';
import { PhantomWalletAdapter } from '@solana/wallet adapter wallets'; // Add more as needed const network = WalletAdapterNetwork.Devnet;
const endpoint = 'https://api.devnet.solana.com';
const wallets = useMemo(() => [new PhantomWalletAdapter()], []); function App() { return ( <ConnectionProvider endpoint={endpoint}> <WalletProvider wallets={wallets} autoConnect> <WalletModalProvider> <YourApp /> </WalletModalProvider> </WalletProvider> </ConnectionProvider> );
}
Copy paste that. Tweak wallets array for Solflare, Backpack, etc. useMemo keeps it from recreating on every render-perf win.
Easiest UI piece: <WalletMultiButton />. Import from @solana/wallet adapter react ui. Slap it anywhere inside your WalletProvider wrapped app.
Like: <WalletMultiButton className="btn btn ghost mr-4" />. Styles it with Tailwind or whatever. Click, select wallet, approve. Done. Shows balance, disconnect option.
What's next? Hook into the wallet state in your components.
Now the fun part-using the connected wallet. Use hooks from @solana/wallet adapter react.
useConnection(): Gets your RPC connection.useWallet(): PublicKey, signTransaction, connected status.useAnchorWallet(): If you're using Anchor framework later.Example: Fetch balance.
import { useConnection, useWallet } from '@solana/wallet adapter react';
import { LAMPORTSPERSOL } from '@solana/web3.js'; function BalancePanel() { const { connection } = useConnection(); const { publicKey } = useWallet(); const [balance, setBalance] = useState(0); useEffect(() => { if (publicKey) { connection.getBalance(publicKey).then(bal => { setBalance(bal / LAMPORTSPERSOL); }); } }, [publicKey, connection]); if (!publicKey) return <p>Connect wallet first!</p>; return <p>Balance: {balance.toFixed(4)} SOL</p>;
}
Throws an error if no wallet? Add a check: if (!wallet.connected || !wallet.publicKey) return null;. I usually wrap this in a try catch for network flakes.
Let's make something real. Say you want a button to list all SPL tokens in the connected wallet. (Fees? Tiny, like 0.000005 SOL per query.)
Create TokenAccounts.tsx:
import { useConnection, useWallet } from '@solana/wallet adapter react';
import { getAssociatedTokenAddress, getAccount } from '@solana/spl token'; function TokenAccounts() { const { connection } = useConnection(); const { publicKey } = useWallet(); const fetchTokens = async () => { if (!publicKey) throw new Error('Connect wallet!'); const tokenAccounts = await connection.getParsedTokenAccountsByOwner(publicKey, { programId: TOKENPROGRAMID }); console.log(tokenAccounts.value); // Your tokens! }; return <button onClick={fetchTokens}>Get My Tokens</button>;
}
Add to your app. Click after connecting. Errors? Check devnet has tokens or airdrop some USDC. In my experience, forgetting to import TOKENPROGRAMID from web3.js bites everyone once.
| Problem | Why? | Fix |
|---|---|---|
| No wallets show up | Browser blocking extensions | Reload, check Phantom/Solflare installed & unlocked |
| "React missing" error | Provider not wrapping app | Move <ConnectionProvider> to root |
| Connection fails | Bad RPC endpoint | Switch to QuickNode free tier: ~0.000005 SOL/call |
| Sign tx hangs | Network congestion | Use devnet, or add retries with exponential backoff |
| Mobile won't connect | Missing mobile adapter | npm i @solana mobile/wallet adapter mobile |
These pop up 80% of the time. Devnet's free airdrops help test without burning real SOL-solana airdrop 1 in terminal.
Phantom's king-15M+ users, NFT gallery, swaps at 0.85% fee, Ledger connect. But don't sleep on others.
new SlopeWalletAdapter().I usually start with Phantom + Solflare. Covers 90% of users. Comment out the rest in your wallets array to slim the dropdown.
Connected wallet? Send SOL.
const tx = new Transaction().add(SystemProgram.transfer({ fromPubkey: wallet.publicKey, toPubkey: recipient, lamports: 1000000 })); // 0.001 SOLconst sig = await sendTransaction(tx, connection);Fees? ~0.000005 SOL. Always simulate first: connection.simulateTransaction(tx). Rejects spam or fails.
const message = new TextEncoder().encode('Sign this!');
await wallet.signMessage(message);
Why? Auth logins, NFT mint proofs. Catches phishing-wrong domain? Wallet warns.
AutoConnect issues? Set autoConnect={false}. Users hate surprise connects.
Devnet for testing (free SOL). Mainnet for real: Update endpoint to 'https://api.mainnet beta.solana.com' and network to WalletAdapterNetwork.Mainnet.
Custom RPC? QuickNode or Helius. Free tiers handle 1M requests/month. No rate limits killing your demo.
Potential issue: Wallet on wrong cluster. Force disconnect/reconnect. Add a network selector UI.
Mobile dApps? Add mobile adapter package. Registers phone wallets in the list.
Hardware? Phantom + Ledger: Connect Ledger, approve in Phantom popup. Extra secure for big txns. I pair 'em for anything over 10 SOL.
One time, my balance hook infinite looped 'cause no publicKey check. Added if (!publicKey) return;. Fixed.
Another: Forgot LAMPORTSPERSOL divide. Showed 500000000 instead of 0.5 SOL. Rookie.
Network spikes? Solana's fast but congests-use priority fees: addComputeBudget(400000, 1000) micropayments for speed.
And yeah, test on testnet first. Devnet airdrops unlimited, mainnet burns real money.
WalletMultiButton accepts className. Tailwind? bg gradient to r from purple-500 to pink-500 text white px-6 py-3 rounded full. Looks pro instantly.
Hide when disconnected? {wallet.connected && <YourPanel />}.
Dark mode? Adapter UI follows your theme-set via CSS vars.
Query tokens like earlier. For NFTs, filter by Metaplex program ID. Phantom shows 'em natively, but query via getParsedTokenAccountsByOwner with decimals=0.
Stake SOL? Use createStakeAccount from spl stake. ~7% APY historical. But that's another guide.