UNPKG

@raydium-io/raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

439 lines (388 loc) 12.8 kB
import BN from "bn.js"; import { LaunchPadConstantProductCurve } from "./constantProductCurve"; import { FixedPriceCurve } from "./fixedPriceCurve"; import { CurveBase, PoolBaseAmount } from "./curveBase"; import { LaunchpadConfigInfo, LaunchpadPoolInfo } from "../type"; import { FEE_RATE_DENOMINATOR_VALUE } from "@/common/fee"; import { LinearPriceCurve } from "./linearPriceCurve"; import { ceilDiv } from "@/common/bignumber"; import Decimal from "decimal.js"; export class Curve { static getPoolCurvePointByPoolInfo({ curveType, pointCount, poolInfo, }: { curveType: number; poolInfo: LaunchpadPoolInfo; pointCount: number; }): { price: Decimal; totalSellSupply: number; }[] { return this.getPoolCurvePointByInit({ curveType, pointCount, supply: poolInfo.supply, totalFundRaising: poolInfo.totalFundRaisingB, totalSell: poolInfo.totalSellA, totalLockedAmount: poolInfo.vestingSchedule.totalLockedAmount, migrateFee: poolInfo.migrateFee, decimalA: poolInfo.mintDecimalsA, decimalB: poolInfo.mintDecimalsB, }); } static getPoolCurvePointByInit({ curveType, pointCount, supply, totalFundRaising, totalSell, totalLockedAmount, migrateFee, decimalA, decimalB, }: { curveType: number; supply: BN; totalSell: BN; totalLockedAmount: BN; totalFundRaising: BN; migrateFee: BN; decimalA: number; decimalB: number; pointCount: number; }): { price: Decimal; totalSellSupply: number; }[] { if (pointCount < 3) throw Error("point count < 3"); const curve = this.getCurve(curveType); const initParam = curve.getInitParam({ supply, totalFundRaising, totalSell, totalLockedAmount, migrateFee }); const initPrice = curve.getPoolInitPriceByInit({ ...initParam, decimalA, decimalB }); const itemStepBuy = totalFundRaising.div(new BN(pointCount - 1)); const zero = new BN(0); const returnPoints: { price: Decimal; totalSellSupply: number }[] = [{ price: initPrice, totalSellSupply: 0 }]; const { a, b } = initParam; let realA = zero; let realB = zero; for (let i = 1; i < pointCount; i++) { const amountB = i !== pointCount - 1 ? itemStepBuy : totalFundRaising.sub(realB); const itemBuy = this.buyExactIn({ poolInfo: { virtualA: a, virtualB: b, realA, realB, totalFundRaisingB: totalFundRaising, totalSellA: totalSell, }, amountB, protocolFeeRate: zero, platformFeeRate: zero, curveType, shareFeeRate: zero, }); realA = realA.add(itemBuy.amountA); realB = realB.add(itemBuy.amountB); const nowPoolPrice = this.getPrice({ poolInfo: { virtualA: a, virtualB: b, realA, realB }, decimalA, decimalB, curveType, }); returnPoints.push({ price: nowPoolPrice, totalSellSupply: new Decimal(realA.toString()).div(10 ** decimalA).toNumber(), }); } return returnPoints; } static getPoolInitPriceByPool({ poolInfo, decimalA, decimalB, curveType, }: { poolInfo: LaunchpadPoolInfo | PoolBaseAmount; decimalA: number; decimalB: number; curveType: number; }): Decimal { const curve = this.getCurve(curveType); return curve.getPoolInitPriceByPool({ poolInfo, decimalA, decimalB }); } static getPoolInitPriceByInit({ a, b, decimalA, decimalB, curveType, }: { a: BN; b: BN; decimalA: number; decimalB: number; curveType: number; }): Decimal { const curve = this.getCurve(curveType); return curve.getPoolInitPriceByInit({ a, b, decimalA, decimalB }); } static getPrice({ poolInfo, curveType, decimalA, decimalB, }: { poolInfo: LaunchpadPoolInfo | PoolBaseAmount; curveType: number; decimalA: number; decimalB: number; }): Decimal { const curve = this.getCurve(curveType); return curve.getPoolPrice({ poolInfo, decimalA, decimalB }); } static getEndPrice({ poolInfo, curveType, decimalA, decimalB, }: { poolInfo: LaunchpadPoolInfo; curveType: number; decimalA: number; decimalB: number; }): Decimal { const curve = this.getCurve(curveType); return curve.getPoolPrice({ poolInfo, decimalA, decimalB }); } static getPoolEndPriceReal({ poolInfo, curveType, decimalA, decimalB, }: { poolInfo: LaunchpadPoolInfo; curveType: number; decimalA: number; decimalB: number; }): Decimal { const curve = this.getCurve(curveType); return curve.getPoolEndPriceReal({ poolInfo, decimalA, decimalB }); } static checkParam({ supply, totalFundRaising, totalSell, totalLockedAmount, decimals, config, migrateType, }: { supply: BN; totalSell: BN; totalLockedAmount: BN; totalFundRaising: BN; decimals: number; config: LaunchpadConfigInfo; migrateType: "amm" | "cpmm"; }): void { if (Number(decimals) !== 6) throw Error("decimals = 6"); const maxLockedA = supply.mul(config.maxLockRate).div(FEE_RATE_DENOMINATOR_VALUE); if (maxLockedA.lt(totalLockedAmount)) throw Error("total lock amount gte max lock amount"); if (supply.lt(config.minSupplyA.mul(new BN(10 ** decimals)))) throw Error("supply lt min supply"); const minSellA = supply.mul(config.minSellRateA).div(FEE_RATE_DENOMINATOR_VALUE); if (totalSell.lt(minSellA)) throw Error("invalid input"); if (totalFundRaising.lt(config.minFundRaisingB)) throw Error("total fund raising lt min fund raising"); const amountMigrate = supply.sub(totalSell).sub(totalLockedAmount); const minAmountMigrate = supply.mul(config.minMigrateRateA).div(FEE_RATE_DENOMINATOR_VALUE); if (amountMigrate.lt(minAmountMigrate)) throw Error("migrate lt min migrate amount"); const migrateAmountA = supply.sub(totalSell).sub(totalLockedAmount); const liquidity = new BN(new Decimal(migrateAmountA.mul(totalFundRaising).toString()).sqrt().toFixed(0)); if (migrateType === "amm") { const minLockLp = new BN(10).pow(new BN(decimals)); if (liquidity.lte(minLockLp)) throw Error("check migrate lp error"); } else if (migrateType === "cpmm") { const minLockLp = new BN(100); if (liquidity.lte(minLockLp)) throw Error("check migrate lp error"); } else { throw Error("migrate type error"); } } /** * @returns Please note that amountA/B is subject to change */ static buyExactIn({ poolInfo, amountB, protocolFeeRate, platformFeeRate, curveType, shareFeeRate, }: { poolInfo: LaunchpadPoolInfo | (PoolBaseAmount & { totalSellA: BN; totalFundRaisingB: BN }); amountB: BN; protocolFeeRate: BN; platformFeeRate: BN; curveType: number; shareFeeRate: BN; }): { amountA: BN; amountB: BN; splitFee: { platformFee: BN; shareFee: BN; protocolFee: BN }; } { const feeRate = protocolFeeRate.add(shareFeeRate).add(platformFeeRate); const _totalFee = this.calculateFee({ amount: amountB, feeRate }); const amountLessFeeB = amountB.sub(_totalFee); const curve = this.getCurve(curveType); const _amountA = curve.buyExactIn({ poolInfo, amount: amountLessFeeB }); const remainingAmountA = poolInfo.totalSellA.sub(poolInfo.realA); let amountA: BN; let realAmountB: BN; let totalFee: BN; if (_amountA.gt(remainingAmountA)) { amountA = remainingAmountA; const amountLessFeeB = poolInfo.totalFundRaisingB.sub(poolInfo.realB); realAmountB = this.calculatePreFee({ postFeeAmount: amountLessFeeB, feeRate }); totalFee = realAmountB.sub(amountLessFeeB); } else { amountA = _amountA; realAmountB = amountB; totalFee = _totalFee; } const splitFee = this.splitFee({ totalFee, protocolFeeRate, platformFeeRate, shareFeeRate }); return { amountA, amountB: realAmountB, splitFee }; } /** * @returns Please note that amountA/B is subject to change */ static buyExactOut({ poolInfo, amountA, protocolFeeRate, platformFeeRate, curveType, shareFeeRate, }: { poolInfo: LaunchpadPoolInfo | (PoolBaseAmount & { totalSellA: BN; totalFundRaisingB: BN }); amountA: BN; protocolFeeRate: BN; platformFeeRate: BN; curveType: number; shareFeeRate: BN; }): { amountA: BN; amountB: BN; splitFee: { platformFee: BN; shareFee: BN; protocolFee: BN }; } { const remainingAmountA = poolInfo.totalSellA.sub(poolInfo.realA); let realAmountA = amountA; let amountInLessFeeB; if (amountA.gte(remainingAmountA)) { realAmountA = remainingAmountA; amountInLessFeeB = poolInfo.totalFundRaisingB.sub(poolInfo.realB); } else { const curve = this.getCurve(curveType); amountInLessFeeB = curve.buyExactOut({ poolInfo, amount: amountA }); } const totalFeeRate = protocolFeeRate.add(shareFeeRate).add(platformFeeRate); const amountB = this.calculatePreFee({ postFeeAmount: amountInLessFeeB, feeRate: totalFeeRate }); const totalFee = amountB.sub(amountInLessFeeB); const splitFee = this.splitFee({ totalFee, protocolFeeRate, platformFeeRate, shareFeeRate }); return { amountA: realAmountA, amountB, splitFee }; } static sellExactIn({ poolInfo, amountA, protocolFeeRate, platformFeeRate, curveType, shareFeeRate, }: { poolInfo: LaunchpadPoolInfo | PoolBaseAmount; amountA: BN; protocolFeeRate: BN; platformFeeRate: BN; curveType: number; shareFeeRate: BN; }): { amountA: BN; amountB: BN; splitFee: { platformFee: BN; shareFee: BN; protocolFee: BN }; } { const curve = this.getCurve(curveType); const amountB = curve.sellExactIn({ poolInfo, amount: amountA }); const totalFee = this.calculateFee({ amount: amountB, feeRate: protocolFeeRate.add(shareFeeRate).add(platformFeeRate), }); const splitFee = this.splitFee({ totalFee, protocolFeeRate, platformFeeRate, shareFeeRate }); return { amountA, amountB: amountB.sub(totalFee), splitFee }; } static sellExactOut({ poolInfo, amountB, protocolFeeRate, platformFeeRate, curveType, shareFeeRate, }: { poolInfo: LaunchpadPoolInfo | PoolBaseAmount; amountB: BN; protocolFeeRate: BN; platformFeeRate: BN; curveType: number; shareFeeRate: BN; }): { amountA: BN; amountB: BN; splitFee: { platformFee: BN; shareFee: BN; protocolFee: BN }; } { const totalFeeRate = protocolFeeRate.add(shareFeeRate).add(platformFeeRate); const amountOutWithFeeB = this.calculatePreFee({ postFeeAmount: amountB, feeRate: totalFeeRate }); if (poolInfo.realB.lt(amountOutWithFeeB)) throw Error("Insufficient liquidity"); const totalFee = amountOutWithFeeB.sub(amountB); const curve = Curve.getCurve(curveType); const amountA = curve.sellExactOut({ poolInfo, amount: amountOutWithFeeB }); if (amountA.gt(poolInfo.realA)) throw Error(); const splitFee = this.splitFee({ totalFee, protocolFeeRate, platformFeeRate, shareFeeRate }); return { amountA, amountB, splitFee }; } static splitFee({ totalFee, protocolFeeRate, platformFeeRate, shareFeeRate, }: { totalFee: BN; protocolFeeRate: BN; platformFeeRate: BN; shareFeeRate: BN; }): { platformFee: BN; shareFee: BN; protocolFee: BN } { const totalFeeRate = protocolFeeRate.add(platformFeeRate).add(shareFeeRate); const platformFee = totalFeeRate.isZero() ? new BN(0) : totalFee.mul(platformFeeRate).div(totalFeeRate); const shareFee = totalFeeRate.isZero() ? new BN(0) : totalFee.mul(shareFeeRate).div(totalFeeRate); const protocolFee = totalFee.sub(platformFee).sub(shareFee); return { platformFee, shareFee, protocolFee }; } static calculateFee({ amount, feeRate }: { amount: BN; feeRate: BN }): BN { return ceilDiv(amount, feeRate, FEE_RATE_DENOMINATOR_VALUE); } static calculatePreFee({ postFeeAmount, feeRate }: { postFeeAmount: BN; feeRate: BN }): BN { if (feeRate.isZero()) return postFeeAmount; const numerator = postFeeAmount.mul(FEE_RATE_DENOMINATOR_VALUE); const denominator = FEE_RATE_DENOMINATOR_VALUE.sub(feeRate); return numerator.add(denominator).sub(new BN(1)).div(denominator); } static getCurve(curveType: number): typeof CurveBase { switch (curveType) { case 0: return LaunchPadConstantProductCurve; case 1: return FixedPriceCurve; case 2: return LinearPriceCurve; } throw Error("find curve error"); } }