dino_landing_page/app/utils/mobileWalletUtils.ts
2025-08-18 19:43:41 +05:30

172 lines
6.0 KiB
TypeScript

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