diff --git a/ts-client/src/dlmm/helpers/strategy.ts b/ts-client/src/dlmm/helpers/strategy.ts index a118c9cf..2a8fc713 100644 --- a/ts-client/src/dlmm/helpers/strategy.ts +++ b/ts-client/src/dlmm/helpers/strategy.ts @@ -1,5 +1,5 @@ import { BN } from "@coral-xyz/anchor"; -import { StrategyType, StrategyParameters } from "../types"; +import { StrategyType, StrategyParameters, Clock } from "../types"; import { autoFillXByWeight, autoFillYByWeight, @@ -7,7 +7,8 @@ import { toAmountBidSide, toAmountBothSide, } from "./weightToAmounts"; -import Decimal from "decimal.js"; +import { Mint } from "@solana/spl-token"; + const DEFAULT_MAX_WEIGHT = 2000; const DEFAULT_MIN_WEIGHT = 200; @@ -146,6 +147,19 @@ function toWeightBidAsk( return distributions; } +/** + * Given a strategy type, calculate the distribution of liquidity for a deposit. + * @param activeId The bin id of the active bin. + * @param binStep The step size of each bin. + * @param minBinId The min bin id. + * @param maxBinId The max bin id. + * @param amount The amount of liquidity to deposit. + * @param strategyType The strategy type. + * @param depositForY Whether the deposit is for Y token. + * @param mint The mint info of token. Pass mintY if depositForY is true. Get from DLMM instance. + * @param clock The clock info. Get from DLMM instance. + * @returns The distribution of liquidity. + */ export function toAmountsOneSideByStrategy( activeId: number, binStep: number, @@ -153,7 +167,9 @@ export function toAmountsOneSideByStrategy( maxBinId: number, amount: BN, strategyType: StrategyType, - depositForY: boolean + depositForY: boolean, + mint: Mint, + clock: Clock ): { binId: number; amount: BN; @@ -190,12 +206,28 @@ export function toAmountsOneSideByStrategy( } } if (depositForY) { - return toAmountBidSide(activeId, amount, weights); + return toAmountBidSide(activeId, amount, weights, mint, clock); } else { - return toAmountAskSide(activeId, binStep, amount, weights); + return toAmountAskSide(activeId, binStep, amount, weights, mint, clock); } } +/** + * Given a strategy type and amounts of X and Y, returns the distribution of liquidity. + * @param activeId The bin id of the active bin. + * @param binStep The step size of each bin. + * @param minBinId The min bin id. + * @param maxBinId The max bin id. + * @param amountX The amount of X token to deposit. + * @param amountY The amount of Y token to deposit. + * @param amountXInActiveBin The amount of X token in the active bin. + * @param amountYInActiveBin The amount of Y token in the active bin. + * @param strategyType The strategy type. + * @param mintX The mint info of X token. Get from DLMM instance. + * @param mintY The mint info of Y token. Get from DLMM instance. + * @param clock The clock info. Get from DLMM instance. + * @returns The distribution of liquidity. + */ export function toAmountsBothSideByStrategy( activeId: number, binStep: number, @@ -205,7 +237,10 @@ export function toAmountsBothSideByStrategy( amountY: BN, amountXInActiveBin: BN, amountYInActiveBin: BN, - strategyType: StrategyType + strategyType: StrategyType, + mintX: Mint, + mintY: Mint, + clock: Clock ): { binId: number; amountX: BN; @@ -228,14 +263,23 @@ export function toAmountsBothSideByStrategy( amountY, amountXInActiveBin, amountYInActiveBin, - weights + weights, + mintX, + mintY, + clock ); } const amountsInBin = []; if (!isSingleSideX) { if (minBinId <= activeId) { const weights = toWeightSpotBalanced(minBinId, activeId); - const amounts = toAmountBidSide(activeId, amountY, weights); + const amounts = toAmountBidSide( + activeId, + amountY, + weights, + mintY, + clock + ); for (let bin of amounts) { amountsInBin.push({ @@ -247,7 +291,14 @@ export function toAmountsBothSideByStrategy( } if (activeId < maxBinId) { const weights = toWeightSpotBalanced(activeId + 1, maxBinId); - const amounts = toAmountAskSide(activeId, binStep, amountX, weights); + const amounts = toAmountAskSide( + activeId, + binStep, + amountX, + weights, + mintX, + clock + ); for (let bin of amounts) { amountsInBin.push({ binId: bin.binId, @@ -262,7 +313,9 @@ export function toAmountsBothSideByStrategy( const amountsIntoBidSide = toAmountBidSide( activeId, amountY, - weights + weights, + mintY, + clock ); for (let bin of amountsIntoBidSide) { amountsInBin.push({ @@ -278,7 +331,9 @@ export function toAmountsBothSideByStrategy( activeId, binStep, amountX, - weights + weights, + mintX, + clock ); for (let bin of amountsIntoAskSide) { amountsInBin.push({ @@ -302,7 +357,10 @@ export function toAmountsBothSideByStrategy( amountY, amountXInActiveBin, amountYInActiveBin, - weights + weights, + mintX, + mintY, + clock ); } // bid side @@ -315,14 +373,23 @@ export function toAmountsBothSideByStrategy( amountY, amountXInActiveBin, amountYInActiveBin, - weights + weights, + mintX, + mintY, + clock ); } const amountsInBin = []; if (!isSingleSideX) { if (minBinId <= activeId) { const weights = toWeightAscendingOrder(minBinId, activeId); - const amounts = toAmountBidSide(activeId, amountY, weights); + const amounts = toAmountBidSide( + activeId, + amountY, + weights, + mintY, + clock + ); for (let bin of amounts) { amountsInBin.push({ @@ -334,7 +401,14 @@ export function toAmountsBothSideByStrategy( } if (activeId < maxBinId) { const weights = toWeightDecendingOrder(activeId + 1, maxBinId); - const amounts = toAmountAskSide(activeId, binStep, amountX, weights); + const amounts = toAmountAskSide( + activeId, + binStep, + amountX, + weights, + mintX, + clock + ); for (let bin of amounts) { amountsInBin.push({ binId: bin.binId, @@ -349,7 +423,9 @@ export function toAmountsBothSideByStrategy( const amountsIntoBidSide = toAmountBidSide( activeId, amountY, - weights + weights, + mintY, + clock ); for (let bin of amountsIntoBidSide) { amountsInBin.push({ @@ -365,7 +441,9 @@ export function toAmountsBothSideByStrategy( activeId, binStep, amountX, - weights + weights, + mintX, + clock ); for (let bin of amountsIntoAskSide) { amountsInBin.push({ @@ -389,7 +467,10 @@ export function toAmountsBothSideByStrategy( amountY, amountXInActiveBin, amountYInActiveBin, - weights + weights, + mintX, + mintY, + clock ); } // bid side @@ -402,14 +483,23 @@ export function toAmountsBothSideByStrategy( amountY, amountXInActiveBin, amountYInActiveBin, - weights + weights, + mintX, + mintY, + clock ); } const amountsInBin = []; if (!isSingleSideX) { if (minBinId <= activeId) { const weights = toWeightDecendingOrder(minBinId, activeId); - const amounts = toAmountBidSide(activeId, amountY, weights); + const amounts = toAmountBidSide( + activeId, + amountY, + weights, + mintY, + clock + ); for (let bin of amounts) { amountsInBin.push({ @@ -421,7 +511,14 @@ export function toAmountsBothSideByStrategy( } if (activeId < maxBinId) { const weights = toWeightAscendingOrder(activeId + 1, maxBinId); - const amounts = toAmountAskSide(activeId, binStep, amountX, weights); + const amounts = toAmountAskSide( + activeId, + binStep, + amountX, + weights, + mintX, + clock + ); for (let bin of amounts) { amountsInBin.push({ binId: bin.binId, @@ -436,7 +533,9 @@ export function toAmountsBothSideByStrategy( const amountsIntoBidSide = toAmountBidSide( activeId, amountY, - weights + weights, + mintY, + clock ); for (let bin of amountsIntoBidSide) { amountsInBin.push({ @@ -452,7 +551,9 @@ export function toAmountsBothSideByStrategy( activeId, binStep, amountX, - weights + weights, + mintX, + clock ); for (let bin of amountsIntoAskSide) { amountsInBin.push({ @@ -474,7 +575,10 @@ export function toAmountsBothSideByStrategy( amountY, amountXInActiveBin, amountYInActiveBin, - weights + weights, + mintX, + mintY, + clock ); } case StrategyType.CurveBalanced: { @@ -486,7 +590,10 @@ export function toAmountsBothSideByStrategy( amountY, amountXInActiveBin, amountYInActiveBin, - weights + weights, + mintX, + mintY, + clock ); } case StrategyType.BidAskBalanced: { @@ -498,7 +605,10 @@ export function toAmountsBothSideByStrategy( amountY, amountXInActiveBin, amountYInActiveBin, - weights + weights, + mintX, + mintY, + clock ); } } diff --git a/ts-client/src/dlmm/helpers/weight.ts b/ts-client/src/dlmm/helpers/weight.ts index a2c396d5..6aed27a7 100644 --- a/ts-client/src/dlmm/helpers/weight.ts +++ b/ts-client/src/dlmm/helpers/weight.ts @@ -2,7 +2,13 @@ import { BN } from "@coral-xyz/anchor"; import gaussian, { Gaussian } from "gaussian"; import { BASIS_POINT_MAX } from "../constants"; import Decimal from "decimal.js"; -import { toAmountAskSide, toAmountBidSide, toAmountBothSide } from "./weightToAmounts"; +import { + toAmountAskSide, + toAmountBidSide, + toAmountBothSide, +} from "./weightToAmounts"; +import { Mint } from "@solana/spl-token"; +import { Clock } from "../types"; export function getPriceOfBinByBinId(binId: number, binStep: number): Decimal { const binStepNum = new Decimal(binStep).div(new Decimal(BASIS_POINT_MAX)); @@ -137,15 +143,15 @@ export function calculateSpotDistribution( const distributions = binIds[0] < activeBin ? binIds.map((binId) => ({ - binId, - xAmountBpsOfTotal: new BN(0), - yAmountBpsOfTotal: dist, - })) + binId, + xAmountBpsOfTotal: new BN(0), + yAmountBpsOfTotal: dist, + })) : binIds.map((binId) => ({ - binId, - xAmountBpsOfTotal: dist, - yAmountBpsOfTotal: new BN(0), - })); + binId, + xAmountBpsOfTotal: dist, + yAmountBpsOfTotal: new BN(0), + })); // Add the loss to the left most bin if (binIds[0] < activeBin) { @@ -458,20 +464,57 @@ export function calculateNormalDistribution( }); } +/** + * Converts a weight distribution into token amounts for one side (either bid or ask). + * + * @param amount - The total amount of liquidity to distribute. + * @param distributions - The array of weight distributions for each bin. + * @param binStep - The step interval between bin ids. + * @param activeId - The id of the active bin. + * @param depositForY - Flag indicating if the deposit is for token Y (bid side). + * @param mint - Mint information for the token. Mint Y if depositForY is true, else Mint X. Get from DLMM instance. + * @param clock - Clock instance for the current epoch. Get from DLMM instance. + * @returns An array of objects containing binId and amount for each bin. + */ + export function fromWeightDistributionToAmountOneSide( amount: BN, distributions: { binId: number; weight: number }[], binStep: number, activeId: number, depositForY: boolean, + mint: Mint, + clock: Clock ): { binId: number; amount: BN }[] { if (depositForY) { - return toAmountBidSide(activeId, amount, distributions); + return toAmountBidSide(activeId, amount, distributions, mint, clock); } else { - return toAmountAskSide(activeId, binStep, amount, distributions); + return toAmountAskSide( + activeId, + binStep, + amount, + distributions, + mint, + clock + ); } } +/** + * Converts a weight distribution into token amounts for both bid and ask sides. + * + * @param amountX - The total amount of token X to distribute. + * @param amountY - The total amount of token Y to distribute. + * @param distributions - The array of weight distributions for each bin. + * @param binStep - The step interval between bin ids. + * @param activeId - The id of the active bin. + * @param amountXInActiveBin - The amount of token X in the active bin. + * @param amountYInActiveBin - The amount of token Y in the active bin. + * @param mintX - Mint information for token X. Get from DLMM instance. + * @param mintY - Mint information for token Y. Get from DLMM instance. + * @param clock - Clock instance for the current epoch. Get from DLMM instance. + * @returns An array of objects containing binId, amountX, and amountY for each bin. + */ export function fromWeightDistributionToAmount( amountX: BN, amountY: BN, @@ -479,7 +522,10 @@ export function fromWeightDistributionToAmount( binStep: number, activeId: number, amountXInActiveBin: BN, - amountYInActiveBin: BN + amountYInActiveBin: BN, + mintX: Mint, + mintY: Mint, + clock: Clock ): { binId: number; amountX: BN; amountY: BN }[] { // sort distribution var distributions = distributions.sort((n1, n2) => { @@ -492,7 +538,13 @@ export function fromWeightDistributionToAmount( // only bid side if (activeId > distributions[distributions.length - 1].binId) { - let amounts = toAmountBidSide(activeId, amountY, distributions); + let amounts = toAmountBidSide( + activeId, + amountY, + distributions, + mintY, + clock + ); return amounts.map((bin) => { return { binId: bin.binId, @@ -504,7 +556,14 @@ export function fromWeightDistributionToAmount( // only ask side if (activeId < distributions[0].binId) { - let amounts = toAmountAskSide(activeId, binStep, amountX, distributions); + let amounts = toAmountAskSide( + activeId, + binStep, + amountX, + distributions, + mintX, + clock + ); return amounts.map((bin) => { return { binId: bin.binId, @@ -513,5 +572,16 @@ export function fromWeightDistributionToAmount( }; }); } - return toAmountBothSide(activeId, binStep, amountX, amountY, amountXInActiveBin, amountYInActiveBin, distributions); -} \ No newline at end of file + return toAmountBothSide( + activeId, + binStep, + amountX, + amountY, + amountXInActiveBin, + amountYInActiveBin, + distributions, + mintX, + mintY, + clock + ); +} diff --git a/ts-client/src/dlmm/helpers/weightToAmounts.ts b/ts-client/src/dlmm/helpers/weightToAmounts.ts index 7796ff3b..fb28d348 100644 --- a/ts-client/src/dlmm/helpers/weightToAmounts.ts +++ b/ts-client/src/dlmm/helpers/weightToAmounts.ts @@ -1,364 +1,466 @@ - import Decimal from "decimal.js"; import { BN } from "@coral-xyz/anchor"; import { getPriceOfBinByBinId } from "./weight"; +import { Mint } from "@solana/spl-token"; +import { calculateTransferFeeIncludedAmount } from "./token_2022"; +import { Clock } from "../types"; +/** + * Distribute totalAmount to all bid side bins according to given distributions. + * @param activeId active bin id + * @param totalAmount total amount of token Y to be distributed + * @param distributions weight distribution of each bin + * @param mintY mint of token Y, get from DLMM instance + * @param clock clock of the program, for calculating transfer fee, get from DLMM instance + * @returns array of {binId, amount} where amount is the amount of token Y in each bin + */ export function toAmountBidSide( - activeId: number, - totalAmount: BN, - distributions: { binId: number; weight: number }[], + activeId: number, + totalAmount: BN, + distributions: { binId: number; weight: number }[], + mintY: Mint, + clock: Clock ): { - binId: number, - amount: BN + binId: number; + amount: BN; }[] { - // get sum of weight - const totalWeight = distributions.reduce(function (sum, el) { - return el.binId > activeId ? sum : sum.add(el.weight); // skip all ask side - }, new Decimal(0)); + totalAmount = calculateTransferFeeIncludedAmount( + totalAmount, + mintY, + clock.epoch.toNumber() + ).amount; + + // get sum of weight + const totalWeight = distributions.reduce(function (sum, el) { + return el.binId > activeId ? sum : sum.add(el.weight); // skip all ask side + }, new Decimal(0)); - if (totalWeight.cmp(new Decimal(0)) != 1) { - throw Error("Invalid parameteres"); + if (totalWeight.cmp(new Decimal(0)) != 1) { + throw Error("Invalid parameteres"); + } + return distributions.map((bin) => { + if (bin.binId > activeId) { + return { + binId: bin.binId, + amount: new BN(0), + }; + } else { + return { + binId: bin.binId, + amount: new BN( + new Decimal(totalAmount.toString()) + .mul(new Decimal(bin.weight).div(totalWeight)) + .floor() + .toString() + ), + }; } - return distributions.map((bin) => { - if (bin.binId > activeId) { - return { - binId: bin.binId, - amount: new BN(0), - }; - } else { - return { - binId: bin.binId, - amount: new BN(new Decimal(totalAmount.toString()) - .mul(new Decimal(bin.weight).div(totalWeight)) - .floor().toString()), - }; - } - }); + }); } -export function toAmountAskSide(activeId: number, - binStep: number, - totalAmount: BN, - distributions: { binId: number; weight: number }[]): { - binId: number, - amount: BN - }[] { - // get sum of weight - const totalWeight: Decimal = distributions.reduce(function (sum, el) { - if (el.binId < activeId) { - return sum; - } else { - const price = getPriceOfBinByBinId(el.binId, binStep); - const weightPerPrice = new Decimal(el.weight).div(price); - return sum.add(weightPerPrice); - } - }, new Decimal(0)); +/** + * Distribute totalAmount to all ask side bins according to given distributions. + * @param activeId active bin id + * @param totalAmount total amount of token Y to be distributed + * @param distributions weight distribution of each bin + * @param mintX mint of token X, get from DLMM instance + * @param clock clock of the program, for calculating transfer fee, get from DLMM instance + * @returns array of {binId, amount} where amount is the amount of token X in each bin + */ +export function toAmountAskSide( + activeId: number, + binStep: number, + totalAmount: BN, + distributions: { binId: number; weight: number }[], + mintX: Mint, + clock: Clock +): { + binId: number; + amount: BN; +}[] { + totalAmount = calculateTransferFeeIncludedAmount( + totalAmount, + mintX, + clock.epoch.toNumber() + ).amount; - if (totalWeight.cmp(new Decimal(0)) != 1) { - throw Error("Invalid parameteres"); + // get sum of weight + const totalWeight: Decimal = distributions.reduce(function (sum, el) { + if (el.binId < activeId) { + return sum; + } else { + const price = getPriceOfBinByBinId(el.binId, binStep); + const weightPerPrice = new Decimal(el.weight).div(price); + return sum.add(weightPerPrice); } + }, new Decimal(0)); - return distributions.map((bin) => { - if (bin.binId < activeId) { - return { - binId: bin.binId, - amount: new BN(0), - }; - } else { - const price = getPriceOfBinByBinId(bin.binId, binStep); - const weightPerPrice = new Decimal(bin.weight).div(price); - return { - binId: bin.binId, - amount: new BN(new Decimal(totalAmount.toString()).mul(weightPerPrice).div(totalWeight).floor().toString()), - }; - } - }) + if (totalWeight.cmp(new Decimal(0)) != 1) { + throw Error("Invalid parameteres"); + } + + return distributions.map((bin) => { + if (bin.binId < activeId) { + return { + binId: bin.binId, + amount: new BN(0), + }; + } else { + const price = getPriceOfBinByBinId(bin.binId, binStep); + const weightPerPrice = new Decimal(bin.weight).div(price); + return { + binId: bin.binId, + amount: new BN( + new Decimal(totalAmount.toString()) + .mul(weightPerPrice) + .div(totalWeight) + .floor() + .toString() + ), + }; + } + }); } +/** + * Distributes the given amounts of tokens X and Y to both bid and ask side bins + * based on the provided weight distributions. + * + * @param activeId - The id of the active bin. + * @param binStep - The step interval between bin ids. + * @param amountX - Total amount of token X to distribute. + * @param amountY - Total amount of token Y to distribute. + * @param amountXInActiveBin - Amount of token X already in the active bin. + * @param amountYInActiveBin - Amount of token Y already in the active bin. + * @param distributions - Array of bins with their respective weight distributions. + * @param mintX - Mint information for token X. Get from DLMM instance. + * @param mintY - Mint information for token Y. Get from DLMM instance. + * @param clock - Clock instance. Get from DLMM instance. + * @returns An array of objects containing binId, amountX, and amountY for each bin. + */ + export function toAmountBothSide( - activeId: number, - binStep: number, - amountX: BN, - amountY: BN, - amountXInActiveBin: BN, - amountYInActiveBin: BN, - distributions: { binId: number; weight: number }[]): { - binId: number, - amountX: BN, - amountY: BN - }[] { - // only bid side - if (activeId > distributions[distributions.length - 1].binId) { - let amounts = toAmountBidSide(activeId, amountY, distributions); - return amounts.map((bin) => { - return { - binId: bin.binId, - amountX: new BN(0), - amountY: bin.amount, - }; - }) - } - // only ask side - if (activeId < distributions[0].binId) { - let amounts = toAmountAskSide(activeId, binStep, amountX, distributions); - return amounts.map((bin) => { - return { - binId: bin.binId, - amountX: bin.amount, - amountY: new BN(0), - }; - }) - } - const activeBins = distributions.filter((element) => { - return element.binId === activeId; + activeId: number, + binStep: number, + amountX: BN, + amountY: BN, + amountXInActiveBin: BN, + amountYInActiveBin: BN, + distributions: { binId: number; weight: number }[], + mintX: Mint, + mintY: Mint, + clock: Clock +): { + binId: number; + amountX: BN; + amountY: BN; +}[] { + // only bid side + if (activeId > distributions[distributions.length - 1].binId) { + let amounts = toAmountBidSide( + activeId, + amountY, + distributions, + mintY, + clock + ); + return amounts.map((bin) => { + return { + binId: bin.binId, + amountX: new BN(0), + amountY: bin.amount, + }; + }); + } + // only ask side + if (activeId < distributions[0].binId) { + let amounts = toAmountAskSide( + activeId, + binStep, + amountX, + distributions, + mintX, + clock + ); + return amounts.map((bin) => { + return { + binId: bin.binId, + amountX: bin.amount, + amountY: new BN(0), + }; }); + } - if (activeBins.length === 1) { - const p0 = getPriceOfBinByBinId(activeId, binStep); - let wx0 = new Decimal(0); - let wy0 = new Decimal(0); - const activeBin = activeBins[0]; - if (amountXInActiveBin.isZero() && amountYInActiveBin.isZero()) { - wx0 = new Decimal(activeBin.weight).div(p0.mul(new Decimal(2))); - wy0 = new Decimal(activeBin.weight).div(new Decimal(2)); - } else { - let amountXInActiveBinDec = new Decimal(amountXInActiveBin.toString()); - let amountYInActiveBinDec = new Decimal(amountYInActiveBin.toString()); + amountX = calculateTransferFeeIncludedAmount( + amountX, + mintX, + clock.epoch.toNumber() + ).amount; - if (!amountXInActiveBin.isZero()) { - wx0 = new Decimal(activeBin.weight).div( - p0.add(amountYInActiveBinDec.div(amountXInActiveBinDec)) - ); - } - if (!amountYInActiveBin.isZero()) { - wy0 = new Decimal(activeBin.weight).div( - new Decimal(1).add( - p0.mul(amountXInActiveBinDec).div(amountYInActiveBinDec) - ) - ); - } - } + amountY = calculateTransferFeeIncludedAmount( + amountY, + mintY, + clock.epoch.toNumber() + ).amount; - let totalWeightX = wx0; - let totalWeightY = wy0; - distributions.forEach((element) => { - if (element.binId < activeId) { - totalWeightY = totalWeightY.add(new Decimal(element.weight)); - } - if (element.binId > activeId) { - let price = getPriceOfBinByBinId(element.binId, binStep); - let weighPerPrice = new Decimal(element.weight).div(price); - totalWeightX = totalWeightX.add(weighPerPrice); - } - }); - const kx = new Decimal(amountX.toString()).div(totalWeightX); - const ky = new Decimal(amountY.toString()).div(totalWeightY); - let k = (kx.lessThan(ky) ? kx : ky); - return distributions.map((bin) => { - if (bin.binId < activeId) { - const amount = k.mul(new Decimal(bin.weight)); - return { - binId: bin.binId, - amountX: new BN(0), - amountY: new BN(amount.floor().toString()), - }; - } - if (bin.binId > activeId) { - const price = getPriceOfBinByBinId(bin.binId, binStep); - const weighPerPrice = new Decimal(bin.weight).div(price); - const amount = k.mul(weighPerPrice); - return { - binId: bin.binId, - amountX: new BN(amount.floor().toString()), - amountY: new BN(0), - }; - } + const activeBins = distributions.filter((element) => { + return element.binId === activeId; + }); - const amountXActiveBin = k.mul(wx0); - const amountYActiveBin = k.mul(wy0); - return { - binId: bin.binId, - amountX: new BN(amountXActiveBin.floor().toString()), - amountY: new BN(amountYActiveBin.floor().toString()), - }; - }); + if (activeBins.length === 1) { + const p0 = getPriceOfBinByBinId(activeId, binStep); + let wx0 = new Decimal(0); + let wy0 = new Decimal(0); + const activeBin = activeBins[0]; + if (amountXInActiveBin.isZero() && amountYInActiveBin.isZero()) { + wx0 = new Decimal(activeBin.weight).div(p0.mul(new Decimal(2))); + wy0 = new Decimal(activeBin.weight).div(new Decimal(2)); } else { - let totalWeightX = new Decimal(0); - let totalWeightY = new Decimal(0); - distributions.forEach((element) => { - if (element.binId < activeId) { - totalWeightY = totalWeightY.add(new Decimal(element.weight)); - } else { - let price = getPriceOfBinByBinId(element.binId, binStep); - let weighPerPrice = new Decimal(element.weight).div(price); - totalWeightX = totalWeightX.add(weighPerPrice); - } - }); - - let kx = new Decimal(amountX.toString()).div(totalWeightX); - let ky = new Decimal(amountY.toString()).div(totalWeightY); - let k = kx.lessThan(ky) ? kx : ky; + let amountXInActiveBinDec = new Decimal(amountXInActiveBin.toString()); + let amountYInActiveBinDec = new Decimal(amountYInActiveBin.toString()); - return distributions.map((bin) => { - if (bin.binId < activeId) { - const amount = k.mul(new Decimal(bin.weight)); - return { - binId: bin.binId, - amountX: new BN(0), - amountY: new BN(amount.floor().toString()), - }; - } else { - let price = getPriceOfBinByBinId(bin.binId, binStep); - let weighPerPrice = new Decimal(bin.weight).div(price); - const amount = k.mul(weighPerPrice); - return { - binId: bin.binId, - amountX: new BN(amount.floor().toString()), - amountY: new BN(0), - }; - } - }); + if (!amountXInActiveBin.isZero()) { + wx0 = new Decimal(activeBin.weight).div( + p0.add(amountYInActiveBinDec.div(amountXInActiveBinDec)) + ); + } + if (!amountYInActiveBin.isZero()) { + wy0 = new Decimal(activeBin.weight).div( + new Decimal(1).add( + p0.mul(amountXInActiveBinDec).div(amountYInActiveBinDec) + ) + ); + } } -} + let totalWeightX = wx0; + let totalWeightY = wy0; + distributions.forEach((element) => { + if (element.binId < activeId) { + totalWeightY = totalWeightY.add(new Decimal(element.weight)); + } + if (element.binId > activeId) { + let price = getPriceOfBinByBinId(element.binId, binStep); + let weighPerPrice = new Decimal(element.weight).div(price); + totalWeightX = totalWeightX.add(weighPerPrice); + } + }); + const kx = new Decimal(amountX.toString()).div(totalWeightX); + const ky = new Decimal(amountY.toString()).div(totalWeightY); + let k = kx.lessThan(ky) ? kx : ky; + return distributions.map((bin) => { + if (bin.binId < activeId) { + const amount = k.mul(new Decimal(bin.weight)); + return { + binId: bin.binId, + amountX: new BN(0), + amountY: new BN(amount.floor().toString()), + }; + } + if (bin.binId > activeId) { + const price = getPriceOfBinByBinId(bin.binId, binStep); + const weighPerPrice = new Decimal(bin.weight).div(price); + const amount = k.mul(weighPerPrice); + return { + binId: bin.binId, + amountX: new BN(amount.floor().toString()), + amountY: new BN(0), + }; + } -export function autoFillYByWeight( - activeId: number, - binStep: number, - amountX: BN, - amountXInActiveBin: BN, - amountYInActiveBin: BN, - distributions: { binId: number; weight: number }[]): BN { - const activeBins = distributions.filter((element) => { - return element.binId === activeId; + const amountXActiveBin = k.mul(wx0); + const amountYActiveBin = k.mul(wy0); + return { + binId: bin.binId, + amountX: new BN(amountXActiveBin.floor().toString()), + amountY: new BN(amountYActiveBin.floor().toString()), + }; + }); + } else { + let totalWeightX = new Decimal(0); + let totalWeightY = new Decimal(0); + distributions.forEach((element) => { + if (element.binId < activeId) { + totalWeightY = totalWeightY.add(new Decimal(element.weight)); + } else { + let price = getPriceOfBinByBinId(element.binId, binStep); + let weighPerPrice = new Decimal(element.weight).div(price); + totalWeightX = totalWeightX.add(weighPerPrice); + } }); - if (activeBins.length === 1) { - const p0 = getPriceOfBinByBinId(activeId, binStep); - let wx0 = new Decimal(0); - let wy0 = new Decimal(0); - const activeBin = activeBins[0]; - if (amountXInActiveBin.isZero() && amountYInActiveBin.isZero()) { - wx0 = new Decimal(activeBin.weight).div(p0.mul(new Decimal(2))); - wy0 = new Decimal(activeBin.weight).div(new Decimal(2)); - } else { - let amountXInActiveBinDec = new Decimal(amountXInActiveBin.toString()); - let amountYInActiveBinDec = new Decimal(amountYInActiveBin.toString()); + let kx = new Decimal(amountX.toString()).div(totalWeightX); + let ky = new Decimal(amountY.toString()).div(totalWeightY); + let k = kx.lessThan(ky) ? kx : ky; - if (!amountXInActiveBin.isZero()) { - wx0 = new Decimal(activeBin.weight).div( - p0.add(amountYInActiveBinDec.div(amountXInActiveBinDec)) - ); - } - if (!amountYInActiveBin.isZero()) { - wy0 = new Decimal(activeBin.weight).div( - new Decimal(1).add( - p0.mul(amountXInActiveBinDec).div(amountYInActiveBinDec) - ) - ); - } - } + return distributions.map((bin) => { + if (bin.binId < activeId) { + const amount = k.mul(new Decimal(bin.weight)); + return { + binId: bin.binId, + amountX: new BN(0), + amountY: new BN(amount.floor().toString()), + }; + } else { + let price = getPriceOfBinByBinId(bin.binId, binStep); + let weighPerPrice = new Decimal(bin.weight).div(price); + const amount = k.mul(weighPerPrice); + return { + binId: bin.binId, + amountX: new BN(amount.floor().toString()), + amountY: new BN(0), + }; + } + }); + } +} - let totalWeightX = wx0; - let totalWeightY = wy0; - distributions.forEach((element) => { - if (element.binId < activeId) { - totalWeightY = totalWeightY.add(new Decimal(element.weight)); - } - if (element.binId > activeId) { - const price = getPriceOfBinByBinId(element.binId, binStep); - const weighPerPrice = new Decimal(element.weight).div(price); - totalWeightX = totalWeightX.add(weighPerPrice); - } - }); - const kx = totalWeightX.isZero() ? new Decimal(1) : new Decimal(amountX.toString()).div(totalWeightX); - const amountY = kx.mul(totalWeightY); - return new BN(amountY.floor().toString()) +export function autoFillYByWeight( + activeId: number, + binStep: number, + amountX: BN, + amountXInActiveBin: BN, + amountYInActiveBin: BN, + distributions: { binId: number; weight: number }[] +): BN { + const activeBins = distributions.filter((element) => { + return element.binId === activeId; + }); + + if (activeBins.length === 1) { + const p0 = getPriceOfBinByBinId(activeId, binStep); + let wx0 = new Decimal(0); + let wy0 = new Decimal(0); + const activeBin = activeBins[0]; + if (amountXInActiveBin.isZero() && amountYInActiveBin.isZero()) { + wx0 = new Decimal(activeBin.weight).div(p0.mul(new Decimal(2))); + wy0 = new Decimal(activeBin.weight).div(new Decimal(2)); } else { - let totalWeightX = new Decimal(0); - let totalWeightY = new Decimal(0); - distributions.forEach((element) => { - if (element.binId < activeId) { - totalWeightY = totalWeightY.add(new Decimal(element.weight)); - } else { - const price = getPriceOfBinByBinId(element.binId, binStep); - const weighPerPrice = new Decimal(element.weight).div(price); - totalWeightX = totalWeightX.add(weighPerPrice); - } - }); - const kx = totalWeightX.isZero() ? new Decimal(1) : new Decimal(amountX.toString()).div(totalWeightX); - const amountY = kx.mul(totalWeightY); - return new BN(amountY.floor().toString()) - } -} + let amountXInActiveBinDec = new Decimal(amountXInActiveBin.toString()); + let amountYInActiveBinDec = new Decimal(amountYInActiveBin.toString()); + if (!amountXInActiveBin.isZero()) { + wx0 = new Decimal(activeBin.weight).div( + p0.add(amountYInActiveBinDec.div(amountXInActiveBinDec)) + ); + } + if (!amountYInActiveBin.isZero()) { + wy0 = new Decimal(activeBin.weight).div( + new Decimal(1).add( + p0.mul(amountXInActiveBinDec).div(amountYInActiveBinDec) + ) + ); + } + } -export function autoFillXByWeight( - activeId: number, - binStep: number, - amountY: BN, - amountXInActiveBin: BN, - amountYInActiveBin: BN, - distributions: { binId: number; weight: number }[]): BN { - const activeBins = distributions.filter((element) => { - return element.binId === activeId; + let totalWeightX = wx0; + let totalWeightY = wy0; + distributions.forEach((element) => { + if (element.binId < activeId) { + totalWeightY = totalWeightY.add(new Decimal(element.weight)); + } + if (element.binId > activeId) { + const price = getPriceOfBinByBinId(element.binId, binStep); + const weighPerPrice = new Decimal(element.weight).div(price); + totalWeightX = totalWeightX.add(weighPerPrice); + } }); + const kx = totalWeightX.isZero() + ? new Decimal(1) + : new Decimal(amountX.toString()).div(totalWeightX); + const amountY = kx.mul(totalWeightY); + return new BN(amountY.floor().toString()); + } else { + let totalWeightX = new Decimal(0); + let totalWeightY = new Decimal(0); + distributions.forEach((element) => { + if (element.binId < activeId) { + totalWeightY = totalWeightY.add(new Decimal(element.weight)); + } else { + const price = getPriceOfBinByBinId(element.binId, binStep); + const weighPerPrice = new Decimal(element.weight).div(price); + totalWeightX = totalWeightX.add(weighPerPrice); + } + }); + const kx = totalWeightX.isZero() + ? new Decimal(1) + : new Decimal(amountX.toString()).div(totalWeightX); + const amountY = kx.mul(totalWeightY); + return new BN(amountY.floor().toString()); + } +} - if (activeBins.length === 1) { - const p0 = getPriceOfBinByBinId(activeId, binStep); - let wx0 = new Decimal(0); - let wy0 = new Decimal(0); - const activeBin = activeBins[0]; - if (amountXInActiveBin.isZero() && amountYInActiveBin.isZero()) { - wx0 = new Decimal(activeBin.weight).div(p0.mul(new Decimal(2))); - wy0 = new Decimal(activeBin.weight).div(new Decimal(2)); - } else { - let amountXInActiveBinDec = new Decimal(amountXInActiveBin.toString()); - let amountYInActiveBinDec = new Decimal(amountYInActiveBin.toString()); - - if (!amountXInActiveBin.isZero()) { - wx0 = new Decimal(activeBin.weight).div( - p0.add(amountYInActiveBinDec.div(amountXInActiveBinDec)) - ); - } - if (!amountYInActiveBin.isZero()) { - wy0 = new Decimal(activeBin.weight).div( - new Decimal(1).add( - p0.mul(amountXInActiveBinDec).div(amountYInActiveBinDec) - ) - ); - } - } +export function autoFillXByWeight( + activeId: number, + binStep: number, + amountY: BN, + amountXInActiveBin: BN, + amountYInActiveBin: BN, + distributions: { binId: number; weight: number }[] +): BN { + const activeBins = distributions.filter((element) => { + return element.binId === activeId; + }); - let totalWeightX = wx0; - let totalWeightY = wy0; - distributions.forEach((element) => { - if (element.binId < activeId) { - totalWeightY = totalWeightY.add(new Decimal(element.weight)); - } - if (element.binId > activeId) { - const price = getPriceOfBinByBinId(element.binId, binStep); - const weighPerPrice = new Decimal(element.weight).div(price); - totalWeightX = totalWeightX.add(weighPerPrice); - } - }); - const ky = totalWeightY.isZero() ? new Decimal(1) : new Decimal(amountY.toString()).div(totalWeightY); - const amountX = ky.mul(totalWeightX); - return new BN(amountX.floor().toString()) + if (activeBins.length === 1) { + const p0 = getPriceOfBinByBinId(activeId, binStep); + let wx0 = new Decimal(0); + let wy0 = new Decimal(0); + const activeBin = activeBins[0]; + if (amountXInActiveBin.isZero() && amountYInActiveBin.isZero()) { + wx0 = new Decimal(activeBin.weight).div(p0.mul(new Decimal(2))); + wy0 = new Decimal(activeBin.weight).div(new Decimal(2)); } else { - let totalWeightX = new Decimal(0); - let totalWeightY = new Decimal(0); - distributions.forEach((element) => { - if (element.binId < activeId) { - totalWeightY = totalWeightY.add(new Decimal(element.weight)); - } else { - const price = getPriceOfBinByBinId(element.binId, binStep); - const weighPerPrice = new Decimal(element.weight).div(price); - totalWeightX = totalWeightX.add(weighPerPrice); - } - }); - const ky = totalWeightY.isZero() ? new Decimal(1) : new Decimal(amountY.toString()).div(totalWeightY); - const amountX = ky.mul(totalWeightX); - return new BN(amountX.floor().toString()) + let amountXInActiveBinDec = new Decimal(amountXInActiveBin.toString()); + let amountYInActiveBinDec = new Decimal(amountYInActiveBin.toString()); + + if (!amountXInActiveBin.isZero()) { + wx0 = new Decimal(activeBin.weight).div( + p0.add(amountYInActiveBinDec.div(amountXInActiveBinDec)) + ); + } + if (!amountYInActiveBin.isZero()) { + wy0 = new Decimal(activeBin.weight).div( + new Decimal(1).add( + p0.mul(amountXInActiveBinDec).div(amountYInActiveBinDec) + ) + ); + } } -} \ No newline at end of file + + let totalWeightX = wx0; + let totalWeightY = wy0; + distributions.forEach((element) => { + if (element.binId < activeId) { + totalWeightY = totalWeightY.add(new Decimal(element.weight)); + } + if (element.binId > activeId) { + const price = getPriceOfBinByBinId(element.binId, binStep); + const weighPerPrice = new Decimal(element.weight).div(price); + totalWeightX = totalWeightX.add(weighPerPrice); + } + }); + const ky = totalWeightY.isZero() + ? new Decimal(1) + : new Decimal(amountY.toString()).div(totalWeightY); + const amountX = ky.mul(totalWeightX); + return new BN(amountX.floor().toString()); + } else { + let totalWeightX = new Decimal(0); + let totalWeightY = new Decimal(0); + distributions.forEach((element) => { + if (element.binId < activeId) { + totalWeightY = totalWeightY.add(new Decimal(element.weight)); + } else { + const price = getPriceOfBinByBinId(element.binId, binStep); + const weighPerPrice = new Decimal(element.weight).div(price); + totalWeightX = totalWeightX.add(weighPerPrice); + } + }); + const ky = totalWeightY.isZero() + ? new Decimal(1) + : new Decimal(amountY.toString()).div(totalWeightY); + const amountX = ky.mul(totalWeightX); + return new BN(amountX.floor().toString()); + } +}