@ocap/util
Version:
utils shared across multiple forge js libs, works in both node.js and browser
590 lines (589 loc) • 19.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fromTokenToUnit = exports.fromUnitToToken = exports.numberToString = exports.toHex = exports.hexToBytes = exports.bytesToHex = exports.numberToHex = exports.hexToNumber = exports.hexToUtf8 = exports.utf8ToHex = exports.toBN = exports.isHex = exports.isHexStrict = exports.isBigNumber = exports.isBN = exports.stripHexPrefix = exports.isHexPrefixed = exports.numberToBN = exports.rightPad = exports.leftPad = exports.BN = exports.isBase58btc = void 0;
exports.isUint8Array = isUint8Array;
exports.UUID = UUID;
exports.isUUID = isUUID;
exports.toUint8Array = toUint8Array;
exports.toBuffer = toBuffer;
exports.toBase58 = toBase58;
exports.fromBase58 = fromBase58;
exports.toBase64 = toBase64;
exports.fromBase64 = fromBase64;
exports.toAddress = toAddress;
exports.toDid = toDid;
exports.isSameDid = isSameDid;
exports.formatTxType = formatTxType;
const isBoolean_1 = __importDefault(require("lodash/isBoolean"));
const isString_1 = __importDefault(require("lodash/isString"));
const isNumber_1 = __importDefault(require("lodash/isNumber"));
const isObject_1 = __importDefault(require("lodash/isObject"));
const upperFirst_1 = __importDefault(require("lodash/upperFirst"));
const camelCase_1 = __importDefault(require("lodash/camelCase"));
const isNull_1 = __importDefault(require("lodash/isNull"));
const padEnd_1 = __importDefault(require("lodash/padEnd"));
exports.rightPad = padEnd_1.default;
const padStart_1 = __importDefault(require("lodash/padStart"));
exports.leftPad = padStart_1.default;
const bs58_1 = __importDefault(require("bs58"));
const base64_url_1 = __importDefault(require("base64-url"));
const bn_js_1 = __importDefault(require("bn.js"));
exports.BN = bn_js_1.default;
const utf8 = __importStar(require("utf8"));
const DID_PREFIX = 'did:abt:';
const zero = new bn_js_1.default(0);
const negative1 = new bn_js_1.default(-1);
const base58btc = {
encode: (v) => `z${bs58_1.default.encode(v)}`,
decode: (v) => bs58_1.default.decode(v.slice(1)),
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isBase58btc = (data) => {
if (typeof data !== 'string') {
return false;
}
if (data[0] === 'z') {
try {
base58btc.decode(data);
return true;
}
catch (err) {
return false;
}
}
return false;
};
exports.isBase58btc = isBase58btc;
/**
* Returns a BN object, converts a number value to a BN
* @param {string|number|BN} `arg` input a string number, hex string number, number, BigNumber or BN object
* @return {BN} `output` BN object of the number
* @throws if the argument is not an array, object that isn't a bignumber, not a string number or number
*/
const numberToBN = (arg) => {
if (typeof arg === 'string' || typeof arg === 'number') {
let multiplier = new bn_js_1.default(1); // eslint-disable-line
const formattedString = String(arg).toLowerCase().trim();
const isHexPrefixed = formattedString.substr(0, 2) === '0x' || formattedString.substr(0, 3) === '-0x';
let stringArg = (0, exports.stripHexPrefix)(formattedString); // eslint-disable-line
if (stringArg.substr(0, 1) === '-') {
stringArg = (0, exports.stripHexPrefix)(stringArg.slice(1));
multiplier = new bn_js_1.default(-1, 10);
}
stringArg = stringArg === '' ? '0' : stringArg;
if ((!stringArg.match(/^-?[0-9]+$/) && stringArg.match(/^[0-9A-Fa-f]+$/)) ||
stringArg.match(/^[a-fA-F]+$/) ||
(isHexPrefixed === true && stringArg.match(/^[0-9A-Fa-f]+$/))) {
return new bn_js_1.default(stringArg, 16).mul(multiplier);
}
if ((stringArg.match(/^-?[0-9]+$/) || stringArg === '') && isHexPrefixed === false) {
return new bn_js_1.default(stringArg, 10).mul(multiplier);
}
}
if ((0, exports.isBN)(arg)) {
return new bn_js_1.default(arg.toString(10), 10);
}
throw new Error(`[number-to-bn] while converting number ${JSON.stringify(arg)} to BN.js instance, error: invalid number value. Value must be an integer, hex string, BN or BigNumber instance. Note, decimals are not supported.`);
};
exports.numberToBN = numberToBN;
/**
* Returns a `boolean` on whether or not the `string` starts with '0x'
*
* @public
* @static
* @param {String} str the string input value
* @return {Boolean} a boolean if it is or is not hex prefixed
* @throws if the str input is not a string
*/
const isHexPrefixed = (str) => {
if (typeof str !== 'string') {
throw new Error('[is-hex-prefixed] value must be type string');
}
return str.slice(0, 2).toLowerCase() === '0x';
};
exports.isHexPrefixed = isHexPrefixed;
/**
* Removes '0x' from a given `String` if present
*
* @public
* @static
*/
const stripHexPrefix = (str) => {
if (typeof str === 'string') {
return (0, exports.isHexPrefixed)(str) ? str.slice(2) : str;
}
return str;
};
exports.stripHexPrefix = stripHexPrefix;
/**
* Returns true if object is BN, otherwise false
*
* @public
* @static
* @method isBN
* @param {Object} object
* @returns {Boolean}
*/
const isBN = (object) => object instanceof bn_js_1.default || (object && object.constructor && object.constructor.name === 'BN');
exports.isBN = isBN;
/**
* Returns true if object is BigNumber, otherwise false
*
* @public
* @static
* @method isBigNumber
* @param {Object} object
* @returns {Boolean}
*/
const isBigNumber = (object) => object && object.constructor && object.constructor.name === 'BigNumber';
exports.isBigNumber = isBigNumber;
/**
* Check if string is HEX, requires a 0x in front
*
* @public
* @static
* @method isHexStrict
* @param {String} hex to be checked
* @returns {Boolean}
*/
const isHexStrict = (hex) => ((0, isString_1.default)(hex) || (0, isNumber_1.default)(hex)) && /^(-)?0x[0-9a-f]*$/i.test(hex);
exports.isHexStrict = isHexStrict;
/**
* Check if string is HEX
*
* @public
* @static
* @method isHex
* @param {String} hex to be checked
* @returns {Boolean}
*/
const isHex = (hex) => ((0, isString_1.default)(hex) || (0, isNumber_1.default)(hex)) && /^(-0x|0x|0X|-0X)?[0-9a-f]*$/i.test(hex);
exports.isHex = isHex;
/**
* Takes an input and transforms it into an BN
*
* @public
* @static
* @method toBN
* @param {Number|String|BN} num, string, HEX string or BN
* @returns {BN} BN
*/
const toBN = (num, base = 10) => {
try {
if (typeof num === 'number' && num > 0) {
const numStr = Number(num).toLocaleString('fullwide', { useGrouping: false });
return new bn_js_1.default(numStr, base);
}
return (0, exports.numberToBN)(num);
}
catch (error) {
throw new Error(`${error} Given value: "${num}"`);
}
};
exports.toBN = toBN;
/**
* Should be called to get hex representation (prefixed by 0x) of utf8 string
*
* @public
* @static
* @method utf8ToHex
* @param {String} str
* @returns {String} hex representation of input string
*/
const utf8ToHex = (str) => {
str = utf8.encode(str);
let hex = '';
/* eslint-disable no-control-regex */
// remove \u0000 padding from either side
str = str.replace(/^(?:\u0000)*/, '');
str = str.split('').reverse().join('');
str = str.replace(/^(?:\u0000)*/, '');
str = str.split('').reverse().join('');
/* eslint-enable no-control-regex */
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
const n = code.toString(16);
hex += n.length < 2 ? `0${n}` : n;
}
return `0x${hex}`;
};
exports.utf8ToHex = utf8ToHex;
/**
* Should be called to get utf8 from it's hex representation
*
* @public
* @static
* @method hexToUtf8
* @param {String} hex
* @returns {String} ascii string representation of hex value
*/
const hexToUtf8 = (hex) => {
if (!(0, exports.isHexStrict)(hex))
throw new Error(`The parameter "${hex}" must be a valid HEX string.`);
let str = '';
let code = 0;
hex = hex.replace(/^0x/i, '');
// remove 00 padding from either side
hex = hex.replace(/^(?:00)*/, '');
hex = hex.split('').reverse().join('');
hex = hex.replace(/^(?:00)*/, '');
hex = hex.split('').reverse().join('');
const l = hex.length;
for (let i = 0; i < l; i += 2) {
code = parseInt(hex.substr(i, 2), 16);
// if (code !== 0) {
str += String.fromCharCode(code);
// }
}
return utf8.decode(str);
};
exports.hexToUtf8 = hexToUtf8;
/**
* Converts value to number representation
*
* @public
* @static
* @method hexToNumber
* @param {String|Number|BN} value
* @returns {Number}
*/
const hexToNumber = (value) => {
if (!value) {
return 0;
}
return (0, exports.toBN)(value).toNumber();
};
exports.hexToNumber = hexToNumber;
/**
* Converts value to hex representation
*
* @public
* @static
* @method numberToHex
* @param {String|Number|BN} value
* @returns {String}
*/
const numberToHex = (value) => {
if ((0, isNull_1.default)(value) || typeof value === 'undefined') {
return value;
}
// eslint-disable-next-line no-restricted-globals
if (!isFinite(value) && !(0, exports.isHex)(value)) {
throw new Error(`Given input "${value}" is not a number.`);
}
const num = (0, exports.toBN)(value);
const result = num.toString(16);
return num.lt(new bn_js_1.default(0)) ? `-0x${result.substr(1)}` : `0x${result}`;
};
exports.numberToHex = numberToHex;
/**
* Convert a byte array to a hex string
* Note: Implementation from crypto-js
*
* @public
* @static
* @method bytesToHex
* @param {Array} bytes
* @returns {String} the hex string
*/
const bytesToHex = (bytes) => {
const hex = [];
for (let i = 0; i < bytes.length; i++) {
hex.push((bytes[i] >>> 4).toString(16));
hex.push((bytes[i] & 0xf).toString(16));
}
return `0x${hex.join('')}`;
};
exports.bytesToHex = bytesToHex;
/**
* Convert a hex string to a byte array
* Note: Implementation from crypto-js
*
* @public
* @static
* @method hexToBytes
* @param {String} hex
* @returns {Array} the byte array
*/
const hexToBytes = (hex) => {
hex = hex.toString(16);
if (!(0, exports.isHex)(hex)) {
throw new Error(`Given value "${hex}" is not a valid hex string.`);
}
hex = hex.replace(/^0x/i, '');
hex = hex.length % 2 ? `0${hex}` : hex;
const bytes = [];
for (let c = 0; c < hex.length; c += 2) {
bytes.push(parseInt(hex.substr(c, 2), 16));
}
return bytes;
};
exports.hexToBytes = hexToBytes;
/**
* Auto converts any given value into it's hex representation.
* And even stringify objects before.
*
* @public
* @static
* @method toHex
* @param {String|Number|BN|Object|TypedArray|Buffer} value
* @param {Boolean} returnType
* @returns {String}
*/
const toHex = (value, returnType = false) => {
if (isUint8Array(value) || Buffer.isBuffer(value)) {
return returnType ? 'bytes' : (0, exports.bytesToHex)(value);
}
if ((0, exports.isBase58btc)(value)) {
return returnType ? 'bytes' : (0, exports.bytesToHex)(base58btc.decode(value));
}
if ((0, isBoolean_1.default)(value)) {
// eslint-disable-next-line no-nested-ternary
return returnType ? 'bool' : value ? '0x01' : '0x00';
}
if ((0, isObject_1.default)(value) && !(0, exports.isBigNumber)(value) && !(0, exports.isBN)(value)) {
return returnType ? 'string' : (0, exports.utf8ToHex)(JSON.stringify(value));
}
// if its a negative number, pass it through numberToHex
if (typeof value === 'string') {
if (value.indexOf('-0x') === 0 || value.indexOf('-0X') === 0) {
return returnType ? 'int256' : (0, exports.numberToHex)(value);
}
if (value.indexOf('0x') === 0 || value.indexOf('0X') === 0) {
return returnType ? 'bytes' : value;
}
// TODO: some edge case may be not properly handled here
return returnType ? 'string' : (0, exports.utf8ToHex)(value);
}
// eslint-disable-next-line no-nested-ternary
return returnType ? (+value < 0 ? 'int256' : 'uint256') : (0, exports.numberToHex)(value);
};
exports.toHex = toHex;
const numberToString = (arg) => {
if (typeof arg === 'string') {
if (!arg.match(/^-?[0-9.]+$/)) {
throw new Error(`Invalid value '${arg}' while converting to string, should be a number matching (^-?[0-9.]+).`);
}
return arg;
}
if (typeof arg === 'number') {
if (Number.isInteger(arg) && arg > 0) {
return Number(arg).toLocaleString('fullwide', { useGrouping: false });
}
return String(arg);
}
if (typeof arg === 'object' && arg.toString && (arg.toTwos || arg.dividedToIntegerBy)) {
if (arg.toPrecision) {
return String(arg.toPrecision());
}
return arg.toString(10);
}
throw new Error(`while converting number to string, invalid number value '${arg}' type ${typeof arg}.`);
};
exports.numberToString = numberToString;
/**
* Format a big number to human readable number, such as 1_0000_0000_0000_000 => 1 Token
*/
const fromUnitToToken = (input, decimal = 18, optionsInput = {}) => {
let unit = (0, exports.toBN)(input);
const negative = unit.lt(zero);
const base = (0, exports.toBN)(`1${'0'.repeat(decimal)}`, 10);
const baseLength = base.toString(10).length - 1 || 1;
const options = optionsInput || {};
if (negative) {
unit = unit.mul(negative1);
}
let fraction = unit.mod(base).toString(10);
while (fraction.length < baseLength) {
fraction = `0${fraction}`;
}
if (!options.pad) {
// eslint-disable-next-line prefer-destructuring
fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1];
}
let whole = unit.div(base).toString(10);
if (options.commify) {
whole = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
let value = `${whole}${fraction === '0' ? '' : `.${fraction}`}`;
if (negative) {
value = `-${value}`;
}
return value;
};
exports.fromUnitToToken = fromUnitToToken;
/**
* Convert human readable token number to big number instance
*/
const fromTokenToUnit = (input, decimal = 18) => {
let token = (0, exports.numberToString)(input);
const base = (0, exports.toBN)(`1${'0'.repeat(decimal)}`, 10);
const baseLength = base.toString(10).length - 1 || 1;
// Is it negative?
const negative = token.substring(0, 1) === '-';
if (negative) {
token = token.substring(1);
}
if (token === '.') {
throw new Error(`error converting token ${input} to unit, invalid value`);
}
// Split it into a whole and fractional part
const comps = token.split('.');
if (comps.length > 2) {
throw new Error(`error converting token ${input} to unit, too many decimal points`);
}
let whole = comps[0];
let fraction = comps[1];
if (!whole) {
whole = '0';
}
if (!fraction) {
fraction = '0';
}
if (fraction.length > baseLength) {
throw new Error(`error converting token ${input} to unit, too many decimal places`);
}
while (fraction.length < baseLength) {
fraction += '0';
}
whole = new bn_js_1.default(whole);
fraction = new bn_js_1.default(fraction);
let unit = whole.mul(base).add(fraction);
if (negative) {
unit = unit.mul(negative1);
}
return new bn_js_1.default(unit.toString(10), 10);
};
exports.fromTokenToUnit = fromTokenToUnit;
/**
* Validates if a value is an Uint8Array.
*/
function isUint8Array(value) {
return Object.prototype.toString.call(value) === '[object Uint8Array]';
}
/**
* Generate a random UUID
*/
function UUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
/**
* Check if a string is valid UUID
*/
function isUUID(str) {
return /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.test(str);
}
/**
* Convert input to Uint8Array on best effort, base64 node supported
*/
function toUint8Array(v) {
let vb = null;
if ([null, undefined, ''].includes(v)) {
vb = new Uint8Array();
}
else if (Buffer.isBuffer(v)) {
vb = new Uint8Array(v);
}
else if ((0, exports.isHexStrict)(v)) {
vb = new Uint8Array((0, exports.hexToBytes)(v));
}
else if (isUint8Array(v)) {
vb = new Uint8Array(v);
}
else if ((0, exports.isBase58btc)(v)) {
vb = new Uint8Array(base58btc.decode(v));
}
else if (typeof v === 'string') {
vb = new Uint8Array((0, exports.hexToBytes)((0, exports.toHex)(v)));
}
else {
throw new Error(`Unsupported input type ${typeof v} detected for toBuffer, only Uint8Array/Buffer/Hex/Base58 are supported`);
}
return vb;
}
/**
* Convert input to Buffer on best effort, base64 not supported
*/
function toBuffer(v) {
return Buffer.from(toUint8Array(v));
}
/**
* Convert input to base58btc format on best effort
*/
function toBase58(v) {
const buf = base58btc.encode(toUint8Array(v));
return Buffer.from(buf).toString('utf-8');
}
/**
* Decode base58 string
*/
function fromBase58(v) {
if ((0, exports.isBase58btc)(v) === false) {
throw new Error('fromBase58 expect strict base58 encoded string as input');
}
return Buffer.from(base58btc.decode(v));
}
/**
* Convert input to base64 format
*/
function toBase64(v, escape = true) {
const encoded = base64_url_1.default.encode(toBuffer(v));
return escape ? base64_url_1.default.escape(encoded) : encoded;
}
/**
* Decode base64(base64_url) string to buffer
*/
function fromBase64(v) {
if (typeof v !== 'string') {
throw new Error('fromBase64 requires input to be a string');
}
return Buffer.from(base64_url_1.default.unescape(v), 'base64');
}
/**
* Convert did to address: remove `did:abt:` prefix
*/
function toAddress(did) {
return did.replace(DID_PREFIX, '');
}
/**
* Convert address to did: prepend `did:abt:` prefix
*/
function toDid(address) {
return `${DID_PREFIX}${toAddress(address)}`;
}
function isSameDid(a, b) {
return toAddress(a).toLowerCase() === toAddress(b).toLowerCase();
}
function formatTxType(type) {
return (0, upperFirst_1.default)((0, camelCase_1.default)(type));
}