瀏覽代碼

fix: 关仓传我的仓位地址,通过父仓位查子仓位,多仓用 copiedAt/positionAgeMs 匹配

Co-authored-by: Cursor <cursoragent@cursor.com>
lushdog@outlook.com 1 月之前
父節點
當前提交
a21eef23cb
共有 2 個文件被更改,包括 85 次插入6 次删除
  1. 17 6
      src/core/sniper.js
  2. 68 0
      src/services/byreal.js

+ 17 - 6
src/core/sniper.js

@@ -152,12 +152,12 @@ export class SniperEngine {
       parentPositionAddress,
       copyUsdValue
     );
-    
+
     if (success) {
       this.copiedCache.add(parentPositionAddress, {
         poolAddress,
         targetUsdValue: copyBaseValue,
-        copiedAt: new Date().toISOString(),
+        copiedAt: new Date().toISOString(), // 关仓时用 copiedAt 与 positionAgeMs 匹配「我的」子仓位
         targetPositionAddress: targetPositionAddress,
         calculation: calculation, // Store calculation details
       });
@@ -429,12 +429,23 @@ export class SniperEngine {
           this.copiedCache.has(parentPositionAddress)) {
         
         logger.warn(`Target closed their copied position: ${targetPositionAddress}`);
-        logger.info(`Closing our copy of parent position: ${parentPositionAddress}`);
+        const copiedData = this.copiedCache.get(parentPositionAddress);
+        logger.info('Resolving our position address by parent (and copy time if multiple)...');
+        const myPositionAddress = await ByrealAPI.getMyPositionAddressForParent(
+          parentPositionAddress,
+          this.myWallet,
+          copiedData?.poolAddress,
+          copiedData?.copiedAt
+        );
+        if (!myPositionAddress) {
+          logger.error(`Could not resolve our position for parent ${parentPositionAddress}, skip close`);
+          continue;
+        }
+        logger.info(`Closing our position: ${myPositionAddress} (parent was ${parentPositionAddress})`);
         
-        const success = await ByrealAPI.closePosition(parentPositionAddress);
+        const success = await ByrealAPI.closePosition(myPositionAddress);
         
         if (success) {
-          const copiedData = this.copiedCache.get(parentPositionAddress);
           this.closedCache.add(parentPositionAddress, {
             closedAt: new Date().toISOString(),
             reason: `Target closed their position ${targetPositionAddress}`,
@@ -444,7 +455,7 @@ export class SniperEngine {
 
           // Send Discord notification
           this.discord.notifyCloseSuccess({
-            positionAddress: parentPositionAddress,
+            positionAddress: myPositionAddress,
             poolName: copiedData?.calculation?.tokenA?.symbol && copiedData?.calculation?.tokenB?.symbol 
               ? `${copiedData.calculation.tokenA.symbol}/${copiedData.calculation.tokenB.symbol}`
               : 'Unknown Pool',

+ 68 - 0
src/services/byreal.js

@@ -82,6 +82,74 @@ export class ByrealAPI {
     }
   }
 
+  /**
+   * 根据父仓位地址查询「我的」子仓位地址(关仓时传自己的仓位)
+   * 通过 CHECK_COPY 按父仓位查子仓位列表,筛出我的;若多个则用 copiedAt 与 positionAgeMs 找最接近的
+   * @param {string} parentPositionAddress - 父仓位地址
+   * @param {string} myWallet - 我的钱包地址
+   * @param {string} [poolAddress] - 池子地址(查 copy 列表需要)
+   * @param {string} [copiedAt] - 复制时间 ISO 字符串,多个子仓时用与 positionAgeMs 最接近的匹配
+   */
+  static async getMyPositionAddressForParent(parentPositionAddress, myWallet, poolAddress = null, copiedAt = null) {
+    try {
+      if (poolAddress) {
+        const payload = {
+          poolAddress,
+          parentPositionAddress,
+          page: 1,
+          pageSize: 50,
+          sortField: 'liquidity',
+        };
+        const response = await axios.post(CONFIG.CHECK_COPY_URL, payload);
+        const records = response.data?.result?.data?.records || [];
+        const myRecords = records.filter(item => item.walletAddress === myWallet);
+        const addr = (r) => r.positionAddress ?? r.address;
+
+        if (myRecords.length === 0) {
+          // 列表里没有,再拉我的仓位列表按 parent 匹配
+          const { positions } = await ByrealAPI.fetchTargetPositions(myWallet);
+          for (const p of positions) {
+            const detail = await ByrealAPI.getPositionDetail(p.positionAddress);
+            if (detail?.parentPositionAddress === parentPositionAddress || detail?.refererPosition === parentPositionAddress) {
+              return p.positionAddress;
+            }
+          }
+          return null;
+        }
+        if (myRecords.length === 1) {
+          return addr(myRecords[0]);
+        }
+        // 多个子仓位:用复制时间与 positionAgeMs 找最接近的(positionAgeMs 为仓位创建至今的毫秒数)
+        const expectedAgeMs = copiedAt ? Date.now() - new Date(copiedAt).getTime() : null;
+        if (expectedAgeMs != null) {
+          let best = myRecords[0];
+          let bestDiff = Math.abs((best.positionAgeMs ?? 0) - expectedAgeMs);
+          for (let i = 1; i < myRecords.length; i++) {
+            const r = myRecords[i];
+            const diff = Math.abs((r.positionAgeMs ?? 0) - expectedAgeMs);
+            if (diff < bestDiff) {
+              bestDiff = diff;
+              best = r;
+            }
+          }
+          return addr(best);
+        }
+        return addr(myRecords[0]);
+      }
+      const { positions } = await ByrealAPI.fetchTargetPositions(myWallet);
+      for (const p of positions) {
+        const detail = await ByrealAPI.getPositionDetail(p.positionAddress);
+        if (detail?.parentPositionAddress === parentPositionAddress || detail?.refererPosition === parentPositionAddress) {
+          return p.positionAddress;
+        }
+      }
+      return null;
+    } catch (error) {
+      logger.error('Error getting my position for parent:', error.message);
+      return null;
+    }
+  }
+
   static async calculatePosition(positionAddress, maxUsdValue, nftMintAddress = null) {
     try {
       const body = {