Users gotta connect their wallets to play, grab some tokens, maybe battle some NFTs. But you're staring at a blank screen thinking, "How the hell do I even get Phantom or Solflare hooked up without screwing it all up?" Sound familiar? I've been there. Like, last week. The thing is, Solana makes this way easier than Ethereum with all that gas nonsense. Fees? We're talking ~0.000005 SOL per tx. Peanuts.
Okay, so we're jumping straight into the practical stuff. No fluff. I'm gonna walk you through using the Solana Wallet Adapter because that's what pros use. It supports like 12+ wallets out the box-Phantom, Solflare, Backpack, you name it. In my experience, skipping this and hacking window.solana checks just leads to headaches later.
Why start from scratch? Solana's got this dApp Scaffold that's basically a pre built kit. Clone it, tweak it, done. I usually do this first.
mkdir my solana dapp && cd my solana dappnpx create solana dapp@latest . Or if you're yarn fan, whatever floats your boat.npm install. Add SPL tokens too: npm i @solana/spl token.env.local: echo "NEXTPUBLICSOLANARPCURL=https://api.devnet.solana.com" > .env.local. Swap in your RPC.npm run dev. Boom. localhost:3000. Click "Select Wallet." Pick Phantom. Connected. Balance shows up.What's next? That top right button? It's the WalletMultiButton. Pure gold. Handles connect, disconnect, switch wallets. No custom CSS hell.
Wallet not popping? Check if Phantom's on devnet. Switch in settings. Airdrop some SOL: Hit that airdrop button in scaffold. Stuck on "No wallets found"? Add more adapters in src/contexts/WalletProvider.tsx. Comment out ones you hate.
Look, the scaffold wraps your app in providers. WalletProvider lists wallets like PhantomWalletAdapter(). AutoConnect? Toggle it in AppBar. I usually leave it on-users hate clicking every refresh.
In ContextProvider.tsx, you got wallets array. Tweak it:
new PhantomWalletAdapter()new SolflareWalletAdapter()new BackpackWalletAdapter()Endpoint sets network: devnet, mainnet beta, localhost. onError? Catches rejects like "User denied." Notify with toasts-scaffold has utils for that.
Pro tip: React 18? Stick to scaffold's React 17 for now. Upgrade drama ain't worth it yet.
Connected? Great. Now query stuff. Use hooks: useConnection(), useWallet(). Super clean.
Create src/components/BalanceChecker.tsx. Paste this bad boy:
Balance: {balance.toFixed(4)} SOL
) : (Connect wallet to see balance
)}Drop it in your home page, under the airdrop button. Refresh. Connect. Boom-live balance. Updates on chain changes. Why does this matter? Users trust dApps that show real data instantly.
Honest heads up: Devnet balances reset. Mainnet? Use real SOL, but fees are tiny-0.000005 SOL to check.
Balances are cute. Tokens? Where it's at. Scaffold example queries SPL tokens. Let's steal that vibe but simplify.
| Hook | What it does | Example use |
|---|---|---|
useWallet() | Grabs publicKey, sign tx | if (!publicKey) return notify('Connect first!'); |
useConnection() | RPC calls, balances | connection.getBalance(pubkey) |
useAnchorWallet() | Anchor provider | Programs, full tx signing |
Now, real steps for token fetcher. New file: GetTokens.tsx.
useConnection, useWallet from adapter react. TOKENPROGRAMID from spl token. GetProgramAccountsFilter from web3.js.const [tokenTable, setTokenTable] = useState(null);async getTokenAccounts(wallet: string). Filters: dataSize 165, memcmp offset 32 bytes=wallet (base58).connection.getParsedProgramAccounts(TOKENPROGRAMID, {filters})Full code? It's in the scaffold guides, but tweak for your needs. Add to page: <GetTokens />. Click after connect. Lists all your USDC, BONK, whatever. Empty? "No tokens." Perfect.
Issue I hit once: "Rate limited." Fix? Better RPC like QuickNode. Free devnet endpoint handles thousands.
Not everyone Reacts. Quick vanilla way-detect Phantom.
javascript window.addEventListener('load', async () => { if (window.solana && window.solana.isPhantom) { try { const resp = await window.solana.connect({ onlyIfTrusted: true }); console.log('Address:', resp.publicKey.toString()); } catch { // Click to connect const resp = await window.solana.connect(); } } else { alert('Install Phantom!'); } });Button onclick: window.solana.connect(). Pop up asks permission. Approve. Address ready. State? Use localStorage for persistence.
But honestly? Adapter's better. Handles all wallets, errors, reconnections. Vanilla's fine for prototypes.
Wallet connects but tx fails? Check network match-both devnet. publicKey null? Provider not wrapping app. See _app.tsx.
Auto connect annoying? Set autoConnect: false. Users toggle in UI.
Mobile? Phantom app detects dApp. Test on real device-emulators lie.
Scaffold's button ugly for your vibe? Build custom.
In component:
tsx import { useWallet } from '@solana/wallet adapter react'; const { publicKey, connect, connecting, disconnect } = useWallet(); return publicKey ? ( ) : ( );Sleek. Add icons. Why bother? Branding. Users click what fits your site.
In my experience, test with 3 wallets: Phantom (80% users), Solflare, Backpack. Edge cases pop.
Mainnet switch: Update RPC to https://api.mainnet beta.solana.com or paid (Helius ~$9/mo unlimited). No airdrops-real SOL.
Security: Never store private keys. Adapter signs client side. Phishers target connects-warn users.
Analytics? Track connects with simple event: window.gtag?('event', 'wallet_connect');
Scale: Queries slow? Cache with React Query or SWR. Token lists? Use Jupiter API for prices.
Quick example. After connect:
tsx const sendSOL = async () => { if (!publicKey) return; const tx = new Transaction().add( SystemProgram.transfer({ fromPubkey: publicKey, toPubkey: new PublicKey('DestAddrHere'), lamports: 1000000, // 0.001 SOL }) ); const sig = await sendTransaction(tx, connection); console.log('Sig:', sig); };Sign, send, confirm. Fees deducted auto. Users see Phantom approve.
That's your base. Build from here-NFT mints, swaps, games. Hit snags? Console.log everything. Solana Discord's gold.