test-tx-parse.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. #!/usr/bin/env node
  2. /**
  3. * Test: Parse transaction and extract copy parameters
  4. * Usage: node test-tx-parse.js <transaction_signature>
  5. *
  6. * Example: node test-tx-parse.js 5SnXmX8T4sytTFu8xeG7RXmui1eK65no8B3eJ3knTtdXqr3PqmDma5CaY9C7NG2oycvNs7KKZ75pPNDXB2T5XYQE
  7. */
  8. import { Connection, PublicKey } from '@solana/web3.js';
  9. import { CONFIG } from './src/config/index.js';
  10. import { ByrealAPI } from './src/services/byreal.js';
  11. import { logger, setLogLevel } from './src/utils/index.js';
  12. setLogLevel('info');
  13. const TX_SIGNATURE = process.argv[2];
  14. if (!TX_SIGNATURE) {
  15. console.error('Usage: node test-tx-parse.js <transaction_signature>');
  16. console.error('Example: node test-tx-parse.js 5SnXmX8T4sytTFu8xeG7RXmui1eK65no8B3eJ3knTtdXqr3PqmDma5CaY9C7NG2oycvNs7KKZ75pPNDXB2T5XYQE');
  17. process.exit(1);
  18. }
  19. const BYREAL_PROGRAM_ID = 'REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2';
  20. const MEMO_PROGRAM_ID = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
  21. async function testTransactionParse() {
  22. console.log('═══════════════════════════════════════════════════');
  23. console.log('🧪 Testing Transaction Parse for Copy Parameters');
  24. console.log(`📍 Transaction: ${TX_SIGNATURE.slice(0, 20)}...`);
  25. console.log('═══════════════════════════════════════════════════\n');
  26. const connection = new Connection(CONFIG.RPC_URL, 'confirmed');
  27. try {
  28. // Step 1: Fetch transaction
  29. console.log('Step 1: Fetching transaction from Solana...');
  30. const tx = await connection.getTransaction(TX_SIGNATURE, {
  31. commitment: 'confirmed',
  32. maxSupportedTransactionVersion: 0
  33. });
  34. if (!tx || !tx.transaction || !tx.meta) {
  35. console.error('❌ Transaction not found or invalid');
  36. process.exit(1);
  37. }
  38. console.log('✅ Transaction found\n');
  39. // Step 2: Check Byreal program
  40. console.log('Step 2: Checking Byreal program involvement...');
  41. const message = tx.transaction.message;
  42. const accountKeys = message.staticAccountKeys || message.accountKeys || [];
  43. const hasByrealProgram = accountKeys.some(
  44. key => key.toString() === BYREAL_PROGRAM_ID
  45. );
  46. if (!hasByrealProgram) {
  47. console.error('❌ Transaction does not involve Byreal program');
  48. process.exit(1);
  49. }
  50. console.log('✅ Byreal program found\n');
  51. // Step 3: Extract parent position from memo
  52. console.log('Step 3: Extracting parent position from memo...');
  53. let parentPosition = null;
  54. const instructions = message.compiledInstructions || message.instructions || [];
  55. for (const instruction of instructions) {
  56. const programId = accountKeys[instruction.programIdIndex]?.toString();
  57. if (programId === MEMO_PROGRAM_ID) {
  58. let memoData;
  59. if (instruction.data) {
  60. try {
  61. memoData = Buffer.from(instruction.data, 'base64').toString('utf8');
  62. } catch {
  63. memoData = instruction.data;
  64. }
  65. }
  66. if (instruction.parsed) {
  67. memoData = instruction.parsed;
  68. }
  69. if (memoData) {
  70. console.log(` Memo data: ${memoData}`);
  71. const match = memoData.match(/referer_position=([A-Za-z0-9]+)/);
  72. if (match) {
  73. parentPosition = match[1];
  74. }
  75. }
  76. }
  77. }
  78. // Check log messages as fallback
  79. if (!parentPosition && tx.meta.logMessages) {
  80. for (const log of tx.meta.logMessages) {
  81. const match = log.match(/referer_position=([A-Za-z0-9]+)/);
  82. if (match) {
  83. parentPosition = match[1];
  84. console.log(` Found in logs: ${memoData}`);
  85. break;
  86. }
  87. }
  88. }
  89. if (!parentPosition) {
  90. console.error('❌ No parent position found in memo');
  91. process.exit(1);
  92. }
  93. console.log(`✅ Parent Position: ${parentPosition}\n`);
  94. // Step 4: Fetch parent position details
  95. console.log('Step 4: Fetching parent position details from Byreal API...');
  96. const parentDetail = await ByrealAPI.getPositionDetail(parentPosition);
  97. if (!parentDetail || !parentDetail.pool) {
  98. console.error('❌ Failed to fetch parent position details');
  99. process.exit(1);
  100. }
  101. console.log('✅ Parent position details:');
  102. console.log(` Pool: ${parentDetail.pool.mintA?.symbol}/${parentDetail.pool.mintB?.symbol}`);
  103. console.log(` Token A: ${parentDetail.pool.mintA?.address}`);
  104. console.log(` Token B: ${parentDetail.pool.mintB?.address}`);
  105. console.log(` NFT Mint: ${parentDetail.nftMintAddress || parentDetail.nftMint}`);
  106. console.log(` Pool Address: ${parentDetail.pool?.poolAddress || parentDetail.poolAddress}\n`);
  107. const tokenAMint = parentDetail.pool.mintA?.address;
  108. const tokenBMint = parentDetail.pool.mintB?.address;
  109. // Step 5: Parse transaction for token transfers
  110. console.log('Step 5: Parsing transaction for token transfers...');
  111. if (!tx.meta.preTokenBalances || !tx.meta.postTokenBalances) {
  112. console.error('❌ No token balance data in transaction');
  113. process.exit(1);
  114. }
  115. // Build pre-balance map
  116. const preBalances = {};
  117. tx.meta.preTokenBalances.forEach(balance => {
  118. if (balance.mint === tokenAMint || balance.mint === tokenBMint) {
  119. const key = `${balance.accountIndex}-${balance.mint}`;
  120. preBalances[key] = {
  121. amount: parseFloat(balance.uiTokenAmount?.uiAmountString || 0),
  122. decimals: balance.decimals
  123. };
  124. }
  125. });
  126. console.log(` Pre-balances found: ${Object.keys(preBalances).length}`);
  127. // Find transfers by comparing pre/post
  128. const transfers = [];
  129. tx.meta.postTokenBalances.forEach(balance => {
  130. if (balance.mint === tokenAMint || balance.mint === tokenBMint) {
  131. const key = `${balance.accountIndex}-${balance.mint}`;
  132. const pre = preBalances[key];
  133. if (pre) {
  134. const diff = pre.amount - parseFloat(balance.uiTokenAmount?.uiAmountString || 0);
  135. if (diff > 0.000001) {
  136. transfers.push({
  137. mint: balance.mint,
  138. amount: diff,
  139. decimals: balance.decimals || pre.decimals || 6,
  140. symbol: balance.mint === tokenAMint ? parentDetail.pool.mintA?.symbol : parentDetail.pool.mintB?.symbol
  141. });
  142. console.log(` Transfer detected: ${diff} ${balance.mint === tokenAMint ? parentDetail.pool.mintA?.symbol : parentDetail.pool.mintB?.symbol}`);
  143. }
  144. }
  145. }
  146. });
  147. if (transfers.length === 0) {
  148. console.error('❌ No token transfers found for target tokens');
  149. process.exit(1);
  150. }
  151. // Assign to tokenA and tokenB
  152. const tokenA = transfers.find(t => t.mint === tokenAMint) || { mint: tokenAMint, amount: 0, decimals: 6, symbol: parentDetail.pool.mintA?.symbol };
  153. const tokenB = transfers.find(t => t.mint === tokenBMint) || { mint: tokenBMint, amount: 0, decimals: 6, symbol: parentDetail.pool.mintB?.symbol };
  154. // Calculate USD value (simplified)
  155. const TOKEN_PRICES = {
  156. 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': 1, // USDC
  157. 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB': 1, // USDT
  158. };
  159. const priceA = TOKEN_PRICES[tokenAMint] || 0;
  160. const priceB = TOKEN_PRICES[tokenBMint] || 0;
  161. const totalUsdValue = (tokenA.amount * priceA) + (tokenB.amount * priceB);
  162. if (totalUsdValue === 0) {
  163. // For non-stable pairs, use sum
  164. console.log(' ⚠️ No price data available, using sum of amounts');
  165. }
  166. console.log(`✅ Token transfers extracted:\n`);
  167. // Step 6: Print copy parameters
  168. console.log('═══════════════════════════════════════════════════');
  169. console.log('📋 COPY PARAMETERS');
  170. console.log('═══════════════════════════════════════════════════\n');
  171. console.log('Parent Position:');
  172. console.log(` Address: ${parentPosition}`);
  173. console.log(` NFT Mint: ${parentDetail.nftMintAddress || parentDetail.nftMint}`);
  174. console.log(` Pool Address: ${parentDetail.pool?.poolAddress || parentDetail.poolAddress}`);
  175. console.log(` Pool: ${parentDetail.pool.mintA?.symbol}/${parentDetail.pool.mintB?.symbol}\n`);
  176. console.log('Token Transfers (Target deposited):');
  177. console.log(` Token A: ${tokenA.amount} ${tokenA.symbol}`);
  178. console.log(` Mint: ${tokenA.mint}`);
  179. console.log(` Decimals: ${tokenA.decimals}`);
  180. console.log(` Token B: ${tokenB.amount} ${tokenB.symbol}`);
  181. console.log(` Mint: ${tokenB.mint}`);
  182. console.log(` Decimals: ${tokenB.decimals}\n`);
  183. console.log('Calculated Values:');
  184. console.log(` Token A USD: $${(tokenA.amount * priceA).toFixed(2)}`);
  185. console.log(` Token B USD: $${(tokenB.amount * priceB).toFixed(2)}`);
  186. console.log(` Total USD Value: $${totalUsdValue.toFixed(2)}\n`);
  187. const copyMultiplier = CONFIG.COPY_MULTIPLIER;
  188. const maxUsd = CONFIG.MAX_USD_VALUE;
  189. const copyUsd = Math.min(totalUsdValue * copyMultiplier, maxUsd);
  190. console.log('Copy Calculation:');
  191. console.log(` Target Value: $${totalUsdValue.toFixed(2)}`);
  192. console.log(` Multiplier: ${copyMultiplier}x`);
  193. console.log(` Calculated: $${(totalUsdValue * copyMultiplier).toFixed(2)}`);
  194. console.log(` Max Limit: $${maxUsd}`);
  195. console.log(` ✅ Final Copy Amount: $${copyUsd.toFixed(2)}\n`);
  196. console.log('═══════════════════════════════════════════════════');
  197. console.log('✅ Test completed successfully!');
  198. console.log('═══════════════════════════════════════════════════');
  199. // Return data structure matching what sniper expects
  200. return {
  201. parentPositionAddress: parentPosition,
  202. transactionSignature: TX_SIGNATURE,
  203. parentDetail: parentDetail,
  204. tokenTransfers: {
  205. tokenA: tokenA,
  206. tokenB: tokenB,
  207. totalUsdValue: totalUsdValue
  208. },
  209. targetUsdValue: totalUsdValue,
  210. copyUsdValue: copyUsd
  211. };
  212. } catch (error) {
  213. console.error('❌ Test failed:', error);
  214. process.exit(1);
  215. }
  216. }
  217. testTransactionParse().then(data => {
  218. console.log('\n📦 Full data structure (as passed to sniper):');
  219. console.log(JSON.stringify({
  220. parentPositionAddress: data.parentPositionAddress,
  221. transactionSignature: data.transactionSignature,
  222. targetUsdValue: data.targetUsdValue,
  223. copyUsdValue: data.copyUsdValue
  224. }, null, 2));
  225. });