Initial rebrand: @polkadot -> @pezkuwi (14 packages)

- Package namespace: @polkadot/* -> @pezkuwi/*
- Repository: polkadot-js/common -> pezkuwichain/pezkuwi-common
- Author: Pezkuwi Team <team@pezkuwichain.io>

Core packages:
- @pezkuwi/util (utilities)
- @pezkuwi/util-crypto (crypto primitives)
- @pezkuwi/keyring (account management)
- @pezkuwi/networks (chain metadata)
- @pezkuwi/hw-ledger (Ledger hardware wallet)
- @pezkuwi/x-* (10 polyfill packages)

Total: 14 packages
Upstream: polkadot-js/common v14.0.1
This commit is contained in:
2026-01-05 14:00:34 +03:00
commit ec06da0ebc
687 changed files with 48096 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
# @pezkuwi/util
Various useful utility functions that are used across all projects in the [@pezkuwi](https://pezkuwi.js.org) namespace. It provides utility functions with additional safety checks, allowing not only for consistent coding, but also reducing the general boilerplate.
## Usage
Installation -
```
yarn add @pezkuwi/util
```
Functions can be imported directly from the package, e.g.
```js
import { isHex } from '@pezkuwi/util';
```
+49
View File
@@ -0,0 +1,49 @@
{
"author": "Jaco Greeff <jacogr@gmail.com>",
"bugs": "https://github.com/pezkuwichain/pezkuwi-common/issues",
"description": "A collection of useful utilities for @pezkuwi",
"engines": {
"node": ">=18"
},
"homepage": "https://github.com/pezkuwichain/pezkuwi-common/tree/master/packages/util#readme",
"license": "Apache-2.0",
"name": "@pezkuwi/util",
"repository": {
"directory": "packages/util",
"type": "git",
"url": "https://github.com/pezkuwichain/pezkuwi-common.git"
},
"sideEffects": [
"./packageDetect.js",
"./packageDetect.cjs"
],
"type": "module",
"version": "14.0.1",
"main": "index.js",
"exports": {
"./hex/toU8a": {
"node": {
"require": "./cjs/hex/toU8aBuffer.js",
"default": "./hex/toU8aBuffer.js"
}
},
"./u8a/toHex": {
"node": {
"require": "./cjs/u8a/toHexBuffer.js",
"default": "./u8a/toHexBuffer.js"
}
}
},
"dependencies": {
"@pezkuwi/x-bigint": "14.0.1",
"@pezkuwi/x-global": "14.0.1",
"@pezkuwi/x-textdecoder": "14.0.1",
"@pezkuwi/x-textencoder": "14.0.1",
"@types/bn.js": "^5.1.6",
"bn.js": "^5.2.1",
"tslib": "^2.8.0"
},
"devDependencies": {
"@pezkuwi/x-randomvalues": "14.0.1"
}
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { arrayChunk, arrayRange } from '../index.js';
import { perf } from '../test/index.js';
describe('arrayChunk', (): void => {
it('chunks with exact', (): void => {
expect(
arrayChunk([1, 2, 3, 4, 5, 6, 7, 8], 8)
).toEqual([[1, 2, 3, 4, 5, 6, 7, 8]]);
});
it('chunks with unequal', (): void => {
expect(
arrayChunk([1, 2, 3, 4, 5, 6, 7], 3)
).toEqual([[1, 2, 3], [4, 5, 6], [7]]);
});
it('chunks with non-empty results', (): void => {
expect(
arrayChunk([[1, 2], [3, 4], [5, 6], [7, 8]], 2)
).toEqual([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]);
});
perf('arrayChunk', 200_000, [[arrayRange(500), 50]], arrayChunk);
});
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @name arrayChunk
* @summary Split T[] into T[][] based on the defind size
* @description
* Returns a set ao arrays based on the chunksize
* @example
* <BR>
*
* ```javascript
* import { arrayChunk } from '@pezkuwi/util';
*
* arrayChunk([1, 2, 3, 4, 5]); // [[1, 2], [3, 4], [5]]
* ```
*/
export function arrayChunk <T> (array: T[], chunkSize: number): T[][] {
const outputSize = Math.ceil(array.length / chunkSize);
// shortcut for the single-split case
if (outputSize === 1) {
return [array];
}
const output = Array<T[]>(outputSize);
for (let i = 0; i < outputSize; i++) {
const offset = i * chunkSize;
output[i] = array.slice(offset, offset + chunkSize);
}
return output;
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { arrayFilter } from './index.js';
describe('filterArray', (): void => {
it('filters arrays, removing undefined', (): void => {
expect(
arrayFilter([0, '', null, false, undefined, NaN])
).toEqual([0, '', null, false, NaN]);
});
it('filters arrays, removing undefined & null (allowNull = false)', (): void => {
expect(
arrayFilter([0, '', null, false, undefined, NaN], false)
).toEqual([0, '', false, NaN]);
});
});
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @name arrayFilter
* @summary Filters undefined and (optionally) null values from an array
* @description
* Returns a new array with all `undefined` values removed. Optionally, when `allowNulls = false`, it removes the `null` values as well
* @example
* <BR>
*
* ```javascript
* import { arrayFilter } from '@pezkuwi/util';
*
* arrayFilter([0, void 0, true, null, false, '']); // [0, true, null, false, '']
* arrayFilter([0, void 0, true, null, false, ''], false); // [0, true, false, '']
* ```
*/
export function arrayFilter <T = unknown> (array: readonly (T | null | undefined)[], allowNulls = true): T[] {
return array.filter((v): v is T =>
v !== undefined &&
(allowNulls || v !== null)
);
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { perf } from '../test/index.js';
import { arrayFlatten } from './index.js';
const PERF_ONE = [[1, 2, 3, 4, 5]];
const PERF_MUL = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
describe('arrayFlatten', (): void => {
it('flattens arrays', (): void => {
expect(
arrayFlatten([[0], [1, 2, 3], [4, 5], [6], [], [7, 8]])
).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8]);
});
it('flattens a single entry', (): void => {
expect(
arrayFlatten([[1, 2, 3, 4, 5]])
).toEqual([1, 2, 3, 4, 5]);
});
it('flattens an empty', (): void => {
expect(
arrayFlatten([])
).toEqual([]);
});
perf('arrayFlatten (single)', 10_000_000, [[PERF_ONE]], arrayFlatten);
perf('arrayFlatten (multi)', 700_000, [[PERF_MUL]], arrayFlatten);
});
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
// This is supposed to be a faster concat...
// https://dev.to/uilicious/javascript-array-push-is-945x-faster-than-array-concat-1oki
/**
* @name arrayFlatten
* @summary Merge T[][] into T[]
* @description
* Returns a new array with all arrays merged into one
* @example
* <BR>
*
* ```javascript
* import { arrayFlatten } from '@pezkuwi/util';
*
* arrayFlatten([[1, 2], [3, 4], [5]]); // [1, 2, 3, 4, 5]
* ```
*/
export function arrayFlatten <T> (arrays: readonly T[][]): T[] {
const num = arrays.length;
// shortcuts for the empty & single-entry case
if (num === 0) {
return [];
} else if (num === 1) {
return arrays[0];
}
// pre-allocate based on the combined size
let size = 0;
for (let i = 0; i < num; i++) {
size += arrays[i].length;
}
const output = new Array<T>(size);
let i = -1;
for (let j = 0; j < num; j++) {
const a = arrays[j];
// instead of pushing, we just set the entries
for (let e = 0, count = a.length; e < count; e++) {
output[++i] = a[e];
}
}
return output;
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Utility methods that operates on arrays
*/
export { arrayChunk } from './chunk.js';
export { arrayFilter } from './filter.js';
export { arrayFlatten } from './flatten.js';
export { arrayRange } from './range.js';
export { arrayShuffle } from './shuffle.js';
export { arrayUnzip } from './unzip.js';
export { arrayZip } from './zip.js';
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { perf } from '../test/index.js';
import { arrayRange } from './index.js';
describe('arrayRange', (): void => {
it('does not allow 0 values', (): void => {
expect(
() => arrayRange(0)
).toThrow(/Expected non-zero, positive number as a range size/);
});
it('creates a range of the specified length', (): void => {
expect(
arrayRange(10)
).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
});
it('creates a range of the specified length, with offset', (): void => {
expect(
arrayRange(7, 3)
).toEqual([3, 4, 5, 6, 7, 8, 9]);
});
perf('arrayRange (100 entries)', 1_000_000, [[100]], arrayRange);
});
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @name arrayRange
* @summary Returns a range of numbers ith the size and the specified offset
* @description
* Returns a new array of numbers with the specific size. Optionally, when `startAt`, is provided, it generates the range to start at a specific value.
* @example
* <BR>
*
* ```javascript
* import { arrayRange } from '@pezkuwi/util';
*
* arrayRange(5); // [0, 1, 2, 3, 4]
* arrayRange(3, 5); // [5, 6, 7]
* ```
*/
export function arrayRange (size: number, startAt = 0): number[] {
if (size <= 0) {
throw new Error('Expected non-zero, positive number as a range size');
}
const result = new Array<number>(size);
for (let i = 0; i < size; i++) {
result[i] = i + startAt;
}
return result;
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { stringify } from '../stringify.js';
import { perf } from '../test/index.js';
import { arrayRange, arrayShuffle } from './index.js';
const ptest = arrayRange(16284);
describe('arrayShuffle', (): void => {
it('returns an empty array as-is', (): void => {
expect(
arrayShuffle([])
).toEqual([]);
});
it('returns a single array as-is', (): void => {
expect(
arrayShuffle([100])
).toEqual([100]);
});
it('shuffles an array', (): void => {
const inp = arrayRange(100);
const out = arrayShuffle(inp);
expect(inp).toHaveLength(out.length);
expect(
inp.filter((v) => !out.includes(v))
).toEqual([]);
expect(
stringify(inp)
).not.toEqual(stringify(out));
});
perf('arrayShuffle', 1000, [[ptest]], arrayShuffle);
});
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @name arrayShuffle
* @description Shuffles the input array (unlike sort, this is not done in-place)
*/
export function arrayShuffle <T> (input: readonly T[]): T[] {
const result = input.slice();
let curr = result.length;
// noop for the single entry
if (curr === 1) {
return result;
}
while (curr !== 0) {
// ~~ is more performant than Math.floor
const rand = ~~(Math.random() * curr);
curr--;
[result[curr], result[rand]] = [result[rand], result[curr]];
}
return result;
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { arrayUnzip } from './index.js';
describe('arrayUnzip', (): void => {
it('unzips entries', (): void => {
expect(
arrayUnzip([['a', 1], ['b', 2], ['c', 3]])
).toEqual([['a', 'b', 'c'], [1, 2, 3]]);
});
});
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @name arrayUnzip
* @description Splits a single [K, V][] into [K[], V[]]
*/
export function arrayUnzip <K, V> (entries: readonly [K, V][]): [K[], V[]] {
const count = entries.length;
const keys = new Array<K>(count);
const values = new Array<V>(count);
for (let i = 0; i < count; i++) {
[keys[i], values[i]] = entries[i];
}
return [keys, values];
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { arrayZip } from './index.js';
describe('arrayZip', (): void => {
it('zips a simple one', (): void => {
expect(
arrayZip(['a', 'b', 'c'], [1, 2, 3])
).toEqual([['a', 1], ['b', 2], ['c', 3]]);
});
it('zips where values > keys', (): void => {
expect(
arrayZip(['a', 'b', 'c'], [1, 2, 3, 4])
).toEqual([['a', 1], ['b', 2], ['c', 3]]);
});
it('zips where values < keys', (): void => {
expect(
arrayZip(['a', 'b', 'c'], [1, 2])
).toEqual([['a', 1], ['b', 2], ['c', undefined]]);
});
});
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @name arrayZip
* @description Combines 2 distinct key/value arrays into a single [K, V] array
*/
export function arrayZip <K, V> (keys: readonly K[], values: readonly V[]): [K, V][] {
const count = keys.length;
const result = new Array<[K, V]>(count);
for (let i = 0; i < count; i++) {
result[i] = [keys[i], values[i]];
}
return result;
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { assert, assertReturn } from './index.js';
describe('assert', (): void => {
it('should not throw an error when test is true', (): void => {
assert(true, 'nothing should be thrown');
});
it('should throw an error when test is not true', (): void => {
expect(
() => assert(false, 'error thrown')
).toThrow(/error thrown/);
});
it('should throw an error when message: () => string', (): void => {
expect(
() => assert(false, (): string => 'message from function')
).toThrow(/message from function/);
});
});
describe('assertReturn', (): void => {
it('should not throw an error when result is true', (): void => {
expect(assertReturn(true, 'nothing should be thrown')).toEqual(true);
});
it('should not throw an error when result is false', (): void => {
expect(assertReturn(false, 'nothing should be thrown')).toEqual(false);
});
it('should throw an error when result is undefined', (): void => {
expect(
() => assertReturn(undefined, 'something thrown')
).toThrow(/something thrown/);
});
});
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { isFunction } from './is/function.js';
type MessageFn = () => string;
/**
* @name assert
* @summary Checks for a valid test, if not Error is thrown.
* @description
* Checks that `test` is a truthy value. If value is falsy (`null`, `undefined`, `false`, ...), it throws an Error with the supplied `message`. When `test` passes, `true` is returned.
* @example
* <BR>
*
* ```javascript
* const { assert } from '@pezkuwi/util';
*
* assert(true, 'True should be true'); // passes
* assert(false, 'False should not be true'); // Error thrown
* assert(false, () => 'message'); // Error with 'message'
* ```
*/
export function assert (condition: unknown, message: string | MessageFn): asserts condition {
if (!condition) {
throw new Error(
isFunction(message)
? message()
: message
);
}
}
/**
* @name assertReturn
* @description Returns when the value is not undefined/null, otherwise throws assertion error
*/
export function assertReturn <T> (value: T | undefined | null, message: string | MessageFn): T {
assert(value !== undefined && value !== null, message);
return value;
}
/**
* @name assertUnreachable
* @description An assertion helper that ensures all codepaths are followed
*/
export function assertUnreachable (x: never): never {
throw new Error(`This codepath should be unreachable. Unhandled input: ${x as unknown as string}`);
}
+112
View File
@@ -0,0 +1,112 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { BigInt } from '@pezkuwi/x-bigint';
/**
* @name _0n
* @summary BigInt constant for 0.
*/
export const _0n = /*#__PURE__*/ BigInt(0);
/**
* @name _1n
* @summary BigInt constant for 1.
*/
export const _1n = /*#__PURE__*/ BigInt(1);
/**
* @name _2n
* @summary BigInt constant for 2.
*/
export const _2n = /*#__PURE__*/ BigInt(2);
/**
* @name _3n
* @summary BigInt constant for 3.
*/
export const _3n = /*#__PURE__*/ BigInt(3);
/**
* @name _4n
* @summary BigInt constant for 4.
*/
export const _4n = /*#__PURE__*/ BigInt(4);
/**
* @name _5n
* @summary BigInt constant for 5.
*/
export const _5n = /*#__PURE__*/ BigInt(5);
/**
* @name _6n
* @summary BigInt constant for 6.
*/
export const _6n = /*#__PURE__*/ BigInt(6);
/**
* @name _7n
* @summary BigInt constant for 7.
*/
export const _7n = /*#__PURE__*/ BigInt(7);
/**
* @name _8n
* @summary BigInt constant for 8.
*/
export const _8n = /*#__PURE__*/ BigInt(8);
/**
* @name _9n
* @summary BigInt constant for 9.
*/
export const _9n = /*#__PURE__*/ BigInt(9);
/**
* @name _10n
* @summary BigInt constant for 10.
*/
export const _10n = /*#__PURE__*/ BigInt(10);
/**
* @name _100n
* @summary BigInt constant for 100.
*/
export const _100n = /*#__PURE__*/ BigInt(100);
/**
* @name _1000n
* @summary BigInt constant for 1000.
*/
export const _1000n = /*#__PURE__*/ BigInt(1_000);
/**
* @name _1Mn
* @summary BigInt constant for 1,000,000 (million).
*/
export const _1Mn = /*#__PURE__*/ BigInt(1_000_000);
/**
* @name _1Bn
* @summary BigInt constant for 1,000,000,000 (billion).
*/
export const _1Bn = /*#__PURE__*/ BigInt(1_000_000_000);
/**
* @name _1Qn
* @summary BigInt constant for 1,000,000,000,000,000,000 (quitillion).
*/
export const _1Qn = _1Bn * _1Bn;
/**
* @name _2pow53n
* @summary BigInt constant for MAX_SAFE_INTEGER
*/
export const _2pow53n = /*#__PURE__*/ BigInt(Number.MAX_SAFE_INTEGER);
/**
* @name _sqrt2pow53n
* @summary BigInt constant for Math.sqrt(MAX_SAFE_INTEGER)
*/
export const _sqrt2pow53n = /*#__PURE__*/ BigInt(94906265);
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/** @internal */
export function createCmp <T> (cmp: (a: T, b: T) => boolean): (...items: T[]) => T {
return (...items: T[]): T => {
const count = items.length;
if (count === 0) {
throw new Error('Must provide one or more arguments');
}
let result = items[0];
for (let i = 1; i < count; i++) {
if (cmp(items[i], result)) {
result = items[i];
}
}
return result;
};
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Utility methods to convert to and from `bigint` objects
*/
// all named
export { nMax, nMin } from './min.js';
export { nSqrt } from './sqrt.js';
export { nToBigInt } from './toBigInt.js';
export { nToHex } from './toHex.js';
export { nToU8a } from './toU8a.js';
// all starred
export * from './consts.js';
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { nMax } from './index.js';
describe('nMax', (): void => {
it('finds maximum (sorted)', (): void => {
expect(
nMax(1n, 2n, 3n)
).toEqual(3n);
});
it('finds maximum (unsorted)', (): void => {
expect(
nMax(2n, 3n, 1n)
).toEqual(3n);
});
it('returns a single item', (): void => {
expect(
nMax(1n)
).toEqual(1n);
});
it('fails when no items are available', (): void => {
expect(
() => nMax()
).toThrow(/Must provide one or more arguments/);
});
});
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { nMin } from './index.js';
describe('nMin', (): void => {
it('finds BN minimum', (): void => {
expect(
nMin(2n, 1n, 3n)
).toEqual(1n);
});
it('returns a single item', (): void => {
expect(
nMin(1n)
).toEqual(1n);
});
it('fails when no items are available', (): void => {
expect(
() => nMin()
).toThrow(/Must provide one or more arguments/);
});
});
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { createCmp } from './helpers.js';
/**
* @name nMax
* @summary Finds and returns the highest value in an array of bigint.
*/
export const nMax = /*#__PURE__*/ createCmp<bigint>((a, b) => a > b);
/**
* @name nMin
* @summary Finds and returns the lowest value in an array of bigint.
*/
export const nMin = /*#__PURE__*/ createCmp<bigint>((a, b) => a < b);
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN } from '../bn/index.js';
import { _sqrt2pow53n, nSqrt } from './index.js';
// eslint-disable-next-line jest/no-export
export const TESTS: [value: string | number | BN | bigint, expected: string | number][] = [
[0, 0],
[1, 1],
[4, 2],
[256 * 256, 256],
[Number.MAX_SAFE_INTEGER, 94906265],
[new BN(Number.MAX_SAFE_INTEGER).iaddn(256), 94906265],
[12345678n * 12345679n, 12345678],
// test cases below from https://github.com/Aisse-258/bigint-isqrt/blob/f5254b9750841959022461c1353437a07a08f501/test/sqrt-test.js
[
'54866395443885995655625',
'234235768925'
],
[
'82120471531550314555681345949499512621827274120673745141541602816614526075010755373654280259022317599142038423759320355177481886719814621305828811322920076213800348341464996337890625',
'9062034624274524065844376014975805577107171799890766992670739972241112960081909332275390625'
],
[
'2068290204957779940494571454815902632050207752627007664813877111611972735314294907791993008455381265730624848057015476004143868408297250386512042711005577373327434315310235583583151773358353534031397385969275475421659036494173765903706963544628565027679444680637920371210258368358706248446698622704138943704561842220533563976838506992543814273403403362954987589637283890281226038798503294069403370486523697009383229578343444337394508531937972775639593763364402389410142290440075517598007930025487176173228232153685095699851764630884360483051570513804913157899314303269924424908606245851172031453725690942288677622973423417902662711606364188470325930406288190754356367655606430407552275285889968860367977288564364154999338284288438279464648663835830140826171534470717958048887869721791862539529444820484648908879364324577951008050851801647916673254009490596379549192296404841454780884358889449367135382779230574781333008561195244620152128505091883408484094800737142399855337799331037607433934520934135669364954550499058123797911762228642423567268540444910163468496805547450495819213412636594319148417857753712526066272727725420886898680432958409892237191284583904066400736417812258327303739879128916456683685232170600928808798462776174857486589598776587550843543720784829419478239563366491666155716208452087857080883906200829769222868347286071365156305396955420561718593130305703074908891135467193627721682044757187980688062367562451981939375400543212890625',
'45478458691536369397924851088318871329614555665525810477032568598360481742967756704524034986984134271202405190906906195123751965012732122548540879928668891416468847696038707003466943811913626146981446753434347552196868717612248915137033581428310504778371173112125038060138252719589122657221314190626986951627417768579641354255445988772645883238873672997347648890137913979299598785628581961589739331483895853398160785485300758458294999124984266041998145956341386549681176624661615465976970855592467853824812945820721933491609237311130118125761849911376495970746282571994312288380577879004620824004760109618074670810797544446334767103935754106103208789391060008291027361311679876439695844637933763721093782805837690830230712890625'
]
];
describe('nSqrt', (): void => {
it('fails on < 0 roots', (): void => {
expect(
() => nSqrt(-1n)
).toThrow(/negative numbers is not supported/);
});
it('has the correct constant for sqrt(Number.MAX_SAFE_INTEGER)', (): void => {
expect(
BigInt(
~~Math.sqrt(
Number.MAX_SAFE_INTEGER
)
) === _sqrt2pow53n
).toEqual(true);
});
describe('conversion tests', (): void => {
TESTS.forEach(([value, expected], i): void => {
it(`#${i}: calcs ${expected}`, (): void => {
expect(
nSqrt(value) === BigInt(expected)
).toEqual(true);
});
});
});
});
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '../bn/index.js';
import type { ToBigInt, ToBn } from '../types.js';
import { BigInt } from '@pezkuwi/x-bigint';
import { _0n, _1n, _2pow53n, _sqrt2pow53n } from './consts.js';
import { nToBigInt } from './toBigInt.js';
/**
* @name nSqrt
* @summary Calculates the integer square root of a bigint
*/
export function nSqrt <ExtToBn extends ToBn | ToBigInt> (value: ExtToBn | BN | bigint | string | number | null): bigint {
const n = nToBigInt(value);
if (n < _0n) {
throw new Error('square root of negative numbers is not supported');
}
// https://stackoverflow.com/questions/53683995/javascript-big-integer-square-root/
// shortcut <= 2^53 - 1 to use the JS utils
if (n <= _2pow53n) {
// ~~ is more performant that Math.floor
return BigInt(~~Math.sqrt(Number(n)));
}
// Use sqrt(MAX_SAFE_INTEGER) as starting point. since we already know the
// output will be larger than this, we expect this to be a safe start
let x0 = _sqrt2pow53n;
while (true) {
const x1 = ((n / x0) + x0) >> _1n;
if (x0 === x1 || (x0 === (x1 - _1n))) {
return x0;
}
x0 = x1;
}
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN } from '../bn/index.js';
import { nToBigInt } from './index.js';
describe('nToBigInt', (): void => {
it('converts null values to 0x00', (): void => {
expect(
nToBigInt(null)
).toEqual(0n);
});
it('converts 0x values to 0x00', (): void => {
expect(
nToBigInt('0x')
).toEqual(0n);
});
it('converts BN values to bigint', (): void => {
expect(
nToBigInt(new BN(128))
).toEqual(128n);
});
it('converts BigInt values to bigint', (): void => {
expect(
nToBigInt(128821n)
).toEqual(128821n);
});
it('converts number values to bigint', (): void => {
expect(
nToBigInt(128)
).toEqual(128n);
});
it('converts string to bigint', (): void => {
expect(
nToBigInt('123')
).toEqual(123n);
});
it('converts hex to bigint', (): void => {
expect(
nToBigInt('0x0123')
).toEqual(0x123n);
});
it('converts Compact to bigint (via toBn)', (): void => {
expect(
nToBigInt({
something: 'test',
toBn: () => new BN(1234)
})
).toEqual(1234n);
});
it('converts Compact to bigint (via toBigInt)', (): void => {
expect(
nToBigInt({
something: 'test',
toBigInt: () => 1234n
})
).toEqual(1234n);
});
});
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '../bn/bn.js';
import type { ToBigInt, ToBn } from '../types.js';
import { BigInt } from '@pezkuwi/x-bigint';
import { hexToBigInt } from '../hex/toBigInt.js';
import { isBn } from '../is/bn.js';
import { isHex } from '../is/hex.js';
import { isToBigInt } from '../is/toBigInt.js';
import { isToBn } from '../is/toBn.js';
/**
* @name nToBigInt
* @summary Creates a bigInt value from a BN, bigint, string (base 10 or hex) or number input.
*/
export function nToBigInt <ExtToBn extends ToBigInt | ToBn> (value?: ExtToBn | BN | bigint | string | number | null): bigint {
return typeof value === 'bigint'
? value
: !value
? BigInt(0)
: isHex(value)
? hexToBigInt(value.toString())
: isBn(value)
? BigInt(value.toString())
: isToBigInt(value)
? value.toBigInt()
: isToBn(value)
? BigInt(value.toBn().toString())
: BigInt(value);
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { nToHex } from './index.js';
describe('nToHex', (): void => {
it('converts null values to 0x00', (): void => {
expect(
nToHex(null)
).toBe('0x00');
});
it('converts null values to 0x00000000 (with bitLength)', (): void => {
expect(
nToHex(null, { bitLength: 32 })
).toBe('0x00000000');
});
it('converts values to a prefixed hex representation', (): void => {
expect(
nToHex(128)
).toBe('0x80');
});
it('converts values to a prefixed hex representation (bitLength)', (): void => {
expect(
nToHex(128n, { bitLength: 16 })
).toBe('0x0080');
});
it('converts values to a prefixed hex representation (bitLength + le)', (): void => {
expect(
nToHex(128, { bitLength: 16, isLe: true })
).toBe('0x8000');
});
it('converts values to a prefixed hex representation (LE)', (): void => {
expect(
nToHex(128, { bitLength: 16, isLe: true })
).toBe('0x8000');
});
it('handles negative numbers', (): void => {
expect(
nToHex(-1234, { isNegative: true })
).toBe('0xfb2e');
});
it('handles negative numbers (with bitLength)', (): void => {
expect(
nToHex(-1234, { bitLength: 32, isNegative: true })
).toBe('0xfffffb2e');
});
});
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '../bn/bn.js';
import type { HexString, NumberOptions, ToBigInt, ToBn } from '../types.js';
import { u8aToHex } from '../u8a/index.js';
import { nToU8a } from './toU8a.js';
/**
* @name nToHex
* @summary Creates a hex value from a bigint object.
*/
export function nToHex <ExtToBn extends ToBn | ToBigInt> (value?: ExtToBn | BN | bigint | number | null, { bitLength = -1, isLe = false, isNegative = false }: NumberOptions = {}): HexString {
return u8aToHex(nToU8a(value || 0, { bitLength, isLe, isNegative }));
}
+99
View File
@@ -0,0 +1,99 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { arrayRange } from '../array/index.js';
import { perf } from '../test/index.js';
import { nToU8a } from './index.js';
// eslint-disable-next-line jest/no-export
export const TESTS: [isLe: boolean, isNegative: boolean, numarr: number[], strval: string][] = [
// LE, positive numbers
[true, false, [0x12], '18'],
[true, false, [0x12, 0x34], '13330'],
[true, false, [0x12, 0x34, 0x56], '5649426'],
[true, false, [0x12, 0x34, 0x56, 0x78], '2018915346'],
[true, false, [0x12, 0x34, 0x56, 0x78, 0x9a], '663443878930'],
[true, false, [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc], '207371629900818'],
[true, false, [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78], '159954953172672629770948536149615195154'],
// LE, positive numbers (w/ signed flag)
[true, true, [12], '12'],
[true, true, [210, 4], '1234'],
[true, true, [64, 226, 1], '123456'],
[true, true, [21, 205, 91, 7], '123456789'],
[true, true, [203, 36, 104, 12, 8], '34567890123'],
[true, true, [255, 159, 114, 78, 24, 9], '9999999999999'],
// LE, negative numbers
[true, true, [244], '-12'],
[true, true, [46, 251], '-1234'],
[true, true, [192, 29, 254], '-123456'],
[true, true, [255, 255, 255, 255], '-1'],
[true, true, [254, 255, 255, 255], '-2'],
[true, true, [235, 50, 164, 248], '-123456789'],
[true, true, [0, 0, 0, 128], '-2147483648'],
[true, true, [0, 0, 0, 240], '-268435456'],
[true, true, [65, 86, 129, 173, 254], '-5678999999'],
[true, true, [1, 96, 141, 177, 231, 246], '-9999999999999'],
[true, true, [1, 0, 156, 88, 76, 73, 31, 242], '-999999999999999999'],
// BE
[false, false, [0x12], '18'],
[false, false, [0x12, 0x34], '4660'],
[false, false, [0x12, 0x34, 0x56], '1193046'],
[false, false, [0x12, 0x34, 0x56, 0x78], '305419896'],
[false, true, [0xf2, 0x34, 0x56, 0x78], '-231451016'],
[false, false, [0x12, 0x34, 0x56, 0x78, 0x9a], '78187493530'],
[false, false, [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc], '20015998343868'],
[false, false, [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78], '24197857161011715162171839636988778104']
];
const ptest = arrayRange(65536).map((v) => [v]);
describe('nToU8a', (): void => {
describe('conversion tests', (): void => {
TESTS.forEach(([isLe, isNegative, numarr, strval], i): void => {
const bitLength = numarr.length * 8;
it(`#${i}: converts from ${strval} (bitLength=${bitLength}, isLe=${isLe}, isNegative=${isNegative})`, (): void => {
expect(
nToU8a(
BigInt(strval),
{ bitLength, isLe, isNegative }
)
).toEqual(new Uint8Array(numarr));
});
});
});
it('converts null values to 0x00', (): void => {
expect(
nToU8a(null)
).toEqual(new Uint8Array(1));
});
it('converts null values to 0x00000000 (bitLength)', (): void => {
expect(
nToU8a(null, { bitLength: 32 })
).toEqual(new Uint8Array([0, 0, 0, 0]));
});
it('converts values to a prefixed hex representation', (): void => {
expect(
nToU8a(0x123456n, { isLe: false })
).toEqual(new Uint8Array([0x12, 0x34, 0x56]));
});
it('converts values to a prefixed hex representation (bitLength)', (): void => {
expect(
nToU8a(0x123456n, { bitLength: 32, isLe: false })
).toEqual(new Uint8Array([0x00, 0x12, 0x34, 0x56]));
});
it('converts using little endian (as set)', (): void => {
expect(
nToU8a(0x123456n, { bitLength: 32, isLe: true })
).toEqual(new Uint8Array([0x56, 0x34, 0x12, 0x00]));
});
perf('nToU8a', 250000, ptest, nToU8a);
});
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '../bn/bn.js';
import type { NumberOptions, ToBigInt, ToBn } from '../types.js';
import { BigInt } from '@pezkuwi/x-bigint';
import { _0n, _1n } from './consts.js';
import { nToBigInt } from './toBigInt.js';
const DIV = BigInt(256);
const NEG_MASK = BigInt(0xff);
function toU8a (value: bigint, isLe: boolean, isNegative: boolean): Uint8Array {
const arr: number[] = [];
const withSigned = isNegative && (value < _0n);
if (withSigned) {
value = (value + _1n) * -_1n;
}
while (value !== _0n) {
const mod = value % DIV;
const val = Number(
withSigned
? mod ^ NEG_MASK
: mod
);
if (isLe) {
arr.push(val);
} else {
arr.unshift(val);
}
value = (value - mod) / DIV;
}
return Uint8Array.from(arr);
}
/**
* @name nToU8a
* @summary Creates a Uint8Array object from a bigint.
*/
export function nToU8a <ExtToBn extends ToBn | ToBigInt> (value?: ExtToBn | BN | bigint | number | null, { bitLength = -1, isLe = true, isNegative = false }: NumberOptions = {}): Uint8Array {
const valueBi = nToBigInt(value);
if (valueBi === _0n) {
return bitLength === -1
? new Uint8Array(1)
: new Uint8Array(Math.ceil((bitLength || 0) / 8));
}
const u8a = toU8a(valueBi, isLe, isNegative);
if (bitLength === -1) {
return u8a;
}
const byteLength = Math.ceil((bitLength || 0) / 8);
const output = new Uint8Array(byteLength);
if (isNegative) {
output.fill(0xff);
}
output.set(u8a, isLe ? 0 : byteLength - u8a.length);
return output;
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import BN from 'bn.js';
export { BN };
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN, BN_HUNDRED, BN_ONE, BN_TEN, BN_THOUSAND, BN_ZERO } from './index.js';
describe('consts', (): void => {
it('BN_ZERO equals 0', (): void => {
expect(
BN_ZERO
).toEqual(new BN(0));
});
it('BN_ONE equals 1', (): void => {
expect(
BN_ONE
).toEqual(new BN(1));
});
it('BN_TEN equals 10', (): void => {
expect(
BN_TEN
).toEqual(new BN(10));
});
it('BN_HUNDRED equals 100', (): void => {
expect(
BN_HUNDRED
).toEqual(new BN(100));
});
it('BN_THOUSAND equals 1000', (): void => {
expect(
BN_THOUSAND
).toEqual(new BN(1000));
});
});
+112
View File
@@ -0,0 +1,112 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { BN } from './bn.js';
/**
* @name BN_ZERO
* @summary BN constant for 0.
*/
export const BN_ZERO: BN = /*#__PURE__*/ new BN(0);
/**
* @name BN_ONE
* @summary BN constant for 1.
*/
export const BN_ONE: BN = /*#__PURE__*/ new BN(1);
/**
* @name BN_TWO
* @summary BN constant for 2.
*/
export const BN_TWO: BN = /*#__PURE__*/ new BN(2);
/**
* @name BN_THREE
* @summary BN constant for 3.
*/
export const BN_THREE: BN = /*#__PURE__*/ new BN(3);
/**
* @name BN_FOUR
* @summary BN constant for 4.
*/
export const BN_FOUR: BN = /*#__PURE__*/ new BN(4);
/**
* @name BN_FIVE
* @summary BN constant for 5.
*/
export const BN_FIVE: BN = /*#__PURE__*/ new BN(5);
/**
* @name BN_SIX
* @summary BN constant for 6.
*/
export const BN_SIX: BN = /*#__PURE__*/ new BN(6);
/**
* @name BN_SEVEN
* @summary BN constant for 7.
*/
export const BN_SEVEN: BN = /*#__PURE__*/ new BN(7);
/**
* @name BN_EIGHT
* @summary BN constant for 8.
*/
export const BN_EIGHT: BN = /*#__PURE__*/ new BN(8);
/**
* @name BN_NINE
* @summary BN constant for 9.
*/
export const BN_NINE: BN = /*#__PURE__*/ new BN(9);
/**
* @name BN_TEN
* @summary BN constant for 10.
*/
export const BN_TEN: BN = /*#__PURE__*/ new BN(10);
/**
* @name BN_HUNDRED
* @summary BN constant for 100.
*/
export const BN_HUNDRED: BN = /*#__PURE__*/ new BN(100);
/**
* @name BN_THOUSAND
* @summary BN constant for 1,000.
*/
export const BN_THOUSAND: BN = /*#__PURE__*/ new BN(1_000);
/**
* @name BN_MILLION
* @summary BN constant for 1,000,000.
*/
export const BN_MILLION: BN = /*#__PURE__*/ new BN(1_000_000);
/**
* @name BN_BILLION
* @summary BN constant for 1,000,000,000.
*/
export const BN_BILLION: BN = /*#__PURE__*/ new BN(1_000_000_000);
/**
* @name BN_QUINTILL
* @summary BN constant for 1,000,000,000,000,000,000.
*/
export const BN_QUINTILL: BN = BN_BILLION.mul(BN_BILLION);
/**
* @name BN_MAX_INTEGER
* @summary BN constant for MAX_SAFE_INTEGER
*/
export const BN_MAX_INTEGER: BN = /*#__PURE__*/ new BN(Number.MAX_SAFE_INTEGER);
/**
* @name BN_SQRT_MAX_INTEGER
* @summary BN constant for Math.sqrt(MAX_SAFE_INTEGER)
*/
export const BN_SQRT_MAX_INTEGER: BN = /*#__PURE__*/ new BN(94906265);
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { isFunction } from '../is/function.js';
import { bnFromHex } from './index.js';
describe('bnFromHex', (): void => {
it('exists as a function', (): void => {
expect(
isFunction(bnFromHex)
).toEqual(true);
});
});
+4
View File
@@ -0,0 +1,4 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { hexToBn as bnFromHex } from '../hex/toBn.js';
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Utility methods to convert to and from `BN` objects
*/
// all named
export { BN } from './bn.js';
export { bnFromHex } from './fromHex.js';
export { bnMax, bnMin } from './min.js';
export { bnSqrt } from './sqrt.js';
export { bnToBn } from './toBn.js';
export { bnToHex } from './toHex.js';
export { bnToU8a } from './toU8a.js';
// all starred
export * from './consts.js';
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN, bnMax } from './index.js';
describe('bnMax', (): void => {
it('finds BN maximum (sorted)', (): void => {
expect(
bnMax(new BN(1), new BN(2), new BN(3))
).toEqual(new BN(3));
});
it('finds BN maximum (unsorted)', (): void => {
expect(
bnMax(new BN(2), new BN(3), new BN(1))
).toEqual(new BN(3));
});
it('returns a single item', (): void => {
expect(
bnMax(new BN(1))
).toEqual(new BN(1));
});
it('fails when no items are available', (): void => {
expect(
() => bnMax()
).toThrow(/Must provide one or more arguments/);
});
});
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN, bnMin } from './index.js';
describe('bnMin', (): void => {
it('finds BN minimum', (): void => {
expect(
bnMin(new BN(2), new BN(1), new BN(3))
).toEqual(new BN(1));
});
it('returns a single item', (): void => {
expect(
bnMin(new BN(1))
).toEqual(new BN(1));
});
it('fails when no items are available', (): void => {
expect(
() => bnMin()
).toThrow(/Must provide one or more arguments/);
});
});
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from './bn.js';
import { createCmp } from '../bi/helpers.js';
/**
* @name bnMax
* @summary Finds and returns the highest value in an array of BNs.
* @example
* <BR>
*
* ```javascript
* import BN from 'bn.js';
* import { bnMax } from '@pezkuwi/util';
*
* bnMax([new BN(1), new BN(3), new BN(2)]).toString(); // => '3'
* ```
*/
export const bnMax = /*#__PURE__*/ createCmp<BN>((a, b) => a.gt(b));
/**
* @name bnMin
* @summary Finds and returns the smallest value in an array of BNs.
* @example
* <BR>
*
* ```javascript
* import BN from 'bn.js';
* import { bnMin } from '@pezkuwi/util';
*
* bnMin([new BN(1), new BN(3), new BN(2)]).toString(); // => '1'
* ```
*/
export const bnMin = /*#__PURE__*/ createCmp<BN>((a, b) => a.lt(b));
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { TESTS } from '../bi/sqrt.spec.js';
import { BN, BN_SQRT_MAX_INTEGER, bnSqrt } from './index.js';
describe('bnSqrt', (): void => {
it('fails on < 0 roots', (): void => {
expect(
() => bnSqrt(new BN(-1))
).toThrow(/negative numbers is not supported/);
});
it('has the correct constant for sqrt(Number.MAX_SAFE_INTEGER)', (): void => {
expect(
BN_SQRT_MAX_INTEGER.eq(
new BN(
~~Math.sqrt(
Number.MAX_SAFE_INTEGER
)
)
)
).toEqual(true);
});
describe('conversion tests', (): void => {
TESTS.forEach(([value, expected], i): void => {
it(`#${i}: calcs ${expected}`, (): void => {
expect(
bnSqrt(value).eq(new BN(expected))
).toEqual(true);
});
});
});
});
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ToBn } from '../types.js';
import { BN } from './bn.js';
import { BN_MAX_INTEGER, BN_ONE, BN_SQRT_MAX_INTEGER } from './consts.js';
import { bnToBn } from './toBn.js';
/**
* @name bnSqrt
* @summary Calculates the integer square root of a BN
* @example
* <BR>
*
* ```javascript
* import BN from 'bn.js';
* import { bnSqrt } from '@pezkuwi/util';
*
* bnSqrt(new BN(16)).toString(); // => '4'
* ```
*/
export function bnSqrt <ExtToBn extends ToBn> (value: ExtToBn | BN | bigint | string | number | null): BN {
const n = bnToBn(value);
if (n.isNeg()) {
throw new Error('square root of negative numbers is not supported');
}
// https://stackoverflow.com/questions/53683995/javascript-big-integer-square-root/
// shortcut <= 2^53 - 1 to use the JS utils
if (n.lte(BN_MAX_INTEGER)) {
// ~~ More performant version of Math.floor
return new BN(~~Math.sqrt(n.toNumber()));
}
// Use sqrt(MAX_SAFE_INTEGER) as starting point. since we already know the
// output will be larger than this, we expect this to be a safe start
let x0 = BN_SQRT_MAX_INTEGER.clone();
while (true) {
const x1 = n.div(x0).iadd(x0).ishrn(1);
if (x0.eq(x1) || x0.eq(x1.sub(BN_ONE))) {
return x0;
}
x0 = x1;
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN, bnToBn } from './index.js';
describe('bnToBn', (): void => {
it('converts null values to 0x00', (): void => {
expect(
bnToBn(null).toNumber()
).toEqual(0);
});
it('converts BN values to BN', (): void => {
expect(
bnToBn(new BN(128)).toNumber()
).toEqual(128);
});
it('converts BigInt values to BN', (): void => {
expect(
bnToBn(128821n).toNumber()
).toEqual(128821);
});
it('converts number values to BN', (): void => {
expect(
bnToBn(128).toNumber()
).toEqual(128);
});
it('converts string to BN', (): void => {
expect(
bnToBn('123').toNumber()
).toEqual(123);
});
it('converts hex to BN', (): void => {
expect(
bnToBn('0x0123').toNumber()
).toEqual(0x123);
});
it('converts Compact to BN', (): void => {
expect(
bnToBn({
something: 'test',
toBn: (): BN => new BN(1234)
}).toNumber()
).toEqual(1234);
});
});
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ToBigInt, ToBn } from '../types.js';
import { hexToBn } from '../hex/toBn.js';
import { isBigInt } from '../is/bigInt.js';
import { isHex } from '../is/hex.js';
import { isToBigInt } from '../is/toBigInt.js';
import { isToBn } from '../is/toBn.js';
import { BN } from './bn.js';
/**
* @name bnToBn
* @summary Creates a BN value from a BN, bigint, string (base 10 or hex) or number input.
* @description
* `null` inputs returns a `0x0` result, BN values returns the value, numbers returns a BN representation.
* @example
* <BR>
*
* ```javascript
* import BN from 'bn.js';
* import { bnToBn } from '@pezkuwi/util';
*
* bnToBn(0x1234); // => BN(0x1234)
* bnToBn(new BN(0x1234)); // => BN(0x1234)
* ```
*/
export function bnToBn <ExtToBn extends ToBigInt | ToBn> (value?: ExtToBn | BN | bigint | string | number | null): BN {
return value
? BN.isBN(value)
? value
: isHex(value)
? hexToBn(value.toString())
: isBigInt(value)
? new BN(value.toString())
: isToBn(value)
? value.toBn()
: isToBigInt(value)
? new BN(value.toBigInt().toString())
: new BN(value)
: new BN(0);
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN, bnToHex } from './index.js';
describe('bnToHex', (): void => {
it('converts null values to 0x00', (): void => {
expect(
bnToHex(null)
).toBe('0x00');
});
it('converts null values to 0x00000000 (with bitLength)', (): void => {
expect(
bnToHex(null, { bitLength: 32 })
).toBe('0x00000000');
});
it('converts BN values to a prefixed hex representation', (): void => {
expect(
bnToHex(new BN(128))
).toBe('0x80');
});
it('converts BN values to a prefixed hex representation (bitLength)', (): void => {
expect(
bnToHex(new BN(128), { bitLength: 16 })
).toBe('0x0080');
});
it('converts BN values to a prefixed hex representation (LE)', (): void => {
expect(
bnToHex(new BN(128), { bitLength: 16, isLe: true })
).toBe('0x8000');
});
it('handles negative numbers', (): void => {
expect(
bnToHex(new BN(-1234), { isNegative: true })
).toBe('0xfb2e');
});
it('handles negative numbers (with bitLength)', (): void => {
expect(
bnToHex(new BN(-1234), { bitLength: 32, isNegative: true })
).toBe('0xfffffb2e');
});
});
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString, NumberOptions, ToBn } from '../types.js';
import type { BN } from './bn.js';
import { u8aToHex } from '../u8a/index.js';
import { bnToU8a } from './toU8a.js';
/**
* @name bnToHex
* @summary Creates a hex value from a BN.js bignumber object.
* @description
* `null` inputs returns a `0x` result, BN values return the actual value as a `0x` prefixed hex value. Anything that is not a BN object throws an error. With `bitLength` set, it fixes the number to the specified length.
* @example
* <BR>
*
* ```javascript
* import BN from 'bn.js';
* import { bnToHex } from '@pezkuwi/util';
*
* bnToHex(new BN(0x123456)); // => '0x123456'
* ```
*/
export function bnToHex <ExtToBn extends ToBn> (value?: ExtToBn | BN | bigint | number | null, { bitLength = -1, isLe = false, isNegative = false }: NumberOptions = {}): HexString {
return u8aToHex(bnToU8a(value, { bitLength, isLe, isNegative }));
}
+60
View File
@@ -0,0 +1,60 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { arrayRange } from '../array/index.js';
import { TESTS } from '../bi/toU8a.spec.js';
import { perf } from '../test/index.js';
import { BN, bnToU8a } from './index.js';
const ptest = arrayRange(65536).map((v) => [v]);
describe('bnToU8a', (): void => {
it('converts null values to 0x00', (): void => {
expect(
bnToU8a(null)
).toEqual(new Uint8Array(1));
});
it('converts null values to 0x00000000 (bitLength)', (): void => {
expect(
bnToU8a(null, { bitLength: 32 })
).toEqual(new Uint8Array([0, 0, 0, 0]));
});
it('converts BN values to a prefixed hex representation', (): void => {
expect(
bnToU8a(new BN(0x123456), { isLe: false })
).toEqual(new Uint8Array([0x12, 0x34, 0x56]));
});
it('converts BN values to a prefixed hex representation (bitLength)', (): void => {
expect(
bnToU8a(new BN(0x123456), { bitLength: 32, isLe: false })
).toEqual(new Uint8Array([0x00, 0x12, 0x34, 0x56]));
});
it('converts using little endian (as set)', (): void => {
expect(
bnToU8a(new BN(0x123456), { bitLength: 32, isLe: true })
).toEqual(new Uint8Array([0x56, 0x34, 0x12, 0x00]));
});
describe('conversion tests', (): void => {
TESTS.forEach(([isLe, isNegative, numarr, strval], i): void => {
const bitLength = numarr.length * 8;
it(`#${i}: converts from ${strval} (bitLength=${bitLength}, isLe=${isLe}, isNegative=${isNegative})`, (): void => {
expect(
bnToU8a(
new BN(strval),
{ bitLength, isLe, isNegative }
)
).toEqual(new Uint8Array(numarr));
});
});
});
perf('bnToU8a', 250000, ptest, bnToU8a);
});
+45
View File
@@ -0,0 +1,45 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { NumberOptions, ToBn } from '../types.js';
import type { BN } from './bn.js';
import { bnToBn } from './toBn.js';
const DEFAULT_OPTS: NumberOptions = { bitLength: -1, isLe: true, isNegative: false };
/**
* @name bnToU8a
* @summary Creates a Uint8Array object from a BN.
* @description
* `null`/`undefined`/`NaN` inputs returns an empty `Uint8Array` result. `BN` input values return the actual bytes value converted to a `Uint8Array`. Optionally convert using little-endian format if `isLE` is set.
* @example
* <BR>
*
* ```javascript
* import { bnToU8a } from '@pezkuwi/util';
*
* bnToU8a(new BN(0x1234)); // => [0x12, 0x34]
* ```
*/
export function bnToU8a <ExtToBn extends ToBn> (value?: ExtToBn | BN | bigint | number | null, { bitLength = -1, isLe = true, isNegative = false } = DEFAULT_OPTS): Uint8Array {
const valueBn = bnToBn(value);
const byteLength = bitLength === -1
? Math.ceil(valueBn.bitLength() / 8)
: Math.ceil((bitLength || 0) / 8);
if (!value) {
return bitLength === -1
? new Uint8Array(1)
: new Uint8Array(byteLength);
}
const output = new Uint8Array(byteLength);
const bn = isNegative
? valueBn.toTwos(byteLength * 8)
: valueBn;
output.set(bn.toArray(isLe ? 'le' : 'be', byteLength), 0);
return output;
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Utility methods to convert to and from `Buffer` objects
*/
export { bufferToU8a } from './toU8a.js';
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { bufferToU8a } from './index.js';
describe('bufferToU8a', (): void => {
it('returns an empty buffer when null provided', (): void => {
expect(
bufferToU8a(null)
).toEqual(new Uint8Array());
});
it('returns a Uint8Buffer with the correct values', (): void => {
expect(
bufferToU8a(Buffer.from([128, 0, 10]))
).toEqual(new Uint8Array([128, 0, 10]));
});
});
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @name bufferToU8a
* @summary Creates a Uint8Array value from a Buffer object.
* @description
* `null` inputs returns an empty result, `Buffer` values return the actual value as a `Uint8Array`. Anything that is not a `Buffer` object throws an error.
* @example
* <BR>
*
* ```javascript
* import { bufferToU8a } from '@pezkuwi/util';
*
* bufferToU8a(Buffer.from([1, 2, 3]));
* ```
*/
export function bufferToU8a (buffer?: Uint8Array | number[] | null): Uint8Array {
return new Uint8Array(buffer || []);
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Utility methods for this package are split into groups
*/
// all named
export { packageInfo } from './packageInfo.js';
// all starred
export * from './array/index.js';
export * from './assert.js';
export * from './bi/index.js';
export * from './bn/index.js';
export * from './buffer/index.js';
export * from './compact/index.js';
export * from './detectPackage.js';
export * from './extractTime.js';
export * from './float/index.js';
export * from './format/index.js';
export * from './has.js';
export * from './hex/index.js';
export * from './is/index.js';
export * from './lazy.js';
export * from './logger.js';
export * from './memoize.js';
export * from './nextTick.js';
export * from './noop.js';
export * from './number/index.js';
export * from './object/index.js';
export * from './promisify.js';
export * from './string/index.js';
export * from './stringify.js';
export * from './u8a/index.js';
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { compactAddLength } from './index.js';
describe('compactAddLength', (): void => {
it('correctly adds the length prefix', (): void => {
expect(
compactAddLength(Uint8Array.from([12, 13]))
).toEqual(Uint8Array.from([2 << 2, 12, 13]));
});
});
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { u8aConcatStrict } from '../u8a/index.js';
import { compactToU8a } from './toU8a.js';
/**
* @name compactAddLength
* @description Adds a length prefix to the input value
* @example
* <BR>
*
* ```javascript
* import { compactAddLength } from '@pezkuwi/util';
*
* console.log(compactAddLength(new Uint8Array([0xde, 0xad, 0xbe, 0xef]))); // Uint8Array([4 << 2, 0xde, 0xad, 0xbe, 0xef])
* ```
*/
export function compactAddLength (input: Uint8Array): Uint8Array {
return u8aConcatStrict([
compactToU8a(input.length),
input
]);
}
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BitLength } from './types.js';
/** @internal */
export const DEFAULT_BITLENGTH: BitLength = 32;
+84
View File
@@ -0,0 +1,84 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN } from '../bn/bn.js';
import { hexToU8a } from '../hex/toU8a.js';
import { perf } from '../test/index.js';
import { compactFromU8a, compactFromU8aLim } from './index.js';
describe('compactFromU8a', (): void => {
it('decoded u8 value', (): void => {
expect(
compactFromU8a(new Uint8Array([0b11111100]))
).toEqual([1, new BN(63)]);
});
it('decodes from same u16 encoded value', (): void => {
expect(
compactFromU8a(new Uint8Array([0b11111101, 0b00000111])).toString()
).toEqual(
[2, new BN(511)].toString()
);
});
it('decodes from same u32 encoded value (short)', (): void => {
expect(
compactFromU8a(new Uint8Array([254, 255, 3, 0])).toString()
).toEqual(
[4, new BN(0xffff)].toString()
);
});
it('decodes from same u32 encoded value (short, max)', (): void => {
expect(
compactFromU8a(new Uint8Array([254, 255, 255, 255])).toString()
).toEqual(
[4, new BN(1073741823)].toString()
);
});
it('decodes from same u32 encoded value (full)', (): void => {
expect(
compactFromU8a(new Uint8Array([3, 249, 255, 255, 255]))
).toEqual([5, new BN(0xfffffff9)]);
});
it('decodes from same u32 as u64 encoded value (full, default)', (): void => {
expect(
compactFromU8a(new Uint8Array([3 + ((4 - 4) << 2), 249, 255, 255, 255]))
).toEqual([5, new BN(0xfffffff9)]);
});
it('decodes an actual value', (): void => {
expect(
compactFromU8a(
hexToU8a('0x0b00407a10f35a')
)
).toEqual([7, new BN('5af3107a4000', 16)]);
});
it('decodes an actual value (Buffer)', (): void => {
expect(
compactFromU8a(
Buffer.from('0b00407a10f35a', 'hex')
)
).toEqual([7, new BN('5af3107a4000', 16)]);
});
it('decodes an actual value (100000000)', (): void => {
expect(
compactFromU8a(
hexToU8a('0x0284d717')
)[1].toString()
).toEqual('100000000');
});
perf('compactFromU8a (u8)', 1_000_000, [[new Uint8Array([63 << 2])]], compactFromU8a);
perf('compactFromU8a (u16)', 1_000_000, [[new Uint8Array([0b11111101, 0b00000111])]], compactFromU8a);
perf('compactFromU8a (u32)', 1_000_000, [[new Uint8Array([254, 255, 3, 0])]], compactFromU8a);
perf('compactFromU8aLim (u32)', 1_000_000, [[new Uint8Array([254, 255, 3, 0])]], compactFromU8aLim);
perf('compactFromU8a (u48)', 1_000_000, [[hexToU8a('0x0b00407a10f35a')]], compactFromU8a);
perf('compactFromU8a (u96)', 250_000, [[new Uint8Array([23, 52, 0x40, 0x7a, 0x10, 0xf3, 0x5a, 0, 0, 18])]], compactFromU8a);
});
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { U8aLike } from '../types.js';
import { BN } from '../bn/index.js';
import { u8aToBn, u8aToU8a } from '../u8a/index.js';
/**
* @name compactFromU8a
* @description Retrives the offset and encoded length from a compact-prefixed value
* @example
* <BR>
*
* ```javascript
* import { compactFromU8a } from '@pezkuwi/util';
*
* const [offset, length] = compactFromU8a(new Uint8Array([254, 255, 3, 0]));
*
* console.log('value offset=', offset, 'length=', length); // 4, 0xffff
* ```
*/
export function compactFromU8a (input: U8aLike): [number, BN] {
const u8a = u8aToU8a(input);
// The u8a is manually converted here for 1, 2 & 4 lengths, it is 2x faster
// than doing an additional call to u8aToBn (as with variable length)
switch (u8a[0] & 0b11) {
case 0b00:
return [1, new BN(u8a[0] >>> 2)];
case 0b01:
return [2, new BN((u8a[0] + (u8a[1] << 8)) >>> 2)];
case 0b10:
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
// 32-bit, in the case where the top-most bit is set this yields a negative value
return [4, new BN((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) >>> 2)];
// 0b11
default: {
// add 5 to shifted (4 for base length, 1 for this byte)
const offset = (u8a[0] >>> 2) + 5;
// we unroll the loop
switch (offset) {
// there still could be 4 bytes data, similar to 0b10 above (with offsets)
case 5:
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
// 32-bit, in the case where the top-most bit is set this yields a negative value
return [5, new BN(u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + (u8a[4] * 0x1_00_00_00))];
case 6:
return [6, new BN(u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + ((u8a[4] + (u8a[5] << 8)) * 0x1_00_00_00))];
// 6 bytes data is the maximum, 48 bits (56 would overflow)
case 7:
return [7, new BN(u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + ((u8a[4] + (u8a[5] << 8) + (u8a[6] << 16)) * 0x1_00_00_00))];
// for anything else, use the non-unrolled version
default:
return [offset, u8aToBn(u8a.subarray(1, offset))];
}
}
}
}
/**
* @name compactFromU8aLim
* @description A limited version of [[compactFromU8a]], accepting only Uint8Array inputs for values <= 48 bits
*/
export function compactFromU8aLim (u8a: Uint8Array): [number, number] {
// The u8a is manually converted here for 1, 2 & 4 lengths, it is 2x faster
// than doing an additional call to u8aToBn (as with variable length)
switch (u8a[0] & 0b11) {
case 0b00:
return [1, u8a[0] >>> 2];
case 0b01:
return [2, (u8a[0] + (u8a[1] << 8)) >>> 2];
case 0b10:
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
// 32-bit, in the case where the top-most bit is set this yields a negative value
return [4, (u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) >>> 2];
// 0b11
default: {
// add 5 to shifted (4 for base length, 1 for this byte)
// we unroll the loop
switch ((u8a[0] >>> 2) + 5) {
// there still could be 4 bytes data, similar to 0b10 above (with offsets)
case 5:
return [5, u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + (u8a[4] * 0x1_00_00_00)];
case 6:
return [6, u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + ((u8a[4] + (u8a[5] << 8)) * 0x1_00_00_00)];
// 6 bytes data is the maximum, 48 bits (56 would overflow)
case 7:
return [7, u8a[1] + (u8a[2] << 8) + (u8a[3] << 16) + ((u8a[4] + (u8a[5] << 8) + (u8a[6] << 16)) * 0x1_00_00_00)];
// for anything else, we are above the actual MAX_SAFE_INTEGER - bail out
default:
throw new Error('Compact input is > Number.MAX_SAFE_INTEGER');
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @description
* Encoding and decoding of parity-codec compact numbers. The codec is created
* to take up the least amount of space for a specific number. It performs the
* same function as Length, however differs in that it uses a variable number of
* bytes to do the actual encoding. From the Rust implementation for compact
* encoding:
*
* 0b00 00 00 00 / 00 00 00 00 / 00 00 00 00 / 00 00 00 00
* (0 ... 2**6 - 1) (u8)
* xx xx xx 00
* (2**6 ... 2**14 - 1) (u8, u16) low LH high
* yL yL yL 01 / yH yH yH yL
* (2**14 ... 2**30 - 1) (u16, u32) low LMMH high
* zL zL zL 10 / zM zM zM zL / zM zM zM zM / zH zH zH zM
* (2**30 ... 2**536 - 1) (u32, u64, u128, U256, U512, U520) straight LE-encoded
* nn nn nn 11 [ / zz zz zz zz ]{4 + n}
*
* Note: we use *LOW BITS* of the LSB in LE encoding to encode the 2 bit key.
*/
export { compactAddLength } from './addLength.js';
export { compactFromU8a, compactFromU8aLim } from './fromU8a.js';
export { compactStripLength } from './stripLength.js';
export { compactToU8a } from './toU8a.js';
@@ -0,0 +1,17 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { compactStripLength } from './index.js';
describe('compactStripLength', (): void => {
it('correctly removes the length prefix', (): void => {
expect(
compactStripLength(Uint8Array.from([2 << 2, 12, 13]))
).toEqual([
3,
Uint8Array.from([12, 13])
]);
});
});
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { compactFromU8a } from './fromU8a.js';
/**
* @name compactStripLength
* @description Removes the length prefix, returning both the total length (including the value + compact encoding) and the decoded value with the correct length
* @example
* <BR>
*
* ```javascript
* import { compactStripLength } from '@pezkuwi/util';
*
* console.log(compactStripLength(new Uint8Array([2 << 2, 0xde, 0xad]))); // [2, Uint8Array[0xde, 0xad]]
* ```
*/
export function compactStripLength (input: Uint8Array): [number, Uint8Array] {
const [offset, length] = compactFromU8a(input);
const total = offset + length.toNumber();
return [
total,
input.subarray(offset, total)
];
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN } from '../bn/index.js';
import { compactToU8a } from './index.js';
const TESTS: [output: string | Uint8Array, input: BN | number][] = [
// Rust tests
// Copied from https://github.com/paritytech/parity-codec/blob/master/src/codec.rs
['00', new BN('0')],
['fc', new BN('63')],
['01 01', new BN('64')],
['fd ff', new BN('16383')],
['02 00 01 00', new BN('16384')],
['fe ff ff ff', new BN('1073741823')],
['03 00 00 00 40', new BN('1073741824')],
['03 ff ff ff ff', new BN(`${1}${'0'.repeat(32)}`, 2).subn(1)],
['07 00 00 00 00 01', new BN(`${1}${'0'.repeat(32)}`, 2)],
['0b 00 00 00 00 00 01', new BN(`${1}${'0'.repeat(40)}`, 2)],
['0f 00 00 00 00 00 00 01', new BN(`${1}${'0'.repeat(48)}`, 2)],
['0f ff ff ff ff ff ff ff', new BN(`${1}${'0'.repeat(56)}`, 2).subn(1)],
['13 00 00 00 00 00 00 00 01', new BN(`${1}${'0'.repeat(56)}`, 2)],
['13 ff ff ff ff ff ff ff ff', new BN(`${1}${'0'.repeat(64)}`, 2).subn(1)],
// own tests
[new Uint8Array([18 << 2]), 18],
[new Uint8Array([0b11111100]), 63],
[new Uint8Array([0xbd, 0x01]), 111],
[new Uint8Array([0b11111101, 0b00000111]), 511],
[new Uint8Array([253, 127]), 0x1fff],
[new Uint8Array([254, 255, 3, 0]), 0xffff],
[new Uint8Array([3 + ((4 - 4) << 2), 249, 255, 255, 255]), 0xfffffff9],
[new Uint8Array([3 + ((6 - 4) << 2), 0x00, 0x40, 0x7a, 0x10, 0xf3, 0x5a]), new BN('00005af3107a4000', 16)],
[new Uint8Array([23, 52, 0x40, 0x7a, 0x10, 0xf3, 0x5a, 0, 0, 18]), new BN('1200005af3107a4034', 16)]
];
describe('encode', (): void => {
it('does not modify the original', (): void => {
const original = new BN(123456);
expect(compactToU8a(original)).toEqual(new Uint8Array([2, 137, 7, 0]));
expect(original.toString()).toEqual('123456');
});
describe('conversion tests', (): void => {
TESTS.forEach(([output, input], i): void => {
it(`#${i}: encodes ${input.toString()}`, (): void => {
expect(
compactToU8a(input)
).toEqual(
output instanceof Uint8Array
? output
: Uint8Array.from(output.split(' ').map((s) => parseInt(s, 16)))
);
});
});
});
});
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { BN, BN_ONE, BN_TWO, bnToBn, bnToU8a } from '../bn/index.js';
import { u8aConcatStrict } from '../u8a/index.js';
const MAX_U8 = BN_TWO.pow(new BN(8 - 2)).isub(BN_ONE);
const MAX_U16 = BN_TWO.pow(new BN(16 - 2)).isub(BN_ONE);
const MAX_U32 = BN_TWO.pow(new BN(32 - 2)).isub(BN_ONE);
const BL_16 = { bitLength: 16 };
const BL_32 = { bitLength: 32 };
/**
* @name compactToU8a
* @description Encodes a number into a compact representation
* @example
* <BR>
*
* ```javascript
* import { compactToU8a } from '@pezkuwi/util';
*
* console.log(compactToU8a(511, 32)); // Uint8Array([0b11111101, 0b00000111])
* ```
*/
export function compactToU8a (value: BN | bigint | number): Uint8Array {
const bn = bnToBn(value);
if (bn.lte(MAX_U8)) {
return new Uint8Array([bn.toNumber() << 2]);
} else if (bn.lte(MAX_U16)) {
return bnToU8a(bn.shln(2).iadd(BN_ONE), BL_16);
} else if (bn.lte(MAX_U32)) {
return bnToU8a(bn.shln(2).iadd(BN_TWO), BL_32);
}
const u8a = bnToU8a(bn);
let length = u8a.length;
// adjust to the minimum number of bytes
while (u8a[length - 1] === 0) {
length--;
}
if (length < 4) {
throw new Error('Invalid length, previous checks match anything less than 2^30');
}
return u8aConcatStrict([
// subtract 4 as minimum (also catered for in decoding)
new Uint8Array([((length - 4) << 2) + 0b11]),
u8a.subarray(0, length)
]);
}
+4
View File
@@ -0,0 +1,4 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
export type BitLength = 8 | 16 | 32 | 64 | 128 | 256;
+110
View File
@@ -0,0 +1,110 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { detectPackage, POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG } from './detectPackage.js';
describe('detectPackage', (): void => {
const PKG = '@polkadot/util';
const VER1 = '9.8.0-beta.45';
const VER2 = '9.7.1';
const VER3 = '9.6.1';
const PATH = '/Users/jaco/Projects/polkadot-js/api/node_modules/@polkadot/util';
const MISMATCH = `@polkadot/util has multiple versions, ensure that there is only one installed.
Either remove and explicitly install matching versions or dedupe using your package manager.
The following conflicting packages were found:
\tesm ${VER1}\t<unknown>
\tesm ${VER2} \tnode_modules/@polkadot/api/node_modules/@polkadot/util`;
it('should not log the first time', (): void => {
const spy = jest.spyOn(console, 'warn');
detectPackage({ name: PKG, path: 'auto', type: 'esm', version: VER1 }, PATH);
expect(spy).not.toHaveBeenCalled();
spy.mockRestore();
});
it('should log the second time', (): void => {
const spy = jest.spyOn(console, 'warn');
detectPackage({ name: PKG, path: '/Users/jaco/Projects/polkadot-js/api/node_modules/@polkadot/api/node_modules/@polkadot/util', type: 'esm', version: VER2 });
expect(spy).toHaveBeenCalledWith(MISMATCH);
spy.mockRestore();
});
it('should allow for function use', (): void => {
const spy = jest.spyOn(console, 'warn');
detectPackage({ name: PKG, path: 'node_modules/@polkadot/util', type: 'cjs', version: VER3 }, () => PATH);
expect(spy).toHaveBeenCalledWith(`${MISMATCH}
\tcjs ${VER3} \tnode_modules/@polkadot/util`);
spy.mockRestore();
});
});
describe('detectPackageDeps', (): void => {
const DEP0 = { name: '@polkadot/keyring', path: 'auto', type: 'esm', version: '1.1.1' };
const DEP1 = { name: '@polkadot/util', path: 'auto', type: 'esm', version: '1.1.2' };
const DEP2 = { name: '@polkadot/util-crypto', path: 'auto', type: 'esm', version: '1.1.3' };
const DEP3 = { name: '@polkadot/networks', path: 'auto', type: 'esm', version: '1.1.1' };
it('should not log when no mismatches are found', (): void => {
const spy = jest.spyOn(console, 'warn');
detectPackage({ name: '@polkadot/one', path: 'auto', type: 'esm', version: '1.1.1' }, false, [DEP0, DEP3]);
expect(spy).not.toHaveBeenCalled();
spy.mockRestore();
});
it('should log when mismatches are found', (): void => {
const spy = jest.spyOn(console, 'warn');
detectPackage({ name: '@polkadot/two', path: 'auto', type: 'esm', version: '1.1.1' }, false, [DEP0, DEP1, DEP2, DEP3]);
expect(spy).toHaveBeenCalledWith(`@polkadot/two requires direct dependencies exactly matching version 1.1.1.
Either remove and explicitly install matching versions or dedupe using your package manager.
The following conflicting packages were found:
\t1.1.2\t@polkadot/util
\t1.1.3\t@polkadot/util-crypto`);
spy.mockRestore();
});
});
describe('detectPackageEsmCjsNoWarnings', (): void => {
const PKG = '@polkadot/wasm-crypto';
const VER1 = '9.8.0-beta.45';
const PATH = '/Users/jaco/Projects/polkadot-js/api/node_modules/@polkadot/api/node_modules/@polkadot/wasm-crypto';
it('should not log when there are concurrent esm and cjs versions of the same package with the same version number and warnings are disabled', (): void => {
const spy = jest.spyOn(console, 'warn');
const pkgEsm = { name: PKG, path: PATH, type: 'esm', version: VER1 };
const pkgCjs = { name: PKG, path: `${PATH}/cjs`, type: 'cjs', version: VER1 };
process.env[POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG] = '1';
detectPackage(pkgEsm, false, []);
detectPackage(pkgCjs, false, []);
expect(spy).not.toHaveBeenCalled();
spy.mockRestore();
});
});
describe('detectPackageEsmCjs', (): void => {
const PKG = '@polkadot/wasm-crypto-wasm';
const VER1 = '9.8.0-beta.45';
const PATH = '/Users/jaco/Projects/polkadot-js/api/node_modules/@polkadot/api/node_modules/@polkadot/wasm-crypto-wasm';
it('should log when there are concurrent esm and cjs versions of the same package with the same version number and warnings are not disabled', (): void => {
const spy = jest.spyOn(console, 'warn');
const pkgEsm = { name: PKG, path: PATH, type: 'esm', version: VER1 };
const pkgCjs = { name: PKG, path: `${PATH}/cjs`, type: 'cjs', version: VER1 };
process.env[POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG] = undefined;
detectPackage(pkgEsm, false, []);
detectPackage(pkgCjs, false, []);
expect(spy).toHaveBeenCalled();
spy.mockRestore();
});
});
+138
View File
@@ -0,0 +1,138 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { xglobal } from '@pezkuwi/x-global';
import { isFunction } from './is/function.js';
type This = typeof globalThis;
interface VersionPath {
path: string;
type: string;
version: string;
}
interface PackageInfo extends VersionPath {
name: string;
}
interface PjsChecks extends This {
__polkadotjs: Record<string, VersionPath[]>;
}
type PjsGlobal = This & PjsChecks & Record<string, unknown>;
type FnString = () => string | undefined;
const DEDUPE = 'Either remove and explicitly install matching versions or dedupe using your package manager.\nThe following conflicting packages were found:';
export const POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG = 'POLKADOTJS_DISABLE_ESM_CJS_WARNING';
/** @internal */
function getEntry (name: string): VersionPath[] {
const _global = xglobal as PjsGlobal;
if (!_global.__polkadotjs) {
_global.__polkadotjs = {};
}
if (!_global.__polkadotjs[name]) {
_global.__polkadotjs[name] = [];
}
return _global.__polkadotjs[name];
}
/** @internal */
function formatDisplay <T extends { version: string }> (all: T[], fmt: (version: string, data: T) => string[]): string {
let max = 0;
for (let i = 0, count = all.length; i < count; i++) {
max = Math.max(max, all[i].version.length);
}
return all
.map((d) => `\t${fmt(d.version.padEnd(max), d).join('\t')}`)
.join('\n');
}
/** @internal */
function formatInfo (version: string, { name }: PackageInfo): string[] {
return [
version,
name
];
}
/** @internal */
function formatVersion (version: string, { path, type }: VersionPath): string[] {
let extracted: string;
if (path && path.length >= 5) {
const nmIndex = path.indexOf('node_modules');
extracted = nmIndex === -1
? path
: path.substring(nmIndex);
} else {
extracted = '<unknown>';
}
return [
`${`${type || ''}`.padStart(3)} ${version}`,
extracted
];
}
/** @internal */
function getPath (infoPath?: string, pathOrFn?: FnString | string | false | null): string {
if (infoPath) {
return infoPath;
} else if (isFunction(pathOrFn)) {
try {
return pathOrFn() || '';
} catch {
return '';
}
}
return pathOrFn || '';
}
/** @internal */
function warn <T extends { version: string }> (pre: string, all: T[], fmt: (version: string, data: T) => string[]): void {
console.warn(`${pre}\n${DEDUPE}\n${formatDisplay(all, fmt)}`);
}
/**
* @name detectPackage
* @summary Checks that a specific package is only imported once
* @description A `@polkadot/*` version detection utility, checking for one occurrence of a package in addition to checking for dependency versions.
*/
export function detectPackage ({ name, path, type, version }: PackageInfo, pathOrFn?: FnString | string | false | null, deps: PackageInfo[] = []): void {
if (!name.startsWith('@polkadot')) {
throw new Error(`Invalid package descriptor ${name}`);
}
const entry = getEntry(name);
entry.push({ path: getPath(path, pathOrFn), type, version });
// if we have more than one entry at DIFFERENT version types then warn. If there is
// more than one entry at the same version and ESM/CJS dual warnings are disabled,
// then do not display warnings
const entriesSameVersion = entry.every((e) => e.version === version);
const esmCjsWarningDisabled = xglobal.process?.env?.[POLKADOTJS_DISABLE_ESM_CJS_WARNING_FLAG] === '1';
const multipleEntries = entry.length !== 1;
const disableWarnings = esmCjsWarningDisabled && entriesSameVersion;
if (multipleEntries && !disableWarnings) {
warn(`${name} has multiple versions, ensure that there is only one installed.`, entry, formatVersion);
} else {
const mismatches = deps.filter((d) => d && d.version !== version);
if (mismatches.length) {
warn(`${name} requires direct dependencies exactly matching version ${version}.`, mismatches, formatInfo);
}
}
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { extractTime } from './index.js';
describe('extractTime', (): void => {
const milliseconds = 1e9 + 123;
it('extracts time components correctly', (): void => {
expect(extractTime(milliseconds))
.toEqual({
days: 11,
hours: 13,
milliseconds: 123,
minutes: 46,
seconds: 40
});
});
});
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Time } from './types.js';
const MIN_MS = 60 * 1000;
const HR_MS = MIN_MS * 60;
const DAY_MS = HR_MS * 24;
const ZERO: Time = { days: 0, hours: 0, milliseconds: 0, minutes: 0, seconds: 0 };
/** @internal */
function add (a: Partial<Time>, b: Time): Time {
return {
days: (a.days || 0) + b.days,
hours: (a.hours || 0) + b.hours,
milliseconds: (a.milliseconds || 0) + b.milliseconds,
minutes: (a.minutes || 0) + b.minutes,
seconds: (a.seconds || 0) + b.seconds
};
}
/** @internal */
function extractSecs (ms: number): Time {
const s = ms / 1000;
if (s < 60) {
const seconds = ~~s;
return add({ seconds }, extractTime(ms - (seconds * 1000)));
}
const m = s / 60;
if (m < 60) {
const minutes = ~~m;
return add({ minutes }, extractTime(ms - (minutes * MIN_MS)));
}
const h = m / 60;
if (h < 24) {
const hours = ~~h;
return add({ hours }, extractTime(ms - (hours * HR_MS)));
}
const days = ~~(h / 24);
return add({ days }, extractTime(ms - (days * DAY_MS)));
}
/**
* @name extractTime
* @summary Convert a quantity of seconds to Time array representing accumulated {days, minutes, hours, seconds, milliseconds}
* @example
* <BR>
*
* ```javascript
* import { extractTime } from '@pezkuwi/util';
*
* const { days, minutes, hours, seconds, milliseconds } = extractTime(6000); // 0, 0, 10, 0, 0
* ```
*/
export function extractTime (milliseconds?: number): Time {
return !milliseconds
? ZERO
: milliseconds < 1000
? add({ milliseconds }, ZERO)
: extractSecs(milliseconds);
}
+4
View File
@@ -0,0 +1,4 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { floatToU8a } from './toU8a.js';
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { u8aToHex } from '../u8a/index.js';
import { floatToU8a } from './index.js';
class ExtNumber extends Number {
foo = 'bar';
}
class ExtString extends String {
foo = 'bar';
}
// NOTE Hex value outputs created via online conversion tool:
// https://www.h-schmidt.net/FloatConverter/IEEE754.html
// eslint-disable-next-line @typescript-eslint/ban-types
const TESTS: [isLe: boolean | undefined, bitLength: 32 | 64 | undefined, input: String | string | number | Number, output: string][] = [
[undefined, undefined, +0.0, '0x00000000'],
[undefined, undefined, -0.0, '0x00000080'],
[undefined, undefined, 123.456, '0x79e9f642'],
[undefined, undefined, '123.456', '0x79e9f642'],
[undefined, undefined, new ExtNumber(123.456), '0x79e9f642'],
[undefined, undefined, new ExtString(123.456), '0x79e9f642'],
[true, 32, new ExtString(123.456), '0x79e9f642'],
[true, undefined, Number.NaN, '0x0000c07f'],
[false, undefined, -0.0, '0x80000000'],
[undefined, 64, +0.0, '0x0000000000000000'],
[true, 64, -0.0, '0x0000000000000080'],
[false, 64, -0.0, '0x8000000000000000'],
[undefined, 64, Number.NaN, '0x000000000000f87f']
];
describe('floatToU8a', (): void => {
it('throws on invalid bitLength', (): void => {
expect(
() => floatToU8a(123, { bitLength: 48 as 32 })
).toThrow();
});
describe('conversion tests', (): void => {
TESTS.forEach(([isLe, bitLength, input, output], i): void => {
it(`#${i}: correctly encodes ${typeof input === 'number' ? input : input.toString()} (typeof=${typeof input})`, (): void => {
expect(
u8aToHex(floatToU8a(input, { bitLength, isLe }))
).toEqual(output);
});
});
});
});
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
interface Options {
bitLength?: 32 | 64;
isLe?: boolean;
}
/**
* @name floatToU8a
* @description Converts a float into a U8a representation (While we don't use BE in SCALE
* we still allow for either representation, although, as elsewhere, isLe is default)
*/
// eslint-disable-next-line @typescript-eslint/ban-types
export function floatToU8a (value: String | string | number | Number = 0.0, { bitLength = 32, isLe = true }: Options = {}): Uint8Array {
if (bitLength !== 32 && bitLength !== 64) {
throw new Error('Invalid bitLength provided, expected 32 or 64');
}
const result = new Uint8Array(bitLength / 8);
const dv = new DataView(result.buffer, result.byteOffset);
if (bitLength === 32) {
dv.setFloat32(0, Number(value), isLe);
} else {
dv.setFloat64(0, Number(value), isLe);
}
return result;
}
@@ -0,0 +1,296 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN } from '../bn/index.js';
import { formatBalance } from './index.js';
const FMT_DEFAULTS = {
decimals: 0,
unit: 'Unit'
};
describe('formatBalance', (): void => {
const TESTVAL = new BN('123456789000');
// We mess around with the global setDefaults inside some tests,
// ensure we restore it to the defaults straight after each run
afterEach((): void => {
formatBalance.setDefaults(FMT_DEFAULTS);
});
it('returns options for dropdown', (): void => {
formatBalance.setDefaults({ decimals: 0, unit: 'TEST' });
expect(
formatBalance.getOptions()
).toEqual([
{ power: 0, text: 'TEST', value: '-' },
{ power: 3, text: 'Kilo', value: 'k' },
{ power: 6, text: 'Mill', value: 'M' },
{ power: 9, text: 'Bill', value: 'B' },
{ power: 12, text: 'Tril', value: 'T' },
{ power: 15, text: 'Peta', value: 'P' },
{ power: 18, text: 'Exa', value: 'E' },
{ power: 21, text: 'Zeta', value: 'Z' },
{ power: 24, text: 'Yotta', value: 'Y' }
]);
});
it('can set defaults from array values', (): void => {
formatBalance.setDefaults({ decimals: [12, 24], unit: ['Multi', 'Unit'] });
expect(
formatBalance(TESTVAL)
).toEqual('123.4567 mMulti');
});
describe('SI formatting', (): void => {
it('formats empty to 0', (): void => {
expect(formatBalance()).toEqual('0');
expect(formatBalance('0')).toEqual('0');
});
// this is after an issue/test from actual values with forceUnit
it('formats 1000 (BN) (decimals = 12, withAll, withZero = false)', (): void => {
expect(
formatBalance(new BN(1000), { decimals: 12, forceUnit: '-', withAll: true, withSi: false, withZero: true })
).toEqual('0.000000001000');
});
it('formats 123,456,789,000 (decimals=15)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 15, withSi: true })
).toEqual('123.4567 µUnit');
});
it('formats 123,456 (decimals=0)', (): void => {
expect(
formatBalance(123456, { decimals: 0, withSi: true })
).toEqual('123.4560 kUnit');
});
it('formats BigInt numbers', (): void => {
expect(
formatBalance(123456789000n, { decimals: 15, withSi: true })
).toEqual('123.4567 µUnit');
});
it('formats 123,456,789,000 (decimals=15, old style)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 15, withSi: true })
).toEqual('123.4567 µUnit');
});
it('formats 123,456,789,000 (decimals=10, withAll=true)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 10, forceUnit: '-', withAll: true, withSi: true })
).toEqual('12.3456789000 Unit');
});
it('formats 123,456,789,000 (decimals=10, withAll=true, withZero=false)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 10, forceUnit: '-', withAll: true, withSi: true, withZero: false })
).toEqual('12.3456789 Unit');
});
it('formats 123,000,000,000 (decimals=9, withAll=true, withZero=false)', (): void => {
expect(
formatBalance('123000000000', { decimals: 9, forceUnit: '-', withAll: true, withSi: true, withZero: false })
).toEqual('123 Unit');
});
it('formats 123,456,789,000 (decimals=15, Compact)', (): void => {
const compact = {
something: 'else',
toBn: (): BN => TESTVAL,
unwrap: (): BN => TESTVAL
};
expect(
formatBalance(compact, { decimals: 15, withSi: true })
).toEqual('123.4567 µUnit');
});
it('formats 123,456,789,000 (decimals=12)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 12, withSi: true })
).toEqual('123.4567 mUnit');
});
it('formats 123,456,789,000 (decimals=9)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 9, withSi: true })
).toEqual('123.4567 Unit');
});
it('formats 123,456,789,000 (decimals=9, no unit)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 9, withSi: true, withUnit: false })
).toEqual('123.4567');
});
it('formats 123,456,789,000 (decimals=9, unit given)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 9, withSi: true, withUnit: 'FOO' })
).toEqual('123.4567 FOO');
});
it('formats 123,456,789,000 (decimals=12, no SI)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 12, withSi: false })
).toEqual('123.4567');
});
it('formats 123,456,789,000 (decimals=12, full SI)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 12, withSiFull: true })
).toEqual('123.4567 milli Unit');
});
it('formats 123,456,789,000 (decimals=12, full SI, no unit)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 12, withSiFull: true, withUnit: false })
).toEqual('123.4567 milli');
});
it('formats 123,456,789,000 (decimals=9, full SI)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 9, withSiFull: true })
).toEqual('123.4567 Unit');
});
it('formats 123,456,789,000 (decimals=6)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 6, withSi: true })
).toEqual('123.4567 kUnit');
});
it('formats 123,456,789,000 (decimals=6, unit specified)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 6, withSi: true, withUnit: 'BAR' })
).toEqual('123.4567 kBAR');
});
it('formats 123,456,789,000 (decimals=0, unit specified)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 0, withSi: true, withUnit: 'BAR' })
).toEqual('123.4567 BBAR');
});
it('formats 123,456,789,000 * 10 (decimals=12)', (): void => {
expect(
formatBalance(TESTVAL.muln(10), { decimals: 12, withSi: true })
).toEqual('1.2345 Unit');
});
it('formats 123,456,789,000 * 100 (decimals=12)', (): void => {
expect(
formatBalance(TESTVAL.muln(100), { decimals: 12, withSi: true })
).toEqual('12.3456 Unit');
});
it('formats 123,456,789,000 * 1000 (decimals=12)', (): void => {
expect(
formatBalance(TESTVAL.muln(1000), { decimals: 12, withSi: true })
).toEqual('123.4567 Unit');
});
it('formats -123,456,789,000 (decimals=15)', (): void => {
expect(
formatBalance(new BN('-123456789000'), { decimals: 15, withSi: true })
).toEqual('-123.4567 µUnit');
});
});
describe('Forced formatting', (): void => {
it('formats 123,456,789,000 (decimals=12, forceUnit=base)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 12, forceUnit: '-' })
).toEqual('0.1234 Unit');
});
it('formats 123,456,789,000 (decimals=9, forceUnit=base)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 9, forceUnit: '-' })
).toEqual('123.4567 Unit');
});
it('formats 123,456,789,000 (decimals=7, forceUnit=base)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 7, forceUnit: '-' })
).toEqual('12,345.6789 Unit');
});
it('formats 123,456,789,000 (decimals=15, forceUnit=micro)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 15, forceUnit: 'µ' })
).toEqual('123.4567 µUnit');
});
it('formats 123,456 (decimals=0, locale=sl)', (): void => {
expect(
formatBalance(123456, { decimals: 0, locale: 'sl', withSi: true })
).toEqual('123,4560 kUnit');
});
it('formats BigInt numbers (locale=sl)', (): void => {
expect(
formatBalance(123456789000n, { decimals: 15, locale: 'sl', withSi: true })
).toEqual('123,4567 µUnit');
});
it('formats 123,456,789,000 (decimals=7, forceUnit=base locale=sl)', (): void => {
expect(
formatBalance(TESTVAL, { decimals: 7, forceUnit: '-', locale: 'sl' })
).toEqual('12.345,6789 Unit');
});
});
describe('calcSi', (): void => {
it('exposes calcSi on formatBalance', (): void => {
expect(
formatBalance.calcSi('12345').value
).toEqual('k');
});
});
describe('findSi', (): void => {
it('finds the SI value', (): void => {
expect(
formatBalance.findSi('k')
).toEqual({ power: 3, text: 'Kilo', value: 'k' });
});
it('returns default on not found', (): void => {
expect(
formatBalance.findSi('blah')
).toEqual({ power: 0, text: 'Unit', value: '-' });
});
});
describe('defaults', (): void => {
it('returns defaults', (): void => {
expect(
formatBalance.getDefaults()
).toEqual(FMT_DEFAULTS);
});
it('formats 123,456,789,000 (defaultDecimals=12)', (): void => {
formatBalance.setDefaults({ decimals: 12 });
expect(
formatBalance(TESTVAL)
).toEqual('123.4567 mUnit');
});
it('formats 123,456,789,000 (defaultUnit=TEST)', (): void => {
formatBalance.setDefaults({ decimals: 12, unit: 'TEST' });
expect(
formatBalance(TESTVAL)
).toEqual('123.4567 mTEST');
});
});
});
+177
View File
@@ -0,0 +1,177 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '../bn/bn.js';
import type { SiDef, ToBn } from '../types.js';
import { bnToBn } from '../bn/toBn.js';
import { isBoolean } from '../is/boolean.js';
import { formatDecimal } from './formatDecimal.js';
import { getSeparator } from './getSeparator.js';
import { calcSi, findSi, SI, SI_MID } from './si.js';
interface Defaults {
decimals: number;
unit: string;
}
interface SetDefaults {
decimals?: number[] | number;
unit?: string[] | string;
}
interface Options {
/**
* @description The number of decimals
*/
decimals?: number;
/**
* @description Format the number with this specific unit
*/
forceUnit?: string;
/**
* @description Returns value using all available decimals
*/
withAll?: boolean;
/**
* @description Format with SI, i.e. m/M/etc. (default = true)
*/
withSi?: boolean;
/**
* @description Format with full SI, i.e. mili/Mega/etc.
*/
withSiFull?: boolean;
/**
* @description Add the unit (useful in Balance formats)
*/
withUnit?: boolean | string;
/**
* @description Returns all trailing zeros, otherwise removes (default = true)
*/
withZero?: boolean;
/**
* @description The locale to use
*/
locale?: string;
}
interface BalanceFormatter {
<ExtToBn extends ToBn> (input?: number | string | BN | bigint | ExtToBn, options?: Options): string;
calcSi (text: string, decimals?: number): SiDef;
findSi (type: string): SiDef;
getDefaults (): Defaults;
getOptions (decimals?: number): SiDef[];
setDefaults (defaults: SetDefaults): void;
}
const DEFAULT_DECIMALS = 0;
const DEFAULT_UNIT = SI[SI_MID].text;
let defaultDecimals = DEFAULT_DECIMALS;
let defaultUnit = DEFAULT_UNIT;
// Formats a string/number with <prefix>.<postfix><type> notation
function _formatBalance <ExtToBn extends ToBn> (input?: number | string | BN | bigint | ExtToBn, { decimals = defaultDecimals, forceUnit, locale = 'en', withAll = false, withSi = true, withSiFull = false, withUnit = true, withZero = true }: Options = {}): string {
// we only work with string inputs here - convert anything
// into the string-only value
let text = bnToBn(input).toString();
if (text.length === 0 || text === '0') {
return '0';
}
// strip the negative sign so we can work with clean groupings, re-add this in the
// end when we return the result (from here on we work with positive numbers)
let sign = '';
if (text[0].startsWith('-')) {
sign = '-';
text = text.substring(1);
}
// We start at midpoint (8) minus 1 - this means that values display as
// 123.4567 instead of 0.1234 k (so we always have the most relevant).
const si = calcSi(text, decimals, forceUnit);
const mid = text.length - (decimals + si.power);
const pre = mid <= 0 ? '0' : text.substring(0, mid);
// get the post from the midpoint onward and then first add max decimals
// before trimming to the correct (calculated) amount of decimals again
let post = text
.padStart(mid < 0 ? decimals : 1, '0')
.substring(mid < 0 ? 0 : mid)
.padEnd(withAll ? Math.max(decimals, 4) : 4, '0')
.substring(0, withAll ? Math.max(4, decimals + si.power) : 4);
// remove all trailing 0's (if required via flag)
if (!withZero) {
let end = post.length - 1;
// This looks inefficient, however it is better to do the checks and
// only make one final slice than it is to do it in multiples
do {
if (post[end] === '0') {
end--;
}
} while (post[end] === '0');
post = post.substring(0, end + 1);
}
// the display unit
const unit = isBoolean(withUnit)
? SI[SI_MID].text
: withUnit;
// format the units for display based on the flags
const units = withSi || withSiFull
? si.value === '-'
? withUnit
? ` ${unit}`
: ''
: ` ${withSiFull ? `${si.text}${withUnit ? ' ' : ''}` : si.value}${withUnit ? unit : ''}`
: '';
const { decimal, thousand } = getSeparator(locale);
return `${sign}${formatDecimal(pre, thousand)}${post && `${decimal}${post}`}${units}`;
}
export const formatBalance = _formatBalance as BalanceFormatter;
formatBalance.calcSi = (text: string, decimals: number = defaultDecimals): SiDef =>
calcSi(text, decimals);
formatBalance.findSi = findSi;
formatBalance.getDefaults = (): Defaults => {
return {
decimals: defaultDecimals,
unit: defaultUnit
};
};
// get allowable options to display in a dropdown
formatBalance.getOptions = (decimals: number = defaultDecimals): SiDef[] => {
return SI.filter(({ power }): boolean =>
power < 0
? (decimals + power) >= 0
: true
);
};
// Sets the default decimals to use for formatting (ui-wide)
formatBalance.setDefaults = ({ decimals, unit }: SetDefaults): void => {
defaultDecimals = (
Array.isArray(decimals)
? decimals[0]
: decimals
) ?? defaultDecimals;
defaultUnit = (
Array.isArray(unit)
? unit[0]
: unit
) ?? defaultUnit;
SI[SI_MID].text = defaultUnit;
};
@@ -0,0 +1,17 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { formatDate } from './index.js';
describe('formatDate', (): void => {
it('formats a known date into the correct format', (): void => {
const date = new Date(Date.UTC(2020, 1, 15, 11, 14, 34));
// make this test-locale agnostic
date.setTime(date.getTime() + date.getTimezoneOffset() * 60 * 1000);
expect(formatDate(date)).toEqual('2020-02-15 11:14:34');
});
});
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/** @internal */
function zeroPad (value: number): string {
return value.toString().padStart(2, '0');
}
/**
* @name formatDate
* @description Formats a date in CCYY-MM-DD HH:MM:SS format
*/
export function formatDate (date: Date): string {
const year = date.getFullYear().toString();
const month = zeroPad((date.getMonth() + 1));
const day = zeroPad(date.getDate());
const hour = zeroPad(date.getHours());
const minute = zeroPad(date.getMinutes());
const second = zeroPad(date.getSeconds());
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}
@@ -0,0 +1,28 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { formatDecimal } from './index.js';
describe('formatDecimal', (): void => {
it('formats decimals in number groupings', (): void => {
expect(formatDecimal('12345')).toEqual('12,345');
});
it('formats decimal-only in number groupings', (): void => {
expect(formatDecimal('test6789')).toEqual('6,789');
});
it('returns input for non-decimal', (): void => {
expect(formatDecimal('test')).toEqual('test');
});
it('returns non-sensical negative text', (): void => {
expect(formatDecimal('-test')).toEqual('-test');
});
it('formats negative numbers', (): void => {
expect(formatDecimal('-123456')).toEqual('-123,456');
});
});
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
// eslint-disable-next-line prefer-regex-literals
const NUMBER_REGEX = new RegExp('(\\d+?)(?=(\\d{3})+(?!\\d)|$)', 'g');
/**
* @name formatDecimal
* @description Formats a number into string format with thousand separators
*/
export function formatDecimal (value: string, separator = ','): string {
// We can do this by adjusting the regx, however for the sake of clarity
// we rather strip and re-add the negative sign in the output
const isNegative = value[0].startsWith('-');
const matched = isNegative
? value.substring(1).match(NUMBER_REGEX)
: value.match(NUMBER_REGEX);
return matched
? `${isNegative ? '-' : ''}${matched.join(separator)}`
: value;
}
@@ -0,0 +1,44 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN } from '../bn/index.js';
import { formatElapsed } from './index.js';
describe('formatElapsed', (): void => {
const start = 12345678;
const now = new Date(12345678);
it('formats a Date', (): void => {
expect(
formatElapsed(now, new Date(start + 9700))
).toEqual('9.7s');
});
it('formats a BN', (): void => {
expect(
formatElapsed(now, new BN(start + 42700))
).toEqual('42s');
});
it('formats a Compact', (): void => {
expect(
formatElapsed(now, {
toBn: (): BN => new BN(start + (5.3 * 60000))
})
).toEqual('5m');
});
it('formats a number', (): void => {
expect(
formatElapsed(now, start + (42 * 60 * 60000))
).toEqual('42h');
});
it('formats defaults', (): void => {
expect(
formatElapsed()
).toEqual('0.0s');
});
});
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '../bn/bn.js';
import type { ToBn } from '../types.js';
import { bnToBn } from '../bn/toBn.js';
/** @internal */
function formatValue (elapsed: number): string {
if (elapsed < 15) {
return `${elapsed.toFixed(1)}s`;
} else if (elapsed < 60) {
return `${elapsed | 0}s`;
} else if (elapsed < 3600) {
return `${elapsed / 60 | 0}m`;
}
return `${elapsed / 3600 | 0}h`;
}
/**
* @name formatElapsed
* @description Formats an elapsed value into s, m, h or day segments
*/
export function formatElapsed <ExtToBn extends ToBn> (now?: Date | null, value?: bigint | BN | ExtToBn | Date | number | null): string {
const tsNow = now?.getTime() || 0;
const tsValue = value instanceof Date
? value.getTime()
: bnToBn(value).toNumber();
return (tsNow && tsValue)
? formatValue(Math.max(Math.abs(tsNow - tsValue), 0) / 1000)
: '0.0s';
}
@@ -0,0 +1,60 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { BN } from '../bn/index.js';
import { formatNumber } from './index.js';
describe('formatNumber', (): void => {
it('formats empty', (): void => {
expect(
formatNumber()
).toEqual('0');
});
it('formats negative numbers', (): void => {
expect(
formatNumber(-123456)
).toEqual('-123,456');
});
it('formats BN numbers', (): void => {
expect(
formatNumber(new BN(12345))
).toEqual('12,345');
});
it('formats BigInt numbers', (): void => {
expect(
formatNumber(123456789n)
).toEqual('123,456,789');
});
it('formats Compact<BN>', (): void => {
expect(
formatNumber({
toBn: (): BN => new BN(12345),
unwrap: (): BN => new BN(0)
})
).toEqual('12,345');
});
it('formats negative numbers (locale=sl)', (): void => {
expect(
formatNumber(-123456, { locale: 'sl' })
).toEqual('-123.456');
});
it('formats BN numbers (locale=sl)', (): void => {
expect(
formatNumber(new BN(12345), { locale: 'sl' })
).toEqual('12.345');
});
it('formats BigInt numbers (locale=sl)', (): void => {
expect(
formatNumber(123456789n, { locale: 'sl' })
).toEqual('123.456.789');
});
});
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '../bn/bn.js';
import type { ToBn } from '../types.js';
import { bnToBn } from '../bn/toBn.js';
import { formatDecimal } from './formatDecimal.js';
import { getSeparator } from './getSeparator.js';
interface Options {
/**
* @description The locale to use
*/
locale?: string;
}
/**
* @name formatNumber
* @description Formats a number into string format with thousand separators
*/
export function formatNumber <ExtToBn extends ToBn> (
value?: ExtToBn | BN | bigint | number | null,
{ locale = 'en' }: Options = {}
): string {
const { thousand } = getSeparator(locale);
return formatDecimal(bnToBn(value).toString(), thousand);
}
@@ -0,0 +1,53 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { getSeparator } from './getSeparator.js';
describe('getSeparator', (): void => {
const normalizeSeparator = (separator: string) =>
separator === '\u202F' ? ' ' : separator;
const testLocales = [
{ decimal: '.', locale: 'en', thousand: ',' },
{ decimal: '.', locale: 'en-gb', thousand: ',' },
{ decimal: ',', locale: 'sl', thousand: '.' },
{ decimal: ',', locale: 'sl-si', thousand: '.' },
{ decimal: ',', locale: 'it-it', thousand: '.' },
{ decimal: '.', locale: 'ja-jp', thousand: ',' },
{ decimal: '.', locale: 'hi-IN', thousand: ',' },
{ decimal: '.', locale: undefined, thousand: ',' } // Fallback test
];
testLocales.forEach(({ decimal, locale, thousand }) => {
it(`uses ${locale || 'system default'} locale`, (): void => {
expect(
getSeparator(locale)
).toEqual({ decimal, thousand });
});
});
it('falls back for invalid locale', (): void => {
expect(
getSeparator('invalid-locale')
).toEqual({ decimal: '.', thousand: ',' }); // Default fallback
});
it('matches system locale dynamically', (): void => {
const n = 1000.1;
const expected = {
decimal: n.toLocaleString().substring(5, 6),
thousand: n.toLocaleString().substring(1, 2)
};
expect(getSeparator()).toEqual(expected);
});
it('handles locales with unique separators', (): void => {
const result = getSeparator('fr-FR');
expect(normalizeSeparator(result.thousand)).toBe(' ');
expect(result.decimal).toBe(',');
});
});
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* Get the decimal and thousand separator of a locale
* @param locale
* @returns {decimal: string, thousand: string}
*/
export function getSeparator (locale?: string):
{ thousand: string, decimal: string } {
return {
decimal: (0.1).toLocaleString(locale, { useGrouping: false }).charAt(1),
thousand: (1000).toLocaleString(locale, { useGrouping: true }).replace(/\d/g, '').charAt(0)
};
}
+9
View File
@@ -0,0 +1,9 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { formatBalance } from './formatBalance.js';
export { formatDate } from './formatDate.js';
export { formatDecimal } from './formatDecimal.js';
export { formatElapsed } from './formatElapsed.js';
export { formatNumber } from './formatNumber.js';
export { calcSi, findSi } from './si.js';
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SiDef } from '../types.js';
/** @internal */
export const SI_MID = 8;
/** @internal */
export const SI: SiDef[] = [
{ power: -24, text: 'yocto', value: 'y' },
{ power: -21, text: 'zepto', value: 'z' },
{ power: -18, text: 'atto', value: 'a' },
{ power: -15, text: 'femto', value: 'f' },
{ power: -12, text: 'pico', value: 'p' },
{ power: -9, text: 'nano', value: 'n' },
{ power: -6, text: 'micro', value: 'µ' },
{ power: -3, text: 'milli', value: 'm' },
{ power: 0, text: 'Unit', value: '-' }, // position 8
{ power: 3, text: 'Kilo', value: 'k' },
{ power: 6, text: 'Mill', value: 'M' }, // Mega, M
{ power: 9, text: 'Bill', value: 'B' }, // Giga, G
{ power: 12, text: 'Tril', value: 'T' }, // Tera, T
{ power: 15, text: 'Peta', value: 'P' },
{ power: 18, text: 'Exa', value: 'E' },
{ power: 21, text: 'Zeta', value: 'Z' },
{ power: 24, text: 'Yotta', value: 'Y' }
];
// Given a SI type (e.g. k, m, Y) find the SI definition
/** @internal */
export function findSi (type: string): SiDef {
// use a loop here, better RN support (which doesn't have [].find)
for (let i = 0, count = SI.length; i < count; i++) {
if (SI[i].value === type) {
return SI[i];
}
}
return SI[SI_MID];
}
/** @internal */
export function calcSi (text: string, decimals: number, forceUnit?: string): SiDef {
if (forceUnit) {
return findSi(forceUnit);
}
const siDefIndex = (SI_MID - 1) + Math.ceil((text.length - decimals) / 3);
return SI[siDefIndex] || SI[siDefIndex < 0 ? 0 : SI.length - 1];
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hasBuffer } from './has.js';
describe('hasBuffer', (): void => {
it('has Buffer (Jest + Node.js)', (): void => {
expect(hasBuffer).toEqual(typeof Buffer !== 'undefined');
});
});
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BufferClass } from './types.js';
import { BigInt } from '@pezkuwi/x-bigint';
import { xglobal } from '@pezkuwi/x-global';
// Since we run in very different environments, we have to ensure we have all
// the types used here for detection (some of these may require Node definitions,
// which are not available in Deno/browser)
declare const __dirname: unknown;
declare const module: unknown;
declare const require: unknown;
/** true if the environment has proper BigInt support */
export const hasBigInt = typeof BigInt === 'function' && typeof BigInt.asIntN === 'function';
/** true if the environment is CJS */
export const hasCjs = typeof require === 'function' && typeof module !== 'undefined';
/** true if the environment has __dirname available */
export const hasDirname = typeof __dirname !== 'undefined';
/** true if the environment is ESM */
export const hasEsm = !hasCjs;
/** true if the environment has WebAssembly available */
export const hasWasm = typeof WebAssembly !== 'undefined';
// NOTE We check the following on globalThis, avoiding specific polyfill detection
// that some bundlers such as parcel would add (this is a check, not a use)
/** true if the environment has support for Buffer (typically Node.js) */
export const hasBuffer = typeof xglobal.Buffer === 'function' && typeof (xglobal.Buffer as unknown as BufferClass).isBuffer === 'function';
/** true if the environment has process available (typically Node.js) */
export const hasProcess = typeof xglobal.process === 'object';
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexAddPrefix } from './index.js';
describe('hexAddPrefix', (): void => {
it('does not add when prefix is available', (): void => {
expect(
hexAddPrefix('0x0123')
).toEqual('0x0123');
});
it('adds the prefix when it is not available', (): void => {
expect(
hexAddPrefix('0123')
).toEqual('0x0123');
});
it('adds extra 0 when length % 2 === 1', (): void => {
expect(
hexAddPrefix('123')
).toEqual('0x0123');
});
it('returns null as 0x', (): void => {
expect(
hexAddPrefix(null)
).toEqual('0x');
});
});
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '../types.js';
import { hexHasPrefix } from './hasPrefix.js';
/**
* @name hexAddPrefix
* @summary Adds the `0x` prefix to string values.
* @description
* Returns a `0x` prefixed string from the input value. If the input is already prefixed, it is returned unchanged.
* @example
* <BR>
*
* ```javascript
* import { hexAddPrefix } from '@pezkuwi/util';
*
* console.log('With prefix', hexAddPrefix('0a0b12')); // => 0x0a0b12
* ```
*/
export function hexAddPrefix (value?: string | null): HexString {
return value && hexHasPrefix(value)
? value
: `0x${value && value.length % 2 === 1 ? '0' : ''}${value || ''}`;
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexFixLength } from './index.js';
describe('hexFixLength', (): void => {
it('returns bitLength === -1 as-is', (): void => {
expect(
hexFixLength('0x12345678')
).toEqual('0x12345678');
});
it('does not change when bitlength === length', (): void => {
expect(
hexFixLength('0x12345678', 32)
).toEqual('0x12345678');
});
it('trims values when bitLength > length', (): void => {
expect(
hexFixLength('0x12345678', 16)
).toEqual('0x5678');
});
it('returns as-is when bitLength < length', (): void => {
expect(
hexFixLength('0x1234', 32)
).toEqual('0x1234');
});
it('adds zeros when bitLength < length (withPadded)', (): void => {
expect(
hexFixLength('0x1234', 32, true)
).toEqual('0x00001234');
});
});
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '../types.js';
import { hexAddPrefix } from './addPrefix.js';
import { hexStripPrefix } from './stripPrefix.js';
/**
* @name hexFixLength
* @summary Shifts a hex string to a specific bitLength
* @description
* Returns a `0x` prefixed string with the specified number of bits contained in the return value. (If bitLength is -1, length checking is not done). Values with more bits are trimmed to the specified length. Input values with less bits are returned as-is by default. When `withPadding` is set, shorter values are padded with `0`.
* @example
* <BR>
*
* ```javascript
* import { hexFixLength } from '@pezkuwi/util';
*
* console.log('fixed', hexFixLength('0x12', 16)); // => 0x12
* console.log('fixed', hexFixLength('0x12', 16, true)); // => 0x0012
* console.log('fixed', hexFixLength('0x0012', 8)); // => 0x12
* ```
*/
export function hexFixLength (value: string, bitLength = -1, withPadding = false): HexString {
const strLength = Math.ceil(bitLength / 4);
const hexLength = strLength + 2;
return hexAddPrefix(
(bitLength === -1 || value.length === hexLength || (!withPadding && value.length < hexLength))
? hexStripPrefix(value)
: (value.length > hexLength)
? hexStripPrefix(value).slice(-1 * strLength)
: `${'0'.repeat(strLength)}${hexStripPrefix(value)}`.slice(-1 * strLength)
);
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexHasPrefix } from './index.js';
describe('hexHasPrefix', (): void => {
it('returns true when hex prefix is found', (): void => {
expect(
hexHasPrefix('0x1234')
).toEqual(true);
});
it('returns false when no prefix attached', (): void => {
expect(
hexHasPrefix('123')
).toEqual(false);
});
it('returns false when null value supplied', (): void => {
expect(
hexHasPrefix(null)
).toEqual(false);
});
it('returns false when non-string value supplied', (): void => {
expect(
hexHasPrefix(false as unknown as string)
).toEqual(false);
});
});
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '../types.js';
import { isHex } from '../is/hex.js';
/**
* @name hexHasPrefix
* @summary Tests for the existence of a `0x` prefix.
* @description
* Checks for a valid hex input value and if the start matched `0x`
* @example
* <BR>
*
* ```javascript
* import { hexHasPrefix } from '@pezkuwi/util';
*
* console.log('has prefix', hexHasPrefix('0x1234')); // => true
* ```
*/
export function hexHasPrefix (value?: string | null): value is HexString {
return !!value && isHex(value, -1);
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Internal utilities to create and test for hex values
*/
export { hexAddPrefix } from './addPrefix.js';
export { hexFixLength } from './fixLength.js';
export { hexHasPrefix } from './hasPrefix.js';
export { hexStripPrefix } from './stripPrefix.js';
export { hexToBigInt } from './toBigInt.js';
export { hexToBn } from './toBn.js';
export { hexToNumber } from './toNumber.js';
export { hexToString } from './toString.js';
export { hexToU8a } from './toU8a.js';
+44
View File
@@ -0,0 +1,44 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexStripPrefix } from './index.js';
describe('hexStripPrefix', (): void => {
it('returns an empty string when null value supplied', (): void => {
expect(
hexStripPrefix(null)
).toEqual('');
});
it('returns an empty string when 0x value supplied', (): void => {
expect(
hexStripPrefix('0x')
).toEqual('');
});
it('strips the prefix from hex strings', (): void => {
expect(
hexStripPrefix('0x1223')
).toEqual('1223');
});
it('strips the prefix from hex strings (non 2 lnegth)', (): void => {
expect(
hexStripPrefix('0x123')
).toEqual('123');
});
it('returns un-prefixed hex as-is', (): void => {
expect(
hexStripPrefix('abcd1223')
).toEqual('abcd1223');
});
it('throws when invalid hex', (): void => {
expect(
() => hexStripPrefix('0x0x01ab')
).toThrow(/Expected hex value to convert/);
});
});
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { REGEX_HEX_NOPREFIX, REGEX_HEX_PREFIXED } from '../is/hex.js';
/**
* @name hexStripPrefix
* @summary Strips any leading `0x` prefix.
* @description
* Tests for the existence of a `0x` prefix, and returns the value without the prefix. Un-prefixed values are returned as-is.
* @example
* <BR>
*
* ```javascript
* import { hexStripPrefix } from '@pezkuwi/util';
*
* console.log('stripped', hexStripPrefix('0x1234')); // => 1234
* ```
*/
export function hexStripPrefix (value?: string | null): string {
if (!value || value === '0x') {
return '';
} else if (REGEX_HEX_PREFIXED.test(value)) {
return value.substring(2);
} else if (REGEX_HEX_NOPREFIX.test(value)) {
return value;
}
throw new Error(`Expected hex value to convert, found '${value}'`);
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexToBigInt } from './index.js';
describe('hexToBigInt', (): void => {
it('converts prefixed hex values to BN', (): void => {
expect(
hexToBigInt('0x81')
).toEqual(0x81n);
});
it('converts null values to BN(0)', (): void => {
expect(
hexToBigInt(null)
).toEqual(0n);
});
it('converts 0x values to BN(0)', (): void => {
expect(
hexToBigInt('0x')
).toEqual(0n);
});
it('should convert with Big Endian by default', (): void => {
expect(
hexToBigInt('0x0100123456')
).toEqual(0x0100123456n);
});
it('converts 0x values to BN(0) (LE)', (): void => {
expect(
hexToBigInt('0x', { isLe: true })
).toEqual(0n);
});
it('converts little-endian', (): void => {
expect(
hexToBigInt('0x4500000000000000', { isLe: true })
).toEqual(69n);
});
it('handles negative numbers (LE)', (): void => {
expect(
hexToBigInt('0x2efb', { isLe: true, isNegative: true })
).toEqual(-1234n);
});
it('handles negative numbers (BE)', (): void => {
expect(
hexToBigInt('0xfb2e', { isLe: false, isNegative: true })
).toEqual(-1234n);
});
it('handles negative numbers (LE, 128)', (): void => {
expect(
hexToBigInt('0x00009c584c491ff2ffffffffffffffff', { isLe: true, isNegative: true })
).toEqual(-1000000000000000000n);
});
it('handles starting zeros correctly (BE)', (): void => {
expect(
hexToBigInt('0x0000000000000100', { isLe: false })
).toEqual(256n);
});
it('handles starting zeros correctly (LE)', (): void => {
expect(
hexToBigInt('0x0001000000000000', { isLe: true })
).toEqual(256n);
});
});
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ToBnOptions } from '../types.js';
import { BigInt } from '@pezkuwi/x-bigint';
import { u8aToBigInt } from '../u8a/toBigInt.js';
import { hexToU8a } from './toU8a.js';
/**
* @name hexToBigInt
* @summary Creates a BigInt instance object from a hex string.
*/
export function hexToBigInt (value?: string | null, { isLe = false, isNegative = false }: ToBnOptions = {}): bigint {
return !value || value === '0x'
? BigInt(0)
: u8aToBigInt(hexToU8a(value), { isLe, isNegative });
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexToBn } from './index.js';
describe('hexToBn', (): void => {
it('converts prefixed hex values to BN', (): void => {
expect(
hexToBn('0x81').toString(16)
).toBe('81');
});
it('converts null values to BN(0)', (): void => {
expect(
hexToBn(null).toNumber()
).toBe(0);
});
it('converts 0x values to BN(0)', (): void => {
expect(
hexToBn('0x').toNumber()
).toBe(0);
});
it('should convert with Big Endian by default', (): void => {
expect(
hexToBn('0x0100').toNumber()
).toBe(256);
});
it('converts 0x values to BN(0) (LE)', (): void => {
expect(
hexToBn('0x', { isLe: true }).toNumber()
).toBe(0);
});
it('converts little-endian', (): void => {
expect(
hexToBn('0x4500000000000000', { isLe: true }).toNumber()
).toBe(69);
});
it('handles negative numbers (LE)', (): void => {
expect(
hexToBn('0x2efb', { isLe: true, isNegative: true }).toNumber()
).toBe(-1234);
});
it('handles negative numbers (BE)', (): void => {
expect(
hexToBn('0xfb2e', { isLe: false, isNegative: true }).toNumber()
).toBe(-1234);
});
it('handles negative numbers (LE, 128)', (): void => {
expect(
hexToBn('0x00009c584c491ff2ffffffffffffffff', { isLe: true, isNegative: true }).toString()
).toEqual('-1000000000000000000');
});
it('handles starting zeros correctly (BE)', (): void => {
expect(
hexToBn('0x0000000000000100', { isLe: false }).toNumber()
).toBe(256);
});
it('handles starting zeros correctly (LE)', (): void => {
expect(
hexToBn('0x0001000000000000', { isLe: true }).toNumber()
).toBe(256);
});
});

Some files were not shown because too many files have changed in this diff Show More