Quellcode durchsuchen

fix: extract token transfers from transaction instead of querying copied position

- onchain-monitor: Parse transaction to extract actual token amounts
  1. Get parent position details to identify token mints
  2. Parse transaction pre/post token balances
  3. Extract token transfers for the specific mints
  4. Calculate USD value from extracted amounts

- sniper: Use parsed token transfer data for copy amount calculation

This fixes the issue where Byreal API takes too long to associate
copied position with parent position, causing missed or incorrect
copy amounts.

The new flow:
1. Detect transaction and extract parent position address
2. Wait 60s then fetch parent position details (token mints)
3. Parse transaction to extract actual token transfer amounts
4. Calculate target position value from on-chain data
5. Execute copy using parsed value
lushdog@outlook.com vor 1 Monat
Ursprung
Commit
9ff390dc08
2 geänderte Dateien mit 133 neuen und 100 gelöschten Zeilen
  1. 17 35
      src/core/sniper.js
  2. 116 65
      src/services/onchain-monitor.js

+ 17 - 35
src/core/sniper.js

@@ -158,7 +158,7 @@ export class SniperEngine {
         targetPositionAddress: targetPositionAddress,
         calculation: calculation, // Store calculation details
       });
-      // Map target's position to the parent position we copied
+      // Map target's reference (tx signature or position address) to the parent position we copied
       this.targetToParentMap.set(targetPositionAddress, parentPositionAddress);
 
       // Send Discord notification
