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; }