import { ethers } from 'ethers'; import { Connection, PublicKey, Keypair, Transaction, SystemProgram, sendAndConfirmTransaction, clusterApiUrl, LAMPORTS_PER_SOL } from '@solana/web3.js'; import { getAssociatedTokenAddress, getAccount, createTransferInstruction } from '@solana/spl-token'; import bs58 from 'bs58'; // Demo configuration - In production, these should be securely managed const RPC_URLS: Record = { ETH: 'https://rpc.ankr.com/eth', POLYGON: 'https://rpc.ankr.com/polygon', BSC: 'https://rpc.ankr.com/bsc' }; // AyrisSplitter Contract Address (Example addresses, in production use real deployed addresses) const SPLITTER_ADDRESSES: Record = { POLYGON: '0x999...AYRIS_SPLITTER_POLYGON', ETH: '0x888...AYRIS_SPLITTER_ETH' }; // 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)" ]; const STABLECOIN_ADDRESSES: Record> = { POLYGON: { USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359' }, ETH: { USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7', USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' }, SOLANA: { USDC: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU', // Devnet USDC USDT: 'EJwZgeZrdC8TXTQbQBoL6bfuAnFUUy1PVCMB4DYPzVaS' // Devnet USDT } }; export class CryptoEngine { private provider!: ethers.JsonRpcProvider; private solConnection!: Connection; private network: string; constructor(network: string = 'POLYGON') { this.network = network; if (network === 'SOLANA') { this.solConnection = new Connection(clusterApiUrl('devnet'), 'confirmed'); } else { this.provider = new ethers.JsonRpcProvider(RPC_URLS[network]); } } /** * 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 tokenAddress = STABLECOIN_ADDRESSES[this.network][tokenSymbol]; const contract = new ethers.Contract(tokenAddress, ERC20_ABI, tempWallet); const balance = await contract.balanceOf(tempWallet.address); if (balance === 0n) throw new Error("Balance is zero"); const platformShare = (balance * 100n) / 10000n; // %1 const merchantShare = balance - platformShare; // %99 console.log(`[Sweep EVM] Total: ${ethers.formatUnits(balance, 6)} ${tokenSymbol}`); console.log(`[Sweep EVM] Sending ${ethers.formatUnits(platformShare, 6)} to Platform...`); console.log(`[Sweep EVM] Sending ${ethers.formatUnits(merchantShare, 6)} to Merchant...`); return { success: true, platformTx: '0x' + Math.random().toString(16).slice(2, 66), merchantTx: '0x' + Math.random().toString(16).slice(2, 66) }; } private async sweepSolana( tempWalletPrivateKey: string, merchantAddress: string, platformAddress: string, tokenSymbol: string ) { const tempKeypair = Keypair.fromSecretKey(bs58.decode(tempWalletPrivateKey)); const pubKey = tempKeypair.publicKey; if (tokenSymbol === 'SOL') { const balance = await this.solConnection.getBalance(pubKey); if (balance === 0) throw new Error("Balance is zero"); // Leave some lamports for tx fees. Mock logic for now const actionableBalance = balance - 5000; const platformShare = Math.floor(actionableBalance * 0.01); const merchantShare = actionableBalance - platformShare; console.log(`[Sweep SOL] Total: ${balance / LAMPORTS_PER_SOL} SOL`); console.log(`[Sweep SOL] Platform Share: ${platformShare / LAMPORTS_PER_SOL} SOL`); console.log(`[Sweep SOL] Merchant Share: ${merchantShare / LAMPORTS_PER_SOL} SOL`); // Off-chain transaction split using SystemProgram const transaction = new Transaction().add( SystemProgram.transfer({ fromPubkey: pubKey, toPubkey: new PublicKey(platformAddress), lamports: platformShare, }), SystemProgram.transfer({ fromPubkey: pubKey, toPubkey: new PublicKey(merchantAddress), lamports: merchantShare, }) ); // Uncomment the following line to actually send the transaction on devnet // const txSig = await sendAndConfirmTransaction(this.solConnection, transaction, [tempKeypair]); const mockTxSig = bs58.encode(ethers.randomBytes(32)); return { success: true, platformTx: mockTxSig, merchantTx: mockTxSig }; } else { // Processing SPL tokens (USDC/USDT) const tokenMint = new PublicKey(STABLECOIN_ADDRESSES['SOLANA'][tokenSymbol]); const tempAta = await getAssociatedTokenAddress(tokenMint, pubKey); // For real transactions we would: // 1. Check/create ATAs for platform & merchant // 2. Add transfer instructions // 3. Send transaction console.log(`[Sweep SOL SPL] Mint: ${tokenMint.toBase58()}`); return { success: true, platformTx: 'sol_mock_tx_spl', merchantTx: 'sol_mock_tx_spl' }; } } /** * 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 { if (this.network === 'SOLANA') { const pubKey = new PublicKey(address); if (!tokenSymbol || tokenSymbol === 'SOL') { 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(STABLECOIN_ADDRESSES['SOLANA'][tokenSymbol]); const ata = await getAssociatedTokenAddress(tokenMint, pubKey); try { const accountInfo = await getAccount(this.solConnection, ata); // Using mock decimals 6 for USDT/USDC const balance = Number(accountInfo.amount) / 1_000_000; if (balance >= parseFloat(expectedAmount)) { return { success: true }; } } catch (e) { // Account might not exist yet } } } else { if (!tokenSymbol || tokenSymbol === 'ETH' || tokenSymbol === 'MATIC') { const balance = await this.provider.getBalance(address); const balanceInEth = ethers.formatEther(balance); if (parseFloat(balanceInEth) >= parseFloat(expectedAmount)) { return { success: true }; } } else { // Check ERC20 balance (USDT/USDC) const tokenAddress = STABLECOIN_ADDRESSES[this.network][tokenSymbol]; if (!tokenAddress) throw new Error("Unsupported token"); const contract = new ethers.Contract(tokenAddress, ERC20_ABI, this.provider); const balance = await contract.balanceOf(address); const decimals = await contract.decimals(); const formattedBalance = ethers.formatUnits(balance, decimals); if (parseFloat(formattedBalance) >= parseFloat(expectedAmount)) { return { success: true }; } } } return { success: false }; } catch (error: any) { return { success: false, error: error.message }; } } }