Browse Source

test: add transaction parse test script

Add test-tx-parse.js to verify copy parameter extraction:
- Parse transaction and extract parent position from memo
- Fetch parent details from Byreal API
- Extract token transfers by comparing pre/post balances
- Calculate USD value and copy amount
- Print all parameters needed for copy operation
lushdog@outlook.com 1 tháng trước cách đây
mục cha
commit
8aa22654d8
1 tập tin đã thay đổi với 274 bổ sung0 xóa
  1. 274 0
      test-tx-parse.js

+ 274 - 0
test-tx-parse.js

@@ -0,0 +1,274 @@
+#!/usr/bin/env node
+
+/**
+ * Test: Parse transaction and extract copy parameters
+ * Usage: node test-tx-parse.js <transaction_signature>
+ * 
+ * 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 <transaction_signature>');
+  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));
+});