test-tx-analysis.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. #!/usr/bin/env node
  2. /**
  3. * Test: Calculate copy amount from target position (not parent)
  4. * Analyzes a specific transaction to get the target's position value
  5. * Transaction: 5SnXmX8T4sytTFu8xeG7RXmui1eK65no8B3eJ3knTtdXqr3PqmDma5CaY9C7NG2oycvNs7KKZ75pPNDXB2T5XYQE
  6. */
  7. import { Connection, PublicKey } from '@solana/web3.js';
  8. import { CONFIG } from './src/config/index.js';
  9. import { logger, setLogLevel } from './src/utils/index.js';
  10. setLogLevel('debug');
  11. const TX_SIGNATURE = '5SnXmX8T4sytTFu8xeG7RXmui1eK65no8B3eJ3knTtdXqr3PqmDma5CaY9C7NG2oycvNs7KKZ75pPNDXB2T5XYQE';
  12. async function analyzeTransaction() {
  13. console.log('═══════════════════════════════════════════');
  14. console.log('🧪 Analyzing Target Position Transaction');
  15. console.log(`📍 TX: ${TX_SIGNATURE.slice(0, 20)}...`);
  16. console.log('═══════════════════════════════════════════\n');
  17. const connection = new Connection(CONFIG.RPC_URL, 'confirmed');
  18. try {
  19. // Fetch transaction details
  20. console.log('Fetching transaction from Solana...');
  21. const tx = await connection.getTransaction(TX_SIGNATURE, {
  22. commitment: 'confirmed',
  23. maxSupportedTransactionVersion: 0
  24. });
  25. if (!tx) {
  26. console.error('❌ Transaction not found');
  27. return;
  28. }
  29. console.log('✅ Transaction found!\n');
  30. // Extract token transfers from pre/post token balances
  31. console.log('Step 1: Extracting token transfers from balance changes...');
  32. const transfers = [];
  33. // Compare pre and post token balances to find what was transferred
  34. if (tx.meta && tx.meta.preTokenBalances && tx.meta.postTokenBalances) {
  35. // Create a map of pre balances
  36. const preBalances = {};
  37. tx.meta.preTokenBalances.forEach(balance => {
  38. const key = `${balance.accountIndex}-${balance.mint}`;
  39. preBalances[key] = parseFloat(balance.uiTokenAmount?.uiAmountString || 0);
  40. });
  41. // Compare with post balances
  42. tx.meta.postTokenBalances.forEach(balance => {
  43. const key = `${balance.accountIndex}-${balance.mint}`;
  44. const preAmount = preBalances[key] || 0;
  45. const postAmount = parseFloat(balance.uiTokenAmount?.uiAmountString || 0);
  46. const diff = postAmount - preAmount;
  47. // If balance decreased significantly (transferred out), record it
  48. if (diff < -0.0001) {
  49. transfers.push({
  50. mint: balance.mint,
  51. uiAmount: Math.abs(diff),
  52. decimals: balance.decimals,
  53. accountIndex: balance.accountIndex,
  54. owner: balance.owner
  55. });
  56. }
  57. });
  58. }
  59. // Also try inner instructions as fallback
  60. if (transfers.length === 0 && tx.meta && tx.meta.innerInstructions) {
  61. for (const innerInst of tx.meta.innerInstructions) {
  62. for (const inst of innerInst.instructions) {
  63. if (inst.parsed?.type === 'transferChecked') {
  64. const info = inst.parsed.info;
  65. transfers.push({
  66. mint: info.mint,
  67. amount: info.tokenAmount?.amount,
  68. uiAmount: parseFloat(info.tokenAmount?.uiAmount || 0),
  69. decimals: info.tokenAmount?.decimals,
  70. destination: info.destination
  71. });
  72. }
  73. }
  74. }
  75. }
  76. console.log(`Found ${transfers.length} token transfers:\n`);
  77. transfers.forEach((t, i) => {
  78. console.log(` Transfer ${i + 1}:`);
  79. console.log(` Mint: ${t.mint}`);
  80. console.log(` Amount: ${t.uiAmount} (decimals: ${t.decimals})`);
  81. console.log(` Raw: ${t.amount}`);
  82. console.log(` Destination: ${t.destination?.slice(0, 16)}...`);
  83. });
  84. // Get token prices to calculate USD value
  85. console.log('\nStep 2: Fetching token prices...');
  86. const mints = [...new Set(transfers.map(t => t.mint))];
  87. console.log(`Token mints: ${mints.join(', ')}`);
  88. // Try to get prices from Jupiter price API
  89. let totalUsdValue = 0;
  90. try {
  91. const axios = (await import('axios')).default;
  92. const response = await axios.get(
  93. `https://api.jup.ag/price/v2?ids=${mints.join(',')}`,
  94. { timeout: 10000 }
  95. );
  96. const prices = response.data.data || {};
  97. console.log('\nToken prices:');
  98. transfers.forEach(t => {
  99. const price = prices[t.mint]?.price || 0;
  100. const value = t.uiAmount * price;
  101. totalUsdValue += value;
  102. console.log(` ${t.mint.slice(0, 8)}...: $${price} × ${t.uiAmount} = $${value.toFixed(2)}`);
  103. });
  104. console.log(`\n💰 Total Position Value: $${totalUsdValue.toFixed(2)}`);
  105. } catch (error) {
  106. console.log('⚠️ Could not fetch prices, showing raw amounts only');
  107. }
  108. // Calculate what we should copy
  109. console.log('\n═══════════════════════════════════════════');
  110. console.log('Step 3: Calculating copy amount');
  111. console.log('═══════════════════════════════════════════');
  112. const copyMultiplier = CONFIG.COPY_MULTIPLIER;
  113. const maxUsdValue = CONFIG.MAX_USD_VALUE;
  114. if (totalUsdValue > 0) {
  115. const copyUsdValue = Math.min(totalUsdValue * copyMultiplier, maxUsdValue);
  116. console.log(`Target Position Value: $${totalUsdValue.toFixed(2)}`);
  117. console.log(`Copy Multiplier: ${copyMultiplier}x`);
  118. console.log(`Calculated Copy: $${(totalUsdValue * copyMultiplier).toFixed(2)}`);
  119. console.log(`Max Allowed: $${maxUsdValue}`);
  120. console.log(`\n✅ Final Copy Amount: $${copyUsdValue.toFixed(2)}`);
  121. // Calculate token amounts proportionally
  122. console.log('\nToken amounts to acquire:');
  123. transfers.forEach(t => {
  124. const ratio = (t.uiAmount * copyMultiplier) / totalUsdValue * copyUsdValue;
  125. const withBuffer = ratio * 1.1; // 10% buffer
  126. console.log(` ${t.mint.slice(0, 8)}...: ${ratio.toFixed(6)} (with 10% buffer: ${withBuffer.toFixed(6)})`);
  127. });
  128. }
  129. // Extract position NFT and parent info from memo
  130. console.log('\n═══════════════════════════════════════════');
  131. console.log('Step 4: Extracting position info from memo');
  132. console.log('═══════════════════════════════════════════');
  133. const message = tx.transaction.message;
  134. const accountKeys = message.staticAccountKeys || message.accountKeys || [];
  135. const instructions = message.compiledInstructions || message.instructions || [];
  136. let parentPosition = null;
  137. let positionNftMint = null;
  138. // Look for memo instruction
  139. const MEMO_PROGRAM_ID = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
  140. for (const instruction of instructions) {
  141. const programId = accountKeys[instruction.programIdIndex]?.toString();
  142. if (programId === MEMO_PROGRAM_ID) {
  143. let memoData;
  144. if (instruction.data) {
  145. try {
  146. memoData = Buffer.from(instruction.data, 'base64').toString('utf8');
  147. } catch {
  148. memoData = instruction.data;
  149. }
  150. }
  151. if (instruction.parsed) {
  152. memoData = instruction.parsed;
  153. }
  154. if (memoData) {
  155. console.log(`Memo found: ${memoData}`);
  156. // Extract parent position
  157. const match = memoData.match(/referer_position=([A-Za-z0-9]+)/);
  158. if (match) {
  159. parentPosition = match[1];
  160. console.log(`✅ Parent Position: ${parentPosition}`);
  161. }
  162. }
  163. }
  164. // Look for Byreal program to extract position NFT
  165. const BYREAL_PROGRAM_ID = 'REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2';
  166. if (programId === BYREAL_PROGRAM_ID) {
  167. console.log('Found Byreal program instruction');
  168. // Position NFT is usually minted in this transaction
  169. // Look for it in postTokenBalances
  170. if (tx.meta && tx.meta.postTokenBalances) {
  171. for (const balance of tx.meta.postTokenBalances) {
  172. if (balance.uiTokenAmount?.uiAmount === 1 && balance.uiTokenAmount?.decimals === 0) {
  173. positionNftMint = balance.mint;
  174. console.log(`✅ Position NFT Mint: ${positionNftMint}`);
  175. break;
  176. }
  177. }
  178. }
  179. }
  180. }
  181. // Also check log messages
  182. if (tx.meta && tx.meta.logMessages) {
  183. for (const log of tx.meta.logMessages) {
  184. const match = log.match(/referer_position=([A-Za-z0-9]+)/);
  185. if (match && !parentPosition) {
  186. parentPosition = match[1];
  187. console.log(`✅ Parent Position (from logs): ${parentPosition}`);
  188. }
  189. }
  190. }
  191. console.log('\n═══════════════════════════════════════════');
  192. console.log('Summary');
  193. console.log('═══════════════════════════════════════════');
  194. console.log(`Transaction: ${TX_SIGNATURE}`);
  195. console.log(`Parent Position: ${parentPosition || 'Not found'}`);
  196. console.log(`Position NFT: ${positionNftMint || 'Not found'}`);
  197. console.log(`Target Position Value: $${totalUsdValue.toFixed(2)}`);
  198. console.log(`Recommended Copy: $${Math.min(totalUsdValue * copyMultiplier, maxUsdValue).toFixed(2)}`);
  199. console.log('\nNote: Copy amount is based on TARGET position value, not parent!');
  200. console.log('═══════════════════════════════════════════');
  201. } catch (error) {
  202. console.error('Error analyzing transaction:', error);
  203. process.exit(1);
  204. }
  205. }
  206. analyzeTransaction();