Part of https://www.notion.so/Laconic-Mainnet-Plan-1eca6b22d47280569cd0d1e6d711d949 Co-authored-by: Shreerang Kale <shreerangkale@gmail.com> Co-authored-by: Nabarun <nabarun@deepstacksoft.com> Reviewed-on: #1 Co-authored-by: shreerang <shreerang@noreply.git.vdb.to> Co-committed-by: shreerang <shreerang@noreply.git.vdb.to>
226 lines
6.5 KiB
TypeScript
226 lines
6.5 KiB
TypeScript
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<SolanaWalletState> => {
|
|
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<void> => {
|
|
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<PublicKey> {
|
|
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<SolanaPaymentResult> {
|
|
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;
|
|
}
|
|
}; |