mobile wallet fixes

This commit is contained in:
Sewmina 2025-08-17 22:46:33 +05:30
parent e9913b1a4f
commit a9313f8b0f
4 changed files with 349 additions and 18 deletions

114
MOBILE_WALLET_FIXES.md Normal file
View File

@ -0,0 +1,114 @@
# Mobile Wallet Signature Verification Fixes
## Issue Description
The mobile wallet adapter was experiencing "signature verification failed, missing signature for public key" errors when trying to pay DINO coins. This is a common issue with mobile Solana wallets due to differences in how they handle transaction signing and verification.
## Root Causes Identified
1. **Missing Transaction Fields**: Mobile wallets require specific transaction fields like `lastValidBlockHeight`
2. **Incorrect Commitment Levels**: Mobile wallets work better with 'confirmed' commitment vs 'finalized'
3. **Missing Preflight Checks**: Mobile wallets benefit from transaction simulation before sending
4. **Insufficient Error Handling**: Generic error messages didn't help debug mobile-specific issues
5. **Transaction Validation**: Missing validation for required transaction properties
## Solutions Implemented
### 1. Enhanced Transaction Construction
- Added `lastValidBlockHeight` to transactions
- Set proper commitment levels for mobile devices
- Added transaction validation before sending
### 2. Mobile-Optimized Transaction Sending
- Created `sendTransactionWithMobileOptimization` utility function
- Added retry logic with exponential backoff
- Implemented proper error handling for mobile wallets
### 3. Enhanced Error Handling
- Mobile-specific error messages
- Better debugging information for mobile devices
- Specific handling for signature verification failures
### 4. Preflight Transaction Checks
- Added transaction simulation before sending
- Mobile-specific validation rules
- Graceful fallback for preflight failures
### 5. Additional Wallet Adapters
- Added support for more mobile-friendly wallets
- Better mobile wallet detection
- Improved wallet connection handling
## Code Changes Made
### `app/page.tsx`
- Updated transaction construction with mobile-optimized parameters
- Added mobile-specific validation and error handling
- Integrated mobile wallet utility functions
### `app/components/WalletProvider.tsx`
- Added more wallet adapters for better mobile support
- Enhanced error handling for mobile wallets
- Added mobile-specific wallet configuration
### `app/utils/mobileWalletUtils.ts` (New File)
- Mobile-optimized transaction sending function
- Mobile device detection utilities
- Transaction validation for mobile wallets
## Testing Recommendations
1. **Test on Multiple Mobile Devices**
- iOS Safari with Phantom wallet
- Android Chrome with Solflare wallet
- Different screen sizes and orientations
2. **Test Different Network Conditions**
- Slow network connections
- Intermittent connectivity
- High latency scenarios
3. **Test Wallet States**
- Fresh wallet connections
- Reconnected wallets
- Wallets with different token balances
## Common Mobile Wallet Issues and Solutions
### Issue: "Signature verification failed"
**Solution**: Ensure transaction has all required fields and proper commitment levels
### Issue: "Missing signature for public key"
**Solution**: Validate transaction before sending and use mobile-optimized sending
### Issue: "Transaction simulation failed"
**Solution**: Check wallet connection and ensure proper transaction construction
### Issue: "User rejected transaction"
**Solution**: Provide clear error messages and retry options
## Future Improvements
1. **Wallet-Specific Optimizations**
- Phantom-specific transaction handling
- Solflare-specific optimizations
- Hardware wallet support
2. **Better Mobile UX**
- Mobile-specific UI components
- Touch-friendly interactions
- Better mobile error display
3. **Advanced Error Recovery**
- Automatic retry mechanisms
- Transaction recovery options
- Better user guidance
## Monitoring and Debugging
The implementation includes enhanced logging for mobile devices:
- Device detection information
- Wallet connection status
- Transaction validation details
- Error context for debugging
Check browser console for detailed mobile wallet debugging information when issues occur.

View File

