+ );
+}
+```
+
+##### Focus Management
+
+Manage focus when opening/closing modals:
+
+```typescript
+import { useEffect, useRef } from 'react';
+
+export function AccessibleWalletModal({ visible, onClose }: WalletModalProps) {
+ const closeButtonRef = useRef(null);
+ const previousFocusRef = useRef(null);
+
+ useEffect(() => {
+ if (visible) {
+ // Store previously focused element
+ previousFocusRef.current = document.activeElement as HTMLElement;
+
+ // Focus close button when modal opens
+ closeButtonRef.current?.focus();
+ } else {
+ // Restore focus when modal closes
+ previousFocusRef.current?.focus();
+ }
+ }, [visible]);
+
+ if (!visible) return null;
+
+ return (
+
+
+
+
+ Connect Wallet
+
+
+
+ {/* Wallet list */}
+
+
+ );
+}
+```
+
+---
+
+#### Lab Exercise: Building Wallet Connection UI
+
+**Objective:** Create a WalletButton component that:
+
+- Uses wallet-ui hooks to access wallet state and functions
+- Displays different UI based on connection status:
+ - When connected: Shows truncated public key with disconnect option
+ - When disconnected: Shows "Connect Wallet" button
+- Implements modal pattern for wallet selection
+- Handles wallet connection through modal selection
+- Applies appropriate styling for different states
+- Manages modal visibility with local state
+
+##### Starter Code
+
+Create `src/components/WalletButton.tsx`:
+
+```typescript
+import { useWallet } from '@solana/wallet-adapter-react';
+import { useWalletModal } from '@solana/wallet-adapter-react-ui';
+import { useState } from 'react';
+
+export function WalletButton() {
+ // TODO: Destructure needed values from useWallet()
+ // TODO: Get setVisible from useWalletModal()
+ // TODO: Add local state for errors
+
+ // TODO: Implement handleConnect with error handling
+
+ // TODO: Implement handleDisconnect with error handling
+
+ // TODO: Handle no wallet selected state
+
+ // TODO: Handle wallet selected but not connected state
+
+ // TODO: Handle connected state with address display
+
+ return
TODO: Implement wallet button
;
+}
+```
+
+##### Implementation Steps
+
+1. Use `useWallet()` to get wallet state
+2. Use `useWalletModal()` to control modal visibility
+3. Implement three UI states:
+ - No wallet: Show "Select Wallet" button
+ - Wallet selected: Show "Connect" button
+ - Connected: Show truncated address with disconnect button
+4. Add loading indicators for connecting/disconnecting states
+5. Add error handling with user-friendly messages
+6. Style components with Tailwind CSS or your preferred method
+
+##### Bonus Challenges
+
+1. Add copy address functionality
+2. Show wallet icon when connected
+3. Add fade-in animations for state transitions
+4. Implement keyboard navigation
+5. Add ARIA labels for accessibility
+
+##### Solution
+
+
+Click to reveal solution
+
+```typescript
+import { useWallet } from '@solana/wallet-adapter-react';
+import { useWalletModal } from '@solana/wallet-adapter-react-ui';
+import { useState, useCallback } from 'react';
+
+export function WalletButton() {
+ const {
+ publicKey,
+ wallet,
+ connect,
+ disconnect,
+ connecting,
+ disconnecting
+ } = useWallet();
+ const { setVisible } = useWalletModal();
+ const [error, setError] = useState(null);
+ const [copied, setCopied] = useState(false);
+
+ const handleConnect = useCallback(async () => {
+ setError(null);
+ try {
+ await connect();
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Connection failed');
+ }
+ }, [connect]);
+
+ const handleDisconnect = useCallback(async () => {
+ setError(null);
+ try {
+ await disconnect();
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Disconnection failed');
+ }
+ }, [disconnect]);
+
+ const handleCopy = useCallback(async () => {
+ if (!publicKey) return;
+ try {
+ await navigator.clipboard.writeText(publicKey.toBase58());
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ console.error('Failed to copy:', err);
+ }
+ }, [publicKey]);
+
+ // Error state
+ if (error) {
+ return (
+
+
{error}
+
+
+ );
+ }
+
+ // No wallet selected
+ if (!wallet) {
+ return (
+
+ );
+ }
+
+ // Wallet selected but not connected
+ if (!publicKey) {
+ return (
+
+ );
+ }
+
+ // Connected - show address
+ return (
+
;
+}
+
+function App() {
+ return (
+
+
+
+ );
+}
+```
+
+---
## Week 2 Quiz Questions
-1. What is the purpose of the wallet adapter pattern?
-2. How do you handle multiple wallet types in a single interface?
-3. Explain the wallet ready states and their meanings
-4. What security considerations apply to auto-connect features?
-5. How can you improve wallet connection UX for mobile users?
+### 1. What is the purpose of the wallet adapter pattern?
+
+
+Click to reveal answer
+
+The wallet adapter pattern provides a **standardized interface** for integrating multiple wallet types into a single application without writing wallet-specific code for each one.
+
+**Key purposes:**
+
+1. **Abstraction** - Hide wallet-specific implementation details behind a common API
+2. **Extensibility** - Add new wallet support without changing existing code
+3. **Consistency** - All wallets work through the same interface (connect, disconnect, signTransaction, etc.)
+4. **Maintenance** - Wallet updates are handled by individual adapters, not your app code
+5. **User Choice** - Let users choose their preferred wallet without forcing a specific one
+
+**Example:**
+
+```typescript
+// Without adapter pattern - need wallet-specific code
+if (walletType === 'phantom') {
+ await window.phantom.solana.connect();
+} else if (walletType === 'solflare') {
+ await window.solflare.connect();
+}
+// ... etc for each wallet
+
+// With adapter pattern - same code for all wallets
+await wallet.adapter.connect();
+```
+
+The pattern is based on the **Adapter design pattern** from software engineering, which allows incompatible interfaces to work together.
+
+
+---
+
+### 2. How do you handle multiple wallet types in a single interface?
+
+
+Click to reveal answer
+
+Handle multiple wallet types by:
+
+**1. Configure wallet adapters in WalletProvider:**
+
+```typescript
+const wallets = useMemo(
+ () => [
+ new PhantomWalletAdapter(),
+ new SolflareWalletAdapter(),
+ new CoinbaseWalletAdapter(),
+ ],
+ []
+);
+
+
+ {children}
+
+```
+
+**2. Use the `useWallet()` hook to access all wallets:**
+
+```typescript
+const { wallet, wallets, select, connect } = useWallet();
+
+// List all available wallets
+wallets.map((w) => (
+
+))
+```
+
+**3. Check ready state before allowing connection:**
+
+```typescript
+const isAvailable =
+ wallet.readyState === WalletReadyState.Installed ||
+ wallet.readyState === WalletReadyState.Loadable;
+
+if (!isAvailable) {
+ // Show "Install" button instead
+ window.open(wallet.adapter.url, '_blank');
+}
+```
+
+**4. Let Wallet Standard handle automatic detection:**
+
+```typescript
+// Empty array enables auto-detection
+const wallets = useMemo(() => [], []);
+```
+
+This detects all wallets implementing the Wallet Standard automatically without explicit configuration.
+
+**Best practices:**
+- Sort wallets by availability (installed first)
+- Show clear indicators for wallet status
+- Provide installation links for missing wallets
+- Remember user's last-used wallet
+
+
+---
+
+### 3. Explain the wallet ready states and their meanings
+
+
+Click to reveal answer
+
+Wallet adapters report their availability through `WalletReadyState` enum:
+
+**1. `WalletReadyState.Installed`**
+- **Meaning:** Wallet extension is installed and ready to use
+- **Example:** User has Phantom extension installed in their browser
+- **Action:** Can connect immediately
+
+```typescript
+if (wallet.readyState === WalletReadyState.Installed) {
+ // Show "Connect" button
+ await wallet.adapter.connect();
+}
+```
+
+**2. `WalletReadyState.NotDetected`**
+- **Meaning:** Wallet is not installed in the current environment
+- **Example:** User doesn't have Solflare extension
+- **Action:** Show installation prompt
+
+```typescript
+if (wallet.readyState === WalletReadyState.NotDetected) {
+ // Show "Install" button
+ window.open(wallet.adapter.url, '_blank');
+}
+```
+
+**3. `WalletReadyState.Loadable`**
+- **Meaning:** Wallet can be loaded on demand (usually on mobile)
+- **Example:** Wallet Connect, mobile wallet adapters
+- **Action:** Can attempt connection (may open app)
+
+```typescript
+if (wallet.readyState === WalletReadyState.Loadable) {
+ // Can connect - may trigger app redirect on mobile
+ await wallet.adapter.connect();
+}
+```
+
+**4. `WalletReadyState.Unsupported`**
+- **Meaning:** Wallet doesn't work in current environment
+- **Example:** Hardware wallet on mobile device
+- **Action:** Show "Not Supported" message
+
+```typescript
+if (wallet.readyState === WalletReadyState.Unsupported) {
+ // Disable connection
+ return
+ );
+}
+```
+
+
+---
## Hands-On Challenge
@@ -209,26 +4091,73 @@ Create a wallet integration feature that includes:
Build a wallet connection interface with these constraints:
-- Must connect in under 3 clicks
-- Should remember last used wallet
-- Must work on mobile devices
-- Include visual feedback for all states
-- Handle errors gracefully
+- Must connect in under 3 clicks
+- Should remember last used wallet
+- Must work on mobile devices
+- Include visual feedback for all states
+- Handle errors gracefully
**Bonus Points:**
-- Add wallet balance display
-- Implement ENS/SNS domain resolution
-- Create custom wallet icons
-- Add connection animations
+- Add wallet balance display
+- Implement ENS/SNS domain resolution
+- Create custom wallet icons
+- Add connection animations
+
+### Challenge Steps
+
+1. **One-Click Connect (for returning users)**
+ - Auto-connect on page load if wallet was previously used
+ - Show "Connect" button that immediately opens last-used wallet
+
+2. **Two-Click Connect (for new users)**
+ - First click: Open wallet selection modal
+ - Second click: Select and connect to wallet
+
+3. **Three-Click Maximum (for wallet installation)**
+ - First click: Open wallet selection modal
+ - Second click: Click "Install" for missing wallet
+ - Third click: Return and connect after installation
+
+**Evaluation:**
+- Count clicks from landing to connected state
+- Test on desktop and mobile
+- Verify auto-connect works
+- Check error handling
+
+---
## Looking Ahead
Next week covers message signing and transaction patterns, including:
-- Message signing for authentication
-- Transaction signing workflows
-- Building transaction preview UIs
-- Implementing approval patterns
+- Message signing for authentication
+- Transaction signing workflows
+- Building transaction preview UIs
+- Implementing approval patterns
Prerequisites for next week include having devnet SOL available for transaction exercises. Free devnet SOL is available from the [Solana Faucet](https://faucet.solana.com/).
+
+---
+
+## Summary
+
+This week you learned to:
+
+1. **Set up wallet providers** using the Solana wallet adapter architecture
+2. **Build custom wallet UI** with hooks and components
+3. **Implement auto-connect** for improved user experience
+4. **Handle wallet events** and state management
+5. **Follow security best practices** for wallet integration
+
+**Key takeaways:**
+
+- Use `ConnectionProvider` → `WalletProvider` → `WalletModalProvider` hierarchy
+- Leverage `useWallet()` and `useConnection()` hooks for wallet interactions
+- Handle all wallet ready states appropriately
+- Implement auto-connect with localStorage persistence
+- Never store private keys - only wallet names
+- Provide great mobile UX with responsive design
+- Add loading states and error handling for all async operations
+
+You're now ready to build production-quality wallet connection experiences for Solana applications!