import assert from 'assert'; import BN from 'bn.js'; import { Connection, PublicKey, Transaction } from '@solana/web3.js'; import { TOKEN_PROGRAM_ID, createTransferInstruction, createAssociatedTokenAccountInstruction, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { SolanaPaymentResult, SolanaWalletType, SolanaWalletState } from '../types'; assert(process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS, 'SOLANA_TOKEN_MINT_ADDRESS is required'); assert(process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS, 'SOLANA_TOKEN_RECIPIENT_ADDRESS is required'); const TOKEN_MINT = process.env.NEXT_PUBLIC_SOLANA_TOKEN_MINT_ADDRESS; const PAYMENT_RECEIVER_ADDRESS = process.env.NEXT_PUBLIC_SOLANA_TOKEN_RECIPIENT_ADDRESS; export const connectSolanaWallet = async (walletType: SolanaWalletType): Promise => { try { if (walletType === 'phantom') { if (!window.phantom?.solana) { throw new Error('Phantom wallet not found. Please install Phantom browser extension.'); } const response = await window.phantom.solana.connect(); return { connected: true, publicKey: response.publicKey.toString(), walletType }; } else if (walletType === 'solflare') { if (!window.solflare) { throw new Error('Solflare wallet not found. Please install Solflare browser extension.'); } await window.solflare.connect(); const publicKey = window.solflare.publicKey?.toString(); if (!publicKey) { throw new Error('Failed to get public key from Solflare wallet'); } return { connected: true, publicKey, walletType }; } throw new Error(`Unsupported wallet type: ${walletType}`); } catch (error) { console.error('Failed to connect to Solana wallet:', error); throw error; } }; export const disconnectSolanaWallet = async (walletType: SolanaWalletType): Promise => { try { let wallet = null; if (walletType === 'phantom') { wallet = window.phantom?.solana; } else if (walletType === 'solflare') { wallet = window.solflare; } if (wallet && wallet.disconnect) { await wallet.disconnect(); } } catch (error) { console.error('Failed to disconnect Solana wallet:', error); } }; async function findAssociatedTokenAddress( walletAddress: PublicKey, tokenMintAddress: PublicKey ): Promise { return PublicKey.findProgramAddressSync( [ walletAddress.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), tokenMintAddress.toBuffer(), ], ASSOCIATED_TOKEN_PROGRAM_ID )[0]; } interface WalletAdapter { signAndSendTransaction(transaction: Transaction): Promise<{ signature: string }>; } export async function sendSolanaTokenPayment( connection: Connection, walletPublicKey: string, tokenAmount: BN, walletType: SolanaWalletType ): Promise { try { let wallet: WalletAdapter | null = null; if (walletType === 'phantom') { wallet = window.phantom?.solana || null; } else if (walletType === 'solflare') { wallet = window.solflare || null; } if (!wallet) { throw new Error(`${walletType} wallet not found`); } const senderPublicKey = new PublicKey(walletPublicKey); const mintPublicKey = new PublicKey(TOKEN_MINT); const receiverPublicKey = new PublicKey(PAYMENT_RECEIVER_ADDRESS); console.log('Processing payment with keys:', { sender: senderPublicKey.toBase58(), mint: mintPublicKey.toBase58(), receiver: receiverPublicKey.toBase58(), }); const senderATA = await findAssociatedTokenAddress( senderPublicKey, mintPublicKey ); const receiverATA = await findAssociatedTokenAddress( receiverPublicKey, mintPublicKey ); console.log('Token accounts:', { senderATA: senderATA.toBase58(), receiverATA: receiverATA.toBase58(), }); const transaction = new Transaction(); // Check if accounts exist const [senderATAInfo, receiverATAInfo] = await Promise.all([ connection.getAccountInfo(senderATA), connection.getAccountInfo(receiverATA), ]); // Create receiver token account if it doesn't exist if (!receiverATAInfo) { console.log('Creating receiver token account'); transaction.add( createAssociatedTokenAccountInstruction( senderPublicKey, receiverATA, receiverPublicKey, mintPublicKey ) ); } // Create sender token account if it doesn't exist if (!senderATAInfo) { console.log('Creating sender token account'); transaction.add( createAssociatedTokenAccountInstruction( senderPublicKey, senderATA, senderPublicKey, mintPublicKey ) ); } const amountToSend = BigInt(tokenAmount.toString()); // Add transfer instruction transaction.add( createTransferInstruction( senderATA, receiverATA, senderPublicKey, amountToSend ) ); // Set transaction details const latestBlockhash = await connection.getLatestBlockhash('confirmed'); transaction.recentBlockhash = latestBlockhash.blockhash; transaction.feePayer = senderPublicKey; console.log('Sending transaction...'); const { signature } = await wallet.signAndSendTransaction(transaction); console.log('Transaction sent:', signature); // Confirm transaction const confirmation = await connection.confirmTransaction({ signature, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, }, 'confirmed'); if (confirmation.value.err) { console.error('Transaction error:', confirmation.value.err); throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`); } return { success: true, transactionSignature: signature }; } catch (error) { console.error('Payment error:', error); return { success: false, error: error instanceof Error ? error.message : 'Payment failed' }; } }; // Helper function to check wallet connection status export const checkSolanaWalletConnection = (walletType: SolanaWalletType): boolean => { try { if (walletType === 'phantom') { return window.phantom?.solana?.isConnected || false; } else if (walletType === 'solflare') { return window.solflare?.isConnected || false; } return false; } catch { return false; } };