goosefx-amm-sdk
Version:
SDK for the GooseFx AMM
83 lines (67 loc) • 2.9 kB
text/typescript
import BN from "bn.js";
import { RoundDirection, SwapWithoutFeesResult, TradingTokenResult } from "./calculator";
import { checkedCeilDiv, checkedRem, ZERO } from "./common";
export class ConstantProductCurve {
static swapWithoutFees(sourceAmount: BN, swapSourceAmount: BN, swapDestinationAmount: BN): SwapWithoutFeesResult {
const invariant = swapSourceAmount.mul(swapDestinationAmount);
const newSwapSourceAmount = swapSourceAmount.add(sourceAmount);
const [newSwapDestinationAmount, _newSwapSourceAmount] = checkedCeilDiv(invariant, newSwapSourceAmount);
const sourceAmountSwapped = _newSwapSourceAmount.sub(swapSourceAmount);
const destinationAmountSwapped = swapDestinationAmount.sub(newSwapDestinationAmount);
if (destinationAmountSwapped.isZero()) throw Error("destinationAmountSwapped is zero");
return {
sourceAmountSwapped,
destinationAmountSwapped,
};
}
static swapWithoutFeesBaseOut(
destinationAmount: BN,
swapSourceAmount: BN,
swapDestinationAmount: BN,
): SwapWithoutFeesResult {
// Ensure inputs are valid
if (destinationAmount.isZero()) {
throw new Error("destinationAmount is zero");
}
if (destinationAmount.gt(swapDestinationAmount)) {
throw new Error("destinationAmount exceeds available destination reserve");
}
// Numerator: x * Δy
const numerator = swapSourceAmount.mul(destinationAmount);
// Denominator: y - Δy
const denominator = swapDestinationAmount.sub(destinationAmount);
if (denominator.isZero()) {
throw new Error("denominator is zero");
}
// Ceiling division: Δx = ceil((x * Δy) / (y - Δy))
const [sourceAmountSwapped] = checkedCeilDiv(numerator, denominator);
return {
sourceAmountSwapped,
destinationAmountSwapped: destinationAmount,
};
}
static lpTokensToTradingTokens(
lpTokenAmount: BN,
lpTokenSupply: BN,
swapTokenAmount0: BN,
swapTokenAmount1: BN,
roundDirection: RoundDirection,
): TradingTokenResult {
let tokenAmount0 = lpTokenAmount.mul(swapTokenAmount0).div(lpTokenSupply);
let tokenAmount1 = lpTokenAmount.mul(swapTokenAmount1).div(lpTokenSupply);
if (roundDirection === RoundDirection.Floor) {
return { tokenAmount0, tokenAmount1 };
} else if (roundDirection === RoundDirection.Ceiling) {
const tokenRemainder0 = checkedRem(lpTokenAmount.mul(swapTokenAmount0), lpTokenSupply);
if (tokenRemainder0.gt(ZERO) && tokenAmount0.gt(ZERO)) {
tokenAmount0 = tokenAmount0.add(new BN(1));
}
const token1Remainder = checkedRem(lpTokenAmount.mul(swapTokenAmount1), lpTokenSupply);
if (token1Remainder.gt(ZERO) && tokenAmount1.gt(ZERO)) {
tokenAmount1 = tokenAmount1.add(new BN(1));
}
return { tokenAmount0, tokenAmount1 };
}
throw Error("roundDirection value error");
}
}