import { ethers } from 'ethers'; import { Connection, PublicKey, Keypair, Transaction, SystemProgram, clusterApiUrl, LAMPORTS_PER_SOL } from '@solana/web3.js'; import { getAssociatedTokenAddress, getAccount } from '@solana/spl-token'; import bs58 from 'bs58'; import cryptoConfig from './crypto-config.json'; // ERC20 ABI for checking USDT/USDC balances const ERC20_ABI = [ "function balanceOf(address owner) view returns (uint256)", "function decimals() view returns (uint8)", "function symbol() view returns (string)", "function transfer(address to, uint256 value) public returns (bool)" ]; export class CryptoEngine { private provider!: ethers.JsonRpcProvider; private solConnection!: Connection; private network: string; private config: any; constructor(networkId: string = 'POLYGON') { this.network = networkId; this.config = cryptoConfig.networks.find(n => n.id === networkId); if (!this.config) throw new Error(`Network ${networkId} not found in config.`); if (this.network === 'SOLANA') { this.solConnection = new Connection(this.config.rpc, 'confirmed'); } else { this.provider = new ethers.JsonRpcProvider(this.config.rpc); } } /** * Helper to get token config from JSON */ private getTokenConfig(symbol: string) { return this.config.tokens.find((t: any) => t.symbol === symbol); } /** * Generates a temporary wallet for a transaction. */ static async createTemporaryWallet(network: string = 'POLYGON'): Promise<{ address: string, privateKey: string }> { if (network === 'SOLANA') { const keypair = Keypair.generate(); return { address: keypair.publicKey.toBase58(), privateKey: bs58.encode(keypair.secretKey) }; } else { const wallet = ethers.Wallet.createRandom(); return { address: wallet.address, privateKey: wallet.privateKey }; } } /** * Sweeps funds from temporary wallet to Platform and Merchant */ async sweepFunds( tempWalletPrivateKey: string, merchantAddress: string, platformAddress: string, tokenSymbol: string = 'USDT' ) { if (this.network === 'SOLANA') { return this.sweepSolana(tempWalletPrivateKey, merchantAddress, platformAddress, tokenSymbol); } else { return this.sweepEVM(tempWalletPrivateKey, merchantAddress, platformAddress, tokenSymbol); } } private async sweepEVM( tempWalletPrivateKey: string, merchantAddress: string, platformAddress: string, tokenSymbol: string ) { const tempWallet = new ethers.Wallet(tempWalletPrivateKey, this.provider); const tokenConfig = this.getTokenConfig(tokenSymbol); if (!tokenConfig) throw new Error(`Unsupported token ${tokenSymbol} on ${this.network}`); console.log(`[Sweep EVM] Network: ${this.network} Total for ${tokenSymbol}`); // Mocking the real transfer for demo return { success: true, platformTx: '0x' + Math.random().toString(16).slice(2, 66), merchantTx: '0x' + Math.random().toString(16).slice(2, 66) }; } async fuelWallet(targetAddress: string, amount: string) { const gasTankKey = process.env.CRYPTO_GAS_TANK_KEY; if (!gasTankKey) { console.warn("[CryptoEngine] No CRYPTO_GAS_TANK_KEY provided. Fueling skipped (Demo mode)."); return; } try { const gasTank = new ethers.Wallet(gasTankKey, this.provider); const tx = await gasTank.sendTransaction({ to: targetAddress, value: ethers.parseEther(amount) }); await tx.wait(); console.log(`[CryptoEngine] Fueled ${targetAddress} with ${amount} native currency. Hash: ${tx.hash}`); } catch (error) { console.error("[CryptoEngine] Fueling failed:", error); } } private async sweepSolana( tempWalletPrivateKey: string, merchantAddress: string, platformAddress: string, tokenSymbol: string ) { const tempKeypair = Keypair.fromSecretKey(bs58.decode(tempWalletPrivateKey)); const pubKey = tempKeypair.publicKey; // Check if wallet needs SOL for gas const solBalance = await this.solConnection.getBalance(pubKey); if (solBalance < 5000000 && tokenSymbol !== 'SOL') { console.log(`[Sweep SOL] Low SOL for gas, fueling...`); } return { success: true, platformTx: 'sol_mock_tx_' + Math.random().toString(36).substring(7), merchantTx: 'sol_mock_tx_' + Math.random().toString(36).substring(7) }; } /** * Verifies if a specific amount has arrived at the address. */ async verifyPayment(address: string, expectedAmount: string, tokenSymbol?: string): Promise<{ success: boolean; txHash?: string; error?: string; }> { try { const tokenConfig = this.getTokenConfig(tokenSymbol || 'USDT'); if (!tokenConfig) return { success: false, error: "Token not supported in config" }; if (this.network === 'SOLANA') { const pubKey = new PublicKey(address); if (tokenConfig.address === 'NATIVE') { const balance = await this.solConnection.getBalance(pubKey); const balanceInSol = balance / LAMPORTS_PER_SOL; if (balanceInSol >= parseFloat(expectedAmount)) return { success: true }; } else { const tokenMint = new PublicKey(tokenConfig.address); const ata = await getAssociatedTokenAddress(tokenMint, pubKey); try { const accountInfo = await getAccount(this.solConnection, ata); const balance = Number(accountInfo.amount) / Math.pow(10, tokenConfig.decimals); if (balance >= parseFloat(expectedAmount)) return { success: true }; } catch (e) {} } } else { if (tokenConfig.address === 'NATIVE') { const balance = await this.provider.getBalance(address); const balanceInEth = ethers.formatEther(balance); if (parseFloat(balanceInEth) >= parseFloat(expectedAmount)) return { success: true }; } else { const contract = new ethers.Contract(tokenConfig.address, ERC20_ABI, this.provider); const balance = await contract.balanceOf(address); const formattedBalance = ethers.formatUnits(balance, tokenConfig.decimals); if (parseFloat(formattedBalance) >= parseFloat(expectedAmount)) return { success: true }; } } return { success: false }; } catch (error: any) { return { success: false, error: error.message }; } } }