import _Big from "big.js"; import BN from "bn.js"; import { Logger } from "../common"; import { BigNumberish, parseBigNumberish } from "./bignumber"; import { Rounding, TEN } from "./constant"; import { Currency, currencyEquals, Token } from "./currency"; import { Fraction } from "./fraction"; import toFormat, { WrappedBig } from "./to-format"; const logger = Logger.from("entity/amount"); const Big = toFormat(_Big); type Big = WrappedBig; export function splitNumber(num: string, decimals: number) { let integral = "0"; let fractional = "0"; if (num.includes(".")) { const splited = num.split("."); if (splited.length === 2) { [integral, fractional] = splited; fractional = fractional.padEnd(decimals, "0"); } else { return logger.throwArgumentError("invalid number string", "num", num); } } else { integral = num; } // fix decimals is 0 return [integral, fractional.slice(0, decimals) || fractional]; } export class CurrencyAmount extends Fraction { public readonly currency: Currency; public constructor(currency: Currency, amount: BigNumberish, isRaw = true) { let parsedAmount = new BN(0); const multiplier = TEN.pow(new BN(currency.decimals)); if (isRaw) { parsedAmount = parseBigNumberish(amount); } else { let integralAmount = new BN(0); let fractionalAmount = new BN(0); // parse fractional string if (typeof amount === "string" || typeof amount === "number" || typeof amount === "bigint") { const [integral, fractional] = splitNumber(amount.toString(), currency.decimals); integralAmount = parseBigNumberish(integral); fractionalAmount = parseBigNumberish(fractional); } // else { // integralAmount = parseBigNumberish(amount); // } integralAmount = integralAmount.mul(multiplier); parsedAmount = integralAmount.add(fractionalAmount); } super(parsedAmount, multiplier); this.currency = currency; } public get raw() { return this.numerator; } public isZero() { return this.raw.isZero(); } /** * a greater than b */ public gt(other: CurrencyAmount) { logger.assert(currencyEquals(this.currency, other.currency), "gt currency not equals"); return this.raw.gt(other.raw); } /** * a less than b */ public lt(other: CurrencyAmount) { logger.assert(currencyEquals(this.currency, other.currency), "lt currency not equals"); return this.raw.lt(other.raw); } public add(other: CurrencyAmount): CurrencyAmount { logger.assert(currencyEquals(this.currency, other.currency), "add currency not equals"); return new CurrencyAmount(this.currency, this.raw.add(other.raw)); } public sub(other: CurrencyAmount): CurrencyAmount { logger.assert(currencyEquals(this.currency, other.currency), "sub currency not equals"); return new CurrencyAmount(this.currency, this.raw.sub(other.raw)); } public toSignificant( significantDigits = this.currency.decimals, format?: object, rounding: Rounding = Rounding.ROUND_DOWN, ): string { return super.toSignificant(significantDigits, format, rounding); } /** * To fixed * * @example * ``` * 1 -> 1.000000000 * 1.234 -> 1.234000000 * 1.123456789876543 -> 1.123456789 * ``` */ public toFixed( decimalPlaces = this.currency.decimals, format?: object, rounding: Rounding = Rounding.ROUND_DOWN, ): string { logger.assert(decimalPlaces <= this.currency.decimals, "decimals overflow"); return super.toFixed(decimalPlaces, format, rounding); } /** * To exact * * @example * ``` * 1 -> 1 * 1.234 -> 1.234 * 1.123456789876543 -> 1.123456789 * ``` */ public toExact(format: object = { groupSeparator: "" }): string { Big.DP = this.currency.decimals; return new Big(this.numerator.toString()).div(this.denominator.toString()).toFormat(format); } } export class TokenAmount extends CurrencyAmount { public readonly token: Token; public constructor(token: Token, amount: BigNumberish, isRaw = true) { super(token, amount, isRaw); this.token = token; } public add(other: TokenAmount): TokenAmount { logger.assert(currencyEquals(this.token, other.token), "add token not equals"); return new TokenAmount(this.token, this.raw.add(other.raw)); } public subtract(other: TokenAmount): TokenAmount { logger.assert(currencyEquals(this.token, other.token), "sub token not equals"); return new TokenAmount(this.token, this.raw.sub(other.raw)); } }