import { NextRequest, NextResponse } from 'next/server' import { Keypair, PublicKey, VersionedTransaction } from '@solana/web3.js' import BN from 'bn.js' import { Decimal } from 'decimal.js' import bs58 from 'bs58' import { chain } from '@/lib/config' export async function POST(request: NextRequest) { try { const body = await request.json() const { positionAddress, maxUsdValue, nftMintAddress } = body if (!positionAddress) { return NextResponse.json( { error: 'Position address is required' }, { status: 400 } ) } if (!maxUsdValue || maxUsdValue <= 0) { return NextResponse.json( { error: 'Max USD value must be greater than 0' }, { status: 400 } ) } let nftMint = undefined if (nftMintAddress) { nftMint = new PublicKey(nftMintAddress) } else { const detailData = await fetch( `https://api2.byreal.io/byreal/api/dex/v2/position/detail?address=${positionAddress}` ).then((res) => res.json()) nftMint = new PublicKey(detailData.result.data.nftMintAddress) } // 获取 position 信息 const positionInfo = await chain.getPositionInfoByNftMint(nftMint) if (!positionInfo) { return NextResponse.json({ error: 'Position not found' }, { status: 404 }) } const { rawPoolInfo, priceLower, priceUpper } = positionInfo const poolInfo = rawPoolInfo // 使用 currentPrice (A/B 价格) 来计算 token 的 USD 价格 // currentPrice 是 tokenA / tokenB 的比率 const currentPrice = poolInfo.currentPrice // 稳定币地址 const USDC_ADDRESS = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' const USDT_ADDRESS = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB' const tokenAAddress = poolInfo.mintA.toBase58() const tokenBAddress = poolInfo.mintB.toBase58() // 判断哪个 token 是稳定币 const isTokenAStable = tokenAAddress === USDC_ADDRESS || tokenAAddress === USDT_ADDRESS const isTokenBStable = tokenBAddress === USDC_ADDRESS || tokenBAddress === USDT_ADDRESS // 计算 token 的 USD 价格 let tokenAPriceUsd: number let tokenBPriceUsd: number if (isTokenBStable) { // tokenB 是稳定币,价格为 1 USD tokenBPriceUsd = 1 // tokenA 的价格 = currentPrice (A/B) * tokenB 价格 tokenAPriceUsd = currentPrice } else if (isTokenAStable) { // tokenA 是稳定币,价格为 1 USD tokenAPriceUsd = 1 // tokenB 的价格 = 1 / currentPrice (因为 currentPrice = A/B) tokenBPriceUsd = 1 / currentPrice } else { // 如果都不是稳定币,假设其中一个的价格为 1(简化处理) // 或者可以从价格 API 获取实际价格 // 这里假设 tokenB 价格为 1,tokenA 价格为 currentPrice tokenBPriceUsd = 1 tokenAPriceUsd = currentPrice } console.log('tokenAPriceUsd', tokenAPriceUsd) console.log('tokenBPriceUsd', tokenBPriceUsd) // 计算比例,使得总价值不超过 maxUsdValue // 使用 position 的 tickLower 和 tickUpper const tickLower = positionInfo.rawPositionInfo.tickLower const tickUpper = positionInfo.rawPositionInfo.tickUpper // 计算需要投入的金额 // 假设使用较小的 token 作为 base const useTokenAAsBase = tokenAPriceUsd <= tokenBPriceUsd const base = useTokenAAsBase ? 'MintA' : 'MintB' // 计算 baseAmount,使得总价值尽可能接近 maxUsdValue // 使用迭代方法精确计算,确保总价值接近但不超过 maxUsdValue const midPrice = priceLower.add(priceUpper).div(2) let baseAmount: BN let otherAmountMax: BN // 使用二分法或迭代方法找到最接近 maxUsdValue 的金额 // 目标值设为接近 100%,尽可能接近最大值 // 由于 slippage 和实际价格曲线的影响,最终值会略低于 maxUsdValue const targetValue = maxUsdValue * 0.995 // 99.5%,更接近最大值 if (base === 'MintA') { // 使用 tokenA 作为 base // 估算:总价值 ≈ baseAmount * (tokenAPriceUsd + midPrice * tokenBPriceUsd) const estimatedPricePerBase = tokenAPriceUsd + midPrice.toNumber() * tokenBPriceUsd // 使用更精确的计算,考虑实际的价格曲线 // 迭代调整 baseAmount 直到总价值接近 targetValue let low = new BN(0) let high = new BN( Math.ceil( (targetValue / estimatedPricePerBase) * 10 ** poolInfo.mintDecimalsA * 1.5 ) ) let bestBaseAmount = new BN(0) let bestValue = 0 // 二分查找最接近目标值的 baseAmount,增加迭代次数以提高精度 for (let i = 0; i < 30; i++) { const mid = low.add(high).div(new BN(2)) if (mid.eq(low) || mid.eq(high)) break const testBaseAmount = mid const testOtherAmount = chain.getAmountBFromAmountA({ priceLower, priceUpper, amountA: testBaseAmount, poolInfo, }) const testUiAmountA = new Decimal(testBaseAmount.toString()).div( 10 ** poolInfo.mintDecimalsA ) const testUiAmountB = new Decimal(testOtherAmount.toString()).div( 10 ** poolInfo.mintDecimalsB ) const testValue = testUiAmountA.toNumber() * tokenAPriceUsd + testUiAmountB.toNumber() * tokenBPriceUsd if (testValue <= targetValue && testValue > bestValue) { bestValue = testValue bestBaseAmount = testBaseAmount } if (testValue > targetValue) { high = mid } else { low = mid } } baseAmount = bestBaseAmount.gt(new BN(0)) ? bestBaseAmount : low // 计算需要的 tokenB 数量(实际需要的,未加 slippage) const otherAmountNeeded = chain.getAmountBFromAmountA({ priceLower, priceUpper, amountA: baseAmount, poolInfo, }) // 添加 2% slippage 作为 otherAmountMax(允许的最大值) otherAmountMax = otherAmountNeeded.mul(new BN(10200)).div(new BN(10000)) } else { // 使用 tokenB 作为 base const estimatedPricePerBase = tokenBPriceUsd + (1 / midPrice.toNumber()) * tokenAPriceUsd // 迭代调整 baseAmount let low = new BN(0) let high = new BN( Math.ceil( (targetValue / estimatedPricePerBase) * 10 ** poolInfo.mintDecimalsB * 1.5 ) ) let bestBaseAmount = new BN(0) let bestValue = 0 // 二分查找,增加迭代次数以提高精度 for (let i = 0; i < 30; i++) { const mid = low.add(high).div(new BN(2)) if (mid.eq(low) || mid.eq(high)) break const testBaseAmount = mid const testOtherAmount = chain.getAmountAFromAmountB({ priceLower, priceUpper, amountB: testBaseAmount, poolInfo, }) const testUiAmountA = new Decimal(testOtherAmount.toString()).div( 10 ** poolInfo.mintDecimalsA ) const testUiAmountB = new Decimal(testBaseAmount.toString()).div( 10 ** poolInfo.mintDecimalsB ) const testValue = testUiAmountA.toNumber() * tokenAPriceUsd + testUiAmountB.toNumber() * tokenBPriceUsd if (testValue <= targetValue && testValue > bestValue) { bestValue = testValue bestBaseAmount = testBaseAmount } if (testValue > targetValue) { high = mid } else { low = mid } } baseAmount = bestBaseAmount.gt(new BN(0)) ? bestBaseAmount : low // 计算需要的 tokenA 数量(实际需要的,未加 slippage) const otherAmountNeeded = chain.getAmountAFromAmountB({ priceLower, priceUpper, amountB: baseAmount, poolInfo, }) // 添加 5% slippage 作为 otherAmountMax(允许的最大值) otherAmountMax = otherAmountNeeded.mul(new BN(10500)).div(new BN(10000)) } // 计算最终总价值(使用实际投入的金额,而不是加了 slippage 的 otherAmountMax) // 实际投入的 otherAmount 是 otherAmountMax / 1.02(因为 otherAmountMax 包含了 2% slippage) const actualOtherAmount = otherAmountMax .mul(new BN(10000)) .div(new BN(10200)) const uiAmountA = base === 'MintA' ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsA) : new Decimal(actualOtherAmount.toString()).div( 10 ** poolInfo.mintDecimalsA ) const uiAmountB = base === 'MintB' ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsB) : new Decimal(actualOtherAmount.toString()).div( 10 ** poolInfo.mintDecimalsB ) let totalValue = uiAmountA.toNumber() * tokenAPriceUsd + uiAmountB.toNumber() * tokenBPriceUsd // 如果总价值仍然超过 maxUsdValue,按比例缩小 if (totalValue > maxUsdValue) { const scale = maxUsdValue / totalValue if (base === 'MintA') { baseAmount = baseAmount .mul(new BN(Math.floor(scale * 10000))) .div(new BN(10000)) otherAmountMax = chain .getAmountBFromAmountA({ priceLower, priceUpper, amountA: baseAmount, poolInfo, }) .mul(new BN(10200)) .div(new BN(10000)) } else { baseAmount = baseAmount .mul(new BN(Math.floor(scale * 10000))) .div(new BN(10000)) otherAmountMax = chain .getAmountAFromAmountB({ priceLower, priceUpper, amountB: baseAmount, poolInfo, }) .mul(new BN(10200)) .div(new BN(10000)) } // 重新计算总价值 const finalUiAmountA = base === 'MintA' ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsA) : new Decimal(otherAmountMax.toString()).div( 10 ** poolInfo.mintDecimalsA ) const finalUiAmountB = base === 'MintB' ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsB) : new Decimal(otherAmountMax.toString()).div( 10 ** poolInfo.mintDecimalsB ) totalValue = finalUiAmountA.toNumber() * tokenAPriceUsd + finalUiAmountB.toNumber() * tokenBPriceUsd } // 重新计算最终的 UI 金额和总价值 const finalUiAmountA = base === 'MintA' ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsA) : new Decimal(otherAmountMax.toString()).div( 10 ** poolInfo.mintDecimalsA ) const finalUiAmountB = base === 'MintB' ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsB) : new Decimal(otherAmountMax.toString()).div( 10 ** poolInfo.mintDecimalsB ) totalValue = finalUiAmountA.toNumber() * tokenAPriceUsd + finalUiAmountB.toNumber() * tokenBPriceUsd // 打印所有信息 console.log('\n========== LP Copy Information ==========') console.log('Original Position Address:', positionAddress) console.log('Referer Position (NFT Mint):', nftMint.toBase58()) console.log('\n--- Pool Information ---') console.log('Pool Address:', poolInfo.poolId.toBase58()) console.log('Token A Address:', poolInfo.mintA.toBase58()) console.log('Token B Address:', poolInfo.mintB.toBase58()) console.log('Token A Decimals:', poolInfo.mintDecimalsA) console.log('Token B Decimals:', poolInfo.mintDecimalsB) console.log('Current Price (A/B):', currentPrice) console.log('Token A Price (USD):', tokenAPriceUsd) console.log('Token B Price (USD):', tokenBPriceUsd) console.log('\n--- Position Range ---') console.log('Tick Lower:', tickLower) console.log('Tick Upper:', tickUpper) console.log('Price Lower:', priceLower.toString()) console.log('Price Upper:', priceUpper.toString()) console.log('\n--- Investment Details ---') console.log('Max USD Value:', maxUsdValue) console.log('Base Token:', base) console.log('Base Amount (raw):', baseAmount.toString()) console.log('Other Amount Max (raw):', otherAmountMax.toString()) console.log('Token A Amount (UI):', finalUiAmountA.toString()) console.log('Token B Amount (UI):', finalUiAmountB.toString()) console.log( 'Token A Value (USD):', (finalUiAmountA.toNumber() * tokenAPriceUsd).toFixed(2) ) console.log( 'Token B Value (USD):', (finalUiAmountB.toNumber() * tokenBPriceUsd).toFixed(2) ) console.log('Total Value (USD):', totalValue.toFixed(2)) console.log('==========================================\n') // 从环境变量读取私钥 const secretKey = process.env.SOL_SECRET_KEY if (!secretKey) { return NextResponse.json( { error: 'SOL_SECRET_KEY not configured' }, { status: 500 } ) } const userKeypair = Keypair.fromSecretKey(bs58.decode(secretKey)) const userAddress = userKeypair.publicKey console.log('User Address:', userAddress.toBase58()) console.log('\n--- Executing Transaction ---') console.log('Creating position on-chain...') // 执行实际上链操作 const signerCallback = async (tx: VersionedTransaction) => { tx.sign([userKeypair]) return tx } const txid = await chain.createPosition({ userAddress, poolInfo, tickLower, tickUpper, base, baseAmount, otherAmountMax, refererPosition: positionAddress, // 添加 referer position signerCallback, }) console.log('Transaction ID:', txid) console.log('Position created successfully!') console.log('==========================================\n') return NextResponse.json({ success: true, txid, positionInfo: { poolAddress: poolInfo.poolId.toBase58(), priceLower: priceLower.toString(), priceUpper: priceUpper.toString(), tickLower, tickUpper, base, baseAmount: baseAmount.toString(), otherAmountMax: otherAmountMax.toString(), estimatedValue: totalValue, tokenA: { address: poolInfo.mintA.toBase58(), amount: finalUiAmountA.toString(), valueUsd: (finalUiAmountA.toNumber() * tokenAPriceUsd).toFixed(2), }, tokenB: { address: poolInfo.mintB.toBase58(), amount: finalUiAmountB.toString(), valueUsd: (finalUiAmountB.toNumber() * tokenBPriceUsd).toFixed(2), }, refererPosition: positionAddress, userAddress: userAddress?.toBase58() || 'N/A', }, }) } catch (error: unknown) { console.error('LP copy error:', error) const errorMessage = error instanceof Error ? error.message : 'Failed to copy LP position' return NextResponse.json({ error: errorMessage }, { status: 500 }) } }