├── .nvmrc ├── test ├── unit │ ├── .gitkeep │ ├── percentage.test.js │ ├── api.test.js │ ├── pool-tokens.test.js │ ├── token-formatting.test.js │ └── fraction-formatting.test.js ├── fixtures │ ├── .gitkeep │ ├── tokens.fix.js │ ├── fractions.fix.js │ └── pool-tokens.fix.js ├── lib │ ├── global-setup.test.js │ └── tester.lib.js └── .eslintrc.yml ├── .vscode └── settings.json ├── .npmignore ├── .prettierrc ├── .github └── FUNDING.yml ├── .gitignore ├── SECURITY.md ├── src ├── constants.js ├── utils.js ├── index.js ├── percentage.js ├── pool-tokens.js ├── tokens.js └── fractions.js ├── LICENSE ├── .editorconfig ├── .eslintrc.yml ├── .circleci └── config.yml ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.18.1 2 | -------------------------------------------------------------------------------- /test/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .nvmrc 2 | test 3 | .vscode 4 | .github 5 | .circleci 6 | .prettierrc 7 | .eslintrc.yml 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80 5 | } 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [thanpolas] 4 | custom: ["thanpolas.eth"] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | dump.rdb 4 | wiki 5 | temp 6 | *.iml 7 | .idea 8 | coverage 9 | .DS_Store 10 | db_data 11 | junit.xml 12 | .env 13 | coverage 14 | -------------------------------------------------------------------------------- /test/lib/global-setup.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Test boot up sequence. 3 | */ 4 | 5 | module.exports = async () => { 6 | console.log('\n\nGlobal Test Setup initiates...'); 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/tokens.fix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Token fixtures. 3 | */ 4 | 5 | const fix = (module.exports = {}); 6 | 7 | fix.token18 = '2083278970151697065687'; 8 | fix.token18Small = '278970151697065687'; 9 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - plugin:jest/recommended 3 | 4 | plugins: 5 | - jest 6 | 7 | env: 8 | jest/globals: true 9 | 10 | rules: 11 | no-console: 'off' 12 | jsdoc/require-jsdoc: 'off' 13 | # Due to extended custom asserters there are many tests without 14 | # an expect statement. 15 | jest/expect-expect: 0 16 | -------------------------------------------------------------------------------- /test/lib/tester.lib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Main testing helper lib. 3 | */ 4 | 5 | const tester = (module.exports = {}); 6 | 7 | /** 8 | * Have a Cooldown period between tests. 9 | * 10 | * @param {number} seconds cooldown in seconds. 11 | * @return {function} use is beforeEach(). 12 | */ 13 | tester.cooldown = function (seconds) { 14 | return function (done) { 15 | setTimeout(done, seconds); 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | >=0.0.0 | :white_check_mark: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Please report any severe vulnerabilities directly to the author, Thanos Polychronakis at thanpolas@gmail.com 15 | -------------------------------------------------------------------------------- /test/fixtures/fractions.fix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Fraction fixtures. 3 | */ 4 | 5 | const JSBI = require('jsbi'); 6 | 7 | const fix = (module.exports = {}); 8 | 9 | fix.fractionAbove1Str = ['1000000', '21']; // 47619.047619047619 10 | fix.fractionBellow1Str = ['21', '49']; // 0.428571428571429 11 | 12 | fix.fractionAbove1Num = [1000000, 21]; // 47619.047619047619 13 | fix.fractionBellow1Num = [21, 49]; // 0.428571428571429 14 | 15 | fix.fractionAbove1BI = [JSBI.BigInt('1000000'), JSBI.BigInt('21')]; // 47619.047619047619 16 | fix.fractionBellow1BI = [JSBI.BigInt('21'), JSBI.BigInt('49')]; // 0.428571428571429 17 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Constants needed. 3 | */ 4 | 5 | const Decimal = require('decimal.js'); 6 | 7 | const consts = (module.exports = {}); 8 | 9 | /** 10 | * @enum {number} Rounding constants of Decimal package. 11 | * @see https://mikemcl.github.io/decimal.js/#modes 12 | */ 13 | consts.Rounding = { 14 | ROUND_UP: Decimal.ROUND_UP, 15 | ROUND_DOWN: Decimal.ROUND_DOWN, 16 | ROUND_CEIL: Decimal.ROUND_CEIL, 17 | ROUND_FLOOR: Decimal.ROUND_FLOOR, 18 | ROUND_HALF_UP: Decimal.ROUND_HALF_UP, 19 | ROUND_HALF_DOWN: Decimal.ROUND_HALF_DOWN, 20 | ROUND_HALF_EVEN: Decimal.ROUND_HALF_EVEN, 21 | ROUND_HALF_CEIL: Decimal.ROUND_HALF_CEIL, 22 | ROUND_HALF_FLOO: Decimal.ROUND_HALF_FLOO, 23 | }; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License (ISC) 2 | 3 | Copyright © Thanos Polychronakis and Authors, Licensed under ISC. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 14 | OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 15 | CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /test/fixtures/pool-tokens.fix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Pool Tokens fixtures. 3 | */ 4 | 5 | const JSBI = require('jsbi'); 6 | 7 | const fix = (module.exports = {}); 8 | 9 | fix.dai_weth_decimals_num = [18, 18]; 10 | fix.dai_weth_decimals_str = ['18', '18']; 11 | 12 | // 3236.0404424781496715 - Reversed: 0.00030901962375791667205 13 | fix.dai_weth_pool_str = [ 14 | '124393780771528474299654469', 15 | '38440119331842498607318', 16 | ]; 17 | 18 | // 3236.0404424781496715 - Reversed: 0.00030901962375791667205 19 | fix.dai_weth_pool_bi = [ 20 | JSBI.BigInt('124393780771528474299654469'), 21 | JSBI.BigInt('38440119331842498607318'), 22 | ]; 23 | 24 | fix.dai_usdc_decimals_str = ['18', '6']; 25 | 26 | // 1.00262163145744571940 - Reversed: 0.99738522352282104205 27 | fix.dai_usdc_pool_str = ['46970232789826458004057', '46847416130']; 28 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Utilities and helpers. 3 | */ 4 | const JSBI = require('jsbi'); 5 | 6 | const utils = (module.exports = {}); 7 | 8 | /** 9 | * Will calculate the exponent for the given decimals number. 10 | * 11 | * @param {string|number} decs The decimal number. 12 | * @return {bigint} The decimals exponent. 13 | */ 14 | utils.expDecs = (decs) => { 15 | return JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decs)); 16 | }; 17 | 18 | /** 19 | * Converts a value to JSBI, if it's already a JSBI will just return it. 20 | * 21 | * @param {string|number|JSBI} numstr The value to convert. 22 | * @return {bigint} JSBI representation of the value. 23 | */ 24 | utils.biConv = (numstr) => { 25 | let bi = numstr; 26 | if (typeof sqrtRatio !== 'bigint') { 27 | bi = JSBI.BigInt(numstr); 28 | } 29 | return bi; 30 | }; 31 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | indent_size = 2 10 | indent_style = space 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | # Matches multiple files with brace expansion notation 15 | # Set default charset 16 | [*.{js,py}] 17 | charset = utf-8 18 | 19 | # Indentation override for all JS under lib directory 20 | [lib/**.js] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | # Matches the exact files either package.json or .travis.yml 25 | [{package.json,.travis.yml}] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | # Rules for markdown documents 30 | [*.md] 31 | trim_trailing_whitespace = false 32 | indent_size = 4 33 | 34 | [*.json] 35 | indent_size = 4 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * crypto-utils 3 | * Crypto utilities for tokens and format 4 | * 5 | * https://github.com/thanpolas/crypto-utils 6 | * 7 | * Copyright © Thanos Polychronakis 8 | * LICENSE on /LICENSE file. 9 | */ 10 | 11 | /** 12 | * @fileoverview bootstrap and master exporting module. 13 | */ 14 | 15 | const { tokenToSignificant, tokenToFixed, tokenToAuto } = require('./tokens'); 16 | const { toSignificant, toFixed, toAuto } = require('./fractions'); 17 | const { poolTokensToAuto } = require('./pool-tokens'); 18 | const { percentage, percentRemainder } = require('./percentage'); 19 | const { expDecs, biConv } = require('./utils'); 20 | const { Rounding } = require('./constants'); 21 | 22 | const app = (module.exports = {}); 23 | 24 | app.tokenToSignificant = tokenToSignificant; 25 | app.tokenToFixed = tokenToFixed; 26 | app.tokenToAuto = tokenToAuto; 27 | app.poolTokensToAuto = poolTokensToAuto; 28 | app.toSignificant = toSignificant; 29 | app.toFixed = toFixed; 30 | app.toAuto = toAuto; 31 | app.expDecs = expDecs; 32 | app.biConv = biConv; 33 | app.Rounding = Rounding; 34 | app.percentage = percentage; 35 | app.percentRemainder = percentRemainder; 36 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | 3 | parserOptions: 4 | ecmaVersion: 2020 5 | 6 | extends: 7 | - airbnb-base 8 | - plugin:jsdoc/recommended 9 | - plugin:security/recommended 10 | - plugin:prettier/recommended 11 | 12 | plugins: 13 | - jsdoc 14 | - security 15 | 16 | rules: 17 | arrow-body-style: 0 18 | camelcase: 0 19 | class-methods-use-this: 0 20 | consistent-return: 0 21 | curly: ['error', 'all'] 22 | func-names: 0 23 | function-paren-newline: 0 24 | global-require: 0 25 | implicit-arrow-linebreak: 'off' 26 | jsdoc/check-examples: 'off' 27 | jsdoc/check-tag-names: 'error' 28 | jsdoc/no-undefined-types: 'off' 29 | jsdoc/require-returns-description: 'off' 30 | no-multi-assign: 0 31 | no-param-reassign: 0 32 | no-restricted-syntax: 33 | ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'] 34 | no-underscore-dangle: 0 35 | prefer-arrow-callback: 0 36 | security/detect-object-injection: 0 37 | 38 | settings: 39 | jsdoc: 40 | preferredTypes: 41 | object: Object 42 | express: Express 43 | Function: function 44 | knex: Knex 45 | tagNamePreference: 46 | constant: const 47 | file: fileoverview 48 | returns: return 49 | 50 | globals: 51 | BigInt: false 52 | -------------------------------------------------------------------------------- /src/percentage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Calculate percentages of bignumbers 3 | */ 4 | 5 | const entity = (module.exports = {}); 6 | 7 | /** 8 | * Get the percentage of the number (i.e. for 100 and 5%, return 5). 9 | * 10 | * @param {bigint} biNum The number to get the percentage of. 11 | * @param {number} basisPoints Percent expressed with a precision of 10000 12 | * (i.e. 1% = 100). 13 | * @param {number=} optPrecision The precision of the percentage, default 10000. 14 | * @return {bigint} The percentage of the number. 15 | */ 16 | entity.percentage = (biNum, basisPoints, optPrecision = 10000) => { 17 | const precision = BigInt(optPrecision); 18 | const percentageMult = biNum * BigInt(basisPoints); 19 | const percentage = percentageMult / precision; 20 | 21 | return percentage; 22 | }; 23 | 24 | /** 25 | * Get the percentage remainter of the number (i.e. for 100 and 5%, return 95). 26 | * 27 | * @param {bigint} biNum The number to get the percentage of. 28 | * @param {number} basisPoints Percent expressed with a precision of 10000 29 | * (i.e. 1% = 100). 30 | * @param {number=} optPrecision The precision of the percentage, default 10000. 31 | * @return {bigint} The percentage remainter of the number. 32 | */ 33 | entity.percentRemainder = (biNum, basisPoints, optPrecision = 10000) => { 34 | const percentage = entity.percentage(biNum, basisPoints, optPrecision); 35 | const remainter = biNum - percentage; 36 | return remainter; 37 | }; 38 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | workflows: 4 | version: 2 5 | node-multi-build: 6 | jobs: 7 | - node-v12 8 | - node-v14 9 | - node-v16 10 | 11 | jobs: 12 | node-v12: 13 | docker: 14 | - image: node:12 15 | steps: 16 | - build 17 | node-v14: 18 | docker: 19 | - image: node:14 20 | steps: 21 | - build 22 | node-v16: 23 | docker: 24 | - image: node:16 25 | steps: 26 | - build 27 | 28 | commands: 29 | build: 30 | steps: 31 | - run: 32 | name: Node Version 33 | command: npm version 34 | - checkout 35 | - restore_cache: 36 | keys: 37 | - v{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ arch }}-npm-cache-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }} 38 | - v{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ arch }}-npm-cache-master-{{ .Environment.CIRCLE_JOB }} 39 | - run: 40 | name: Install dependencies 41 | command: npm ci 42 | - run: 43 | name: Run Test 44 | command: npm run jest 45 | - run: 46 | name: Run ESLINT 47 | command: npm run eslint 48 | - save-npm-cache 49 | save-npm-lock: 50 | steps: 51 | - save_cache: 52 | key: v{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ arch }}-npm-lock-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package-lock.json" }} 53 | paths: 54 | - node_modules 55 | save-npm-cache: 56 | steps: 57 | - save_cache: 58 | key: v{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ arch }}-npm-cache-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package-lock.json" }} 59 | paths: 60 | - ~/.npm/_cacache 61 | -------------------------------------------------------------------------------- /test/unit/percentage.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Test Percentage functions. 3 | */ 4 | 5 | const { percentage, percentRemainder } = require('../..'); 6 | 7 | describe('Percentage', () => { 8 | describe(`happy path`, () => { 9 | describe('Percentage Calculations', () => { 10 | test(`100 to 5%`, () => { 11 | const percentageRes = percentage(BigInt(100), 500); 12 | expect(percentageRes).toEqual(BigInt(5)); 13 | }); 14 | test(`100 to 5% with lower precision`, () => { 15 | const percentageRes = percentage(BigInt(100), 5, 100); 16 | expect(percentageRes).toEqual(BigInt(5)); 17 | }); 18 | test(`100 to 5% with higher precision`, () => { 19 | const percentageRes = percentage(BigInt(100), 50000, 1000000); 20 | expect(percentageRes).toEqual(BigInt(5)); 21 | }); 22 | test(`100 to 0.5%`, () => { 23 | const percentageRes = percentage(BigInt(100), 50); 24 | expect(percentageRes).toEqual(BigInt(0)); 25 | }); 26 | }); 27 | describe('Percent Remainder Calculations', () => { 28 | test(`100 to 5%`, () => { 29 | const percentageRes = percentRemainder(BigInt(100), 500); 30 | expect(percentageRes).toEqual(BigInt(95)); 31 | }); 32 | test(`100 to 5% with lower precision`, () => { 33 | const percentageRes = percentRemainder(BigInt(100), 5, 100); 34 | expect(percentageRes).toEqual(BigInt(95)); 35 | }); 36 | test(`100 to 5% with higher precision`, () => { 37 | const percentageRes = percentRemainder(BigInt(100), 50000, 1000000); 38 | expect(percentageRes).toEqual(BigInt(95)); 39 | }); 40 | test(`100 to 0.5%`, () => { 41 | const percentageRes = percentRemainder(BigInt(100), 50); 42 | expect(percentageRes).toEqual(BigInt(100)); 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/pool-tokens.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Calculates rate between two liquidity pool tokens. 3 | */ 4 | 5 | const JSBI = require('jsbi'); 6 | 7 | const { expDecs, biConv } = require('./utils'); 8 | const { toAuto } = require('./fractions'); 9 | 10 | const token = (module.exports = {}); 11 | 12 | /** 13 | * Calculates rate between two liquidity pool tokens. 14 | * 15 | * @param {Array} poolFraction Array tuple with liquidity pool 16 | * quantity of Tokens. 17 | * @param {Array} decimalFraction Array tuple of decimal 18 | * places of each token in poolFraction. 19 | * @param {Object=} optOptions Calculation options. 20 | * @param {number=} optOptions.decimalPlaces How many decimals to use. 21 | * @param {boolean|Array=} optOptions.format Format the output using Intl.NumberFormat. 22 | * @param {boolean=} optOptions.reverse Set to true to reverse the ratio calculation. 23 | * @param {number=} optOptions.rounding Decimal.js rounding constant. 24 | * @return {string} the formatted result. 25 | */ 26 | token.poolTokensToAuto = (poolFraction, decimalFraction, optOptions = {}) => { 27 | const [token0Reserves, token1Reserves] = poolFraction; 28 | const [token0Decimals, token1Decimals] = decimalFraction; 29 | 30 | const scalarNumerator = expDecs(biConv(token0Decimals)); 31 | const scalarDenominator = expDecs(biConv(token1Decimals)); 32 | 33 | const adjustedForDecimalsNumerator = JSBI.BigInt( 34 | JSBI.multiply(scalarDenominator, biConv(token0Reserves)), 35 | ); 36 | const adjustedForDecimalsDenominator = JSBI.BigInt( 37 | JSBI.multiply(scalarNumerator, biConv(token1Reserves)), 38 | ); 39 | 40 | const numerator = adjustedForDecimalsNumerator; 41 | const denominator = adjustedForDecimalsDenominator; 42 | 43 | const fraction = [numerator, denominator]; 44 | 45 | return toAuto(fraction, optOptions); 46 | }; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": false, 3 | "name": "@thanpolas/crypto-utils", 4 | "version": "0.4.1", 5 | "main": "./src/", 6 | "description": "Crypto utilities for tokens and formatting", 7 | "homepage": "https://github.com/thanpolas/crypto-utils", 8 | "bugs": "https://github.com/thanpolas/crypto-utils/issues", 9 | "author": { 10 | "name": "Thanos Polychronakis", 11 | "email": "thanpolas@gmail.com" 12 | }, 13 | "contributors": [ 14 | "" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/thanpolas/crypto-utils" 19 | }, 20 | "license": "ISC", 21 | "engines": { 22 | "node": ">=12" 23 | }, 24 | "scripts": { 25 | "test": "npm run eslint && npm run jest", 26 | "eslint": "eslint src test", 27 | "jest": "TZ=utc jest --ci --coverage && codecov", 28 | "release": "release-it --ci", 29 | "release:minor": "release-it minor --ci", 30 | "release:major": "release-it major --ci" 31 | }, 32 | "jest": { 33 | "coverageDirectory": "./coverage/", 34 | "collectCoverage": true, 35 | "collectCoverageFrom": [ 36 | "./src/*.js", 37 | "./src/**/*.js" 38 | ], 39 | "coverageReporters": [ 40 | "json" 41 | ], 42 | "roots": [ 43 | "./test/unit" 44 | ], 45 | "testEnvironment": "node", 46 | "setupFilesAfterEnv": [ 47 | "jest-extended/all" 48 | ], 49 | "globalSetup": "./test/lib/global-setup.test.js", 50 | "testTimeout": 10000 51 | }, 52 | "dependencies": { 53 | "decimal.js": "^10.3.1", 54 | "invariant": "^2.2.4", 55 | "jsbi": "^4.0.0", 56 | "toformat": "^2.0.0" 57 | }, 58 | "devDependencies": { 59 | "@types/jest": "27.0.2", 60 | "codecov": "^3.8.3", 61 | "eslint": "8.0.1", 62 | "eslint-config-airbnb-base": "14.2.1", 63 | "eslint-config-prettier": "8.3.0", 64 | "eslint-plugin-import": "2.25.2", 65 | "eslint-plugin-jest": "25.2.2", 66 | "eslint-plugin-jsdoc": "36.1.1", 67 | "eslint-plugin-prettier": "4.0.0", 68 | "eslint-plugin-security": "1.4.0", 69 | "expect": "27.3.1", 70 | "jest": "27.3.1", 71 | "jest-extended": "1.1.0", 72 | "jest-junit": "13.0.0", 73 | "prettier": "2.4.1", 74 | "release-it": "14.11.6" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/tokens.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Token related utilities. 3 | */ 4 | 5 | const { expDecs } = require('./utils'); 6 | const { toSignificant, toFixed, toAuto } = require('./fractions'); 7 | 8 | const token = (module.exports = {}); 9 | 10 | /** 11 | * Will format the token quantity with significant decimals. 12 | * 13 | * @param {string|bigint} tokens Quantity of Tokens. 14 | * @param {string|number} decimals Decimal places of the token. 15 | * @param {Object=} optOptions Calculation options. 16 | * @param {number=} optOptions.decimalPlaces How many decimals to use. 17 | * @param {boolean|Array=} optOptions.format Format the output using Intl.NumberFormat. 18 | * @param {number=} optOptions.rounding Decimal.js rounding constant. 19 | * @return {string} the formatted result. 20 | */ 21 | token.tokenToSignificant = (tokens, decimals, optOptions) => { 22 | const decimalsExp = expDecs(decimals); 23 | 24 | const fraction = [tokens, decimalsExp]; 25 | return toSignificant(fraction, optOptions); 26 | }; 27 | 28 | /** 29 | * Will format the token quantity with fixed decimals. 30 | * 31 | * @param {string|bigint} tokens Quantity of Tokens. 32 | * @param {string|number} decimals Decimal places of the token. 33 | * @param {Object=} optOptions Calculation options. 34 | * @param {number=} optOptions.decimalPlaces How many decimals to use. 35 | * @param {boolean|Array=} optOptions.format Format the output using Intl.NumberFormat. 36 | * @param {number=} optOptions.rounding Decimal.js rounding constant. 37 | * @return {string} the formatted result. 38 | */ 39 | token.tokenToFixed = (tokens, decimals, optOptions) => { 40 | const decimalsExp = expDecs(decimals); 41 | 42 | const fraction = [tokens, decimalsExp]; 43 | return toFixed(fraction, optOptions); 44 | }; 45 | 46 | /** 47 | * Will format the token quantity with fixed decimals. 48 | * 49 | * @param {string|bigint} tokens Quantity of Tokens. 50 | * @param {string|number} decimals Decimal places of the token. 51 | * @param {Object=} optOptions Calculation options. 52 | * @param {number=} optOptions.decimalPlaces How many decimals to use. 53 | * @param {boolean|Array=} optOptions.format Format the output using Intl.NumberFormat. 54 | * @param {number=} optOptions.rounding Decimal.js rounding constant. 55 | * @return {string} the formatted result. 56 | */ 57 | token.tokenToAuto = (tokens, decimals, optOptions) => { 58 | const decimalsExp = expDecs(decimals); 59 | 60 | const fraction = [tokens, decimalsExp]; 61 | return toAuto(fraction, optOptions); 62 | }; 63 | -------------------------------------------------------------------------------- /test/unit/api.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview API surface tests. 3 | */ 4 | 5 | const Decimal = require('decimal.js'); 6 | 7 | const cryptoUtils = require('../..'); 8 | 9 | describe('API Surface Tests', () => { 10 | describe('Properties', () => { 11 | it('Exposes only the expected properties at root level', () => { 12 | expect(cryptoUtils).toContainAllKeys([ 13 | 'tokenToSignificant', 14 | 'tokenToFixed', 15 | 'tokenToAuto', 16 | 'poolTokensToAuto', 17 | 'toSignificant', 18 | 'toFixed', 19 | 'toAuto', 20 | 'expDecs', 21 | 'biConv', 22 | 'Rounding', 23 | 'percentage', 24 | 'percentRemainder', 25 | ]); 26 | }); 27 | }); 28 | describe('Check Types', () => { 29 | it('Default export is an object', () => { 30 | expect(cryptoUtils).toBeObject(); 31 | }); 32 | 33 | it('poolTokensToAuto is a function', () => { 34 | expect(cryptoUtils.poolTokensToAuto).toBeFunction(); 35 | }); 36 | 37 | it('tokenToSignificant is a function', () => { 38 | expect(cryptoUtils.tokenToSignificant).toBeFunction(); 39 | }); 40 | 41 | it('tokenToFixed is a function', () => { 42 | expect(cryptoUtils.tokenToFixed).toBeFunction(); 43 | }); 44 | 45 | it('tokenToAuto is a function', () => { 46 | expect(cryptoUtils.tokenToAuto).toBeFunction(); 47 | }); 48 | 49 | it('toSignificant is a function', () => { 50 | expect(cryptoUtils.toSignificant).toBeFunction(); 51 | }); 52 | 53 | it('toFixed is a function', () => { 54 | expect(cryptoUtils.toFixed).toBeFunction(); 55 | }); 56 | it('toAuto is a function', () => { 57 | expect(cryptoUtils.toAuto).toBeFunction(); 58 | }); 59 | 60 | it('expDecs is a function', () => { 61 | expect(cryptoUtils.expDecs).toBeFunction(); 62 | }); 63 | 64 | it('biConv is a function', () => { 65 | expect(cryptoUtils.biConv).toBeFunction(); 66 | }); 67 | 68 | it('Rounding is an Object', () => { 69 | expect(cryptoUtils.Rounding).toBeObject(); 70 | }); 71 | }); 72 | 73 | describe('Values', () => { 74 | it('Should have Rounding values matching the ones from Decimal.js', () => { 75 | expect(cryptoUtils.Rounding.ROUND_UP).toEqual(Decimal.ROUND_UP); 76 | expect(cryptoUtils.Rounding.ROUND_DOWN).toEqual(Decimal.ROUND_DOWN); 77 | expect(cryptoUtils.Rounding.ROUND_CEIL).toEqual(Decimal.ROUND_CEIL); 78 | expect(cryptoUtils.Rounding.ROUND_FLOOR).toEqual(Decimal.ROUND_FLOOR); 79 | expect(cryptoUtils.Rounding.ROUND_HALF_UP).toEqual(Decimal.ROUND_HALF_UP); 80 | expect(cryptoUtils.Rounding.ROUND_HALF_DOWN).toEqual( 81 | Decimal.ROUND_HALF_DOWN, 82 | ); 83 | expect(cryptoUtils.Rounding.ROUND_HALF_EVEN).toEqual( 84 | Decimal.ROUND_HALF_EVEN, 85 | ); 86 | expect(cryptoUtils.Rounding.ROUND_HALF_CEIL).toEqual( 87 | Decimal.ROUND_HALF_CEIL, 88 | ); 89 | expect(cryptoUtils.Rounding.ROUND_HALF_FLOO).toEqual( 90 | Decimal.ROUND_HALF_FLOO, 91 | ); 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /test/unit/pool-tokens.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Test Pooled tokens fraction calculation and format. 3 | */ 4 | 5 | const { poolTokensToAuto } = require('../..'); 6 | 7 | const { 8 | dai_weth_decimals_num, 9 | dai_weth_decimals_str, 10 | dai_weth_pool_str, 11 | dai_weth_pool_bi, 12 | dai_usdc_decimals_str, 13 | dai_usdc_pool_str, 14 | } = require('../fixtures/pool-tokens.fix'); 15 | 16 | describe('Pool Tokens', () => { 17 | describe(`happy path`, () => { 18 | describe('Default Calculations', () => { 19 | test(`poolTokensToAuto - above 1 - default`, () => { 20 | expect( 21 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str), 22 | ).toEqual('3236.04044'); 23 | }); 24 | test(`poolTokensToAuto - stables - default`, () => { 25 | expect( 26 | poolTokensToAuto(dai_usdc_pool_str, dai_usdc_decimals_str), 27 | ).toEqual('1.00262'); 28 | }); 29 | }); 30 | 31 | describe('Decimal Places Option', () => { 32 | test(`poolTokensToAuto - above 1 - decimals 7`, () => { 33 | const opts = { decimalPlaces: 7 }; 34 | expect( 35 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts), 36 | ).toEqual('3236.0404425'); 37 | }); 38 | test(`poolTokensToAuto - above 1 - decimals 10`, () => { 39 | const opts = { decimalPlaces: 10 }; 40 | expect( 41 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts), 42 | ).toEqual('3236.0404424781'); 43 | }); 44 | test(`poolTokensToAuto - above 1 - decimals 1`, () => { 45 | const opts = { decimalPlaces: 1 }; 46 | expect( 47 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts), 48 | ).toEqual('3236.0'); 49 | }); 50 | test(`poolTokensToAuto - above 1 - decimals 3`, () => { 51 | const opts = { decimalPlaces: 3 }; 52 | 53 | expect( 54 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts), 55 | ).toEqual('3236.040'); 56 | }); 57 | test(`poolTokensToAuto - stables - decimals 7`, () => { 58 | const opts = { decimalPlaces: 7 }; 59 | 60 | expect( 61 | poolTokensToAuto(dai_usdc_pool_str, dai_usdc_decimals_str, opts), 62 | ).toEqual('1.0026216'); 63 | }); 64 | test(`poolTokensToAuto - stables - decimals 1`, () => { 65 | const opts = { decimalPlaces: 1 }; 66 | expect( 67 | poolTokensToAuto(dai_usdc_pool_str, dai_usdc_decimals_str, opts), 68 | ).toEqual('1.0'); 69 | }); 70 | test(`poolTokensToAuto - stables - decimals 0`, () => { 71 | const opts = { decimalPlaces: 0 }; 72 | expect( 73 | poolTokensToAuto(dai_usdc_pool_str, dai_usdc_decimals_str, opts), 74 | ).toEqual('1.00262'); 75 | }); 76 | }); 77 | describe('format', () => { 78 | test(`Above1 - default format`, () => { 79 | const opts = { format: true }; 80 | expect( 81 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts), 82 | ).toEqual('3,236.04044'); 83 | }); 84 | test(`Above1 - decimals 7 - default format`, () => { 85 | const opts = { format: true, decimalPlaces: 7 }; 86 | expect( 87 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts), 88 | ).toEqual('3,236.0404425'); 89 | }); 90 | test(`Above1 - money format`, () => { 91 | const opts = { 92 | format: ['en-US', { style: 'currency', currency: 'USD' }], 93 | }; 94 | expect( 95 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts), 96 | ).toEqual('$3,236.04'); 97 | }); 98 | }); 99 | describe('Reversing', () => { 100 | test(`Above1 - Reverse true`, () => { 101 | const opts = { reverse: true }; 102 | expect( 103 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts), 104 | ).toEqual('0.00030902'); 105 | }); 106 | test(`Above1 - decimals 7 - reverse true`, () => { 107 | const opts = { decimalPlaces: 7, reverse: true }; 108 | expect( 109 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts), 110 | ).toEqual('0.0003090196'); 111 | }); 112 | test(`Stables - Decimals 7 - reverse true`, () => { 113 | const opts = { decimalPlaces: 7, reverse: true }; 114 | expect( 115 | poolTokensToAuto(dai_usdc_pool_str, dai_usdc_decimals_str, opts), 116 | ).toEqual('0.9973852'); 117 | }); 118 | }); 119 | describe('All types of input', () => { 120 | test(`poolTokensToAuto - above 1 - decimals as numbers`, () => { 121 | expect( 122 | poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_num), 123 | ).toEqual('3236.04044'); 124 | }); 125 | test(`poolTokensToAuto - above 1 - values as bigint - decimals as numbers`, () => { 126 | expect( 127 | poolTokensToAuto(dai_weth_pool_bi, dai_weth_decimals_num), 128 | ).toEqual('3236.04044'); 129 | }); 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /test/unit/token-formatting.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Test token utilities. 3 | */ 4 | 5 | const { tokenToSignificant, tokenToFixed, tokenToAuto } = require('../..'); 6 | 7 | const { token18, token18Small } = require('../fixtures/tokens.fix'); 8 | 9 | describe('Token format', () => { 10 | describe('toSignificant', () => { 11 | test('18 decimals default', () => { 12 | expect(tokenToSignificant(token18, 18)).toEqual('2083.3'); 13 | }); 14 | test('18 decimals 7 significant', () => { 15 | const opts = { decimalPlaces: 7 }; 16 | expect(tokenToSignificant(token18, 18, opts)).toEqual('2083.279'); 17 | }); 18 | test('18 decimals 10 significant', () => { 19 | const opts = { decimalPlaces: 10 }; 20 | expect(tokenToSignificant(token18, 18, opts)).toEqual('2083.27897'); 21 | }); 22 | test('Small 18 decimals default', () => { 23 | expect(tokenToSignificant(token18Small, 18)).toEqual('0.27897'); 24 | }); 25 | test('Small 18 decimals 7 significant', () => { 26 | const opts = { decimalPlaces: 7 }; 27 | expect(tokenToSignificant(token18Small, 18, opts)).toEqual('0.2789702'); 28 | }); 29 | test('Small 18 decimals 1 significant', () => { 30 | const opts = { decimalPlaces: 1 }; 31 | expect(tokenToSignificant(token18Small, 18, opts)).toEqual('0.3'); 32 | }); 33 | test('Small 18 decimals 0 significant', () => { 34 | const opts = { decimalPlaces: 0 }; 35 | expect(tokenToSignificant(token18Small, 18, opts)).toEqual('0.27897'); 36 | }); 37 | test('Small 18 decimals default significant and custom formating', () => { 38 | const opts = { format: true }; 39 | expect(tokenToSignificant(token18Small, 18, opts)).toEqual('0.27897'); 40 | }); 41 | }); 42 | describe('toFixed', () => { 43 | test('18 decimals fixed default', () => { 44 | expect(tokenToFixed(token18, 18)).toEqual('2083.27897'); 45 | }); 46 | test('18 decimals 7 fixed', () => { 47 | const opts = { decimalPlaces: 7 }; 48 | expect(tokenToFixed(token18, 18, opts)).toEqual('2083.2789702'); 49 | }); 50 | test('18 decimals 10 fixed', () => { 51 | const opts = { decimalPlaces: 10 }; 52 | expect(tokenToFixed(token18, 18, opts)).toEqual('2083.2789701517'); 53 | }); 54 | test('Small 18 decimals fixed default', () => { 55 | expect(tokenToFixed(token18Small, 18)).toEqual('0.27897'); 56 | }); 57 | test('Small 18 decimals 7 fixed', () => { 58 | const opts = { decimalPlaces: 7 }; 59 | expect(tokenToFixed(token18Small, 18, opts)).toEqual('0.2789702'); 60 | }); 61 | test('Small 18 decimals 1 fixed', () => { 62 | const opts = { decimalPlaces: 1 }; 63 | expect(tokenToFixed(token18Small, 18, opts)).toEqual('0.3'); 64 | }); 65 | test('Small 18 decimals 0 fixed', () => { 66 | const opts = { decimalPlaces: 0 }; 67 | expect(tokenToFixed(token18Small, 18, opts)).toEqual('0.27897'); 68 | }); 69 | }); 70 | describe('toAuto', () => { 71 | test('18 decimals auto default', () => { 72 | expect(tokenToAuto(token18, 18)).toEqual('2083.27897'); 73 | }); 74 | test('18 decimals 7 auto', () => { 75 | const opts = { decimalPlaces: 7 }; 76 | expect(tokenToAuto(token18, 18, opts)).toEqual('2083.2789702'); 77 | }); 78 | test('18 decimals 10 auto', () => { 79 | const opts = { decimalPlaces: 10 }; 80 | expect(tokenToAuto(token18, 18, opts)).toEqual('2083.2789701517'); 81 | }); 82 | test('18 decimals 1 auto', () => { 83 | const opts = { decimalPlaces: 1 }; 84 | expect(tokenToAuto(token18, 18, opts)).toEqual('2083.3'); 85 | }); 86 | test('18 decimals 3 auto', () => { 87 | const opts = { decimalPlaces: 3 }; 88 | expect(tokenToAuto(token18, 18, opts)).toEqual('2083.279'); 89 | }); 90 | test('Small 18 decimals auto default', () => { 91 | expect(tokenToAuto(token18Small, 18)).toEqual('0.27897'); 92 | }); 93 | test('Small 18 decimals 7 auto', () => { 94 | const opts = { decimalPlaces: 7 }; 95 | expect(tokenToAuto(token18Small, 18, opts)).toEqual('0.2789702'); 96 | }); 97 | test('Small 18 decimals 1 auto', () => { 98 | const opts = { decimalPlaces: 1 }; 99 | expect(tokenToAuto(token18Small, 18, opts)).toEqual('0.3'); 100 | }); 101 | test('Small 18 decimals 0 auto', () => { 102 | const opts = { decimalPlaces: 0 }; 103 | expect(tokenToAuto(token18Small, 18, opts)).toEqual('0.27897'); 104 | }); 105 | describe('format', () => { 106 | test('null decimals, default format', () => { 107 | const opts = { format: true }; 108 | expect(tokenToAuto(token18, 18, opts)).toEqual('2,083.27897'); 109 | }); 110 | test('5 decimals, default format', () => { 111 | const opts = { format: true, decimalPlaces: 2 }; 112 | expect(tokenToAuto(token18, 18, opts)).toEqual('2,083.28'); 113 | }); 114 | test('null decimals, money format', () => { 115 | const opts = { 116 | format: ['en-US', { style: 'currency', currency: 'USD' }], 117 | }; 118 | expect(tokenToAuto(token18, 18, opts)).toEqual('$2,083.28'); 119 | }); 120 | test('zero value', () => { 121 | expect(tokenToAuto(BigInt(0), 18)).toEqual('0'); 122 | }); 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /src/fractions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Converts Big Integers to human readable format. 3 | */ 4 | 5 | const invariant = require('invariant'); 6 | const Decimal = require('decimal.js'); 7 | 8 | const fractions = (module.exports = {}); 9 | 10 | /** 11 | * Will convert the fraction of the two tokens into a significant representation. 12 | * 13 | * @param {Array} fraction Fraction tupple array containing the numerator 14 | * and denominator. 15 | * @param {Object=} optOptions Calculation options. 16 | * @param {number=} optOptions.significantDigits How many significant digits to use. 17 | * @param {boolean|Array=} optOptions.format Format the output using Intl.NumberFormat. 18 | * @param {boolean=} optOptions.reverse Set to true to reverse the ratio calculation. 19 | * @param {number=} optOptions.rounding Decimal.js rounding constant. 20 | * @return {string} The result. 21 | */ 22 | fractions.toSignificant = (fraction, optOptions = {}) => { 23 | const { format, reverse } = optOptions; 24 | let { decimalPlaces: significantDigits, rounding } = optOptions; 25 | 26 | if (!significantDigits) { 27 | significantDigits = 5; 28 | } 29 | 30 | if (!rounding) { 31 | rounding = Decimal.ROUND_HALF_UP; 32 | } 33 | 34 | let [numerator, denominator] = fraction; 35 | 36 | if (reverse) { 37 | const tmpNumerator = numerator; 38 | numerator = denominator; 39 | denominator = tmpNumerator; 40 | } 41 | 42 | const res = new Decimal(numerator.toString()) 43 | .div(denominator.toString()) 44 | .toSignificantDigits(significantDigits, rounding) 45 | .toString(); 46 | 47 | return fractions._checkformat(res, 'significant', significantDigits, format); 48 | }; 49 | 50 | /** 51 | * Will convert the fraction of the two tokens into a fixed representation. 52 | * 53 | * @param {Array} fraction Fraction tupple Array containing the numerator 54 | * and denominator. 55 | * @param {Object=} optOptions Calculation options. 56 | * @param {number=} optOptions.decimalPlaces How many decimals to use. 57 | * @param {boolean|Array=} optOptions.format Format the output using Intl.NumberFormat. 58 | * @param {boolean=} optOptions.reverse Set to true to reverse the ratio calculation. 59 | * @param {number=} optOptions.rounding Decimal.js rounding constant. 60 | * @return {string} The result. 61 | */ 62 | fractions.toFixed = (fraction, optOptions = {}) => { 63 | const { format, reverse } = optOptions; 64 | let { decimalPlaces, rounding } = optOptions; 65 | 66 | if (!decimalPlaces) { 67 | decimalPlaces = 5; 68 | } 69 | 70 | if (!rounding) { 71 | rounding = Decimal.ROUND_HALF_UP; 72 | } 73 | 74 | let [numerator, denominator] = fraction; 75 | 76 | if (reverse) { 77 | const tmpNumerator = numerator; 78 | numerator = denominator; 79 | denominator = tmpNumerator; 80 | } 81 | 82 | const res = new Decimal(numerator.toString()) 83 | .div(denominator.toString()) 84 | .toFixed(decimalPlaces, rounding); 85 | 86 | return fractions._checkformat(res, 'fixed', decimalPlaces, format); 87 | }; 88 | 89 | /** 90 | * Will divide and format the fraction by either using toFixed or toSignificant 91 | * automatically, depending if the fracrtion division is above or bellow 1. 92 | * 93 | * @param {Array} fraction Fraction tupple Array containing the numerator 94 | * and denominator. 95 | * @param {Object=} optOptions Calculation options. 96 | * @param {number=} optOptions.decimalPlaces How many decimals to use. 97 | * @param {boolean|Array=} optOptions.format Format the output using Intl.NumberFormat. 98 | * @param {boolean=} optOptions.reverse Set to true to reverse the ratio calculation. 99 | * @param {number=} optOptions.rounding Decimal.js rounding constant. 100 | * @return {string} The result. 101 | */ 102 | fractions.toAuto = (fraction, optOptions = {}) => { 103 | const { reverse } = optOptions; 104 | 105 | let [numerator, denominator] = fraction; 106 | 107 | if (reverse) { 108 | const tmpNumerator = numerator; 109 | numerator = denominator; 110 | denominator = tmpNumerator; 111 | } 112 | 113 | const tempRes = Decimal.div( 114 | numerator.toString(), 115 | denominator.toString(), 116 | ).toNumber(); 117 | 118 | if (tempRes > 1) { 119 | return fractions.toFixed(fraction, optOptions); 120 | } 121 | 122 | return fractions.toSignificant(fraction, optOptions); 123 | }; 124 | 125 | /** 126 | * Checks and applies format if it exists. 127 | * 128 | * @param {string} res result from the calculations 129 | * @param {string} callee Invoking function. 130 | * @param {number} decimalPlaces How many decimal places to use. 131 | * @param {boolean|Array=} optFormat Format the output using Intl.NumberFormat. 132 | * @return {string} Formatted outcome. 133 | * @private 134 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat 135 | */ 136 | fractions._checkformat = (res, callee, decimalPlaces, optFormat) => { 137 | if (!optFormat) { 138 | return res; 139 | } 140 | 141 | if (optFormat === true) { 142 | const options = {}; 143 | if (callee === 'significant') { 144 | options.maximumSignificantDigits = decimalPlaces; 145 | } 146 | if (callee === 'fixed') { 147 | options.maximumFractionDigits = decimalPlaces; 148 | } 149 | return Intl.NumberFormat('en-US', options).format(res); 150 | } 151 | 152 | if (Array.isArray(optFormat)) { 153 | return Intl.NumberFormat.apply(null, optFormat).format(res); 154 | } 155 | 156 | invariant(false, 'format argument can be either a boolean or an Array'); 157 | }; 158 | -------------------------------------------------------------------------------- /test/unit/fraction-formatting.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Test fraction format. 3 | */ 4 | 5 | const { toSignificant, toFixed, toAuto, Rounding } = require('../..'); 6 | 7 | const { 8 | fractionAbove1Str, 9 | fractionBellow1Str, 10 | fractionAbove1Num, 11 | fractionBellow1Num, 12 | fractionAbove1BI, 13 | fractionBellow1BI, 14 | } = require('../fixtures/fractions.fix'); 15 | 16 | describe('Fraction format', () => { 17 | describe('toSignificant', () => { 18 | test('Above 1 fraction - significant default decimals', () => { 19 | expect(toSignificant(fractionAbove1Str)).toEqual('47619'); 20 | }); 21 | test('Above 1 fraction - 7 significant', () => { 22 | const opts = { decimalPlaces: 7 }; 23 | expect(toSignificant(fractionAbove1Str, opts)).toEqual('47619.05'); 24 | }); 25 | test('Above 1 fraction - 10 significant', () => { 26 | const opts = { decimalPlaces: 10 }; 27 | expect(toSignificant(fractionAbove1Str, opts)).toEqual('47619.04762'); 28 | }); 29 | test('Above 1 fraction - significant default decimals - default format', () => { 30 | const opts = { format: true }; 31 | expect(toSignificant(fractionAbove1Str, opts)).toEqual('47,619'); 32 | }); 33 | test('Bellow 1 fraction - significant default decimals', () => { 34 | expect(toSignificant(fractionBellow1Str)).toEqual('0.42857'); 35 | }); 36 | test('Bellow 1 fraction - 7 significant', () => { 37 | const opts = { decimalPlaces: 7 }; 38 | expect(toSignificant(fractionBellow1Str, opts)).toEqual('0.4285714'); 39 | }); 40 | test('Bellow 1 fraction - 1 significant', () => { 41 | const opts = { decimalPlaces: 1 }; 42 | expect(toSignificant(fractionBellow1Str, opts)).toEqual('0.4'); 43 | }); 44 | test('Bellow 1 fraction - 0 significant', () => { 45 | const opts = { decimalPlaces: 0 }; 46 | expect(toSignificant(fractionBellow1Str, opts)).toEqual('0.42857'); 47 | }); 48 | test('Bellow 1 fraction - default significant decimals - default formating', () => { 49 | const opts = { format: true }; 50 | expect(toSignificant(fractionBellow1Str, opts)).toEqual('0.42857'); 51 | }); 52 | test('Bellow 1 fraction - Rounding - significant', () => { 53 | const opts = { 54 | decimalPlaces: 3, 55 | rounding: Rounding.ROUND_FLOOR, 56 | }; 57 | expect(toSignificant(fractionBellow1Str, opts)).toEqual('0.428'); 58 | }); 59 | }); 60 | 61 | describe('toFixed', () => { 62 | test('Above 1 fraction - fixed default decimals', () => { 63 | expect(toFixed(fractionAbove1Str)).toEqual('47619.04762'); 64 | }); 65 | test('Above 1 fraction - fixed - default decimals - reversed', () => { 66 | expect(toFixed(fractionAbove1Str, { reverse: true })).toEqual('0.00002'); 67 | }); 68 | test('Above 1 fraction - 7 fixed', () => { 69 | const opts = { decimalPlaces: 7 }; 70 | expect(toFixed(fractionAbove1Str, opts)).toEqual('47619.0476190'); 71 | }); 72 | test('Above 1 fraction - 10 fixed', () => { 73 | const opts = { decimalPlaces: 10 }; 74 | expect(toFixed(fractionAbove1Str, opts)).toEqual('47619.0476190476'); 75 | }); 76 | test('Above 1 fraction - default decimals - default format', () => { 77 | const opts = { format: true }; 78 | expect(toFixed(fractionAbove1Str, opts)).toEqual('47,619.04762'); 79 | }); 80 | test('Above 1 fraction - default decimals - custom format', () => { 81 | const opts = { format: ['en-US'] }; 82 | expect(toFixed(fractionAbove1Str, opts)).toEqual('47,619.048'); 83 | }); 84 | test('Above 1 fraction - default decimals - bad format', () => { 85 | const opts = { format: 1 }; 86 | expect(() => toFixed(fractionAbove1Str, opts)).toThrow( 87 | 'format argument can be either a boolean or an Array', 88 | ); 89 | }); 90 | 91 | test('Above 1 fraction - default decimals - custom format - currency', () => { 92 | const opts = { 93 | format: [ 94 | 'en-US', 95 | { 96 | style: 'currency', 97 | currency: 'USD', 98 | }, 99 | ], 100 | }; 101 | expect(toFixed(fractionAbove1Str, opts)).toEqual('$47,619.05'); 102 | }); 103 | test('Above 1 fraction - default decimals - custom format - currency and decimals', () => { 104 | const opts = { 105 | format: [ 106 | 'en-US', 107 | { style: 'currency', currency: 'USD', maximumFractionDigits: 3 }, 108 | ], 109 | }; 110 | expect(toFixed(fractionAbove1Str, opts)).toEqual('$47,619.048'); 111 | }); 112 | 113 | test('Bellow 1 fraction - fixed default', () => { 114 | expect(toFixed(fractionBellow1Str)).toEqual('0.42857'); 115 | }); 116 | test('Bellow 1 fraction - 7 fixed', () => { 117 | const opts = { decimalPlaces: 7 }; 118 | expect(toFixed(fractionBellow1Str, opts)).toEqual('0.4285714'); 119 | }); 120 | test('Bellow 1 fraction - 1 fixed', () => { 121 | const opts = { decimalPlaces: 1 }; 122 | expect(toFixed(fractionBellow1Str, opts)).toEqual('0.4'); 123 | }); 124 | test('Bellow 1 fraction - 0 fixed', () => { 125 | const opts = { decimalPlaces: 0 }; 126 | expect(toFixed(fractionBellow1Str, opts)).toEqual('0.42857'); 127 | }); 128 | test('Bellow 1 fraction - Rounding - fixed', () => { 129 | const opts = { 130 | decimalPlaces: 3, 131 | rounding: Rounding.ROUND_FLOOR, 132 | }; 133 | expect(toFixed(fractionBellow1Str, opts)).toEqual('0.428'); 134 | }); 135 | }); 136 | 137 | describe('toAuto()', () => { 138 | const fixtures = { 139 | string: [fractionAbove1Str, fractionBellow1Str], 140 | number: [fractionAbove1Num, fractionBellow1Num], 141 | bigint: [fractionAbove1BI, fractionBellow1BI], 142 | }; 143 | 144 | const fixturesAr = ['string', 'number', 'bigint']; 145 | 146 | fixturesAr.forEach((fixType) => { 147 | const [above1, bellow1] = fixtures[fixType]; 148 | 149 | describe(`${fixType} Input`, () => { 150 | test(`toAuto above 1 default - ${fixType}`, () => { 151 | expect(toAuto(above1)).toEqual('47619.04762'); 152 | }); 153 | test(`toAuto above 1 decimals 7 - ${fixType}`, () => { 154 | const opts = { decimalPlaces: 7 }; 155 | expect(toAuto(above1, opts)).toEqual('47619.0476190'); 156 | }); 157 | test(`toAuto above 1 decimals 10 - ${fixType}`, () => { 158 | const opts = { decimalPlaces: 10 }; 159 | expect(toAuto(above1, opts)).toEqual('47619.0476190476'); 160 | }); 161 | test(`toAuto above 1 decimals 1 - ${fixType}`, () => { 162 | const opts = { decimalPlaces: 1 }; 163 | expect(toAuto(above1, opts)).toEqual('47619.0'); 164 | }); 165 | test(`toAuto above 1 decimals 3 - ${fixType}`, () => { 166 | const opts = { decimalPlaces: 3 }; 167 | expect(toAuto(above1, opts)).toEqual('47619.048'); 168 | }); 169 | test(`toAuto bellow 1 default - ${fixType}`, () => { 170 | expect(toAuto(bellow1)).toEqual('0.42857'); 171 | }); 172 | test(`toAuto bellow 1 decimals 7 - ${fixType}`, () => { 173 | const opts = { decimalPlaces: 7 }; 174 | expect(toAuto(bellow1, opts)).toEqual('0.4285714'); 175 | }); 176 | test(`toAuto bellow 1 decimals 1 - ${fixType}`, () => { 177 | const opts = { decimalPlaces: 1 }; 178 | expect(toAuto(bellow1, opts)).toEqual('0.4'); 179 | }); 180 | test(`toAuto bellow 1 decimals 0 - ${fixType}`, () => { 181 | const opts = { decimalPlaces: 0 }; 182 | expect(toAuto(bellow1, opts)).toEqual('0.42857'); 183 | }); 184 | test('toAuto Bellow 1 fraction - Rounding', () => { 185 | const opts = { 186 | decimalPlaces: 3, 187 | rounding: Rounding.ROUND_FLOOR, 188 | }; 189 | expect(toAuto(fractionBellow1Str, opts)).toEqual('0.428'); 190 | }); 191 | 192 | describe('format', () => { 193 | test('null decimals, default format', () => { 194 | const opts = { format: true }; 195 | expect(toAuto(above1, opts)).toEqual('47,619.04762'); 196 | }); 197 | test('5 decimals, default format', () => { 198 | const opts = { decimalPlaces: 2, format: true }; 199 | expect(toAuto(above1, opts)).toEqual('47,619.05'); 200 | }); 201 | test('null decimals, money format', () => { 202 | const opts = { 203 | format: ['en-US', { style: 'currency', currency: 'USD' }], 204 | }; 205 | expect(toAuto(above1, opts)).toEqual('$47,619.05'); 206 | }); 207 | }); 208 | }); 209 | }); 210 | }); 211 | }); 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crypto Utils 2 | 3 | > Crypto utilities for formatting tokens and fractions. 4 | 5 | [![NPM Version][npm-image]][npm-url] 6 | [![CircleCI](https://circleci.com/gh/thanpolas/crypto-utils.svg?style=svg)](https://circleci.com/gh/thanpolas/crypto-utils) 7 | [![codecov](https://codecov.io/gh/thanpolas/crypto-utils/branch/main/graph/badge.svg?token=GMSGENFPYS)](https://codecov.io/gh/thanpolas/crypto-utils) 8 | [![Discord](https://img.shields.io/discord/847075821276758096?label=discord&color=CBE9F0)](https://discord.gg/GkyEqzJWEY) 9 | [![Twitter Follow](https://img.shields.io/twitter/follow/thanpolas.svg?label=thanpolas&style=social)](https://twitter.com/thanpolas) 10 | 11 | ##
12 | 13 |
14 | 15 | > Check out the other Uniswap and crypto libraries, that depend on this library: 16 | > 17 | > 💰 [@thanpolas/univ3prices][univ3prices] for calculating Uniswap V3 Prices. 18 | >
☎️ [@thanpolas/uniswap-chain-queries][uni-queries] for fetching on-chain data for ERC20 tokens, Uniswap V2 (and clones) and Uniswap V3. 19 | 20 | ##
21 | 22 |
23 | 24 | ## Features 25 | 26 | - [Get Liquidity Pool ratios of Tokens][liquidity_pool_tokens]. 27 | - [Get crypto token values in human readable format][token_formatting]. 28 | - [Get fraction value in human readable format][fraction_formatting]. 29 | - [Get percentage and remainters of bigint numbers][percentage]. 30 | 31 | ## Install 32 | 33 | Install the module using NPM: 34 | 35 | ``` 36 | npm install @thanpolas/crypto-utils --save 37 | ``` 38 | 39 | ## Quick Start 40 | 41 | ```js 42 | const { tokenToAuto } = require('@thanpolas/crypto-utils'); 43 | 44 | const tokenQuantity = '2083278970151697065687'; 45 | const decimals = 18; 46 | 47 | const value = tokenToAuto(tokenQuantity, decimals); 48 | 49 | console.log(value); 50 | // "2083.27897" 51 | ``` 52 | 53 | # Liquidity Pool Tokens Ratio 54 | 55 | ## poolTokensToAuto(poolFraction, decimalFraction, optOptions) 56 | 57 | Calculates the ratio between the reserves of two tokens in a liquidity pool. 58 | 59 | - **poolFraction** `{Array}` The tuple fraction, an Array with two items representing the liquidity pool token reserves. 60 | - **decimalFraction** `{Array}` An Array tuple with two items representing the decimal places of token0 and token1 as defined in the "poolFraction" argument. 61 | - **optOptions** `{Object=}` An object with calculation options, [read more about available options here][options]. 62 | - **Returns** `{string}` Formatted token. 63 | 64 | ```js 65 | const { poolTokensToAuto } = require('@thanpolas/crypto-utils'); 66 | 67 | 68 | // 3236.0404424781496715 - Reversed: 0.00030901962375791667205 69 | const dai_weth_pool_str = [ 70 | '124393780771528474299654469', 71 | '38440119331842498607318', 72 | ]; 73 | 74 | const dai_weth_decimals_str = ['18', '18']; 75 | 76 | const value = poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str); 77 | console.log(value); 78 | // "3236.04044" 79 | 80 | const opts { 81 | reverse: true; 82 | } 83 | const value = poolTokensToAuto(dai_weth_pool_str, dai_weth_decimals_str, opts); 84 | console.log(value); 85 | // "0.00030902" 86 | ``` 87 | 88 | # Token Formatting 89 | 90 | ## tokenToSignificant(tokenQuantity, tokenDecimals, optOptions) 91 | 92 | Calculates the value of token quantity to significant digits, default of significant digits is 5. 93 | 94 | `tokenToSignificant()` is better suited for values that are bellow `1`. 95 | 96 | - **tokenQuantity** `{number|string|bigint}` The quantity of tokens to be formatted. 97 | - **tokenDecimals** `{number|string}` How many decimals this token has. 98 | - **optOptions** `{Object=}` An object with calculation options, [read more about available options here][options]. 99 | - **Returns** `{string}` Formatted token. 100 | 101 | ```js 102 | const { tokenToSignificant } = require('@thanpolas/crypto-utils'); 103 | 104 | const tokenQuantity = '2083278970151697065687'; 105 | const decimals = 18; 106 | 107 | const value = tokenToSignificant(tokenQuantity, decimals); 108 | console.log(value); 109 | // "2083.3" 110 | 111 | const opts = { decimalPlaces: 7 }; 112 | const value = tokenToSignificant(tokenQuantity, decimals, opts); 113 | console.log(value); 114 | // "2083.279" 115 | ``` 116 | 117 | ## tokenToFixed(tokenQuantity, tokenDecimals, optOptions) 118 | 119 | Calculates the value of token quantity with fixed decimal digits, default of decimal digits is 5. 120 | 121 | `tokenToFixed()` is better suited for values that are above `1`. 122 | 123 | - **tokenQuantity** `{number|string|bigint}` The quantity of tokens to be formatted. 124 | - **tokenDecimals** `{number|string}` How many decimals this token has. 125 | - **optOptions** `{Object=}` An object with calculation options, [read more about available options here][options]. 126 | - **Returns** `{string}` Formatted token. 127 | 128 | ```js 129 | const { tokenToFixed } = require('@thanpolas/crypto-utils'); 130 | 131 | const tokenQuantity = '2083278970151697065687'; 132 | const decimals = 18; 133 | 134 | const value = tokenToFixed(tokenQuantity, decimals); 135 | console.log(value); 136 | // "2083.27897" 137 | 138 | const opts = { decimalPlaces: 7 }; 139 | const value = tokenToFixed(tokenQuantity, decimals, opts); 140 | console.log(value); 141 | // "2083.2789702" 142 | ``` 143 | 144 | ## tokenToAuto(tokenQuantity, tokenDecimals, optOptions) 145 | 146 | Will automatically use `toFixed()` if the value is above `1` or use `toSignificant()` if the value is bellow `1`. 147 | 148 | - **tokenQuantity** `{number|string|bigint}` The quantity of tokens to be formatted. 149 | - **tokenDecimals** `{number|string}` How many decimals this token has. 150 | - **optOptions** `{Object=}` An object with calculation options, [read more about available options here][options]. 151 | - **Returns** `{string}` Formatted token. 152 | 153 | ```js 154 | const { tokenToAuto } = require('@thanpolas/crypto-utils'); 155 | 156 | const tokenQuantity = '2083278970151697065687'; 157 | const decimals = 18; 158 | 159 | const value = tokenToAuto(tokenQuantity, decimals); 160 | console.log(value); 161 | // "2083.27897" 162 | 163 | const opts = { decimalPlaces: 7 }; 164 | const value = tokenToAuto(tokenQuantity, decimals, opts; 165 | console.log(value); 166 | // "2083.2789701" 167 | 168 | // 169 | // Use a quantity that's bellow 1 170 | // 171 | const tokenSmallQuantity = '278970151697065687'; 172 | 173 | const value = tokenToAuto(tokenSmallQuantity, decimals); 174 | console.log(value); 175 | // "0.27897" 176 | 177 | const opts = { decimalPlaces: 7 }; 178 | const value = tokenToAuto(tokenSmallQuantity, decimals, opts); 179 | console.log(value); 180 | // "0.2789702" 181 | ``` 182 | 183 | --- 184 | 185 | # Fraction Formatting 186 | 187 | ## toSignificant(fraction, optOptions) 188 | 189 | Underlying function that calculates to significant digits of a fraction. 190 | 191 | - **fraction** `{Array}` The tuple fraction, an Array with two items representing the numerator and denominator. 192 | - **optOptions** `{Object=}` An object with calculation options, [read more about available options here][options]. 193 | - **Returns** `{string}` Formatted token. 194 | 195 | ```js 196 | const { toSignificant } = require('@thanpolas/crypto-utils'); 197 | 198 | const fraction = [1000000, 21]; // 47619.047619047619 199 | 200 | console.log(toSignificant(fraction)); 201 | // "47619" 202 | 203 | const opts = { decimalPlaces: 7 }; 204 | console.log(toSignificant(fraction, opts)); 205 | // "47619.05" 206 | 207 | const opts = { decimalPlaces: 7, format: true }; 208 | console.log(toSignificant(fraction, opts)); 209 | // "47,619.05" 210 | ``` 211 | 212 | ## toFixed(fraction, optOptions) 213 | 214 | Underlying function that calculates to fixed decimals of a fraction. 215 | 216 | - **fraction** `{Array}` The tuple fraction, an Array with two items representing the numerator and denominator. 217 | - **optOptions** `{Object=}` An object with calculation options, [read more about available options here][options]. 218 | - **Returns** `{string}` Formatted token. 219 | 220 | ```js 221 | const { toFixed } = require('@thanpolas/crypto-utils'); 222 | 223 | const fraction = [1000000, 21]; // 47619.047619047619 224 | 225 | console.log(toFixed(fraction)); 226 | // "47619.04762" 227 | 228 | const opts = { decimalPlaces: 7 }; 229 | console.log(toFixed(fraction, opts)); 230 | // "47619.0476190" 231 | 232 | const opts = { decimalPlaces: 7, format: true }; 233 | console.log(toFixed(fraction, opts)); 234 | // "47,619.0476190" 235 | ``` 236 | 237 | ## toAuto(fraction, optOptions) 238 | 239 | Underlying function that does automatic decimal calculation and applies appropriate function. If result is above `1` then [toFixed()][tofixed] is applied, if under `1` then [toSignificant()][tosignificant] is applied. 240 | 241 | Tuple array items can be of type `string`, `number` or `bigint`. 242 | 243 | - **fraction** `{Array}` The tuple fraction, an Array with two items representing the numerator and denominator. 244 | - **optOptions** `{Object=}` An object with calculation options, [read more about available options here][options]. 245 | - **Returns** `{string}` Formatted token. 246 | 247 | ```js 248 | const { toAuto } = require('@thanpolas/crypto-utils'); 249 | 250 | // A fraction that has a result of above 1. 251 | const fractionAbove1 = [1000000, 21]; // 47619.047619047619 252 | 253 | console.log(toAuto(fractionAbove1)); 254 | // "47619.05" 255 | 256 | const opts = { decimalPlaces: 7 }; 257 | console.log(toAuto(fractionAbove1, opts)); 258 | // "47619.0476190" 259 | 260 | const opts = { format: true }; 261 | console.log(toAuto(fractionAbove1, opts)); 262 | // "47,619.04762" 263 | 264 | // 265 | // A fraction that has a result of below 1. 266 | // 267 | const fractionBelow1 = [21, 49]; // 0.428571428571429 268 | 269 | const value = toAuto(fractionBelow1, decimals); 270 | console.log(value); 271 | // "0.42857" 272 | 273 | const opts = { decimalPlaces: 7 }; 274 | const value = toAuto(fractionBelow1, decimals, opts); 275 | console.log(value); 276 | // "0.4285714" 277 | ``` 278 | 279 | --- 280 | 281 | # Calculation and Formatting Options 282 | 283 | The following options are available on all functions: 284 | 285 | - **decimalPlaces** `{string|number}` Define how many decimal places you want the result to be. When the calculation function is "toSignificant()" then this parameter gets translated to how many significant digits should be returned. 286 | - **reverse** `{boolean}` Set to true to reverse the fraction before the calculation. 287 | - **format** `{boolean|Array}` Format the output, [see next section about formatting][formatting]. 288 | - **rounding** `{number}` Value for rounding function, default `Rounding.ROUND_HALF_UP`, [see Roudning][rounding]. 289 | 290 | ## Rounding 291 | 292 | Rounding is an enumeration of constants from [Decimal.js][decimal]: 293 | 294 | | Property | Value | Description | 295 | | ---------------- | ----- | ------------------------------------------------------------------------------- | 296 | | ROUND_UP | 0 | Rounds away from zero | 297 | | ROUND_DOWN | 1 | Rounds towards zero | 298 | | ROUND_CEIL | 2 | Rounds towards Infinity | 299 | | ROUND_FLOOR | 3 | Rounds towards -Infinity | 300 | | ROUND_HALF_UP | 4 | Rounds towards nearest neighbour. If equidistant, rounds away from zero | 301 | | ROUND_HALF_DOWN | 5 | Rounds towards nearest neighbour. If equidistant, rounds towards zero | 302 | | ROUND_HALF_EVEN | 6 | Rounds towards nearest neighbour. If equidistant, rounds towards even neighbour | 303 | | ROUND_HALF_CEIL | 7 | Rounds towards nearest neighbour. If equidistant, rounds towards Infinity | 304 | | ROUND_HALF_FLOOR | 8 | Rounds towards nearest neighbour. If equidistant, rounds towards -Infinity | 305 | 306 | ### Rounding Example 307 | 308 | ```js 309 | const { toFixed } = require('@thanpolas/crypto-utils'); 310 | 311 | const fraction = [21, 49]; // 0.428571428571429 312 | 313 | // 314 | // Default Rounding 315 | // 316 | const opts = { 317 | decimalPlaces: 3, 318 | }; 319 | toFixed(fraction, opts); 320 | // 0.429 - Default rounding is ROUND_HALF_UP 321 | 322 | // 323 | // ROUND_FLOOR Rounding 324 | // 325 | const opts = { 326 | decimalPlaces: 3, 327 | rounding: Rounding.ROUND_FLOOR, 328 | }; 329 | 330 | toFixed(fraction, opts); 331 | // 0.428 - Override rounding to ROUND_FLOOR 332 | ``` 333 | 334 | ## Formatting 335 | 336 | The Crypto Utilities package uses the [`Intl.NumberFormat` function][intl-numberformat] to format the output when desired. By default, no formatting will be applied. Let's have a look at the formatting argument one more time: 337 | 338 | - **optFormatting** `{boolean|Array=}` Number formatting, read more on [Formatting][formatting]. 339 | 340 | ### Boolean True Formatting 341 | 342 | When a boolean `true` is used, the default formatting is applied: 343 | 344 | - **Locale**: `en-US`. 345 | - **Options**: Either `maximumSignificantDigits` when toSignificant() function is used, or `maximumFractionDigits` when toFixed() function is used. The value passed to this option is the decimal parts. 346 | 347 | ```js 348 | const { toFixed } = require('@thanpolas/crypto-utils'); 349 | 350 | const fraction = [1000000, 21]; // 47619.047619047619 351 | 352 | const opts = { decimalPlaces: 7, format: true }; 353 | console.log(toFixed(fraction, opts)); 354 | // '47,619.0476190' 355 | ``` 356 | 357 | ### Array Custom Formatting 358 | 359 | When an array is used, then you can provide one or both arguments of the [`Intl.NumberFormat` function][intl-numberformat]. 360 | 361 | **Note**: Custom formatting options will **override** the decimal places argument. 362 | 363 | ```js 364 | const { toFixed } = require('@thanpolas/crypto-utils'); 365 | 366 | const fraction = [1000000, 21]; // 47619.047619047619 367 | 368 | const opts = { decimalPlaces: 7, format: ['en-US'] }; 369 | console.log(toFixed(fraction, opts)); 370 | // '47,619.048' -- decimal places (7) gets overriden by Intl.NumberFormat! 371 | 372 | const opts = { 373 | decimalPlaces: 7, 374 | format: ['en-US', { style: 'currency', currency: 'USD' }], 375 | }; 376 | console.log(toFixed(fraction, opts)); 377 | // '$47,619.05' -- decimal places (7) gets overriden by Intl.NumberFormat! 378 | 379 | const opts = { 380 | decimalPlaces: 7, 381 | format: [ 382 | 'en-US', 383 | { style: 'currency', currency: 'USD', maximumFractionDigits: 3 }, 384 | ], 385 | }; 386 | console.log(toFixed(fraction, opts)); 387 | // '$47,619.048' -- decimal places (7) gets overriden by Intl.NumberFormat! 388 | ``` 389 | 390 | --- 391 | 392 | # Percentage 393 | 394 | Percentage functions use native `BigInt` type. 395 | 396 | ## percentage(biNum, basisPoints, optPrecision) 397 | 398 | Calculates the percentage of a bigint number (i.e. for 100 and 5% returns 5). 399 | 400 | - **biNum** `{bigint}` The big integer number to calculate the percentage for. 401 | - **basisPoints** `{number|string}` Percent expressed with a precision of 10000 (i.e. 1% = 100). 402 | - **optPrecision** `{number|string=}` The precision of the percentage, default 10000. 403 | - **Returns** `{bigint}` The percentage number. 404 | 405 | ```js 406 | const { percentage } = require('@thanpolas/crypto-utils'); 407 | 408 | const biNum = BigInt(100); 409 | 410 | const percentageRes = percentage(biNum, 500); 411 | console.log(value); 412 | // 5n 413 | 414 | const percentageRes = percentage(biNum, 50); 415 | console.log(value); // Bigints have no decimals 416 | // 0n 417 | ``` 418 | 419 | ## percentRemainder(biNum, basisPoints, optPrecision) 420 | 421 | Calculates the percent remainder of a bigint number (i.e. for 100 and 5% returns 95). 422 | 423 | - **biNum** `{bigint}` The big integer number to calculate the percentage for. 424 | - **basisPoints** `{number|string}` Percent expressed with a precision of 10000 (i.e. 1% = 100). 425 | - **optPrecision** `{number|string=}` The precision of the percentage, default 10000. 426 | - **Returns** `{bigint}` The percentage number. 427 | 428 | ```js 429 | const { percentRemainder } = require('@thanpolas/crypto-utils'); 430 | 431 | const biNum = BigInt(100); 432 | 433 | const percentRemainderRes = percentRemainder(biNum, 500); 434 | console.log(value); 435 | // 95n 436 | 437 | const percentRemainderRes = percentRemainder(biNum, 50); 438 | console.log(value); // Bigints have no decimals 439 | // 100n 440 | ``` 441 | 442 | --- 443 | 444 | ## Available Utility Functions 445 | 446 | The crypto-utils exposes a few utility functions for more low-level calculations: 447 | 448 | - `expDecs(decimals)` Will return the exponent of the given decimals number. 449 | - `biConv(value)` Will safely convert any value to JSBI and not touch values that are of JSBI type. 450 | 451 | --- 452 | 453 | # Maintenance & Development 454 | 455 | ## Update Node Version 456 | 457 | When a new node version is available you need to updated it in the following: 458 | 459 | - `/package.json` 460 | - `/.nvmrc` 461 | - `/.circleci/config.yml` 462 | 463 | ## Releasing 464 | 465 | 1. Update the changelog bellow ("Release History"). 466 | 1. Ensure you are on master and your repository is clean. 467 | 1. Type: `npm run release` for patch version jump. 468 | - `npm run release:minor` for minor version jump. 469 | - `npm run release:major` for major major jump. 470 | 471 | ## Release History 472 | 473 | - **v0.4.1**, _20 Oct 2021_ 474 | - Fixed typo of `percentRemainder()` function (was "percentRemainter"), [thanks vfat][vfat]. 475 | - **v0.4.0**, _20 Oct 2021_ 476 | - Added [percentage functions][percentage]. 477 | - Updated all dependencies to latest. 478 | - **v0.3.1**, _22 Aug 2021_ 479 | - Expose the `Rounding` enumeration for [Rounding constants][rounding]. 480 | - **v0.3.0**, _21 Aug 2021_ 481 | - Implemented the new `poolTokensToAuto()` to calculate pooled tokens ratios. 482 | - **Breaking** Moved all options in an object argument. 483 | - Introduced the `reverse` option to reverse numerator and denominator in fractions before division. 484 | - Forgot to add documentation for `toAuto()`, now done. 485 | - Changed default decimal places on auto functions to `5` for both toFixed and toSignificant calls. 486 | - **v0.2.0**, _18 Aug 2021_ 487 | - **Breaking** renamed `tokenAuto` to `tokenToAuto`. 488 | - Added [formatting][formatting] argument on all methods. 489 | - Created `toAuto()` function for fractions. 490 | - More tests, especially for fractions. 491 | - More lax node engine setting (set to `>=12`). 492 | - **v0.1.1**, _17 Aug 2021_ 493 | - Fixed and tweaked README badges. 494 | - **v0.1.0**, _17 Aug 2021_ 495 | - Big Bang 496 | 497 | ### Acknowledgements & Credits 498 | 499 | This library was inspired from [Uniswap SDK core][unisdkcore]. 500 | 501 | ## License 502 | 503 | Copyright © [Thanos Polychronakis][thanpolas] and Authors, [Licensed under ISC](/LICENSE). 504 | 505 | [decimal]: https://github.com/MikeMcl/decimal.js/ 506 | [unisdkcore]: https://github.com/uniswap/uniswap-sdk-core 507 | [utils]: #available-utility-functions 508 | [token_formatting]: #token-formatting 509 | [fraction_formatting]: #fraction-formatting 510 | [formatting]: #formatting 511 | [npm-image]: https://img.shields.io/npm/v/@thanpolas/crypto-utils.svg 512 | [npm-url]: https://npmjs.org/package/@thanpolas/crypto-utils 513 | [intl-numberformat]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat 514 | [thanpolas]: https://github.com/thanpolas 515 | [tosignificant]: #tosignificantfraction-optoptions 516 | [tofixed]: #tofixedfraction-optoptions 517 | [liquidity_pool_tokens]: #liquidity-pool-tokens-ratio 518 | [options]: #calculation-and-formatting-options 519 | [rounding]: #rounding 520 | [univ3prices]: https://github.com/thanpolas/univ3prices 521 | [crypto-utils]: https://github.com/thanpolas/crypto-utils 522 | [uni-queries]: https://github.com/thanpolas/uniswap-chain-queries 523 | [percentage]: #percentage 524 | [vfat]: https://github.com/vfat0 525 | --------------------------------------------------------------------------------