@ -3,7 +3,14 @@
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base'; import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import { ConnectionProvider, WalletProvider as SolanaWalletProvider } from '@solana/wallet-adapter-react'; import { ConnectionProvider, WalletProvider as SolanaWalletProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'; import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets'; import {
PhantomWalletAdapter,
SolflareWalletAdapter,
AlphaWalletAdapter,
BitKeepWalletAdapter,
BitpieWalletAdapter,
CloverWalletAdapter
} from '@solana/wallet-adapter-wallets';
import { clusterApiUrl } from '@solana/web3.js'; import { clusterApiUrl } from '@solana/web3.js';
import { useMemo } from 'react'; import { useMemo } from 'react';
@ -25,13 +32,27 @@ export default function WalletProvider({ children }: WalletProviderProps) {
() => [ () => [
new PhantomWalletAdapter(), new PhantomWalletAdapter(),
new SolflareWalletAdapter(), new SolflareWalletAdapter(),
new AlphaWalletAdapter(),
new BitKeepWalletAdapter(),
new BitpieWalletAdapter(),
new CloverWalletAdapter(),
], ],
[] []
); );
return ( return (
<ConnectionProvider endpoint={endpoint}> <ConnectionProvider endpoint={endpoint}>
<SolanaWalletProvider wallets={wallets} autoConnect> <SolanaWalletProvider
wallets={wallets}
autoConnect
onError={(error) => {
console.error('Wallet adapter error:', error);
// Handle mobile-specific wallet errors
if (error.message.includes('signature verification failed')) {
console.warn('Mobile wallet signature verification issue detected');
}
}}
>
<WalletModalProvider> <WalletModalProvider>
{children} {children}
</WalletModalProvider> </WalletModalProvider>

View File

@ -8,6 +8,12 @@ import Header, { HeaderRef } from './components/Header';
import Leaderboard from './components/Leaderboard'; import Leaderboard from './components/Leaderboard';
import { CLUSTER_URL, ENTRY_FEE_DINO } from './shared'; import { CLUSTER_URL, ENTRY_FEE_DINO } from './shared';
import { DINO_TOKEN_ADDRESS, FEE_COLLECTOR } from './constants'; import { DINO_TOKEN_ADDRESS, FEE_COLLECTOR } from './constants';
import {
sendTransactionWithMobileOptimization,
isMobileDevice,
getMobileCommitmentLevel,
validateTransactionForMobile
} from './utils/mobileWalletUtils';
interface DashboardData { interface DashboardData {
leaderboard: Array<{ owner: string; score: string }>; leaderboard: Array<{ owner: string; score: string }>;
@ -16,7 +22,8 @@ interface DashboardData {
} }
export default function Home() { export default function Home() {
const { publicKey, sendTransaction } = useWallet(); const wallet = useWallet();
const { publicKey, sendTransaction } = wallet;
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = useState(false);
const [txHash, setTxHash] = useState<string | null>(null); const [txHash, setTxHash] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -111,6 +118,8 @@ export default function Home() {
} }
const data: DashboardData = await response.json(); const data: DashboardData = await response.json();
setHighscore(parseInt(data.leaderboard[0].score));
setDashboardData(data); setDashboardData(data);
setDashboardError(null); setDashboardError(null);
} catch (err) { } catch (err) {
@ -164,7 +173,7 @@ export default function Home() {
setTxHash(null); setTxHash(null);
try { try {
const connection = new Connection(CLUSTER_URL); const connection = new Connection(CLUSTER_URL, 'confirmed');
const dinoMint = new PublicKey(DINO_TOKEN_ADDRESS); const dinoMint = new PublicKey(DINO_TOKEN_ADDRESS);
// Get the user's DINO token account // Get the user's DINO token account
@ -194,8 +203,8 @@ export default function Home() {
); );
} }
// Add the transfer instruction (1 DINO token = 1,000,000,000 lamports for 9 decimals) // Add the transfer instruction (10 DINO tokens = 10,000,000,000 lamports for 9 decimals)
const transferAmount = ENTRY_FEE_DINO * 1_000_000_000; // 1 DINO token const transferAmount = ENTRY_FEE_DINO * 1_000_000_000; // 10 DINO tokens
transaction.add( transaction.add(
createTransferInstruction( createTransferInstruction(
@ -206,21 +215,50 @@ export default function Home() {
) )
); );
// Get recent blockhash // Get recent blockhash with higher commitment for mobile wallets
const { blockhash } = await connection.getLatestBlockhash(); const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('finalized');
transaction.recentBlockhash = blockhash; transaction.recentBlockhash = blockhash;
transaction.lastValidBlockHeight = lastValidBlockHeight;
transaction.feePayer = publicKey; transaction.feePayer = publicKey;
// Send the transaction // Validate transaction for mobile wallets
const signature = await sendTransaction(transaction, connection); const validationErrors = validateTransactionForMobile(transaction);
if (validationErrors.length > 0) {
// Wait for confirmation console.warn('Transaction validation errors:', validationErrors);
const confirmation = await connection.confirmTransaction(signature, 'confirmed'); // For mobile wallets, we might want to be more strict
if (isMobileDevice()) {
if (confirmation.value.err) { throw new Error(`Transaction validation failed: ${validationErrors.join(', ')}`);
throw new Error('Transaction failed'); }
} }
// Preflight check for mobile wallets
try {
const { value: { err } } = await connection.simulateTransaction(transaction);
if (err) {
throw new Error(`Transaction simulation failed: ${err}`);
}
} catch (simError) {
console.warn('Preflight check failed, proceeding anyway:', simError);
// On mobile, we might want to be more strict about preflight failures
if (isMobileDevice()) {
throw new Error('Transaction preflight check failed. Please try again.');
}
}
// Send the transaction with mobile-optimized options
const signature = await sendTransactionWithMobileOptimization(
transaction,
connection,
wallet,
{
skipPreflight: false,
preflightCommitment: 'confirmed',
maxRetries: 3,
commitment: getMobileCommitmentLevel()
}
);
// Remove the old confirmation logic since it's handled in the utility
setTxHash(signature); setTxHash(signature);
console.log('Transaction successful! Hash:', signature); console.log('Transaction successful! Hash:', signature);
console.log('txHash state set to:', signature); console.log('txHash state set to:', signature);
@ -244,7 +282,7 @@ export default function Home() {
// Send transaction to validator // Send transaction to validator
try { try {
const validatorUrl = `https://solpay.playpoolstudios.com/tx/new?tx=${signature}&target_address=${FEE_COLLECTOR.toString()}&amount=1000000000&sender_address=${publicKey.toString()}&token_mint=${dinoMint.toString()}`; const validatorUrl = `https://solpay.playpoolstudios.com/tx/new?tx=${signature}&target_address=${FEE_COLLECTOR.toString()}&amount=10000000000&sender_address=${publicKey.toString()}&token_mint=${dinoMint.toString()}`;
// Add leaderboard entry // Add leaderboard entry
const leaderboardUrl = 'https://vps.playpoolstudios.com/dino/api/add_leaderboard.php'; const leaderboardUrl = 'https://vps.playpoolstudios.com/dino/api/add_leaderboard.php';
const leaderboardData = new FormData(); const leaderboardData = new FormData();
@ -277,7 +315,37 @@ export default function Home() {
} catch (err) { } catch (err) {
console.error('Error processing payment:', err); console.error('Error processing payment:', err);
setError(err instanceof Error ? err.message : 'Failed to process payment');
// Enhanced error handling for mobile wallets
let errorMessage = 'Failed to process payment';
if (err instanceof Error) {
// Log additional debugging info for mobile wallets
if (isMobileDevice()) {
console.log('Mobile device detected, additional debugging info:');
console.log('User agent:', navigator.userAgent);
console.log('Screen size:', window.innerWidth, 'x', window.innerHeight);
console.log('Wallet connected:', !!publicKey);
}
if (err.message.includes('signature verification failed')) {
errorMessage = 'Mobile wallet signature verification failed. Please try again or check your wallet settings.';
} else if (err.message.includes('missing signature')) {
errorMessage = 'Transaction signature missing. Please approve the transaction in your wallet.';
} else if (err.message.includes('User rejected')) {
errorMessage = 'Transaction was rejected. Please try again.';
} else if (err.message.includes('insufficient funds')) {
errorMessage = 'Insufficient DINO tokens. Please check your balance.';
} else if (err.message.includes('Transaction simulation failed')) {
errorMessage = 'Transaction validation failed. Please check your wallet connection and try again.';
} else if (err.message.includes('blockhash')) {
errorMessage = 'Transaction expired. Please try again.';
} else {
errorMessage = err.message;
}
}
setError(errorMessage);
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }

View File

@ -0,0 +1,128 @@
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;
}