#!/usr/bin/env node /** * Test: Parse transaction and extract copy parameters * Usage: node test-tx-parse.js * * Example: node test-tx-parse.js 5SnXmX8T4sytTFu8xeG7RXmui1eK65no8B3eJ3knTtdXqr3PqmDma5CaY9C7NG2oycvNs7KKZ75pPNDXB2T5XYQE */ import { Connection, PublicKey } from '@solana/web3.js'; import { CONFIG } from './src/config/index.js'; import { ByrealAPI } from './src/services/byreal.js'; import { logger, setLogLevel } from './src/utils/index.js'; setLogLevel('info'); const TX_SIGNATURE = process.argv[2]; if (!TX_SIGNATURE) { console.error('Usage: node test-tx-parse.js '); console.error('Example: node test-tx-parse.js 5SnXmX8T4sytTFu8xeG7RXmui1eK65no8B3eJ3knTtdXqr3PqmDma5CaY9C7NG2oycvNs7KKZ75pPNDXB2T5XYQE'); process.exit(1); } const BYREAL_PROGRAM_ID = 'REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2'; const MEMO_PROGRAM_ID = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'; async function testTransactionParse() { console.log('═══════════════════════════════════════════════════'); console.log('🧪 Testing Transaction Parse for Copy Parameters'); console.log(`📍 Transaction: ${TX_SIGNATURE.slice(0, 20)}...`); console.log('═══════════════════════════════════════════════════\n'); const connection = new Connection(CONFIG.RPC_URL, 'confirmed'); try { // Step 1: Fetch transaction console.log('Step 1: Fetching transaction from Solana...'); const tx = await connection.getTransaction(TX_SIGNATURE, { commitment: 'confirmed', maxSupportedTransactionVersion: 0 }); if (!tx || !tx.transaction || !tx.meta) { console.error('❌ Transaction not found or invalid'); process.exit(1); } console.log('✅ Transaction found\n'); // Step 2: Check Byreal program console.log('Step 2: Checking Byreal program involvement...'); const message = tx.transaction.message; const accountKeys = message.staticAccountKeys || message.accountKeys || []; const hasByrealProgram = accountKeys.some( key => key.toString() === BYREAL_PROGRAM_ID ); if (!hasByrealProgram) { console.error('❌ Transaction does not involve Byreal program'); process.exit(1); } console.log('✅ Byreal program found\n'); // Step 3: Extract parent position from memo console.log('Step 3: Extracting parent position from memo...'); let parentPosition = null; const instructions = message.compiledInstructions || message.instructions || []; for (const instruction of instructions) { const programId = accountKeys[instruction.programIdIndex]?.toString(); if (programId === MEMO_PROGRAM_ID) { let memoData; if (instruction.data) { try { memoData = Buffer.from(instruction.data, 'base64').toString('utf8'); } catch { memoData = instruction.data; } } if (instruction.parsed) { memoData = instruction.parsed; } if (memoData) { console.log(` Memo data: ${memoData}`); const match = memoData.match(/referer_position=([A-Za-z0-9]+)/); if (match) { parentPosition = match[1]; } } } } // Check log messages as fallback if (!parentPosition && tx.meta.logMessages) { for (const log of tx.meta.logMessages) { const match = log.match(/referer_position=([A-Za-z0-9]+)/); if (match) { parentPosition = match[1]; console.log(` Found in logs: ${memoData}`); break; } } } if (!parentPosition) { console.error('❌ No parent position found in memo'); process.exit(1); } console.log(`✅ Parent Position: ${parentPosition}\n`); // Step 4: Fetch parent position details console.log('Step 4: Fetching parent position details from Byreal API...'); const parentDetail = await ByrealAPI.getPositionDetail(parentPosition); if (!parentDetail || !parentDetail.pool) { console.error('❌ Failed to fetch parent position details'); process.exit(1); } console.log('✅ Parent position details:'); console.log(` Pool: ${parentDetail.pool.mintA?.symbol}/${parentDetail.pool.mintB?.symbol}`); console.log(` Token A: ${parentDetail.pool.mintA?.address}`); console.log(` Token B: ${parentDetail.pool.mintB?.address}`); console.log(` NFT Mint: ${parentDetail.nftMintAddress || parentDetail.nftMint}`); console.log(` Pool Address: ${parentDetail.pool?.poolAddress || parentDetail.poolAddress}\n`); const tokenAMint = parentDetail.pool.mintA?.address; const tokenBMint = parentDetail.pool.mintB?.address; // Step 5: Parse transaction for token transfers console.log('Step 5: Parsing transaction for token transfers...'); if (!tx.meta.preTokenBalances || !tx.meta.postTokenBalances) { console.error('❌ No token balance data in transaction'); process.exit(1); } // Build pre-balance map const preBalances = {}; tx.meta.preTokenBalances.forEach(balance => { if (balance.mint === tokenAMint || balance.mint === tokenBMint) { const key = `${balance.accountIndex}-${balance.mint}`; preBalances[key] = { amount: parseFloat(balance.uiTokenAmount?.uiAmountString || 0), decimals: balance.decimals }; } }); console.log(` Pre-balances found: ${Object.keys(preBalances).length}`); // Find transfers by comparing pre/post const transfers = []; tx.meta.postTokenBalances.forEach(balance => { if (balance.mint === tokenAMint || balance.mint === tokenBMint) { const key = `${balance.accountIndex}-${balance.mint}`; const pre = preBalances[key]; if (pre) { const diff = pre.amount - parseFloat(balance.uiTokenAmount?.uiAmountString || 0); if (diff > 0.000001) { transfers.push({ mint: balance.mint, amount: diff, decimals: balance.decimals || pre.decimals || 6, symbol: balance.mint === tokenAMint ? parentDetail.pool.mintA?.symbol : parentDetail.pool.mintB?.symbol }); console.log(` Transfer detected: ${diff} ${balance.mint === tokenAMint ? parentDetail.pool.mintA?.symbol : parentDetail.pool.mintB?.symbol}`); } } } }); if (transfers.length === 0) { console.error('❌ No token transfers found for target tokens'); process.exit(1); } // Assign to tokenA and tokenB const tokenA = transfers.find(t => t.mint === tokenAMint) || { mint: tokenAMint, amount: 0, decimals: 6, symbol: parentDetail.pool.mintA?.symbol }; const tokenB = transfers.find(t => t.mint === tokenBMint) || { mint: tokenBMint, amount: 0, decimals: 6, symbol: parentDetail.pool.mintB?.symbol }; // Calculate USD value (simplified) const TOKEN_PRICES = { 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': 1, // USDC 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB': 1, // USDT }; const priceA = TOKEN_PRICES[tokenAMint] || 0; const priceB = TOKEN_PRICES[tokenBMint] || 0; const totalUsdValue = (tokenA.amount * priceA) + (tokenB.amount * priceB); if (totalUsdValue === 0) { // For non-stable pairs, use sum console.log(' ⚠️ No price data available, using sum of amounts'); } console.log(`✅ Token transfers extracted:\n`); // Step 6: Print copy parameters console.log('═══════════════════════════════════════════════════'); console.log('📋 COPY PARAMETERS'); console.log('═══════════════════════════════════════════════════\n'); console.log('Parent Position:'); console.log(` Address: ${parentPosition}`); console.log(` NFT Mint: ${parentDetail.nftMintAddress || parentDetail.nftMint}`); console.log(` Pool Address: ${parentDetail.pool?.poolAddress || parentDetail.poolAddress}`); console.log(` Pool: ${parentDetail.pool.mintA?.symbol}/${parentDetail.pool.mintB?.symbol}\n`); console.log('Token Transfers (Target deposited):'); console.log(` Token A: ${tokenA.amount} ${tokenA.symbol}`); console.log(` Mint: ${tokenA.mint}`); console.log(` Decimals: ${tokenA.decimals}`); console.log(` Token B: ${tokenB.amount} ${tokenB.symbol}`); console.log(` Mint: ${tokenB.mint}`); console.log(` Decimals: ${tokenB.decimals}\n`); console.log('Calculated Values:'); console.log(` Token A USD: $${(tokenA.amount * priceA).toFixed(2)}`); console.log(` Token B USD: $${(tokenB.amount * priceB).toFixed(2)}`); console.log(` Total USD Value: $${totalUsdValue.toFixed(2)}\n`); const copyMultiplier = CONFIG.COPY_MULTIPLIER; const maxUsd = CONFIG.MAX_USD_VALUE; const copyUsd = Math.min(totalUsdValue * copyMultiplier, maxUsd); console.log('Copy Calculation:'); console.log(` Target Value: $${totalUsdValue.toFixed(2)}`); console.log(` Multiplier: ${copyMultiplier}x`); console.log(` Calculated: $${(totalUsdValue * copyMultiplier).toFixed(2)}`); console.log(` Max Limit: $${maxUsd}`); console.log(` ✅ Final Copy Amount: $${copyUsd.toFixed(2)}\n`); console.log('═══════════════════════════════════════════════════'); console.log('✅ Test completed successfully!'); console.log('═══════════════════════════════════════════════════'); // Return data structure matching what sniper expects return { parentPositionAddress: parentPosition, transactionSignature: TX_SIGNATURE, parentDetail: parentDetail, tokenTransfers: { tokenA: tokenA, tokenB: tokenB, totalUsdValue: totalUsdValue }, targetUsdValue: totalUsdValue, copyUsdValue: copyUsd }; } catch (error) { console.error('❌ Test failed:', error); process.exit(1); } } testTransactionParse().then(data => { console.log('\n📦 Full data structure (as passed to sniper):'); console.log(JSON.stringify({ parentPositionAddress: data.parentPositionAddress, transactionSignature: data.transactionSignature, targetUsdValue: data.targetUsdValue, copyUsdValue: data.copyUsdValue }, null, 2)); });