import { CryptoEngine } from './crypto-engine'; import { db } from './db'; /** * Background Sync Worker * Checks blockchain for pending payments and completes them if detected. */ export async function syncPendingPayments() { console.log("[SyncWorker] Starting manual sync..."); // 1. Fetch pending transactions that have an intent (or check all crypto transactions) // We filter for transactions with statuses that indicate they are waiting for payment // and have a provider type that suggests they are crypto (or just check metadata.wallets) const result = await db.query(` SELECT * FROM transactions WHERE status IN ('waiting', 'pending') AND (metadata->>'wallets' IS NOT NULL) ORDER BY created_at DESC LIMIT 20 `); const pendingTxs = result.rows; console.log(`[SyncWorker] Found ${pendingTxs.length} pending crypto transactions.`); const results = []; // 2. Fetch System Settings once const settingsResult = await db.query('SELECT key, value FROM system_settings WHERE key IN (\'sol_platform_address\', \'evm_platform_address\', \'tron_platform_address\', \'btc_platform_address\', \'default_fee_percent\')'); const systemMap: Record = {}; settingsResult.rows.forEach(r => systemMap[r.key] = r.value); const sysSettings = { sol: systemMap.sol_platform_address || process.env.SOL_PLATFORM_ADDRESS || "Ajr4nKieZJVu9q2d1eVF9pQPRCLoZ6v4tapB3iQn2SyQ", evm: systemMap.evm_platform_address || process.env.EVM_PLATFORM_ADDRESS || "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", tron: systemMap.tron_platform_address || process.env.TRON_PLATFORM_ADDRESS || "TLYpfG6rre8Gv9m8pYjR7yvX7S9rK6G1P", btc: systemMap.btc_platform_address || process.env.BTC_PLATFORM_ADDRESS || "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfJH", fee: parseFloat(systemMap.default_fee_percent || '1.0') }; for (const tx of pendingTxs) { try { const metadata = tx.metadata || {}; const wallets = metadata.wallets || {}; const scanningMatrix = [ { network: 'SOLANA', tokens: ['SOL', 'USDT', 'USDC'] }, { network: 'POLYGON', tokens: ['MATIC', 'USDT', 'USDC'] }, { network: 'TRON', tokens: ['TRX', 'USDT', 'USDC'] } ]; if (metadata.intent_network && metadata.intent_token) { const intentIdx = scanningMatrix.findIndex(m => m.network === metadata.intent_network); if (intentIdx > -1) { const intentItem = scanningMatrix.splice(intentIdx, 1)[0]; const tokenIdx = intentItem.tokens.indexOf(metadata.intent_token); if (tokenIdx > -1) { intentItem.tokens.splice(tokenIdx, 1); intentItem.tokens.unshift(metadata.intent_token); } scanningMatrix.unshift(intentItem); } } let foundPayment = false; for (const scan of scanningMatrix) { if (foundPayment) break; const networkId = scan.network; const walletConfig = wallets[networkId] || (networkId === 'POLYGON' ? wallets['EVM'] : null); if (!walletConfig) continue; const depositAddress = typeof walletConfig === 'string' ? walletConfig : walletConfig.address; const depositPrivateKey = walletConfig.privateKey; if (!depositPrivateKey) continue; // Create engine with explicit network and ensured config const cryptoEngine = new CryptoEngine(networkId); for (const tokenSymbol of scan.tokens) { if (foundPayment) break; console.log(`[SyncWorker] Checking TX ${tx.id.slice(0,8)} | Network: ${networkId} | Token: ${tokenSymbol} | Address: ${depositAddress}`); let expectedCryptoAmount = tx.amount.toString(); try { const symbolMap: Record = { 'SOL': 'SOLUSDT', 'MATIC': 'MATICUSDT', 'POLYGON': 'MATICUSDT', 'USDC': 'USDCUSDT', 'USDT': 'USDTUSDT', 'TRX': 'TRXUSDT' }; const pair = symbolMap[tokenSymbol] || 'SOLUSDT'; // 1. Get USD/TRY rate const tryRes = await fetch(`https://api.binance.com/api/v3/ticker/price?symbol=USDTTRY`); const tryData = await tryRes.json(); const usdTryPrice = parseFloat(tryData.price) || 32.5; // 2. Get Crypto/USD rate let cryptoUsdPrice = 1.0; if (pair !== 'USDTUSDT' && pair !== 'USDCUSDT') { const cryptoRes = await fetch(`https://api.binance.com/api/v3/ticker/price?symbol=${pair}`); const cryptoData = await cryptoRes.json(); cryptoUsdPrice = parseFloat(cryptoData.price) || 1.0; } const priceInTry = cryptoUsdPrice * usdTryPrice; const currency = (tx.currency || 'TRY').toUpperCase(); const priceInTarget = currency === 'USD' ? cryptoUsdPrice : priceInTry; if (priceInTarget > 0) { const rawExpected = parseFloat(tx.amount) / priceInTarget; expectedCryptoAmount = (rawExpected * 0.98).toFixed(6); console.log(`[SyncWorker] Price for ${tokenSymbol}: ${priceInTarget.toFixed(2)} ${currency} | Expected: ${expectedCryptoAmount}`); } } catch (e) { console.warn(`[SyncWorker] Price fetch failed for ${tokenSymbol}, using backup static rates.`); // Absolute fallback for safety const staticRates: Record = { 'SOL': 4000, 'USDT': 33, 'USDC': 33, 'TRX': 5, 'MATIC': 25 }; const rate = staticRates[tokenSymbol] || 1; expectedCryptoAmount = (parseFloat(tx.amount) / rate * 0.95).toFixed(6); } const verification = await cryptoEngine.verifyPayment(depositAddress, expectedCryptoAmount, tokenSymbol); if (verification.success) { console.log(`[SyncWorker] Found ${tokenSymbol} matching ~${expectedCryptoAmount} on ${networkId}`); let platformAddress = sysSettings.evm; if (networkId === 'SOLANA') platformAddress = sysSettings.sol; else if (networkId === 'TRON') platformAddress = sysSettings.tron; const sweepResult = await cryptoEngine.sweepFunds(depositPrivateKey, platformAddress, tokenSymbol); if (sweepResult.success) { const merchantResult = await db.query('SELECT * FROM merchants WHERE id = $1', [tx.merchant_id]); const merchant = merchantResult.rows[0]; const feePercent = merchant?.fee_percent !== undefined && merchant?.fee_percent !== null ? parseFloat(merchant.fee_percent) : sysSettings.fee; const grossAmount = parseFloat(tx.amount); const feeAmount = (grossAmount * feePercent) / 100; const merchantNetCredit = grossAmount - feeAmount; const cryptoAmount = parseFloat(expectedCryptoAmount); const cryptoFee = (cryptoAmount * feePercent) / 100; const cryptoNetCredit = cryptoAmount - cryptoFee; await db.query(`UPDATE merchants SET available_balance = available_balance + $1 WHERE id = $2`, [merchantNetCredit, tx.merchant_id]); await db.query(` INSERT INTO merchant_balances (merchant_id, network, token, balance, total_gross) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (merchant_id, network, token) DO UPDATE SET balance = merchant_balances.balance + $4, total_gross = merchant_balances.total_gross + $5 `, [tx.merchant_id, networkId, tokenSymbol, cryptoNetCredit, cryptoAmount]); await db.query(` UPDATE transactions SET status = 'succeeded', paid_network = $2, paid_token = $3, paid_amount_crypto = $4 WHERE id = $1`, [tx.id, networkId, tokenSymbol, expectedCryptoAmount]); if (tx.callback_url) { fetch(tx.callback_url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'success', txId: tx.stripe_pi_id, orderRef: tx.source_ref_id, hashes: { txHash: sweepResult.txHash } }) }).catch(() => {}); } results.push({ id: tx.id, status: 'synced', network: networkId, token: tokenSymbol }); foundPayment = true; } } } } if (!foundPayment) results.push({ id: tx.id, status: 'no_payment' }); } catch (err: any) { console.error(`[SyncWorker] Error processing TX ${tx.id}:`, err); results.push({ id: tx.id, status: 'error', error: err.message }); } } return results; }