Ver código fonte

feat(lp-copy): add out-of-range check, price slippage retry, and insufficient funds handling

- Skip copying LP when position is out of tick range
- Retry up to 3 times on price slippage errors with progressive delay
- Return user-friendly '余额不足' message for insufficient funds errors
- Refactor POST handler to use copyLPPosition helper function
lushdog@outlook.com 1 mês atrás
pai
commit
6b856618b2
1 arquivos alterados com 56 adições e 10 exclusões
  1. 56 10
      src/app/api/lp-copy/route.ts

+ 56 - 10
src/app/api/lp-copy/route.ts

@@ -6,16 +6,21 @@ import bs58 from 'bs58'
 import { chain } from '@/lib/config'
 import { TickMath } from '@/lib/byreal-clmm-sdk/src/instructions/utils/tickMath'
 
-export async function POST(request: NextRequest) {
+async function copyLPPosition(
+	request: NextRequest,
+	attempt: number = 1
+): Promise<NextResponse> {
+	const body = await request.json()
+	const {
+		positionAddress,
+		maxUsdValue,
+		nftMintAddress,
+		priceLower: priceLowerPct = 0,
+		priceUpper: priceUpperPct = 0,
+	} = body
+
 	try {
-		const body = await request.json()
-		const {
-			positionAddress,
-			maxUsdValue,
-			nftMintAddress,
-			priceLower: priceLowerPct = 0,
-			priceUpper: priceUpperPct = 0,
-		} = body
+		// 原有的主要逻辑会放在这里
 
 		if (!positionAddress) {
 			return NextResponse.json(
@@ -52,6 +57,29 @@ export async function POST(request: NextRequest) {
 		const poolInfo = rawPoolInfo
 		const tickSpacing = poolInfo.tickSpacing
 
+		// 检查 LP 是否在区间内
+		const currentTick = TickMath.getTickWithPriceAndTickspacing(
+			new Decimal(poolInfo.currentPrice),
+			tickSpacing,
+			poolInfo.mintDecimalsA,
+			poolInfo.mintDecimalsB
+		)
+		const positionTickLower = positionInfo.rawPositionInfo.tickLower
+		const positionTickUpper = positionInfo.rawPositionInfo.tickUpper
+
+		if (currentTick < positionTickLower || currentTick > positionTickUpper) {
+			return NextResponse.json(
+				{
+					error:
+						'Position is out of range (current price is outside the position tick range)',
+					currentTick,
+					tickLower: positionTickLower,
+					tickUpper: positionTickUpper,
+				},
+				{ status: 400 }
+			)
+		}
+
 		// 使用 currentPrice (A/B 价格) 来计算 token 的 USD 价格
 		// currentPrice 是 tokenA / tokenB 的比率
 		const currentPrice = poolInfo.currentPrice
@@ -503,9 +531,27 @@ export async function POST(request: NextRequest) {
 			},
 		})
 	} catch (error: unknown) {
-		console.error('LP copy error:', error)
+		console.error(`LP copy error (attempt ${attempt}):`, error)
 		const errorMessage =
 			error instanceof Error ? error.message : 'Failed to copy LP position'
+
+		// 如果错误包含 "Price slippage check",最多重试 3 次
+		if (errorMessage.includes('Price slippage check') && attempt < 3) {
+			console.log(`Retrying LP copy (attempt ${attempt + 1})...`)
+			await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)) // 延迟重试,逐渐增加
+			return copyLPPosition(request, attempt + 1)
+		}
+
+		// 如果错误包含 "insufficient funds",返回友好的余额不足提示
+		if (errorMessage.includes('insufficient funds')) {
+			return NextResponse.json({ error: '余额不足' }, { status: 400 })
+		}
+
 		return NextResponse.json({ error: errorMessage }, { status: 500 })
 	}
 }
+
+// 主入口函数
+export async function POST(request: NextRequest) {
+	return copyLPPosition(request, 1)
+}