UNPKG

@erfffun/utils

Version:

Energi javascript utilities for web development

232 lines (209 loc) 6.58 kB
/* eslint-disable prefer-const */ // @ts-nocheck /** * Module formats energi bignumber amounts (wei) into NRG amount, with decimal and thousands separator, internationalized. * * * <i>Copyright (c) 2020 Energi Cryptocurrency - https://energi.world</i><br> * Proprietary License * * * @module format-bn.js * @since 0.0.1 */ import { utils } from 'web3'; const { toBN, isBN } = utils; const REGEXP_REMOVE_TRAILING_ZEROS = /(\d+?)0+$/; const REGEXP_ONLY_NUMBERS = /[^\d]/g; const suffixes = ['K', 'M', 'B', 'T', 'P', 'E']; const INTL = {}; // object to cache localized Intl instances const MEMOIZATION = {}; // object to memoize formatBN function calls const TIMERS = {}; // object to track cleanup timers const MEM_TIME_MS = 120 * 1000; // time to keep memoized formatBN functions. 2 minutes: this way refresh ocuuring during changes of blockheights will benefit the cache let defaultLocale; // eslint-disable-next-line func-names (function (win) { defaultLocale = win.navigator ? win.navigator.language : 'en-US'; })(typeof global !== 'undefined' ? global : /* istanbul ignore next */ this); /** * Returns an Intl object for a specific locale. * Creates maximum 1 instance per locale: uses internal cache. * * @function getIntl * @private * @param {string} locale - the locale to generate the Intl instance for * @return {object} Intl object for the locale * @since 0.0.8 */ const getIntl = locale => { if (!INTL[locale]) { INTL[locale] = new Intl.NumberFormat(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1, }); } return INTL[locale]; }; /** * Memoizes a formattedBN and sets a timer to cleanup after 2 minutes. * If the second argument is undefined, the timer will be resetted. * * @function memoize * @private * @param {string} memoizeKey - the key * @param {string} [formattedBN] - value to be memoized * @since 0.0.8 */ const memoize = (memoizeKey, formattedBN) => { const timeoutID = TIMERS[memoizeKey]; if (timeoutID) { clearTimeout(timeoutID); } TIMERS[memoizeKey] = setTimeout(() => { clearTimeout(TIMERS[memoizeKey]); delete TIMERS[memoizeKey]; delete MEMOIZATION[memoizeKey]; }, MEM_TIME_MS); if (formattedBN) { MEMOIZATION[memoizeKey] = formattedBN; } }; /** * Configuration object for formatBN * @typedef {object} BNoptions * @property {number} decimals - force the format to have specific number of decimals * @property {string} locale="en-US" - locale to format to: will effect comma's and dots * @property {number} minDigits - force the format to have minimal amount of digits * @property {boolean} onlySignificant - will ignore insignificant zero's * @property {boolean} withSuffix - will render with a suffix like "K" instead of rendering all digits * @property {number} assetDigits=0 - definition of hof may digits an asset has. Will lead into division of the output by 10^assetDigits /** * Formats energi bignumber amounts (wei) into NRG amount, with decimal and thousands separator, internationalized. * * @function formatBN * @param {BigNumber} bigNumber - The amount to be formatted, in wei * @param {BNoptions} options - Configuration object * @return {String} The internationalized representation of the amount of NRG's * @since 0.0.2 */ const formatBN = (bigNumber, options = {}) => { let bigN = isBN(bigNumber) ? bigNumber : toBN(bigNumber); let { decimals, locale = defaultLocale, minDigits, onlySignificant, withSuffix, assetDigits = 0, } = options; let suffix = ''; let length; let padding; let tier; let multiplyFactor; let stringNumber; let stringIntl; let numberString; let stringFraction; let stringIntlFormatted; let match; let formattedString; let digitcount; const memoizeKey = bigN.toString() + JSON.stringify(options); const memoized = MEMOIZATION[memoizeKey]; if (memoized) { memoize(memoizeKey); // reset timer return memoized; } const intl = getIntl(locale); if (assetDigits < 0) { assetDigits = 0; } if (withSuffix) { length = bigN.toString().length; if (length >= assetDigits + 4) { tier = Math.ceil((length - assetDigits - 3) / 3); padding = toBN('1'.padEnd(3 * tier + 1, '0')); bigN = bigN.div(padding); suffix = suffixes[tier - 1] || suffixes[suffixes.length - 1]; } } if (decimals < 0) { decimals = 0; } if (typeof decimals !== 'number') { decimals = assetDigits; if (typeof onlySignificant !== 'boolean') { onlySignificant = true; } } else if (decimals < assetDigits) { multiplyFactor = toBN(10 ** (assetDigits - decimals)); bigN = bigN.divRound(multiplyFactor).mul(multiplyFactor); } else if (decimals > assetDigits) { decimals = assetDigits; } stringNumber = bigN.toString(); // make sure it has at least the first full digit: stringNumber = stringNumber.padStart(assetDigits + 1, '0'); // eslint-disable-next-line prefer-const stringIntl = `${stringNumber.substr( 0, stringNumber.length - assetDigits, )}.${stringNumber.substr(stringNumber.length - assetDigits, 1)}`; if (decimals !== 0) { stringFraction = stringNumber.substring( stringNumber.length - assetDigits, stringNumber.length - assetDigits + decimals, ); if (onlySignificant) { match = stringFraction.match(REGEXP_REMOVE_TRAILING_ZEROS); if (match) { // eslint-disable-next-line prefer-destructuring stringFraction = match[1]; } if (stringFraction === '0') { stringFraction = ''; } } } else { stringFraction = ''; } stringIntlFormatted = intl.format(stringIntl); stringIntlFormatted = stringIntlFormatted.substr( 0, stringIntlFormatted.length - (stringFraction === '' ? 2 : 1), ); formattedString = stringIntlFormatted + stringFraction + suffix; if (typeof minDigits === 'number' && decimals < assetDigits) { // only if decimals < assetDigits we are able to increadse the minimal digits // recursion will only take place max 1 time numberString = formattedString.replace(REGEXP_ONLY_NUMBERS, ''); digitcount = numberString.length; if (digitcount < minDigits) { formattedString = formatBN(bigNumber, { locale, decimals: decimals + minDigits - digitcount, onlySignificant: false, withSuffix, minDigits, assetDigits, }); } } memoize(memoizeKey, formattedString); return formattedString; }; /** * Cleans up the formatBN memoized cache. * * @function cleanCacheFormatBN * @since 0.0.8 */ export function cleanCacheFormatBN() { Object.keys(TIMERS).forEach(k => { clearTimeout(TIMERS[k]); delete TIMERS[k]; delete MEMOIZATION[k]; }); } export default formatBN;