fix: resolve solana sync verification issues and improve logging
This commit is contained in:
16
check_txs.js
Normal file
16
check_txs.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
const { Client } = require('pg');
|
||||||
|
const client = new Client({ connectionString: process.env.DATABASE_URL });
|
||||||
|
|
||||||
|
async function checkSpecificTxs() {
|
||||||
|
await client.connect();
|
||||||
|
const res = await client.query(`
|
||||||
|
SELECT id, source_ref_id, metadata
|
||||||
|
FROM transactions
|
||||||
|
WHERE source_ref_id IN ('TEST_ORDER_999', 'TEST_ORDER_1000')
|
||||||
|
`);
|
||||||
|
console.log(JSON.stringify(res.rows, null, 2));
|
||||||
|
await client.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSpecificTxs();
|
||||||
53
check_wallets.js
Normal file
53
check_wallets.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
const { Client } = require('pg');
|
||||||
|
const { Connection, PublicKey } = require('@solana/web3.js');
|
||||||
|
|
||||||
|
async function debugSolanaPayments() {
|
||||||
|
const client = new Client({ connectionString: process.env.DATABASE_URL });
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
|
const connection = new Connection('https://api.devnet.solana.com', 'confirmed');
|
||||||
|
|
||||||
|
const res = await client.query(`
|
||||||
|
SELECT id, source_ref_id, amount, currency, metadata
|
||||||
|
FROM transactions
|
||||||
|
WHERE source_ref_id IN ('TEST_ORDER_999', 'TEST_ORDER_1000')
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log(`Found ${res.rows.length} transactions for debugging.\n`);
|
||||||
|
|
||||||
|
for (const tx of res.rows) {
|
||||||
|
const solAddr = tx.metadata?.wallets?.SOLANA?.address;
|
||||||
|
console.log(`TX: ${tx.id} | Ref: ${tx.source_ref_id}`);
|
||||||
|
console.log(`Amount: ${tx.amount} ${tx.currency}`);
|
||||||
|
|
||||||
|
if (!solAddr) {
|
||||||
|
console.log("❌ No Solana wallet found in metadata.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Wallet Address: ${solAddr}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pubKey = new PublicKey(solAddr);
|
||||||
|
const balance = await connection.getBalance(pubKey);
|
||||||
|
console.log(`Balance: ${balance / 1e9} SOL`);
|
||||||
|
|
||||||
|
// Also check common tokens
|
||||||
|
const USDT_MINT = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'; // Placeholder for USDT on mainnet/devnet logic
|
||||||
|
// Since this is a check, let's just see transactions on that address
|
||||||
|
const signatures = await connection.getSignaturesForAddress(pubKey);
|
||||||
|
console.log(`Recent Activity: ${signatures.length} signatures found.`);
|
||||||
|
if (signatures.length > 0) {
|
||||||
|
console.log(`Latest Sig: ${signatures[0].signature}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error checking address: ${err.message}`);
|
||||||
|
}
|
||||||
|
console.log("-" . repeat(40));
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
debugSolanaPayments();
|
||||||
52
debug_verification.js
Normal file
52
debug_verification.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
const { Client } = require('pg');
|
||||||
|
const { Connection, PublicKey } = require('@solana/web3.js');
|
||||||
|
|
||||||
|
async function debugVerification() {
|
||||||
|
const client = new Client({ connectionString: process.env.DATABASE_URL });
|
||||||
|
await client.connect();
|
||||||
|
const connection = new Connection('https://api.devnet.solana.com', 'confirmed');
|
||||||
|
|
||||||
|
const res = await client.query(`
|
||||||
|
SELECT id, source_ref_id, amount, currency, metadata
|
||||||
|
FROM transactions
|
||||||
|
WHERE source_ref_id IN ('TEST_ORDER_999', 'TEST_ORDER_1000')
|
||||||
|
`);
|
||||||
|
|
||||||
|
for (const tx of res.rows) {
|
||||||
|
console.log(`\n--- Checking ${tx.source_ref_id} ---`);
|
||||||
|
const solAddr = tx.metadata?.wallets?.SOLANA?.address;
|
||||||
|
|
||||||
|
// 1. Calculate Backend's expected amount
|
||||||
|
// Simple mock of logic in sync-worker
|
||||||
|
const amountTry = parseFloat(tx.amount);
|
||||||
|
// Let's assume SOL price is around 4000 TRY for this debug
|
||||||
|
const solPriceRes = await fetch('https://api.binance.com/api/v3/ticker/price?symbol=SOLUSDT');
|
||||||
|
const solPriceData = await solPriceRes.json();
|
||||||
|
const usdtTryRes = await fetch('https://api.binance.com/api/v3/ticker/price?symbol=USDTTRY');
|
||||||
|
const usdtTryData = await usdtTryRes.json();
|
||||||
|
|
||||||
|
const priceSolTry = parseFloat(solPriceData.price) * parseFloat(usdtTryData.price);
|
||||||
|
const rawExpected = amountTry / priceSolTry;
|
||||||
|
const expectedWithTolerance = (rawExpected * 0.98).toFixed(6);
|
||||||
|
|
||||||
|
console.log(`Amount: ${amountTry} TRY`);
|
||||||
|
console.log(`Current SOL Price: ~${priceSolTry.toFixed(2)} TRY`);
|
||||||
|
console.log(`Backend Minimum Expected: ${expectedWithTolerance} SOL`);
|
||||||
|
|
||||||
|
// 2. Check Actual Balance
|
||||||
|
const pubKey = new PublicKey(solAddr);
|
||||||
|
const balance = await connection.getBalance(pubKey);
|
||||||
|
const actualSol = balance / 1e9;
|
||||||
|
console.log(`Actual Wallet Balance: ${actualSol} SOL`);
|
||||||
|
|
||||||
|
if (actualSol >= parseFloat(expectedWithTolerance)) {
|
||||||
|
console.log("✅ VERIFICATION SHOULD PASS!");
|
||||||
|
} else {
|
||||||
|
console.log("❌ VERIFICATION FAILED: Insufficient balance for tolerance.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await client.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
debugVerification();
|
||||||
@@ -41,109 +41,132 @@ export async function syncPendingPayments() {
|
|||||||
for (const tx of pendingTxs) {
|
for (const tx of pendingTxs) {
|
||||||
try {
|
try {
|
||||||
const metadata = tx.metadata || {};
|
const metadata = tx.metadata || {};
|
||||||
const intentNetwork = metadata.intent_network || 'POLYGON';
|
|
||||||
const intentToken = metadata.intent_token || 'USDT';
|
|
||||||
|
|
||||||
console.log(`[SyncWorker] Syncing TX ${tx.id} | Intent: ${intentNetwork}/${intentToken}`);
|
|
||||||
|
|
||||||
// Re-use logic from crypto-sweep but optimized for background
|
|
||||||
const wallets = metadata.wallets || {};
|
const wallets = metadata.wallets || {};
|
||||||
const tempWalletConfig = wallets[intentNetwork] || wallets['EVM'];
|
|
||||||
|
|
||||||
if (!tempWalletConfig) continue;
|
const scanningMatrix = [
|
||||||
|
{ network: 'SOLANA', tokens: ['SOL', 'USDT', 'USDC'] },
|
||||||
const depositAddress = typeof tempWalletConfig === 'string' ? tempWalletConfig : tempWalletConfig.address;
|
{ network: 'POLYGON', tokens: ['MATIC', 'USDT', 'USDC'] },
|
||||||
const depositPrivateKey = tempWalletConfig.privateKey;
|
{ network: 'TRON', tokens: ['TRX', 'USDT', 'USDC'] }
|
||||||
|
];
|
||||||
if (!depositPrivateKey) continue;
|
|
||||||
|
|
||||||
const cryptoEngine = new CryptoEngine(intentNetwork);
|
if (metadata.intent_network && metadata.intent_token) {
|
||||||
|
const intentIdx = scanningMatrix.findIndex(m => m.network === metadata.intent_network);
|
||||||
// Get expected amount logic
|
if (intentIdx > -1) {
|
||||||
let expectedCryptoAmount = tx.amount.toString();
|
const intentItem = scanningMatrix.splice(intentIdx, 1)[0];
|
||||||
try {
|
const tokenIdx = intentItem.tokens.indexOf(metadata.intent_token);
|
||||||
const coinIdMap: Record<string, string> = {
|
if (tokenIdx > -1) {
|
||||||
'SOL': 'solana', 'USDC': 'usd-coin', 'USDT': 'tether', 'TRX': 'tron', 'BTC': 'bitcoin'
|
intentItem.tokens.splice(tokenIdx, 1);
|
||||||
};
|
intentItem.tokens.unshift(metadata.intent_token);
|
||||||
const coinId = coinIdMap[intentToken] || 'solana';
|
|
||||||
const priceUrl = `https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=usd,try`;
|
|
||||||
const priceRes = await fetch(priceUrl);
|
|
||||||
const priceData = await priceRes.json();
|
|
||||||
const currencyKey = (tx.currency || 'TRY').toLowerCase();
|
|
||||||
const priceInCurrency = priceData[coinId][currencyKey] || priceData[coinId]['usd'];
|
|
||||||
|
|
||||||
if (priceInCurrency) {
|
|
||||||
const rawExpected = parseFloat(tx.amount) / priceInCurrency;
|
|
||||||
expectedCryptoAmount = (rawExpected * 0.98).toFixed(6);
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
const verification = await cryptoEngine.verifyPayment(depositAddress, expectedCryptoAmount, intentToken);
|
|
||||||
|
|
||||||
if (verification.success) {
|
|
||||||
console.log(`[SyncWorker] Payment DETECTED for TX ${tx.id}. Sweeping...`);
|
|
||||||
|
|
||||||
let platformAddress = sysSettings.evm;
|
|
||||||
if (intentNetwork === 'SOLANA') platformAddress = sysSettings.sol;
|
|
||||||
else if (intentNetwork === 'TRON') platformAddress = sysSettings.tron;
|
|
||||||
else if (intentNetwork === 'BITCOIN') platformAddress = sysSettings.btc;
|
|
||||||
|
|
||||||
// Sweep
|
|
||||||
const sweepResult = await cryptoEngine.sweepFunds(depositPrivateKey, platformAddress, intentToken);
|
|
||||||
|
|
||||||
if (sweepResult.success) {
|
|
||||||
// Update Balances & Transaction (Same as crypto-sweep logic)
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Execute DB updates in transaction if possible, or sequential
|
|
||||||
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, intentNetwork, intentToken, 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, intentNetwork, intentToken, expectedCryptoAmount]);
|
|
||||||
|
|
||||||
// Webhook
|
|
||||||
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(() => {});
|
|
||||||
}
|
}
|
||||||
|
scanningMatrix.unshift(intentItem);
|
||||||
results.push({ id: tx.id, status: 'synced', txHash: sweepResult.txHash });
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
results.push({ id: tx.id, status: 'no_payment' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 coinIdMap: Record<string, string> = {
|
||||||
|
'SOL': 'solana', 'MATIC': 'matic-network', 'POLYGON': 'matic-network',
|
||||||
|
'USDC': 'usd-coin', 'USDT': 'tether', 'TRX': 'tron', 'BTC': 'bitcoin'
|
||||||
|
};
|
||||||
|
const coinId = coinIdMap[tokenSymbol] || 'solana';
|
||||||
|
const priceUrl = `https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=usd,try`;
|
||||||
|
const priceRes = await fetch(priceUrl);
|
||||||
|
const priceData = await priceRes.json();
|
||||||
|
const currencyKey = (tx.currency || 'TRY').toLowerCase();
|
||||||
|
const priceInCurrency = priceData[coinId][currencyKey] || priceData[coinId]['usd'];
|
||||||
|
|
||||||
|
if (priceInCurrency) {
|
||||||
|
const rawExpected = parseFloat(tx.amount) / priceInCurrency;
|
||||||
|
expectedCryptoAmount = (rawExpected * 0.98).toFixed(6);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[SyncWorker] Price fetch failed for ${tokenSymbol}, using raw amount.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
} catch (err: any) {
|
||||||
console.error(`[SyncWorker] Error processing TX ${tx.id}:`, err.message);
|
console.error(`[SyncWorker] Error processing TX ${tx.id}:`, err);
|
||||||
results.push({ id: tx.id, status: 'error', error: err.message });
|
results.push({ id: tx.id, status: 'error', error: err.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user