Ver Fonte

feat: swap完成后发送Discord通知

- 新增 notifySwapAfterClose 方法发送swap详情
- analyzeCloseTxAndSwapRemains 收集swap结果并通知
- 显示每个swap的代币、金额、价值和tx链接
zhangchunrui há 1 mês atrás
pai
commit
ca8740f0f0
3 ficheiros alterados com 78 adições e 9 exclusões
  1. 1 1
      src/core/sniper.js
  2. 27 8
      src/services/jupiter.js
  3. 50 0
      src/utils/discord.js

+ 1 - 1
src/core/sniper.js

@@ -455,7 +455,7 @@ export class SniperEngine {
           
           if (closeTxSignature) {
             logger.info(`Close transaction: ${closeTxSignature}`);
-            await this.swapper.analyzeCloseTxAndSwapRemains(closeTxSignature, 10);
+            await this.swapper.analyzeCloseTxAndSwapRemains(closeTxSignature, 10, this.discord);
           }
 
           this.closedCache.add(parentPositionAddress, {

+ 27 - 8
src/services/jupiter.js

@@ -350,19 +350,22 @@ export class JupiterSwapper {
    * Analyze close transaction and swap excess tokens to USDC
    * @param {string} txSignature - Transaction signature from close position
    * @param {number} keepUsdValue - USD value to keep (default: 10)
-   * @returns {Promise<boolean>}
+   * @param {object} discord - Optional DiscordWebhook instance for notifications
+   * @returns {Promise<{success: boolean, swaps: Array}>}
    */
-  async analyzeCloseTxAndSwapRemains(txSignature, keepUsdValue = 10) {
+  async analyzeCloseTxAndSwapRemains(txSignature, keepUsdValue = 10, discord = null) {
     const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
     
     if (!txSignature) {
       logger.warn('No tx signature provided, skipping swap analysis');
-      return false;
+      return { success: false, swaps: [] };
     }
 
     logger.info(`\n═══════════════════════════════════════════`);
     logger.info(`🔍 Analyzing close transaction: ${txSignature.slice(0, 20)}...`);
 
+    const swaps = [];
+
     try {
       const tx = await this.connection.getTransaction(txSignature, {
         commitment: 'confirmed',
@@ -371,13 +374,13 @@ export class JupiterSwapper {
 
       if (!tx || !tx.meta) {
         logger.error('Transaction not found or invalid');
-        return false;
+        return { success: false, swaps: [] };
       }
 
       const changedTokens = this._extractBalanceChanges(tx);
       if (changedTokens.length === 0) {
         logger.info('No token balance changes found');
-        return true;
+        return { success: true, swaps: [] };
       }
 
       logger.info(`Found ${changedTokens.length} tokens with balance changes:`);
@@ -390,6 +393,7 @@ export class JupiterSwapper {
 
       for (const token of changedTokens) {
         const mint = token.mint;
+        const symbol = prices[mint]?.symbol || null;
         const isStablecoin = JupiterSwapper.STABLECOIN_MINTS.includes(mint);
         
         if (isStablecoin) {
@@ -421,7 +425,6 @@ export class JupiterSwapper {
         logger.info(`  Swapping $${swapValueUsd.toFixed(2)} worth to USDC (keeping $${keepUsdValue})`);
 
         const swapAmountRaw = Math.floor(swapValueUsd / priceUsd * Math.pow(10, 6));
-        const decimals = 6;
 
         try {
           const tryQuote = async (restrictIntermediate = true) => {
@@ -461,16 +464,32 @@ export class JupiterSwapper {
           }
 
           logger.success(`  Swap confirmed: https://solscan.io/tx/${signature}`);
+          
+          swaps.push({
+            mint: mint,
+            symbol: symbol,
+            amount: swapValueUsd / priceUsd,
+            swappedUsd: swapValueUsd,
+            txSignature: signature,
+          });
         } catch (swapError) {
           logger.error(`  Swap failed for ${mint.slice(0, 8)}...: ${swapError.message}`);
         }
       }
 
       logger.info(`═══════════════════════════════════════════\n`);
-      return true;
+
+      if (swaps.length > 0 && discord) {
+        await discord.notifySwapAfterClose({
+          closeTxSignature: txSignature,
+          swaps: swaps,
+        });
+      }
+
+      return { success: true, swaps: swaps };
     } catch (error) {
       logger.error('Error analyzing close transaction:', error.message);
-      return false;
+      return { success: false, swaps: swaps };
     }
   }
 

+ 50 - 0
src/utils/discord.js

@@ -188,6 +188,56 @@ export class DiscordWebhook {
 
     await this.sendNotification(embed);
   }
+
+  /**
+   * Send swap notification after close position
+   */
+  async notifySwapAfterClose({
+    closeTxSignature,
+    swaps,
+  }) {
+    if (!swaps || swaps.length === 0) {
+      return;
+    }
+
+    const totalSwappedUsd = swaps.reduce((sum, s) => sum + (s.swappedUsd || 0), 0);
+    
+    const fields = [
+      {
+        name: '🔗 Close TX',
+        value: `[${closeTxSignature.slice(0, 8)}...](https://solscan.io/tx/${closeTxSignature})`,
+        inline: false,
+      },
+      {
+        name: '💰 Total Swapped',
+        value: `$${totalSwappedUsd.toFixed(2)} → USDC`,
+        inline: true,
+      },
+    ];
+
+    swaps.forEach((swap, index) => {
+      fields.push({
+        name: `💱 Swap ${index + 1}`,
+        value: `${swap.symbol || swap.mint.slice(0, 8)}...\n` +
+               `Amount: ${swap.amount.toFixed(4)}\n` +
+               `Value: $${swap.swappedUsd.toFixed(2)}\n` +
+               `[TX](https://solscan.io/tx/${swap.txSignature})`,
+        inline: true,
+      });
+    });
+
+    const embed = {
+      title: '🔄 Tokens Swapped After Close',
+      color: 0x00bfff, // Blue
+      timestamp: new Date().toISOString(),
+      fields,
+      footer: {
+        text: 'Byreal Sniper Bot',
+      },
+    };
+
+    await this.sendNotification(embed);
+  }
 }
 
 export default DiscordWebhook;