129 lines
3.9 KiB
TypeScript
129 lines
3.9 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;
|
|
}
|
|
|
|
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;
|
|
}
|