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; } // 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 try { console.log('Mobile wallet utility: Running preflight check...'); const { value: { err, logs } } = await connection.simulateTransaction(transaction); if (err) { console.warn('Mobile wallet utility: Preflight check failed:', err); console.log('Mobile wallet utility: Preflight logs:', logs); // For mobile wallets, we'll be more lenient with preflight failures // as some mobile wallets handle this differently console.log('Mobile wallet utility: Proceeding despite preflight failure (mobile wallet behavior)'); } else { console.log('Mobile wallet utility: Preflight check passed'); } } catch (simError) { console.warn('Mobile wallet utility: Preflight simulation failed:', simError); // Don't throw here, some mobile wallets handle this differently console.log('Mobile wallet utility: Proceeding despite preflight simulation failure'); } // Send transaction with retry logic let lastError: Error | null = null; 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, blockhash: transaction.recentBlockhash!, lastValidBlockHeight: transaction.lastValidBlockHeight! }, commitment); if (confirmation.value.err) { 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(`Mobile wallet utility: Transaction attempt ${attempt} failed:`, error); if (attempt < maxRetries) { // Wait before retry (exponential backoff) const waitTime = Math.pow(2, attempt) * 1000; console.log(`Mobile wallet utility: Waiting ${waitTime}ms before retry...`); await new Promise(resolve => setTimeout(resolve, waitTime)); } } } 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; }