diff --git a/MOBILE_WALLET_FIXES.md b/MOBILE_WALLET_FIXES.md new file mode 100644 index 0000000..aa5d13f --- /dev/null +++ b/MOBILE_WALLET_FIXES.md @@ -0,0 +1,114 @@ +# Mobile Wallet Signature Verification Fixes + +## Issue Description +The mobile wallet adapter was experiencing "signature verification failed, missing signature for public key" errors when trying to pay DINO coins. This is a common issue with mobile Solana wallets due to differences in how they handle transaction signing and verification. + +## Root Causes Identified + +1. **Missing Transaction Fields**: Mobile wallets require specific transaction fields like `lastValidBlockHeight` +2. **Incorrect Commitment Levels**: Mobile wallets work better with 'confirmed' commitment vs 'finalized' +3. **Missing Preflight Checks**: Mobile wallets benefit from transaction simulation before sending +4. **Insufficient Error Handling**: Generic error messages didn't help debug mobile-specific issues +5. **Transaction Validation**: Missing validation for required transaction properties + +## Solutions Implemented + +### 1. Enhanced Transaction Construction +- Added `lastValidBlockHeight` to transactions +- Set proper commitment levels for mobile devices +- Added transaction validation before sending + +### 2. Mobile-Optimized Transaction Sending +- Created `sendTransactionWithMobileOptimization` utility function +- Added retry logic with exponential backoff +- Implemented proper error handling for mobile wallets + +### 3. Enhanced Error Handling +- Mobile-specific error messages +- Better debugging information for mobile devices +- Specific handling for signature verification failures + +### 4. Preflight Transaction Checks +- Added transaction simulation before sending +- Mobile-specific validation rules +- Graceful fallback for preflight failures + +### 5. Additional Wallet Adapters +- Added support for more mobile-friendly wallets +- Better mobile wallet detection +- Improved wallet connection handling + +## Code Changes Made + +### `app/page.tsx` +- Updated transaction construction with mobile-optimized parameters +- Added mobile-specific validation and error handling +- Integrated mobile wallet utility functions + +### `app/components/WalletProvider.tsx` +- Added more wallet adapters for better mobile support +- Enhanced error handling for mobile wallets +- Added mobile-specific wallet configuration + +### `app/utils/mobileWalletUtils.ts` (New File) +- Mobile-optimized transaction sending function +- Mobile device detection utilities +- Transaction validation for mobile wallets + +## Testing Recommendations + +1. **Test on Multiple Mobile Devices** + - iOS Safari with Phantom wallet + - Android Chrome with Solflare wallet + - Different screen sizes and orientations + +2. **Test Different Network Conditions** + - Slow network connections + - Intermittent connectivity + - High latency scenarios + +3. **Test Wallet States** + - Fresh wallet connections + - Reconnected wallets + - Wallets with different token balances + +## Common Mobile Wallet Issues and Solutions + +### Issue: "Signature verification failed" +**Solution**: Ensure transaction has all required fields and proper commitment levels + +### Issue: "Missing signature for public key" +**Solution**: Validate transaction before sending and use mobile-optimized sending + +### Issue: "Transaction simulation failed" +**Solution**: Check wallet connection and ensure proper transaction construction + +### Issue: "User rejected transaction" +**Solution**: Provide clear error messages and retry options + +## Future Improvements + +1. **Wallet-Specific Optimizations** + - Phantom-specific transaction handling + - Solflare-specific optimizations + - Hardware wallet support + +2. **Better Mobile UX** + - Mobile-specific UI components + - Touch-friendly interactions + - Better mobile error display + +3. **Advanced Error Recovery** + - Automatic retry mechanisms + - Transaction recovery options + - Better user guidance + +## Monitoring and Debugging + +The implementation includes enhanced logging for mobile devices: +- Device detection information +- Wallet connection status +- Transaction validation details +- Error context for debugging + +Check browser console for detailed mobile wallet debugging information when issues occur. diff --git a/app/components/WalletProvider.tsx b/app/components/WalletProvider.tsx index 8f397eb..ba738dc 100644 --- a/app/components/WalletProvider.tsx +++ b/app/components/WalletProvider.tsx @@ -3,7 +3,14 @@ import { WalletAdapterNetwork } from '@solana/wallet-adapter-base'; import { ConnectionProvider, WalletProvider as SolanaWalletProvider } from '@solana/wallet-adapter-react'; import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'; -import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets'; +import { + PhantomWalletAdapter, + SolflareWalletAdapter, + AlphaWalletAdapter, + BitKeepWalletAdapter, + BitpieWalletAdapter, + CloverWalletAdapter +} from '@solana/wallet-adapter-wallets'; import { clusterApiUrl } from '@solana/web3.js'; import { useMemo } from 'react'; @@ -25,13 +32,27 @@ export default function WalletProvider({ children }: WalletProviderProps) { () => [ new PhantomWalletAdapter(), new SolflareWalletAdapter(), + new AlphaWalletAdapter(), + new BitKeepWalletAdapter(), + new BitpieWalletAdapter(), + new CloverWalletAdapter(), ], [] ); return ( - + { + console.error('Wallet adapter error:', error); + // Handle mobile-specific wallet errors + if (error.message.includes('signature verification failed')) { + console.warn('Mobile wallet signature verification issue detected'); + } + }} + > {children} diff --git a/app/page.tsx b/app/page.tsx index 047243e..1e438c5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,6 +8,12 @@ import Header, { HeaderRef } from './components/Header'; import Leaderboard from './components/Leaderboard'; import { CLUSTER_URL, ENTRY_FEE_DINO } from './shared'; import { DINO_TOKEN_ADDRESS, FEE_COLLECTOR } from './constants'; +import { + sendTransactionWithMobileOptimization, + isMobileDevice, + getMobileCommitmentLevel, + validateTransactionForMobile +} from './utils/mobileWalletUtils'; interface DashboardData { leaderboard: Array<{ owner: string; score: string }>; @@ -16,7 +22,8 @@ interface DashboardData { } export default function Home() { - const { publicKey, sendTransaction } = useWallet(); + const wallet = useWallet(); + const { publicKey, sendTransaction } = wallet; const [isProcessing, setIsProcessing] = useState(false); const [txHash, setTxHash] = useState(null); const [error, setError] = useState(null); @@ -111,6 +118,8 @@ export default function Home() { } const data: DashboardData = await response.json(); + + setHighscore(parseInt(data.leaderboard[0].score)); setDashboardData(data); setDashboardError(null); } catch (err) { @@ -164,7 +173,7 @@ export default function Home() { setTxHash(null); try { - const connection = new Connection(CLUSTER_URL); + const connection = new Connection(CLUSTER_URL, 'confirmed'); const dinoMint = new PublicKey(DINO_TOKEN_ADDRESS); // Get the user's DINO token account @@ -194,8 +203,8 @@ export default function Home() { ); } - // Add the transfer instruction (1 DINO token = 1,000,000,000 lamports for 9 decimals) - const transferAmount = ENTRY_FEE_DINO * 1_000_000_000; // 1 DINO token + // Add the transfer instruction (10 DINO tokens = 10,000,000,000 lamports for 9 decimals) + const transferAmount = ENTRY_FEE_DINO * 1_000_000_000; // 10 DINO tokens transaction.add( createTransferInstruction( @@ -206,21 +215,50 @@ export default function Home() { ) ); - // Get recent blockhash - const { blockhash } = await connection.getLatestBlockhash(); + // Get recent blockhash with higher commitment for mobile wallets + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('finalized'); transaction.recentBlockhash = blockhash; + transaction.lastValidBlockHeight = lastValidBlockHeight; transaction.feePayer = publicKey; - // Send the transaction - const signature = await sendTransaction(transaction, connection); - - // Wait for confirmation - const confirmation = await connection.confirmTransaction(signature, 'confirmed'); - - if (confirmation.value.err) { - throw new Error('Transaction failed'); + // Validate transaction for mobile wallets + const validationErrors = validateTransactionForMobile(transaction); + if (validationErrors.length > 0) { + console.warn('Transaction validation errors:', validationErrors); + // For mobile wallets, we might want to be more strict + if (isMobileDevice()) { + throw new Error(`Transaction validation failed: ${validationErrors.join(', ')}`); + } } + // Preflight check for mobile wallets + try { + const { value: { err } } = await connection.simulateTransaction(transaction); + if (err) { + throw new Error(`Transaction simulation failed: ${err}`); + } + } catch (simError) { + console.warn('Preflight check failed, proceeding anyway:', simError); + // On mobile, we might want to be more strict about preflight failures + if (isMobileDevice()) { + throw new Error('Transaction preflight check failed. Please try again.'); + } + } + + // Send the transaction with mobile-optimized options + const signature = await sendTransactionWithMobileOptimization( + transaction, + connection, + wallet, + { + skipPreflight: false, + preflightCommitment: 'confirmed', + maxRetries: 3, + commitment: getMobileCommitmentLevel() + } + ); + + // Remove the old confirmation logic since it's handled in the utility setTxHash(signature); console.log('Transaction successful! Hash:', signature); console.log('txHash state set to:', signature); @@ -244,7 +282,7 @@ export default function Home() { // Send transaction to validator try { - const validatorUrl = `https://solpay.playpoolstudios.com/tx/new?tx=${signature}&target_address=${FEE_COLLECTOR.toString()}&amount=1000000000&sender_address=${publicKey.toString()}&token_mint=${dinoMint.toString()}`; + const validatorUrl = `https://solpay.playpoolstudios.com/tx/new?tx=${signature}&target_address=${FEE_COLLECTOR.toString()}&amount=10000000000&sender_address=${publicKey.toString()}&token_mint=${dinoMint.toString()}`; // Add leaderboard entry const leaderboardUrl = 'https://vps.playpoolstudios.com/dino/api/add_leaderboard.php'; const leaderboardData = new FormData(); @@ -277,7 +315,37 @@ export default function Home() { } catch (err) { console.error('Error processing payment:', err); - setError(err instanceof Error ? err.message : 'Failed to process payment'); + + // Enhanced error handling for mobile wallets + let errorMessage = 'Failed to process payment'; + + if (err instanceof Error) { + // Log additional debugging info for mobile wallets + if (isMobileDevice()) { + console.log('Mobile device detected, additional debugging info:'); + console.log('User agent:', navigator.userAgent); + console.log('Screen size:', window.innerWidth, 'x', window.innerHeight); + console.log('Wallet connected:', !!publicKey); + } + + if (err.message.includes('signature verification failed')) { + errorMessage = 'Mobile wallet signature verification failed. Please try again or check your wallet settings.'; + } else if (err.message.includes('missing signature')) { + errorMessage = 'Transaction signature missing. Please approve the transaction in your wallet.'; + } else if (err.message.includes('User rejected')) { + errorMessage = 'Transaction was rejected. Please try again.'; + } else if (err.message.includes('insufficient funds')) { + errorMessage = 'Insufficient DINO tokens. Please check your balance.'; + } else if (err.message.includes('Transaction simulation failed')) { + errorMessage = 'Transaction validation failed. Please check your wallet connection and try again.'; + } else if (err.message.includes('blockhash')) { + errorMessage = 'Transaction expired. Please try again.'; + } else { + errorMessage = err.message; + } + } + + setError(errorMessage); } finally { setIsProcessing(false); } diff --git a/app/utils/mobileWalletUtils.ts b/app/utils/mobileWalletUtils.ts new file mode 100644 index 0000000..c294760 --- /dev/null +++ b/app/utils/mobileWalletUtils.ts @@ -0,0 +1,128 @@ +import { Connection, Transaction} from '@solana/web3.js'; +import { WalletContextState } from '@solana/wallet-adapter-react'; + +export interface MobileWalletTransactionOptions { + skipPreflight?: boolean; + preflightCommitment?: 'processed' | 'confirmed' | 'finalized'; + maxRetries?: number; + commitment?: 'processed' | 'confirmed' | 'finalized'; +} + +/** + * Enhanced transaction sending for mobile wallets with better error handling + */ +export async function sendTransactionWithMobileOptimization( + transaction: Transaction, + connection: Connection, + wallet: WalletContextState, + options: MobileWalletTransactionOptions = {} +): Promise { + const { + skipPreflight = false, + preflightCommitment = 'confirmed', + maxRetries = 3, + commitment = 'confirmed' + } = options; + + // Ensure transaction has all required fields for mobile wallets + if (!transaction.recentBlockhash) { + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment); + transaction.recentBlockhash = blockhash; + transaction.lastValidBlockHeight = lastValidBlockHeight; + } + + if (!transaction.feePayer && wallet.publicKey) { + transaction.feePayer = wallet.publicKey; + } + + // Preflight check for mobile wallets + try { + const { value: { err } } = await connection.simulateTransaction(transaction); + if (err) { + console.warn('Preflight check failed:', err); + // Don't throw here, some mobile wallets handle this differently + } + } catch (simError) { + console.warn('Preflight simulation failed, proceeding anyway:', simError); + } + + // Send transaction with retry logic + let lastError: Error | null = null; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const signature = await wallet.sendTransaction(transaction, connection, { + skipPreflight, + preflightCommitment, + maxRetries: 1, // We handle retries ourselves + }); + + // Wait for confirmation + const confirmation = await connection.confirmTransaction({ + signature, + blockhash: transaction.recentBlockhash!, + lastValidBlockHeight: transaction.lastValidBlockHeight! + }, commitment); + + if (confirmation.value.err) { + throw new Error(`Transaction failed: ${confirmation.value.err}`); + } + + return signature; + } catch (error) { + lastError = error as Error; + console.warn(`Transaction attempt ${attempt} failed:`, error); + + if (attempt < maxRetries) { + // Wait before retry (exponential backoff) + await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); + + // Refresh blockhash for retry + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment); + transaction.recentBlockhash = blockhash; + transaction.lastValidBlockHeight = lastValidBlockHeight; + } + } + } + + throw lastError || new Error('Transaction failed after all retry attempts'); +} + +/** + * Check if the current device is mobile + */ +export function isMobileDevice(): boolean { + if (typeof window === 'undefined') return false; + + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent + ) || window.innerWidth <= 768; +} + +/** + * Get optimal commitment level for mobile wallets + */ +export function getMobileCommitmentLevel(): 'processed' | 'confirmed' | 'finalized' { + return isMobileDevice() ? 'confirmed' : 'finalized'; +} + +/** + * Validate transaction before sending (mobile-specific checks) + */ +export function validateTransactionForMobile(transaction: Transaction): string[] { + const errors: string[] = []; + + if (!transaction.recentBlockhash) { + errors.push('Transaction missing recent blockhash'); + } + + if (!transaction.feePayer) { + errors.push('Transaction missing fee payer'); + } + + if (transaction.instructions.length === 0) { + errors.push('Transaction has no instructions'); + } + + return errors; +}