|
@@ -0,0 +1,456 @@
|
|
|
|
|
+import { NextRequest, NextResponse } from 'next/server'
|
|
|
|
|
+import { PublicKey } from '@solana/web3.js'
|
|
|
|
|
+import BN from 'bn.js'
|
|
|
|
|
+import { Decimal } from 'decimal.js'
|
|
|
|
|
+import { chain } from '@/lib/config'
|
|
|
|
|
+import { TickMath } from '@/lib/byreal-clmm-sdk/src/instructions/utils/tickMath'
|
|
|
|
|
+
|
|
|
|
|
+export async function POST(request: NextRequest): Promise<NextResponse> {
|
|
|
|
|
+ const body = await request.json()
|
|
|
|
|
+ const {
|
|
|
|
|
+ positionAddress,
|
|
|
|
|
+ maxUsdValue,
|
|
|
|
|
+ nftMintAddress,
|
|
|
|
|
+ priceLower: priceLowerPct = 0,
|
|
|
|
|
+ priceUpper: priceUpperPct = 0,
|
|
|
|
|
+ } = body
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ 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 } = positionInfo
|
|
|
|
|
+ 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 价格
|
|
|
|
|
+ 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 获取真实 USD 价格
|
|
|
|
|
+ try {
|
|
|
|
|
+ const detailData = await fetch(
|
|
|
|
|
+ `https://api2.byreal.io/byreal/api/dex/v2/position/detail?address=${positionAddress}`
|
|
|
|
|
+ ).then((res) => res.json())
|
|
|
|
|
+
|
|
|
|
|
+ const poolData = detailData.result?.data?.pool
|
|
|
|
|
+ if (poolData?.mintA?.price && poolData?.mintB?.price) {
|
|
|
|
|
+ tokenAPriceUsd = parseFloat(poolData.mintA.price)
|
|
|
|
|
+ tokenBPriceUsd = parseFloat(poolData.mintB.price)
|
|
|
|
|
+ priceFromApi = true
|
|
|
|
|
+ }
|
|
|
|
|
+ } 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 {
|
|
|
|
|
+ return NextResponse.json(
|
|
|
|
|
+ {
|
|
|
|
|
+ error:
|
|
|
|
|
+ '无法获取代币价格:非稳定币交易对且 API 价格获取失败。请确保交易对包含 USDC/USDT 或稍后重试。',
|
|
|
|
|
+ tokenA: tokenAAddress,
|
|
|
|
|
+ tokenB: tokenBAddress,
|
|
|
|
|
+ },
|
|
|
|
|
+ { status: 400 }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算比例,使得总价值不超过 maxUsdValue
|
|
|
|
|
+ let tickLower = positionInfo.rawPositionInfo.tickLower
|
|
|
|
|
+ let tickUpper = positionInfo.rawPositionInfo.tickUpper
|
|
|
|
|
+ let priceLower = positionInfo.priceLower
|
|
|
|
|
+ let priceUpper = positionInfo.priceUpper
|
|
|
|
|
+
|
|
|
|
|
+ const shouldOverrideTicks =
|
|
|
|
|
+ Number(priceLowerPct) !== 0 && Number(priceUpperPct) !== 0
|
|
|
|
|
+
|
|
|
|
|
+ if (shouldOverrideTicks) {
|
|
|
|
|
+ const currentPriceDecimal = new Decimal(currentPrice)
|
|
|
|
|
+ const lowerMultiplier = new Decimal(1).sub(
|
|
|
|
|
+ new Decimal(priceLowerPct).div(100)
|
|
|
|
|
+ )
|
|
|
|
|
+ const upperMultiplier = new Decimal(1).add(
|
|
|
|
|
+ new Decimal(priceUpperPct).div(100)
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (lowerMultiplier.lte(0) || upperMultiplier.lte(0)) {
|
|
|
|
|
+ return NextResponse.json(
|
|
|
|
|
+ { error: 'Invalid priceLower/priceUpper percent' },
|
|
|
|
|
+ { status: 400 }
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const lowerPrice = currentPriceDecimal.mul(lowerMultiplier)
|
|
|
|
|
+ const upperPrice = currentPriceDecimal.mul(upperMultiplier)
|
|
|
|
|
+
|
|
|
|
|
+ const alignedLower = TickMath.getTickWithPriceAndTickspacing(
|
|
|
|
|
+ lowerPrice,
|
|
|
|
|
+ tickSpacing,
|
|
|
|
|
+ poolInfo.mintDecimalsA,
|
|
|
|
|
+ poolInfo.mintDecimalsB
|
|
|
|
|
+ )
|
|
|
|
|
+ const alignedUpper = TickMath.getTickWithPriceAndTickspacing(
|
|
|
|
|
+ upperPrice,
|
|
|
|
|
+ tickSpacing,
|
|
|
|
|
+ poolInfo.mintDecimalsA,
|
|
|
|
|
+ poolInfo.mintDecimalsB
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ tickLower = Math.min(alignedLower, alignedUpper)
|
|
|
|
|
+ tickUpper = Math.max(alignedLower, alignedUpper)
|
|
|
|
|
+
|
|
|
|
|
+ priceLower = TickMath.getPriceFromTick({
|
|
|
|
|
+ tick: tickLower,
|
|
|
|
|
+ decimalsA: poolInfo.mintDecimalsA,
|
|
|
|
|
+ decimalsB: poolInfo.mintDecimalsB,
|
|
|
|
|
+ baseIn: true,
|
|
|
|
|
+ })
|
|
|
|
|
+ priceUpper = TickMath.getPriceFromTick({
|
|
|
|
|
+ tick: tickUpper,
|
|
|
|
|
+ decimalsA: poolInfo.mintDecimalsA,
|
|
|
|
|
+ decimalsB: poolInfo.mintDecimalsB,
|
|
|
|
|
+ baseIn: true,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 使用较小的 token 价格作为 base
|
|
|
|
|
+ const useTokenAAsBase = tokenAPriceUsd <= tokenBPriceUsd
|
|
|
|
|
+ const base = useTokenAAsBase ? 'MintA' : 'MintB'
|
|
|
|
|
+
|
|
|
|
|
+ const midPrice = priceLower.add(priceUpper).div(2)
|
|
|
|
|
+ const targetValue = maxUsdValue * 0.995
|
|
|
|
|
+
|
|
|
|
|
+ let baseAmount: BN
|
|
|
|
|
+ let otherAmountMax: BN
|
|
|
|
|
+
|
|
|
|
|
+ if (base === 'MintA') {
|
|
|
|
|
+ const estimatedPricePerBase =
|
|
|
|
|
+ tokenAPriceUsd + midPrice.toNumber() * tokenBPriceUsd
|
|
|
|
|
+
|
|
|
|
|
+ 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
|
|
|
|
|
+ const otherAmountNeeded = chain.getAmountBFromAmountA({
|
|
|
|
|
+ priceLower,
|
|
|
|
|
+ priceUpper,
|
|
|
|
|
+ amountA: baseAmount,
|
|
|
|
|
+ poolInfo,
|
|
|
|
|
+ })
|
|
|
|
|
+ otherAmountMax = otherAmountNeeded.mul(new BN(10200)).div(new BN(10000))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ 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
|
|
|
|
|
+ const otherAmountNeeded = chain.getAmountAFromAmountB({
|
|
|
|
|
+ priceLower,
|
|
|
|
|
+ priceUpper,
|
|
|
|
|
+ amountB: baseAmount,
|
|
|
|
|
+ poolInfo,
|
|
|
|
|
+ })
|
|
|
|
|
+ otherAmountMax = otherAmountNeeded.mul(new BN(10500)).div(new BN(10000))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算最终 UI 金额
|
|
|
|
|
+ const actualOtherAmount = otherAmountMax
|
|
|
|
|
+ .mul(new BN(10000))
|
|
|
|
|
+ .div(new BN(base === 'MintA' ? 10200 : 10500))
|
|
|
|
|
+
|
|
|
|
|
+ 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))
|
|
|
|
|
+ const otherAmountNeeded = chain.getAmountBFromAmountA({
|
|
|
|
|
+ priceLower,
|
|
|
|
|
+ priceUpper,
|
|
|
|
|
+ amountA: baseAmount,
|
|
|
|
|
+ poolInfo,
|
|
|
|
|
+ })
|
|
|
|
|
+ otherAmountMax = otherAmountNeeded.mul(new BN(10200)).div(new BN(10000))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ baseAmount = baseAmount
|
|
|
|
|
+ .mul(new BN(Math.floor(scale * 10000)))
|
|
|
|
|
+ .div(new BN(10000))
|
|
|
|
|
+ const otherAmountNeeded = chain.getAmountAFromAmountB({
|
|
|
|
|
+ priceLower,
|
|
|
|
|
+ priceUpper,
|
|
|
|
|
+ amountB: baseAmount,
|
|
|
|
|
+ poolInfo,
|
|
|
|
|
+ })
|
|
|
|
|
+ otherAmountMax = otherAmountNeeded.mul(new BN(10200)).div(new BN(10000))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重新计算
|
|
|
|
|
+ const finalOtherAmount = otherAmountMax
|
|
|
|
|
+ .mul(new BN(10000))
|
|
|
|
|
+ .div(new BN(10200))
|
|
|
|
|
+ const finalUiAmountA =
|
|
|
|
|
+ base === 'MintA'
|
|
|
|
|
+ ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsA)
|
|
|
|
|
+ : new Decimal(finalOtherAmount.toString()).div(
|
|
|
|
|
+ 10 ** poolInfo.mintDecimalsA
|
|
|
|
|
+ )
|
|
|
|
|
+ const finalUiAmountB =
|
|
|
|
|
+ base === 'MintB'
|
|
|
|
|
+ ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsB)
|
|
|
|
|
+ : new Decimal(finalOtherAmount.toString()).div(
|
|
|
|
|
+ 10 ** poolInfo.mintDecimalsB
|
|
|
|
|
+ )
|
|
|
|
|
+ totalValue =
|
|
|
|
|
+ finalUiAmountA.toNumber() * tokenAPriceUsd +
|
|
|
|
|
+ finalUiAmountB.toNumber() * tokenBPriceUsd
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 最终金额
|
|
|
|
|
+ const finalOtherAmount = otherAmountMax
|
|
|
|
|
+ .mul(new BN(10000))
|
|
|
|
|
+ .div(new BN(10200))
|
|
|
|
|
+ const finalUiAmountA =
|
|
|
|
|
+ base === 'MintA'
|
|
|
|
|
+ ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsA)
|
|
|
|
|
+ : new Decimal(finalOtherAmount.toString()).div(
|
|
|
|
|
+ 10 ** poolInfo.mintDecimalsA
|
|
|
|
|
+ )
|
|
|
|
|
+ const finalUiAmountB =
|
|
|
|
|
+ base === 'MintB'
|
|
|
|
|
+ ? new Decimal(baseAmount.toString()).div(10 ** poolInfo.mintDecimalsB)
|
|
|
|
|
+ : new Decimal(finalOtherAmount.toString()).div(
|
|
|
|
|
+ 10 ** poolInfo.mintDecimalsB
|
|
|
|
|
+ )
|
|
|
|
|
+ totalValue =
|
|
|
|
|
+ finalUiAmountA.toNumber() * tokenAPriceUsd +
|
|
|
|
|
+ finalUiAmountB.toNumber() * tokenBPriceUsd
|
|
|
|
|
+
|
|
|
|
|
+ return NextResponse.json({
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ calculation: {
|
|
|
|
|
+ poolAddress: poolInfo.poolId.toBase58(),
|
|
|
|
|
+ priceLower: priceLower.toString(),
|
|
|
|
|
+ priceUpper: priceUpper.toString(),
|
|
|
|
|
+ tickLower,
|
|
|
|
|
+ tickUpper,
|
|
|
|
|
+ currentPrice,
|
|
|
|
|
+ estimatedValue: totalValue,
|
|
|
|
|
+ tokenA: {
|
|
|
|
|
+ address: poolInfo.mintA.toBase58(),
|
|
|
|
|
+ decimals: poolInfo.mintDecimalsA,
|
|
|
|
|
+ amount: finalUiAmountA.toString(),
|
|
|
|
|
+ amountRaw: (base === 'MintA'
|
|
|
|
|
+ ? baseAmount
|
|
|
|
|
+ : otherAmountMax
|
|
|
|
|
+ ).toString(),
|
|
|
|
|
+ priceUsd: tokenAPriceUsd,
|
|
|
|
|
+ valueUsd: (finalUiAmountA.toNumber() * tokenAPriceUsd).toFixed(2),
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenB: {
|
|
|
|
|
+ address: poolInfo.mintB.toBase58(),
|
|
|
|
|
+ decimals: poolInfo.mintDecimalsB,
|
|
|
|
|
+ amount: finalUiAmountB.toString(),
|
|
|
|
|
+ amountRaw: (base === 'MintB'
|
|
|
|
|
+ ? baseAmount
|
|
|
|
|
+ : otherAmountMax
|
|
|
|
|
+ ).toString(),
|
|
|
|
|
+ priceUsd: tokenBPriceUsd,
|
|
|
|
|
+ valueUsd: (finalUiAmountB.toNumber() * tokenBPriceUsd).toFixed(2),
|
|
|
|
|
+ },
|
|
|
|
|
+ refererPosition: positionAddress,
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (error: unknown) {
|
|
|
|
|
+ console.error('LP copy calculate error:', error)
|
|
|
|
|
+ const errorMessage =
|
|
|
|
|
+ error instanceof Error ? error.message : 'Failed to calculate LP copy'
|
|
|
|
|
+ return NextResponse.json({ error: errorMessage }, { status: 500 })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|