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 { nftMintAddress, addUsdValue } = body // 验证输入 if (!nftMintAddress) { return NextResponse.json( { error: 'Position NFT address is required' }, { status: 400 } ) } if (!addUsdValue || addUsdValue <= 0) { return NextResponse.json( { error: 'Add USD value must be greater than 0' }, { status: 400 } ) } const nftMint = new PublicKey(nftMintAddress) // 获取仓位信息 const positionInfo = await chain.getPositionInfoByNftMint(nftMint) if (!positionInfo) { return NextResponse.json({ error: 'Position not found' }, { status: 404 }) } const { rawPoolInfo } = positionInfo const poolInfo = rawPoolInfo // 使用 currentPrice (A/B 价格) 来计算 token 的 USD 价格 const currentPrice = poolInfo.currentPrice // 稳定币地址 const USDC_ADDRESS = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' const USDT_ADDRESS = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB' const USD1_ADDRESS = 'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB' const tokenAAddress = poolInfo.mintA.toBase58() const tokenBAddress = poolInfo.mintB.toBase58() // 判断哪个 token 是稳定币 const isTokenAStable = tokenAAddress === USDC_ADDRESS || tokenAAddress === USDT_ADDRESS || tokenAAddress === USD1_ADDRESS const isTokenBStable = tokenBAddress === USDC_ADDRESS || tokenBAddress === USDT_ADDRESS || tokenBAddress === USD1_ADDRESS // 计算 token 的 USD 价格 let tokenAPriceUsd = 0 let tokenBPriceUsd = 0 let priceFromApi = false // 尝试从 ByReal API 获取真实价格 try { const response = await fetch( `https://api2.byreal.io/byreal/api/dex/v2/position/detail?address=${nftMintAddress}`, { method: 'GET', headers: { accept: 'application/json', }, } ) if (response.ok) { const data = await response.json() const poolData = data.result?.data?.pool if (poolData?.mintA?.price && poolData?.mintB?.price) { tokenAPriceUsd = parseFloat(poolData.mintA.price) tokenBPriceUsd = parseFloat(poolData.mintB.price) priceFromApi = true console.log('Using prices from ByReal API:', { tokenAPriceUsd, tokenBPriceUsd, }) } } } catch (error) { console.warn('Failed to fetch prices from API:', error) } // 如果 API 获取失败,使用稳定币逻辑或默认逻辑 if (!priceFromApi) { if (isTokenBStable) { tokenBPriceUsd = 1 tokenAPriceUsd = currentPrice } else if (isTokenAStable) { tokenAPriceUsd = 1 tokenBPriceUsd = 1 / currentPrice } else { // 非稳定币对且无法获取价格时,使用相对价格 // 以 tokenB 为基准(价格为1),tokenA 价格为 currentPrice tokenBPriceUsd = 1 tokenAPriceUsd = currentPrice console.warn( 'Warning: Non-stablecoin pair without API prices. Using relative pricing.' ) } } // 获取仓位的当前价格范围 const priceLower = positionInfo.priceLower const priceUpper = positionInfo.priceUpper // 使用较小的 token 作为 base const useTokenAAsBase = tokenAPriceUsd <= tokenBPriceUsd const base = useTokenAAsBase ? 'MintA' : 'MintB' // 计算 baseAmount const targetValue = addUsdValue * 0.995 // 99.5%,更接近最大值 let baseAmount: BN let otherAmountMax: BN if (base === 'MintA') { const estimatedPricePerBase = tokenAPriceUsd + ((priceLower.toNumber() + priceUpper.toNumber()) / 2) * tokenBPriceUsd // 迭代调整 baseAmount 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 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 数量 const otherAmountNeeded = chain.getAmountBFromAmountA({ priceLower, priceUpper, amountA: baseAmount, poolInfo, }) // 添加 2% slippage otherAmountMax = otherAmountNeeded.mul(new BN(10200)).div(new BN(10000)) } else { const midPrice = priceLower.add(priceUpper).div(2) const estimatedPricePerBase = tokenBPriceUsd + (1 / midPrice.toNumber()) * tokenAPriceUsd 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 数量 const otherAmountNeeded = chain.getAmountAFromAmountB({ priceLower, priceUpper, amountB: baseAmount, poolInfo, }) // 添加 2% slippage otherAmountMax = otherAmountNeeded.mul(new BN(10200)).div(new BN(10000)) } // 检查代币数量是否为0 if (baseAmount.eq(new BN(0)) || otherAmountMax.eq(new BN(0))) { console.error('Error: One of the token amounts is zero') return NextResponse.json( { error: '添加流动性失败:其中一个代币数量为0。请确保两个代币都有数量。', details: { baseAmount: baseAmount.toString(), otherAmountMax: otherAmountMax.toString(), base, }, }, { status: 400 } ) } // 计算最终 UI 金额 const uiAmountA = base === 'MintA' ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsA) : new Decimal(otherAmountMax.toString()).div( 10 ** poolInfo.mintDecimalsA ) const uiAmountB = base === 'MintB' ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsB) : new Decimal(otherAmountMax.toString()).div( 10 ** poolInfo.mintDecimalsB ) // 打印所有信息 console.log('\n========== Add Liquidity Information ==========') console.log('Position NFT Address:', 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('Price Lower:', priceLower.toString()) console.log('Price Upper:', priceUpper.toString()) console.log('\n--- Investment Details ---') console.log('Add USD Value:', addUsdValue) 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):', uiAmountA.toString()) console.log('Token B Amount (UI):', uiAmountB.toString()) console.log( 'Token A Value (USD):', (uiAmountA.toNumber() * tokenAPriceUsd).toFixed(2) ) console.log( 'Token B Value (USD):', (uiAmountB.toNumber() * tokenBPriceUsd).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('Adding liquidity on-chain...') // 执行实际上链操作 const signerCallback = async (tx: VersionedTransaction) => { tx.sign([userKeypair]) return tx } const txid = await chain.addLiquidity({ userAddress, nftMint, base, baseAmount, otherAmountMax, signerCallback, }) console.log('Transaction ID:', txid) console.log('Liquidity added successfully!') console.log('==========================================\n') return NextResponse.json({ success: true, txid, positionInfo: { nftMint: nftMint.toBase58(), poolAddress: poolInfo.poolId.toBase58(), priceLower: priceLower.toString(), priceUpper: priceUpper.toString(), base, baseAmount: baseAmount.toString(), otherAmountMax: otherAmountMax.toString(), tokenA: { address: poolInfo.mintA.toBase58(), amount: uiAmountA.toString(), valueUsd: (uiAmountA.toNumber() * tokenAPriceUsd).toFixed(2), }, tokenB: { address: poolInfo.mintB.toBase58(), amount: uiAmountB.toString(), valueUsd: (uiAmountB.toNumber() * tokenBPriceUsd).toFixed(2), }, userAddress: userAddress?.toBase58() || 'N/A', }, }) } catch (error: unknown) { console.error('Add Liquidity Error:', error) const errorMessage = error instanceof Error ? error.message : 'Failed to add liquidity' // 如果错误包含 "insufficient funds",返回友好的余额不足提示 if (errorMessage.includes('insufficient funds')) { return NextResponse.json({ error: '余额不足' }, { status: 400 }) } return NextResponse.json({ error: errorMessage }, { status: 500 }) } }