UNPKG

lemora-bot

Version:

Advanced AI-Powered Trading Signals & Wallet Monitoring for Solana

192 lines (159 loc) 7.39 kB
import { Connection, PublicKey, ParsedTransactionWithMeta } from '@solana/web3.js'; import { WalletAddress, WalletTransaction, TransactionType } from '@types/wallet'; export class SolanaWalletService { private connection: Connection; private heliusEndpoint: string; constructor(rpcEndpoint: string, heliusApiKey: string) { this.connection = new Connection(rpcEndpoint, 'confirmed'); this.heliusEndpoint = `https://api.helius.xyz/v0/addresses/${heliusApiKey}`; } async getWalletInfo(address: string): Promise<WalletAddress> { const publicKey = new PublicKey(address); const accountInfo = await this.connection.getAccountInfo(publicKey); if (!accountInfo) { throw new Error(`Wallet ${address} not found`); } const signatures = await this.connection.getSignaturesForAddress(publicKey, { limit: 100 }); return { address, label: '', isWhitelisted: false, lastActivity: new Date(signatures[0]?.blockTime * 1000 || Date.now()), totalTransactions: signatures.length, totalVolume: await this.calculateTotalVolume(address), averageTransactionValue: 0, riskScore: this.calculateRiskScore(signatures.length), tags: [] }; } async getRecentTransactions(address: string, limit: number = 20): Promise<WalletTransaction[]> { const publicKey = new PublicKey(address); const signatures = await this.connection.getSignaturesForAddress(publicKey, { limit }); const transactions: WalletTransaction[] = []; for (const sig of signatures) { const tx = await this.connection.getParsedTransaction(sig.signature); if (tx) { const parsedTx = this.parseTransaction(tx, address); if (parsedTx) { transactions.push(parsedTx); } } } return transactions; } private async calculateTotalVolume(address: string): Promise<number> { // Implementation for calculating total volume const signatures = await this.connection.getSignaturesForAddress(new PublicKey(address), { limit: 1000 }); let totalVolume = 0; // Calculate based on transaction history for (const sig of signatures.slice(0, 100)) { const tx = await this.connection.getParsedTransaction(sig.signature); if (tx?.meta?.postBalances && tx.meta.preBalances) { const balanceChange = Math.abs(tx.meta.postBalances[0] - tx.meta.preBalances[0]); totalVolume += balanceChange / 1e9; // Convert lamports to SOL } } return totalVolume; } private calculateRiskScore(transactionCount: number): number { // Simple risk scoring algorithm if (transactionCount < 10) return 0.9; if (transactionCount < 100) return 0.5; if (transactionCount < 1000) return 0.3; return 0.1; } private parseTransaction(tx: ParsedTransactionWithMeta, walletAddress: string): WalletTransaction | null { if (!tx.meta || !tx.blockTime) return null; const instruction = tx.transaction.message.instructions[0]; const transactionType = this.determineTransactionType(instruction); const balanceChange = this.calculateBalanceChange(tx.meta.preBalances, tx.meta.postBalances); const involvedAddresses = this.extractInvolvedAddresses(tx); return { signature: tx.transaction.signatures[0], blockTime: tx.blockTime, slot: tx.slot, fee: tx.meta.fee, status: tx.meta.err ? 'failed' : 'success', type: transactionType, from: walletAddress, to: involvedAddresses.recipient || '', amount: balanceChange.amount, mint: balanceChange.mint || '', symbol: this.getTokenSymbol(balanceChange.mint) || 'SOL', price: 0, valueUSD: 0, programId: instruction.programId.toString(), instructions: this.extractInstructionData(tx.transaction.message.instructions), computeUnitsConsumed: tx.meta.computeUnitsConsumed || 0, logs: tx.meta.logMessages || [], rewards: tx.meta.rewards || [] }; } private determineTransactionType(instruction: any): TransactionType { const programId = instruction.programId.toString(); const PROGRAM_MAPPINGS = { '11111111111111111111111111111111': TransactionType.TRANSFER, 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA': TransactionType.SWAP, 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4': TransactionType.SWAP, 'srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX': TransactionType.SWAP, 'whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc': TransactionType.SWAP, 'DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1': TransactionType.STAKING, 'Stake11111111111111111111111111111111111111': TransactionType.STAKING, 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb': TransactionType.NFT, 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s': TransactionType.NFT }; for (const [programAddress, type] of Object.entries(PROGRAM_MAPPINGS)) { if (programId.includes(programAddress)) { return type; } } return TransactionType.UNKNOWN; } private calculateBalanceChange(preBalances: number[], postBalances: number[]): { amount: number; mint?: string } { if (!preBalances || !postBalances || preBalances.length === 0) { return { amount: 0 }; } const mainAccountChange = Math.abs(postBalances[0] - preBalances[0]); return { amount: mainAccountChange / 1e9, // Convert lamports to SOL mint: 'So11111111111111111111111111111111111111112' // SOL mint }; } private extractInvolvedAddresses(tx: ParsedTransactionWithMeta): { recipient?: string; sender?: string } { const accountKeys = tx.transaction.message.accountKeys; if (accountKeys.length > 1) { return { sender: accountKeys[0].pubkey.toString(), recipient: accountKeys[1].pubkey.toString() }; } return {}; } private extractInstructionData(instructions: any[]): any[] { return instructions.map(instruction => ({ programId: instruction.programId.toString(), accounts: instruction.accounts?.map((acc: any) => acc.toString()) || [], data: instruction.data || null })); } private getTokenSymbol(mint?: string): string | null { const TOKEN_REGISTRY = { 'So11111111111111111111111111111111111111112': 'SOL', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': 'USDC', 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB': 'USDT', 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So': 'mSOL', '7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj': 'stSOL' }; return mint ? TOKEN_REGISTRY[mint as keyof typeof TOKEN_REGISTRY] || null : null; } async subscribeToWallet(address: string, callback: (transaction: WalletTransaction) => void): Promise<number> { const publicKey = new PublicKey(address); return this.connection.onAccountChange(publicKey, (accountInfo, context) => { // Handle account changes and emit transaction events console.log(`Account ${address} changed at slot ${context.slot}`); }); } unsubscribeFromWallet(subscriptionId: number): void { this.connection.removeAccountChangeListener(subscriptionId); } }