@drift-labs/sdk-browser
Version:
SDK for Drift Protocol
501 lines (500 loc) • 21.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BigNum = void 0;
const anchor_1 = require("@coral-xyz/anchor");
const assert_1 = require("../assert/assert");
const numericConstants_1 = require("./../constants/numericConstants");
class BigNum {
static setLocale(locale) {
BigNum.delim = (1.1).toLocaleString(locale).slice(1, 2) || '.';
BigNum.spacer = (1000).toLocaleString(locale).slice(1, 2) || ',';
}
constructor(val, precisionVal = new anchor_1.BN(0)) {
this.toString = (base, length) => this.val.toString(base, length);
this.val = new anchor_1.BN(val);
this.precision = new anchor_1.BN(precisionVal);
}
bigNumFromParam(bn) {
return anchor_1.BN.isBN(bn) ? BigNum.from(bn) : bn;
}
add(bn) {
(0, assert_1.assert)(bn.precision.eq(this.precision), 'Adding unequal precisions');
return BigNum.from(this.val.add(bn.val), this.precision);
}
sub(bn) {
(0, assert_1.assert)(bn.precision.eq(this.precision), 'Subtracting unequal precisions');
return BigNum.from(this.val.sub(bn.val), this.precision);
}
mul(bn) {
const mulVal = this.bigNumFromParam(bn);
return BigNum.from(this.val.mul(mulVal.val), this.precision.add(mulVal.precision));
}
/**
* Multiplies by another big number then scales the result down by the big number's precision so that we're in the same precision space
* @param bn
* @returns
*/
scalarMul(bn) {
if (anchor_1.BN.isBN(bn))
return BigNum.from(this.val.mul(bn), this.precision);
return BigNum.from(this.val.mul(bn.val), this.precision.add(bn.precision)).shift(bn.precision.neg());
}
div(bn) {
if (anchor_1.BN.isBN(bn))
return BigNum.from(this.val.div(bn), this.precision);
return BigNum.from(this.val.div(bn.val), this.precision.sub(bn.precision));
}
/**
* Shift precision up or down
* @param exponent
* @param skipAdjustingPrecision
* @returns
*/
shift(exponent, skipAdjustingPrecision = false) {
const shiftVal = typeof exponent === 'number' ? new anchor_1.BN(exponent) : exponent;
return BigNum.from(shiftVal.isNeg()
? this.val.div(new anchor_1.BN(10).pow(shiftVal))
: this.val.mul(new anchor_1.BN(10).pow(shiftVal)), skipAdjustingPrecision ? this.precision : this.precision.add(shiftVal));
}
/**
* Shift to a target precision
* @param targetPrecision
* @returns
*/
shiftTo(targetPrecision) {
return this.shift(targetPrecision.sub(this.precision));
}
/**
* Scale the number by a fraction
* @param numerator
* @param denominator
* @returns
*/
scale(numerator, denominator) {
return this.mul(BigNum.from(new anchor_1.BN(numerator))).div(new anchor_1.BN(denominator));
}
toPercentage(denominator, precision) {
return this.shift(precision)
.shift(2, true)
.div(denominator)
.toPrecision(precision);
}
gt(bn, ignorePrecision) {
const comparisonVal = this.bigNumFromParam(bn);
if (!ignorePrecision && !comparisonVal.eq(numericConstants_1.ZERO)) {
(0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter');
}
return this.val.gt(comparisonVal.val);
}
lt(bn, ignorePrecision) {
const comparisonVal = this.bigNumFromParam(bn);
if (!ignorePrecision && !comparisonVal.val.eq(numericConstants_1.ZERO)) {
(0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter');
}
return this.val.lt(comparisonVal.val);
}
gte(bn, ignorePrecision) {
const comparisonVal = this.bigNumFromParam(bn);
if (!ignorePrecision && !comparisonVal.val.eq(numericConstants_1.ZERO)) {
(0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter');
}
return this.val.gte(comparisonVal.val);
}
lte(bn, ignorePrecision) {
const comparisonVal = this.bigNumFromParam(bn);
if (!ignorePrecision && !comparisonVal.val.eq(numericConstants_1.ZERO)) {
(0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter');
}
return this.val.lte(comparisonVal.val);
}
eq(bn, ignorePrecision) {
const comparisonVal = this.bigNumFromParam(bn);
if (!ignorePrecision && !comparisonVal.val.eq(numericConstants_1.ZERO)) {
(0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter');
}
return this.val.eq(comparisonVal.val);
}
eqZero() {
return this.val.eq(numericConstants_1.ZERO);
}
gtZero() {
return this.val.gt(numericConstants_1.ZERO);
}
ltZero() {
return this.val.lt(numericConstants_1.ZERO);
}
gteZero() {
return this.val.gte(numericConstants_1.ZERO);
}
lteZero() {
return this.val.lte(numericConstants_1.ZERO);
}
abs() {
return new BigNum(this.val.abs(), this.precision);
}
neg() {
return new BigNum(this.val.neg(), this.precision);
}
/**
* Pretty print the underlying value in human-readable form. Depends on precision being correct for the output string to be correct
* @returns
*/
print() {
(0, assert_1.assert)(this.precision.gte(numericConstants_1.ZERO), 'Tried to print a BN with precision lower than zero');
const isNeg = this.isNeg();
const plainString = this.abs().toString();
const precisionNum = this.precision.toNumber();
// make a string with at least the precisionNum number of zeroes
let printString = [
...Array(this.precision.toNumber()).fill(0),
...plainString.split(''),
].join('');
// inject decimal
printString =
printString.substring(0, printString.length - precisionNum) +
BigNum.delim +
printString.substring(printString.length - precisionNum);
// remove leading zeroes
printString = printString.replace(/^0+/, '');
// add zero if leading delim
if (printString[0] === BigNum.delim)
printString = `0${printString}`;
// Add minus if negative
if (isNeg)
printString = `-${printString}`;
// remove trailing delim
if (printString[printString.length - 1] === BigNum.delim)
printString = printString.slice(0, printString.length - 1);
return printString;
}
prettyPrint(useTradePrecision, precisionOverride, decimalOverride) {
const [leftSide, rightSide] = this.printShort(useTradePrecision, precisionOverride).split(BigNum.delim);
let formattedLeftSide = leftSide;
let formattedRightSide = rightSide;
// Apply decimal override if specified
if (decimalOverride !== undefined) {
if (decimalOverride === 0) {
formattedRightSide = undefined;
}
else {
// If no decimal part exists, create one with zeros
const currentRightSide = rightSide || '';
// Pad with zeros if needed or truncate if too long
formattedRightSide = currentRightSide
.padEnd(decimalOverride, '0')
.substring(0, decimalOverride);
}
}
const isNeg = formattedLeftSide.includes('-');
if (isNeg) {
formattedLeftSide = formattedLeftSide.replace('-', '');
}
let index = formattedLeftSide.length - 3;
while (index >= 1) {
const formattedLeftSideArray = formattedLeftSide.split('');
formattedLeftSideArray.splice(index, 0, BigNum.spacer);
formattedLeftSide = formattedLeftSideArray.join('');
index -= 3;
}
return `${isNeg ? '-' : ''}${formattedLeftSide}${formattedRightSide ? `${BigNum.delim}${formattedRightSide}` : ''}`;
}
/**
* Print and remove unnecessary trailing zeroes
* @returns
*/
printShort(useTradePrecision, precisionOverride) {
const printVal = precisionOverride
? this.toPrecision(precisionOverride)
: useTradePrecision
? this.toTradePrecision()
: this.print();
if (!printVal.includes(BigNum.delim))
return printVal;
return printVal.replace(/0+$/g, '').replace(/\.$/, '').replace(/,$/, '');
}
debug() {
console.log(`${this.toString()} | ${this.print()} | ${this.precision.toString()}`);
}
/**
* Pretty print with the specified number of decimal places
* @param fixedPrecision
* @returns
*/
toFixed(fixedPrecision, rounded = false) {
if (rounded) {
return this.toRounded(fixedPrecision).toFixed(fixedPrecision);
}
const printString = this.print();
const [leftSide, rightSide] = printString.split(BigNum.delim);
const filledRightSide = [
...(rightSide !== null && rightSide !== void 0 ? rightSide : '').slice(0, fixedPrecision),
...Array(fixedPrecision).fill('0'),
]
.slice(0, fixedPrecision)
.join('');
return `${leftSide}${BigNum.delim}${filledRightSide}`;
}
getZeroes(count) {
return new Array(Math.max(count, 0)).fill('0').join('');
}
toRounded(roundingPrecision) {
const printString = this.toString();
let shouldRoundUp = false;
const roundingDigitChar = printString[roundingPrecision];
if (roundingDigitChar) {
const roundingDigitVal = Number(roundingDigitChar);
if (roundingDigitVal >= 5)
shouldRoundUp = true;
}
if (shouldRoundUp) {
const valueWithRoundedPrecisionAdded = this.add(BigNum.from(new anchor_1.BN(10).pow(new anchor_1.BN(printString.length - roundingPrecision)), this.precision));
const roundedUpPrintString = valueWithRoundedPrecisionAdded.toString().slice(0, roundingPrecision) +
this.getZeroes(printString.length - roundingPrecision);
return BigNum.from(roundedUpPrintString, this.precision);
}
else {
const roundedDownPrintString = printString.slice(0, roundingPrecision) +
this.getZeroes(printString.length - roundingPrecision);
return BigNum.from(roundedDownPrintString, this.precision);
}
}
/**
* Pretty print to the specified number of significant figures
* @param fixedPrecision
* @returns
*/
toPrecision(fixedPrecision, trailingZeroes = false, rounded = false) {
if (rounded) {
return this.toRounded(fixedPrecision).toPrecision(fixedPrecision, trailingZeroes);
}
const isNeg = this.isNeg();
const printString = this.abs().print();
const thisString = this.abs().toString();
// Handle small numbers (those with leading zeros after decimal)
if (printString.includes(BigNum.delim)) {
const [leftSide, rightSide] = printString.split(BigNum.delim);
if (leftSide === '0' && rightSide) {
// Count leading zeros
let leadingZeros = 0;
for (let i = 0; i < rightSide.length; i++) {
if (rightSide[i] === '0') {
leadingZeros++;
}
else {
break;
}
}
// Get significant digits starting after leading zeros
const significantPart = rightSide.slice(leadingZeros);
let significantDigits = significantPart.slice(0, fixedPrecision);
// Remove trailing zeros if not requested
if (!trailingZeroes) {
significantDigits = significantDigits.replace(/0+$/, '');
}
// Only return result if we have significant digits
if (significantDigits.length > 0) {
const result = `${isNeg ? '-' : ''}0${BigNum.delim}${rightSide.slice(0, leadingZeros)}${significantDigits}`;
return result;
}
}
}
let precisionPrintString = printString.slice(0, fixedPrecision + 1);
if (!printString.includes(BigNum.delim) &&
thisString.length < fixedPrecision) {
const precisionMismatch = fixedPrecision - thisString.length;
return BigNum.from((isNeg ? '-' : '') + thisString + this.getZeroes(precisionMismatch), precisionMismatch).toPrecision(fixedPrecision, trailingZeroes);
}
if (!precisionPrintString.includes(BigNum.delim) ||
precisionPrintString[precisionPrintString.length - 1] === BigNum.delim) {
precisionPrintString = printString.slice(0, fixedPrecision);
}
const pointsOfPrecision = precisionPrintString.replace(BigNum.delim, '').length;
if (pointsOfPrecision < fixedPrecision) {
precisionPrintString = [
...precisionPrintString.split(''),
...Array(fixedPrecision - pointsOfPrecision).fill('0'),
].join('');
}
if (!precisionPrintString.includes(BigNum.delim)) {
const delimFullStringLocation = printString.indexOf(BigNum.delim);
let skipExponent = false;
if (delimFullStringLocation === -1) {
// no decimal, not missing any precision
skipExponent = true;
}
if (precisionPrintString[precisionPrintString.length - 1] === BigNum.delim) {
// decimal is at end of string, not missing any precision, do nothing
skipExponent = true;
}
if (printString.indexOf(BigNum.delim) === fixedPrecision) {
// decimal is at end of string, not missing any precision, do nothing
skipExponent = true;
}
if (!skipExponent) {
const exponent = delimFullStringLocation - fixedPrecision;
if (trailingZeroes) {
precisionPrintString = `${precisionPrintString}${Array(exponent)
.fill('0')
.join('')}`;
}
else {
precisionPrintString = `${precisionPrintString}e${exponent}`;
}
}
}
return `${isNeg ? '-' : ''}${precisionPrintString}`;
}
toTradePrecision(rounded = false) {
return this.toPrecision(6, true, rounded);
}
/**
* Print dollar formatted value. Defaults to fixed decimals two unless a given precision is given.
* @param useTradePrecision
* @param precisionOverride
* @returns
*/
toNotional(useTradePrecision, precisionOverride, decimalOverride) {
var _a;
const prefix = `${this.lt(BigNum.zero()) ? `-` : ``}$`;
const usingCustomPrecision = true && (useTradePrecision || precisionOverride || decimalOverride);
let val = usingCustomPrecision
? this.prettyPrint(useTradePrecision, precisionOverride, decimalOverride)
: BigNum.fromPrint(this.toFixed(2), new anchor_1.BN(2)).prettyPrint();
// Append trailing zeroes out to 2 decimal places if not using custom precision
if (!usingCustomPrecision) {
const [_, rightSide] = val.split(BigNum.delim);
const trailingLength = (_a = rightSide === null || rightSide === void 0 ? void 0 : rightSide.length) !== null && _a !== void 0 ? _a : 0;
if (trailingLength === 0) {
val = `${val}${BigNum.delim}00`;
}
else if (trailingLength === 1) {
val = `${val}0`;
}
}
return `${prefix}${val.replace('-', '')}`;
}
toMillified(precision = 3, rounded = false, type = 'financial') {
if (rounded) {
return this.toRounded(precision).toMillified(precision);
}
const isNeg = this.isNeg();
const stringVal = this.abs().print();
const [leftSide] = stringVal.split(BigNum.delim);
if (!leftSide) {
return this.shift(new anchor_1.BN(precision)).toPrecision(precision, true);
}
if (leftSide.length <= precision) {
return this.toPrecision(precision);
}
if (leftSide.length <= 3) {
return this.shift(new anchor_1.BN(precision)).toPrecision(precision, true);
}
const unitTicks = type === 'financial'
? ['', 'K', 'M', 'B', 'T', 'Q']
: ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
// TODO -- handle nubers which are larger than the max unit tick
const unitNumber = Math.floor((leftSide.length - 1) / 3);
const unit = unitTicks[unitNumber];
let leadDigits = leftSide.slice(0, precision);
if (leadDigits.length < precision) {
leadDigits = [
...leadDigits.split(''),
...Array(precision - leadDigits.length).fill('0'),
].join('');
}
const decimalLocation = leftSide.length - 3 * unitNumber;
let leadString = '';
if (decimalLocation >= precision) {
leadString = `${leadDigits}`;
}
else {
leadString = `${leadDigits.slice(0, decimalLocation)}${BigNum.delim}${leadDigits.slice(decimalLocation)}`;
}
return `${isNeg ? '-' : ''}${leadString}${unit}`;
}
toJSON() {
return {
val: this.val.toString(),
precision: this.precision.toString(),
};
}
isNeg() {
return this.lt(numericConstants_1.ZERO, true);
}
isPos() {
return !this.isNeg();
}
/**
* Get the numerical value of the BigNum. This can break if the BigNum is too large.
* @returns
*/
toNum() {
let printedValue = this.print();
// Must convert any non-US delimiters and spacers to US format before using parseFloat
if (BigNum.delim !== '.' || BigNum.spacer !== ',') {
printedValue = printedValue
.split('')
.map((char) => {
if (char === BigNum.delim)
return '.';
if (char === BigNum.spacer)
return ',';
return char;
})
.join('');
}
return parseFloat(printedValue);
}
static fromJSON(json) {
return BigNum.from(new anchor_1.BN(json.val), new anchor_1.BN(json.precision));
}
/**
* Create a BigNum instance
* @param val
* @param precision
* @returns
*/
static from(val = numericConstants_1.ZERO, precision) {
(0, assert_1.assert)(new anchor_1.BN(precision).lt(new anchor_1.BN(100)), 'Tried to create a bignum with precision higher than 10^100');
return new BigNum(val, precision);
}
/**
* Create a BigNum instance from a printed BigNum
* @param val
* @param precisionOverride
* @returns
*/
static fromPrint(val, precisionShift) {
var _a, _b;
// Handle empty number edge cases
if (!val)
return BigNum.from(numericConstants_1.ZERO, precisionShift);
if (!val.replace(BigNum.delim, '')) {
return BigNum.from(numericConstants_1.ZERO, precisionShift);
}
if (val.includes('e'))
val = (+val).toFixed((_a = precisionShift === null || precisionShift === void 0 ? void 0 : precisionShift.toNumber()) !== null && _a !== void 0 ? _a : 9); // prevent small numbers e.g. 3.1e-8, use assume max precision 9 as default
const sides = val.split(BigNum.delim);
const rightSide = sides[1];
const leftSide = sides[0].replace(/\s/g, '');
const bnInput = `${leftSide !== null && leftSide !== void 0 ? leftSide : ''}${rightSide !== null && rightSide !== void 0 ? rightSide : ''}`;
const rawBn = new anchor_1.BN(bnInput);
const rightSideLength = (_b = rightSide === null || rightSide === void 0 ? void 0 : rightSide.length) !== null && _b !== void 0 ? _b : 0;
const totalShift = precisionShift
? precisionShift.sub(new anchor_1.BN(rightSideLength))
: numericConstants_1.ZERO;
return BigNum.from(rawBn, precisionShift).shift(totalShift, true);
}
static max(a, b) {
return a.gt(b) ? a : b;
}
static min(a, b) {
return a.lt(b) ? a : b;
}
static zero(precision) {
return BigNum.from(0, precision);
}
}
exports.BigNum = BigNum;
BigNum.delim = '.';
BigNum.spacer = ',';