@@ -217,7 +217,7 @@ export class SniperEngine {
    * Copy amount is based on TARGET position value, not parent position value
    */
   async handleOnchainPosition(positionInfo) {
-    const { parentPositionAddress, targetPositionAddress, transactionSignature, positionDetail } = positionInfo;
+    const { parentPositionAddress, transactionSignature, parentDetail, tokenTransfers, targetUsdValue } = positionInfo;
 
     logger.info(`\n🔗 Onchain position detected!`);
     logger.info(`  Parent Position: ${parentPositionAddress}`);
@@ -235,40 +235,27 @@ export class SniperEngine {
       return;
     }
 
-    // Position detail already fetched from Byreal API (passed from onchain monitor)
-    if (!positionDetail) {
-      logger.error(`Position detail not available from Byreal API: ${parentPositionAddress}`);
+    // Validate data from onchain monitor
+    if (!parentDetail || !tokenTransfers) {
+      logger.error(`Missing position data from onchain monitor`);
       return;
     }
 
-    const poolInfo = positionDetail.pool;
+    const poolInfo = parentDetail.pool;
     if (!poolInfo) {
       logger.warn(`Pool info not found for parent position ${parentPositionAddress}`);
       return;
     }
 
-    logger.info(`Target copied position details from Byreal API:`);
+    logger.info(`Position details from transaction parsing:`);
     logger.info(`  Pool: ${poolInfo.mintA?.symbol || '?'}/${poolInfo.mintB?.symbol || '?'}`);
-    logger.info(`  Target NFT Mint: ${positionDetail.nftMintAddress || positionDetail.nftMint}`);
-    logger.info(`  Target Total Deposit: $${positionDetail.totalDeposit || 0}`);
-    logger.info(`  Parent Position: ${positionDetail.parentPositionAddress || parentPositionAddress}`);
+    logger.info(`  Token A transferred: ${tokenTransfers.tokenA.amount} ${poolInfo.mintA?.symbol}`);
+    logger.info(`  Token B transferred: ${tokenTransfers.tokenB.amount} ${poolInfo.mintB?.symbol}`);
+    logger.info(`  Calculated USD Value: $${targetUsdValue.toFixed(2)}`);
 
-    // Get parent position detail for copy operation
-    logger.info(`Fetching parent position detail for copy operation...`);
-    const parentDetail = await ByrealAPI.getPositionDetail(parentPositionAddress);
-    
-    if (!parentDetail) {
-      logger.error(`Failed to fetch parent position detail: ${parentPositionAddress}`);
-      return;
-    }
-
-    logger.info(`Parent position details:`);
-    logger.info(`  Parent NFT Mint: ${parentDetail.nftMintAddress || parentDetail.nftMint}`);
-    logger.info(`  Parent Total Deposit: $${parentDetail.totalDeposit || 0}`);
-
-    // Check 3: API check via CHECK_COPY_URL (as fallback)
+    // Check 3: API check via CHECK_COPY_URL
     logger.info(`Checking copy status via API...`);
-    const poolAddress = positionDetail.pool?.poolAddress || positionDetail.poolAddress;
+    const poolAddress = parentDetail.pool?.poolAddress || parentDetail.poolAddress;
     const isCopiedViaApi = await ByrealAPI.checkIfCopied(
       poolAddress,
       parentPositionAddress,
@@ -277,9 +264,7 @@ export class SniperEngine {
 
     if (isCopiedViaApi) {
       logger.info('Parent position already copied (confirmed by API)');
-      // Sync cache with API result
       this.copiedCache.add(parentPositionAddress, { poolAddress });
-      this.targetToParentMap.set(targetPositionAddress, parentPositionAddress);
       return;
     }
 
@@ -287,22 +272,19 @@ export class SniperEngine {
 
     // Execute copy using:
     // - parentDetail: for NFT mint and calculate API
-    // - positionDetail.totalDeposit: for target's deposit amount (copy amount base)
+    // - targetUsdValue: from transaction parsing (actual amount target deposited)
     const success = await this.executeCopy(
       parentDetail,
       poolInfo,
       parentPositionAddress,
-      targetPositionAddress,
-      positionDetail.totalDeposit || 0 // Use TARGET's totalDeposit from Byreal API
+      transactionSignature, // Use tx signature as reference
+      targetUsdValue // Use parsed USD value from transaction
     );
 
     if (success) {
       logger.success(`Successfully copied position via onchain detection!`);
-    }
-
-    // Mark as processed
-    if (targetPositionAddress) {
-      this.initialPositions.add(targetPositionAddress);
+      // Mark transaction as processed
+      this.initialPositions.add(transactionSignature);
     }
   }
 

+ 116 - 65
src/services/onchain-monitor.js

@@ -5,7 +5,7 @@ import { ByrealAPI } from './byreal.js';
 
 /**
  * Onchain Monitor - Monitor target wallet for Byreal LP opening transactions
- * Detects transactions, waits 60s for Byreal API to sync, then fetches position details
+ * Detects transactions, extracts token transfers based on parent position info
  */
 export class OnchainMonitor {
   constructor(callback) {
@@ -13,15 +13,12 @@ export class OnchainMonitor {
     this.targetWallet = new PublicKey(CONFIG.TARGET_WALLET);
     this.byrealProgramId = new PublicKey('REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2');
     this.memoProgramId = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr');
-    this.callback = callback; // Callback when new position detected
+    this.callback = callback;
     this.isRunning = false;
-    this.processedSignatures = new Set(); // Track processed transactions
+    this.processedSignatures = new Set();
     this.lastSignature = null;
   }
 
-  /**
-   * Start monitoring for new transactions
-   */
   async start() {
     if (this.isRunning) {
       logger.warn('Onchain monitor already running');
@@ -32,13 +29,11 @@ export class OnchainMonitor {
     logger.info('═══════════════════════════════════════════');
     logger.info('🔗 Onchain Monitor Started');
     logger.info(`🎯 Target: ${CONFIG.TARGET_WALLET}`);
-    logger.info(`📋 Mode: Detect transaction -> Wait 60s -> Fetch from Byreal API`);
+    logger.info(`📋 Mode: Parse transaction -> Get parent info -> Extract token transfers`);
     logger.info('═══════════════════════════════════════════\n');
 
-    // Get initial last signature to avoid processing old transactions
     await this.getLatestSignature();
 
-    // Start monitoring loop
     while (this.isRunning) {
       try {
         await this.checkNewTransactions();
@@ -50,9 +45,6 @@ export class OnchainMonitor {
     }
   }
 
-  /**
-   * Get the latest signature to establish baseline
-   */
   async getLatestSignature() {
     try {
       const signatures = await this.connection.getSignaturesForAddress(
@@ -69,9 +61,6 @@ export class OnchainMonitor {
     }
   }
 
-  /**
-   * Check for new transactions from target wallet
-   */
   async checkNewTransactions() {
     try {
       const signatures = await this.connection.getSignaturesForAddress(
@@ -86,27 +75,22 @@ export class OnchainMonitor {
         return;
       }
 
-      // Update last signature for next iteration
       this.lastSignature = signatures[0].signature;
 
-      // Process signatures in reverse order (oldest first)
       for (const sigInfo of signatures.reverse()) {
         const signature = sigInfo.signature;
         
-        // Skip already processed
         if (this.processedSignatures.has(signature)) {
           continue;
         }
 
         this.processedSignatures.add(signature);
         
-        // Limit set size to prevent memory issues
         if (this.processedSignatures.size > 1000) {
           const iterator = this.processedSignatures.values();
           this.processedSignatures.delete(iterator.next().value);
         }
 
-        // Process the transaction
         await this.handleNewTransaction(signature);
       }
     } catch (error) {
@@ -115,7 +99,7 @@ export class OnchainMonitor {
   }
 
   /**
-   * Handle new transaction - extract parent position, wait 60s, fetch from Byreal API
+   * Handle new transaction - extract parent position, get parent info, parse token transfers
    */
   async handleNewTransaction(signature) {
     try {
@@ -131,7 +115,7 @@ export class OnchainMonitor {
         return;
       }
 
-      // Check if transaction involves Byreal program
+      // Check if Byreal program
       const message = tx.transaction.message;
       const accountKeys = message.staticAccountKeys || message.accountKeys || [];
       
@@ -140,7 +124,7 @@ export class OnchainMonitor {
       );
 
       if (!hasByrealProgram) {
-        logger.debug(`No Byreal program involvement: ${signature.slice(0, 16)}`);
+        logger.debug(`No Byreal program: ${signature.slice(0, 16)}`);
         return;
       }
 
@@ -150,54 +134,54 @@ export class OnchainMonitor {
       const parentPosition = this.extractParentPosition(tx);
       
       if (!parentPosition) {
-        logger.info(`No parent position found in memo, skipping...`);
+        logger.info(`No parent position in memo, skipping...`);
         return;
       }
 
-      logger.success(`Found parent position from memo: ${parentPosition}`);
+      logger.success(`Found parent position: ${parentPosition}`);
 
-      // Wait 60 seconds for Byreal API to sync
-      logger.info(`Waiting 60 seconds for Byreal API to sync...`);
+      // Wait 60s for Byreal API
+      logger.info(`Waiting 60s for Byreal API...`);
       await sleep(60000);
 
-      // Fetch target wallet's positions to find the copied position
-      logger.info(`Fetching target wallet positions to find copied position...`);
-      const { positions } = await ByrealAPI.fetchTargetPositions(CONFIG.TARGET_WALLET);
-      
-      // Find the position that references this parent position
-      const copiedPosition = positions.find(p => 
-        p.parentPositionAddress === parentPosition
-      );
+      // Get parent position details to know which tokens
+      logger.info(`Fetching parent position details...`);
+      const parentDetail = await ByrealAPI.getPositionDetail(parentPosition);
 
-      if (!copiedPosition) {
-        logger.error(`Could not find copied position for parent ${parentPosition} in target wallet`);
+      if (!parentDetail || !parentDetail.pool) {
+        logger.error(`Failed to get parent position details`);
         return;
       }
 
-      logger.info(`Found copied position: ${copiedPosition.positionAddress}`);
+      const tokenAMint = parentDetail.pool.mintA?.address;
+      const tokenBMint = parentDetail.pool.mintB?.address;
+      
+      logger.info(`Parent position tokens:`);
+      logger.info(`  Token A: ${parentDetail.pool.mintA?.symbol} (${tokenAMint})`);
+      logger.info(`  Token B: ${parentDetail.pool.mintB?.symbol} (${tokenBMint})`);
 
-      // Fetch the copied position details (this is the position created by target wallet)
-      logger.info(`Fetching copied position details from Byreal API...`);
-      const positionDetail = await ByrealAPI.getPositionDetail(copiedPosition.positionAddress);
+      // Parse transaction to extract token transfers
+      logger.info(`Parsing transaction for token transfers...`);
+      const tokenTransfers = this.extractTokenTransfers(tx, tokenAMint, tokenBMint);
 
-      if (!positionDetail) {
-        logger.error(`Failed to fetch copied position detail from Byreal API: ${copiedPosition.positionAddress}`);
+      if (!tokenTransfers) {
+        logger.error(`Failed to extract token transfers from transaction`);
         return;
       }
 
-      logger.success(`Copied position details fetched successfully!`);
-      logger.info(`  Pool: ${positionDetail.pool?.mintA?.symbol}/${positionDetail.pool?.mintB?.symbol}`);
-      logger.info(`  NFT Mint: ${positionDetail.nftMintAddress || positionDetail.nftMint}`);
-      logger.info(`  Total Deposit (Target's amount): $${positionDetail.totalDeposit || positionDetail.totalUsdValue || positionDetail.liquidityUsd || 0}`);
-      logger.info(`  Parent Position: ${positionDetail.parentPositionAddress}`);
+      logger.success(`Token transfers extracted:`);
+      logger.info(`  Token A: ${tokenTransfers.tokenA.amount} ${parentDetail.pool.mintA?.symbol}`);
+      logger.info(`  Token B: ${tokenTransfers.tokenB.amount} ${parentDetail.pool.mintB?.symbol}`);
+      logger.info(`  Total USD Value: $${tokenTransfers.totalUsdValue.toFixed(2)}`);
 
-      // Call the callback with position info from Byreal API
+      // Call callback
       if (this.callback) {
         await this.callback({
           parentPositionAddress: parentPosition,
-          targetPositionAddress: copiedPosition.positionAddress, // Target's copied position
           transactionSignature: signature,
-          positionDetail: positionDetail // Full position detail from Byreal API (TARGET's position)
+          parentDetail: parentDetail,
+          tokenTransfers: tokenTransfers,
+          targetUsdValue: tokenTransfers.totalUsdValue
         });
       }
 
@@ -207,43 +191,114 @@ export class OnchainMonitor {
   }
 
   /**
-   * Extract parent position address from Memo instruction
+   * Extract token transfers from transaction based on token mints
    */
+  extractTokenTransfers(tx, tokenAMint, tokenBMint) {
+    try {
+      const result = {
+        tokenA: { mint: tokenAMint, amount: 0, decimals: 6 },
+        tokenB: { mint: tokenBMint, amount: 0, decimals: 6 },
+        totalUsdValue: 0
+      };
+
+      if (!tx.meta?.preTokenBalances || !tx.meta?.postTokenBalances) {
+        logger.warn('No token balance data in transaction');
+        return null;
+      }
+
+      // 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
+          };
+        }
+      });
+
+      // Find transfers by comparing pre/post balances
+      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
+              });
+            }
+          }
+        }
+      });
+
+      // Assign to tokenA and tokenB
+      transfers.forEach(t => {
+        if (t.mint === tokenAMint) {
+          result.tokenA.amount = t.amount;
+          result.tokenA.decimals = t.decimals;
+        } else if (t.mint === tokenBMint) {
+          result.tokenB.amount = t.amount;
+          result.tokenB.decimals = t.decimals;
+        }
+      });
+
+      // Calculate USD value (simplified - uses fixed prices for common tokens)
+      const TOKEN_PRICES = {
+        'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': 1, // USDC
+        'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB': 1, // USDT
+      };
+
+      // Try to get prices or use token amount as-is for calculation
+      const priceA = TOKEN_PRICES[tokenAMint] || 0;
+      const priceB = TOKEN_PRICES[tokenBMint] || 0;
+      
+      result.totalUsdValue = (result.tokenA.amount * priceA) + (result.tokenB.amount * priceB);
+
+      // If no prices available, use sum of amounts (for non-stable pairs)
+      if (result.totalUsdValue === 0) {
+        result.totalUsdValue = result.tokenA.amount + result.tokenB.amount;
+      }
+
+      return result;
+    } catch (error) {
+      logger.error('Error extracting token transfers:', error.message);
+      return null;
+    }
+  }
+
   extractParentPosition(tx) {
     try {
       const message = tx.transaction.message;
       const instructions = message.compiledInstructions || message.instructions || [];
       
       for (const instruction of instructions) {
-        // Check if it's a memo instruction
         const programId = message.staticAccountKeys 
           ? message.staticAccountKeys[instruction.programIdIndex].toString()
           : instruction.programId?.toString();
         
         if (programId === this.memoProgramId.toString()) {
-          // Parse memo data
           let memoData;
           
           if (instruction.data) {
-            // Try to decode base64 data
             try {
               memoData = Buffer.from(instruction.data, 'base64').toString('utf8');
             } catch {
-              // Try as string directly
               memoData = instruction.data;
             }
           }
 
-          // Check if it's already parsed
           if (instruction.parsed) {
             memoData = instruction.parsed;
           }
 
           if (memoData) {
-            logger.debug(`Memo data: ${memoData}`);
-            
-            // Extract referer_position from memo
-            // Format: referer_position=GdMQaDtzcaAh4XVcHsUJV8yQShkVXDp9sJLNwvd23TxQ
             const match = memoData.match(/referer_position=([A-Za-z0-9]+)/);
             if (match) {
               return match[1];
@@ -252,7 +307,6 @@ export class OnchainMonitor {
         }
       }
 
-      // Also check log messages as fallback
       if (tx.meta.logMessages) {
         for (const log of tx.meta.logMessages) {
           const match = log.match(/referer_position=([A-Za-z0-9]+)/);
@@ -269,9 +323,6 @@ export class OnchainMonitor {
     }
   }
 
-  /**
-   * Stop monitoring
-   */
   stop() {
     logger.info('Stopping onchain monitor...');
     this.isRunning = false;