mobile wallet fixes
This commit is contained in:
parent
e9913b1a4f
commit
a9313f8b0f
114
MOBILE_WALLET_FIXES.md
Normal file
114
MOBILE_WALLET_FIXES.md
Normal 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.
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
100
app/page.tsx
100
app/page.tsx
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
128
app/utils/mobileWalletUtils.ts
Normal file
128
app/utils/mobileWalletUtils.ts
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user