diff --git a/app/page.tsx b/app/page.tsx index 40bcc8b..2af65cf 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,7 +3,7 @@ import { useState, useRef, useEffect } from 'react'; import { useWallet } from '@solana/wallet-adapter-react'; import { Connection, PublicKey, Transaction } from '@solana/web3.js'; -import {createTransferInstruction, getAssociatedTokenAddress, createAssociatedTokenAccountInstruction } from '@solana/spl-token'; +import {createTransferInstruction, getAssociatedTokenAddress, createAssociatedTokenAccountInstruction, TOKEN_PROGRAM_ID } from '@solana/spl-token'; import Header, { HeaderRef } from './components/Header'; import Leaderboard from './components/Leaderboard'; import { CLUSTER_URL, ENTRY_FEE_DINO } from './shared'; @@ -168,6 +168,25 @@ export default function Home() { return; } + // CRITICAL: Verify wallet connection is stable for mobile wallets + if (isMobileDevice()) { + console.log('Mobile device detected - verifying wallet connection...'); + + // Check if wallet is still connected + if (!wallet.connected || !wallet.publicKey) { + setError('Wallet connection lost. Please reconnect your wallet and try again.'); + return; + } + + // Verify public key matches + if (!wallet.publicKey.equals(publicKey)) { + setError('Wallet public key mismatch. Please refresh the page and reconnect your wallet.'); + return; + } + + console.log('Mobile wallet connection verified successfully'); + } + setIsProcessing(true); setError(null); setTxHash(null); @@ -239,11 +258,30 @@ export default function Home() { transferAmount ); - // Validate the transfer instruction - if (!transferIx.programId.equals(dinoMint)) { - console.warn('Transfer instruction program ID mismatch'); + // CRITICAL: Validate the transfer instruction for mobile wallets + console.log('Transfer instruction validation:'); + console.log('- From account:', userTokenAccount.toString()); + console.log('- To account:', feeCollectorTokenAccount.toString()); + console.log('- Authority:', publicKey.toString()); + console.log('- Amount (lamports):', transferAmount); + console.log('- Program ID:', transferIx.programId.toString()); + console.log('- Expected SPL Token Program ID:', TOKEN_PROGRAM_ID.toString()); + + // Ensure the instruction is using the correct SPL Token program + if (!transferIx.programId.equals(TOKEN_PROGRAM_ID)) { + throw new Error(`Transfer instruction program ID mismatch. Expected ${TOKEN_PROGRAM_ID.toString()}, got ${transferIx.programId.toString()}`); } + // Ensure the instruction is properly formatted + if (!transferIx.keys || transferIx.keys.length === 0) { + throw new Error('Transfer instruction missing required account keys'); + } + + // Log all account keys in the instruction + transferIx.keys.forEach((key, index) => { + console.log(`- Key ${index}: ${key.pubkey.toString()} (${key.isSigner ? 'signer' : 'non-signer'}, ${key.isWritable ? 'writable' : 'readonly'})`); + }); + transaction.add(transferIx); console.log('Added transfer instruction'); @@ -253,6 +291,17 @@ export default function Home() { transaction.lastValidBlockHeight = lastValidBlockHeight; transaction.feePayer = publicKey; + // CRITICAL: Double-check that all required fields are set for mobile wallets + if (!transaction.feePayer) { + throw new Error('Transaction fee payer not set - this will cause signature verification to fail'); + } + if (!transaction.recentBlockhash) { + throw new Error('Transaction blockhash not set - this will cause signature verification to fail'); + } + if (!transaction.lastValidBlockHeight) { + throw new Error('Transaction lastValidBlockHeight not set - this will cause signature verification to fail'); + } + // Ensure the transaction is properly formatted for mobile wallets console.log('Transaction details before preflight:'); console.log('- Recent blockhash:', transaction.recentBlockhash); @@ -337,8 +386,25 @@ export default function Home() { // If mobile-optimized fails, try with preflight disabled for mobile wallets if (isMobileDevice()) { try { + // Create a completely fresh transaction with new blockhash + console.log('Creating fresh transaction for mobile wallet fallback...'); + const freshTransaction = new Transaction(); + + // Re-add all instructions + transaction.instructions.forEach(instruction => { + freshTransaction.add(instruction); + }); + + // Get fresh blockhash + const { blockhash: freshBlockhash, lastValidBlockHeight: freshLastValidBlockHeight } = await connection.getLatestBlockhash('confirmed'); + freshTransaction.recentBlockhash = freshBlockhash; + freshTransaction.lastValidBlockHeight = freshLastValidBlockHeight; + freshTransaction.feePayer = publicKey; + + console.log('Fresh transaction created with new blockhash'); + signature = await sendTransactionWithMobileOptimization( - transaction, + freshTransaction, connection, wallet, { diff --git a/app/utils/mobileWalletUtils.ts b/app/utils/mobileWalletUtils.ts index cae3a13..d547254 100644 --- a/app/utils/mobileWalletUtils.ts +++ b/app/utils/mobileWalletUtils.ts @@ -31,8 +31,32 @@ export async function sendTransactionWithMobileOptimization( transaction.lastValidBlockHeight = lastValidBlockHeight; } - if (!transaction.feePayer && wallet.publicKey) { - transaction.feePayer = wallet.publicKey; + // CRITICAL: Ensure fee payer is set correctly for mobile wallets + if (!transaction.feePayer) { + if (wallet.publicKey) { + transaction.feePayer = wallet.publicKey; + console.log('Mobile wallet utility: Set fee payer to:', wallet.publicKey.toString()); + } else { + throw new Error('Wallet public key not available'); + } + } + + // Double-check that all required fields are present + console.log('Mobile wallet utility: Transaction validation:'); + console.log('- Fee payer:', transaction.feePayer?.toString()); + console.log('- Recent blockhash:', transaction.recentBlockhash); + console.log('- Last valid block height:', transaction.lastValidBlockHeight); + console.log('- Number of instructions:', transaction.instructions.length); + + // Validate transaction structure for mobile wallets + if (!transaction.feePayer) { + throw new Error('Transaction missing fee payer - required for mobile wallets'); + } + if (!transaction.recentBlockhash) { + throw new Error('Transaction missing recent blockhash - required for mobile wallets'); + } + if (transaction.instructions.length === 0) { + throw new Error('Transaction has no instructions'); } // Preflight check for mobile wallets @@ -60,12 +84,24 @@ export async function sendTransactionWithMobileOptimization( for (let attempt = 1; attempt <= maxRetries; attempt++) { try { + console.log(`Mobile wallet utility: Attempt ${attempt} of ${maxRetries}`); + + // Ensure transaction is fresh for each attempt + if (attempt > 1) { + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment); + transaction.recentBlockhash = blockhash; + transaction.lastValidBlockHeight = lastValidBlockHeight; + console.log('Mobile wallet utility: Refreshed blockhash for retry'); + } + const signature = await wallet.sendTransaction(transaction, connection, { skipPreflight, preflightCommitment, maxRetries: 1, // We handle retries ourselves }); + console.log('Mobile wallet utility: Transaction sent successfully, signature:', signature); + // Wait for confirmation const confirmation = await connection.confirmTransaction({ signature, @@ -77,19 +113,17 @@ export async function sendTransactionWithMobileOptimization( throw new Error(`Transaction failed: ${confirmation.value.err}`); } + console.log('Mobile wallet utility: Transaction confirmed successfully'); return signature; } catch (error) { lastError = error as Error; - console.warn(`Transaction attempt ${attempt} failed:`, error); + console.warn(`Mobile wallet utility: 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; + const waitTime = Math.pow(2, attempt) * 1000; + console.log(`Mobile wallet utility: Waiting ${waitTime}ms before retry...`); + await new Promise(resolve => setTimeout(resolve, waitTime)); } } }