Browse Source

feat: 开仓时检查 USDC+USDT 总余额,不足时自动互换

- checkUsdcBalance → checkStablecoinBalance,检查 USDC+USDT 总额
- ensureTokenBalance 支持 USDC↔USDT 自动互换补齐
- 新增 getUsdtBalance 导出

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zhangchunrui 1 week ago
parent
commit
8af1b5e055
2 changed files with 42 additions and 14 deletions
  1. 18 9
      src/lib/copier/index.ts
  2. 24 5
      src/lib/copier/swap.ts

+ 18 - 9
src/lib/copier/index.ts

@@ -16,7 +16,7 @@ import {
 import type { ParsedOperation } from '../monitor/types'
 import { scaleAmount } from './ratio'
 import { getTokenPrices, calculateCopyScale } from './price'
-import { ensureSufficientBalances, getUsdcBalance, swapTokensBackToUsdc, USDC_MINT } from './swap'
+import { ensureSufficientBalances, getUsdcBalance, getUsdtBalance, swapTokensBackToUsdc, USDC_MINT } from './swap'
 import { sendDiscordNotification } from '../discord'
 
 // We import from the built SDK dist using relative path
@@ -156,17 +156,26 @@ export class CopyEngine {
   }
 
   /**
-   * 检查 USDC 余额是否足够
+   * 检查 USDC+USDT 总余额是否足够,不够则跳过;
+   * 如果总余额够但 USDC 不足,自动从 USDT swap 到 USDC
    */
-  private async checkUsdcBalance(requiredUsd: number): Promise<boolean> {
-    const balance = await getUsdcBalance(this.connection)
-    const balanceUsd = Number(balance) / 1e6
-    if (balanceUsd < requiredUsd) {
+  private async checkStablecoinBalance(requiredUsd: number): Promise<boolean> {
+    const usdcBalance = await getUsdcBalance(this.connection)
+    const usdtBalance = await getUsdtBalance(this.connection)
+    const usdcUsd = Number(usdcBalance) / 1e6
+    const usdtUsd = Number(usdtBalance) / 1e6
+    const totalUsd = usdcUsd + usdtUsd
+
+    if (totalUsd < requiredUsd) {
       console.log(
-        `[CopyEngine] Insufficient USDC balance: $${balanceUsd.toFixed(2)} < $${requiredUsd.toFixed(2)}, skipping`,
+        `[CopyEngine] Insufficient stablecoin balance: USDC=$${usdcUsd.toFixed(2)} + USDT=$${usdtUsd.toFixed(2)} = $${totalUsd.toFixed(2)} < $${requiredUsd.toFixed(2)}, skipping`,
       )
       return false
     }
+
+    console.log(
+      `[CopyEngine] Stablecoin balance: USDC=$${usdcUsd.toFixed(2)}, USDT=$${usdtUsd.toFixed(2)}, need=$${requiredUsd.toFixed(2)}`,
+    )
     return true
   }
 
@@ -232,7 +241,7 @@ export class CopyEngine {
 
       // Check USDC balance before proceeding
       const neededUsd = mintA === USDC_MINT || mintB === USDC_MINT ? ourUsd * 0.6 : ourUsd
-      if (!(await this.checkUsdcBalance(neededUsd))) {
+      if (!(await this.checkStablecoinBalance(neededUsd))) {
         const skipMsg = `Insufficient USDC balance for $${ourUsd.toFixed(2)} position`
         updateCopyHistory(historyId, { status: 'skipped', errorMessage: skipMsg })
         sendDiscordNotification({
@@ -410,7 +419,7 @@ export class CopyEngine {
 
       // Check USDC balance before proceeding
       const neededUsd = mintA === USDC_MINT || mintB === USDC_MINT ? ourUsd * 0.6 : ourUsd
-      if (!(await this.checkUsdcBalance(neededUsd))) {
+      if (!(await this.checkStablecoinBalance(neededUsd))) {
         const skipMsg = `Insufficient USDC balance for $${ourUsd.toFixed(2)} add liquidity`
         updateCopyHistory(historyId, { status: 'skipped', errorMessage: skipMsg })
         sendDiscordNotification({

+ 24 - 5
src/lib/copier/swap.ts

@@ -404,11 +404,6 @@ async function ensureTokenBalance(params: {
     return { swapped: false }
   }
 
-  // 稳定币不需要 swap(USDC 本身、USDT、USD1)
-  if (STABLECOIN_MINTS.has(tokenMint)) {
-    return { swapped: false }
-  }
-
   const currentBalance = await getTokenBalance(connection, tokenMint)
   if (currentBalance >= requiredAmount) {
     console.log(
@@ -425,6 +420,23 @@ async function ensureTokenBalance(params: {
     `[Swap] ${tokenMint.slice(0, 8)}...: have ${currentBalance}, need ${requiredAmount}, deficit ${deficit}, swap(ExactOut) ${swapAmount}`,
   )
 
+  // 稳定币之间互换:USDC↔USDT
+  if (tokenMint === USDC_MINT) {
+    const result = await signAndExecuteSwap(USDT_MINT, USDC_MINT, swapAmount.toString(), 100, 'ExactOut')
+    if (result.success) return { swapped: true, txid: result.txid }
+    return { swapped: false, error: result.error }
+  }
+  if (tokenMint === USDT_MINT) {
+    const result = await signAndExecuteSwap(USDC_MINT, USDT_MINT, swapAmount.toString(), 100, 'ExactOut')
+    if (result.success) return { swapped: true, txid: result.txid }
+    return { swapped: false, error: result.error }
+  }
+
+  // 其他稳定币(USD1)不 swap
+  if (STABLECOIN_MINTS.has(tokenMint)) {
+    return { swapped: false }
+  }
+
   // 优先 USDC → token(ExactOut: amount = 需要获得的代币数量)
   let result = await signAndExecuteSwap(USDC_MINT, tokenMint, swapAmount.toString(), 300, 'ExactOut')
   if (result.success) {
@@ -485,6 +497,13 @@ export async function getUsdcBalance(connection: Connection): Promise<bigint> {
   return getTokenBalance(connection, USDC_MINT)
 }
 
+/**
+ * 获取 USDT 余额(raw amount, 6 decimals)
+ */
+export async function getUsdtBalance(connection: Connection): Promise<bigint> {
+  return getTokenBalance(connection, USDT_MINT)
+}
+
 /**
  * 关仓后将收到的代币换回 USDC
  * 跳过 SOL、USDC、USDT、USD1