/** * @file common utils * @author atom-yang */ import BigNumber from 'bignumber.js'; // import bs58 from 'bs58'; import { base58 as bs58 } from '@scure/base'; import { UNIT_MAP, UNSIGNED_256_INT } from '../common/unitConstants.js'; import sha256 from './sha256.js'; export const base58 = { encode(data, encoding = 'hex') { let result = data; if (typeof data === 'string') { result = Buffer.from(data, encoding); } if (!(result instanceof Buffer)) { throw new TypeError('"data" argument must be an Array of Buffers'); } let hash = result; hash = Buffer.from(sha256(result), 'hex'); hash = Buffer.from(sha256(hash), 'hex'); hash = Buffer.from(result.toString('hex') + hash.slice(0, 4).toString('hex'), 'hex'); // Convert Buffer to Uint8Array for @scure/base return bs58.encode(new Uint8Array(hash)); }, decode(str, encoding) { // @scure/base returns Uint8Array, convert to Buffer const decoded = bs58.decode(str); const buffer = Buffer.from(decoded); let data = buffer.slice(0, -4); let hash = data; hash = Buffer.from(sha256(hash), 'hex'); hash = Buffer.from(sha256(hash), 'hex'); buffer.slice(-4).forEach((check, index) => { if (check !== hash[index]) { throw new Error('Invalid checksum'); } }); if (encoding) { data = data.toString(encoding); } return data; } }; export const chainIdConvertor = { // chainIdToBase58 (int32 chainId) chainIdToBase58(chainId) { const bufferTemp = Buffer.alloc(4); bufferTemp.writeInt32LE(`0x${chainId.toString('16')}`, 0); const bytes = Buffer.concat([bufferTemp], 3); // Convert Buffer to Uint8Array for @scure/base return bs58.encode(new Uint8Array(bytes)); }, base58ToChainId(base58String) { // @scure/base returns Uint8Array, convert to Buffer const decoded = bs58.decode(base58String); return Buffer.concat([Buffer.from(decoded)], 4).readInt32LE(0); } }; const arrayBufferToHex = arrayBuffer => Array.prototype.map.call(new Uint8Array(arrayBuffer), n => `0${n.toString(16)}`.slice(-2)).join(''); export const arrayToHex = value => { let hex = ''; if (value instanceof Buffer) { hex = value.toString('hex'); } else { // Uint8Array hex = arrayBufferToHex(value); } return hex; }; /** * Should be called to pad string to expected length * * @method padLeft * @param {String} string to be padded * @param {Number} charLen that result string should have * @param {String} sign, by default 0 * @returns {String} right aligned string */ export const padLeft = (string, charLen, sign) => { const length = charLen - string.length + 1; return new Array(length < 0 ? 0 : length).join(sign || '0') + string; }; /** * Should be called to pad string to expected length * * @method padRight * @param {String} string to be padded * @param {Number} charLen that result string should have * @param {String} sign, by default 0 * @returns {String} right aligned string */ export const padRight = (string, charLen, sign) => { const length = charLen - string.length + 1; return string + new Array(length < 0 ? 0 : length).join(sign || '0'); }; /** * Returns a hex rep from the encoded address * * @method decodeAddressRep * @param {String} address * @return {String} */ export const decodeAddressRep = address => { if (address.indexOf('_') > -1) { const parts = address.split('_'); const b58rep = parts[1]; return base58.decode(b58rep, 'hex'); } return base58.decode(address, 'hex'); }; /** * Returns a encoded address from the hex rep * * @method encodeAddressRep * @param {String} hex * @return {String} */ export const encodeAddressRep = hex => { const buf = Buffer.from(hex.replace('0x', ''), 'hex'); return base58.encode(buf, 'hex'); }; /** * Returns true if object is BigNumber, otherwise false * * @method isBigNumber * @param {Object} object * @return {Boolean} */ export const isBigNumber = object => object instanceof BigNumber || (object && object.constructor && object.constructor.name === 'BigNumber'); /** * Returns true if object is string, otherwise false * * @method isString * @param {Object} object * @return {Boolean} */ export const isString = object => typeof object === 'string' || (object && object.constructor && object.constructor.name === 'String'); /** * Returns true if object is function, otherwise false * * @method isFunction * @param {Object} object * @return {Boolean} */ export const isFunction = object => typeof object === 'function'; /** * Returns true if object is Object, otherwise false * * @method isObject * @param {Object} object * @return {Boolean} */ export const isObject = object => object !== null && !Array.isArray(object) && typeof object === 'object'; /** * Returns true if object is boolean, otherwise false * * @method isBoolean * @param {Object} object * @return {Boolean} */ export const isBoolean = object => typeof object === 'boolean'; /** * Returns true if given string is valid json object * * @method isJson * @param {String} str * @return {Boolean} */ export const isJson = str => { try { return !!JSON.parse(str); } catch (e) { return false; } }; /** * Returns true if given number is valid number * * @method isNumber * @param {Number} number * @return {Boolean} */ export const isNumber = number => number === +number; /** * Takes an input and transforms it into an bignumber * * @method toBigNumber * @param {Number|String|BigNumber} number, a number, string, HEX string or BigNumber * @return {BigNumber} BigNumber */ export const toBigNumber = number => { const num = number || 0; if (isBigNumber(num)) { return num; } if (isString(num) && (num.indexOf('0x') === 0 || num.indexOf('-0x') === 0)) { return new BigNumber(num.replace('0x', ''), 16); } return new BigNumber(num.toString(10), 10); }; /** * Returns value of unit in Wei * * @method getValueOfUnit * @param {String} unit the unit to convert to, default ether * @returns {BigNumber} value of the unit (in Wei) * @throws error if the unit is not correct:w */ export const getValueOfUnit = unit => { const unitValue = UNIT_MAP[unit ? unit.toLowerCase() : 'ether']; if (unitValue === undefined) { // eslint-disable-next-line max-len throw new Error( `This unit doesn\'t exists, please use the one of the following units ${JSON.stringify(UNIT_MAP, null, 2)}` ); } return new BigNumber(unitValue, 10); }; /** * Takes a number of wei and converts it to any other ether unit. * * Possible units are: * SI Short SI Full Effigy Other * - kwei femtoether babbage * - mwei picoether lovelace * - gwei nanoether shannon nano * - -- microether szabo micro * - -- milliether finney milli * - ether -- -- * - kether -- grand * - mether * - gether * - tether * * @method fromWei * @param {Number|String} number can be a number, number string or a HEX of a decimal * @param {String} unit the unit to convert to, default ether * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number */ export const fromWei = (number, unit) => { const returnValue = toBigNumber(number).dividedBy(getValueOfUnit(unit)); return isBigNumber(number) ? returnValue : returnValue.toString(10); }; /** * Takes a number of a unit and converts it to wei. * * Possible units are: * SI Short SI Full Effigy Other * - kwei femtoether babbage * - mwei picoether lovelace * - gwei nanoether shannon nano * - -- microether szabo micro * - -- milliether finney milli * - ether -- -- * - kether -- grand * - mether * - gether * - tether * * @method toWei * @param {Number|String|BigNumber} number can be a number, number string or a HEX of a decimal * @param {String} unit the unit to convert from, default ether * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number */ export const toWei = (number, unit) => { const returnValue = toBigNumber(number).times(getValueOfUnit(unit)); return isBigNumber(number) ? returnValue : returnValue.toString(10); }; /** * Takes and input transforms it into bignumber and if it is negative value, into two's complement * bignumber.js get rid of round + floor in 6.0 https://github.com/MikeMcl/bignumber.js/issues/139 * the method lessThan was named isLessThan after 6.0 https://github.com/MikeMcl/bignumber.js/issues/152 * @method toTwosComplement * @param {Number|String|BigNumber} number * @return {BigNumber} */ export const toTwosComplement = number => { const bigNumber = toBigNumber(number).integerValue(); if (bigNumber.isLessThan(0)) { return new BigNumber(UNSIGNED_256_INT, 16).plus(bigNumber).plus(1); } return bigNumber; }; /** * Returns hex * * @method uint8ArrayToHex * @param {Array} uint8Array * @return {String} */ export const uint8ArrayToHex = uint8Array => { let string = ''; uint8Array.forEach(item => { let hex = item.toString(16); if (hex.length <= 1) { hex = `0${hex}`; } string += hex; }); return string; }; export function byteStringToHex(byteString) { return Array.from(byteString) .map(byte => byte.charCodeAt(0).toString(16).padStart(2, '0')) .join(''); } /** * empty function */ export const noop = () => {}; /** * * @param {object} obj The object to modify * @param {string} path The path of the property to set * @param {*} value The value to set * @example * * const object = { 'a': [{ 'b': { 'c': 3 } }] } * * set(object, 'test.b.c', 4) * console.log(object.test.b.c) * // => 4 */ export const setPath = (obj, path, value) => { const paths = path.split('.'); paths.reduce((acc, p, index) => { if (index === paths.length - 1) { acc[p] = value; return acc; } acc[p] = {}; return acc[p]; }, obj); }; export const unpackSpecifiedTypeData = ({ data, dataType, encoding = 'hex' }) => { const buffer = Buffer.from(data, encoding); const decoded = dataType.decode(buffer); const result = dataType.toObject(decoded, { enums: String, // enums as string names longs: String, // longs as strings (requires long.js) bytes: String, // bytes as base64 encoded strings defaults: true, // includes default values arrays: true, // populates empty arrays (repeated fields) even if defaults=false objects: true, // populates empty objects (map fields) even if defaults=false oneofs: true // includes virtual oneof fields set to the present field's name }); return result; }; // eslint-disable-next-line no-unused-vars export function deserializeTransaction(rawTx, paramsDataType) { console.error( `deprecated method (>=3.5.0), please use use utils/transaction.js deserializeTransaction` ); } /** * * @param {String} userName Username * @param {String} password Password * @return {any} Authorization information * * const authorization = getAuthorization('test','pass') * console.log(authorization) * // => Basic dGVzdDpwYXNz */ export function getAuthorization(userName, password) { const base = Buffer.from(`${userName}:${password}`).toString('base64'); return `Basic ${base}`; } /** * * Use rawTransaction to get transaction id * @param {String} rawTx rawTransaction * @return {String} string * * const txId = getTransactionId('0a220a2071a4dc8cdf109bd72913c90c3fc666c78d080cdda0da7f3abbc7105c6b651fd512220a2089ac786c8ad3b56f63a6f2767369a5273f801de2415b613c783cad3d148ce3ab18d5d3bb35220491cf6ba12a18537761704578616374546f6b656e73466f72546f6b656e73325008c0f7f27110bbe5947c1a09534752544553542d311a03454c4622220a2071a4dc8cdf109bd72913c90c3fc666c78d080cdda0da7f3abbc7105c6b651fd52a08088996ceb0061000320631323334353682f10441ec6ad50c4b210976ba0ba5c287ab6fabd0c444839e2505ecb1b5f52838095b290cb245ec1c97dade3bde6ac14c6892e526569e9b71240d3c120b1a6c8e41afba00'); * console.log(txId); * // => cf564f3169012cb173efcf5543b2a71b030b16fad3ddefe3e04a5c1e1bc0047d */ // eslint-disable-next-line no-unused-vars export function getTransactionId(rawTx) { console.error( `deprecated method (>=3.5.0), please use utils/transaction.js getTransactionId` ); } export function validateMulti(obj) { if (Object.keys(obj).length !== 2) { return false; } // check if every item has chainUrl and contractAddress return Object.values(obj).every( value => // eslint-disable-next-line operator-linebreak Object.prototype.hasOwnProperty.call(value, 'chainUrl') && Object.prototype.hasOwnProperty.call(value, 'contractAddress') ); } // /** // * Converts value to it's hex representation // * // * @method fromDecimal // * @param {String|Number|BigNumber} // * @return {String} // */ // export const fromDecimal = value => { // const number = toBigNumber(value); // const result = number.toString(16); // // return number.lessThan(0) ? `-0x${result.substr(1)}` : `0x${result}`; // }; // // /** // * Should be called to get hex representation (prefixed by 0x) of utf8 string // * // * @method fromUtf8 // * @param {String} string // * @param {Boolean} allowZero to convert code point zero to 00 instead of end of string // * @returns {String} hex representation of input string // */ // export const fromUtf8 = (str, allowZero) => { // const encodeStr = utf8.encode(str); // let hex = ''; // for (let i = 0; i < encodeStr.length; i++) { // const code = encodeStr.charCodeAt(i); // if (code === 0) { // if (allowZero) { // hex += '00'; // } else { // break; // } // } else { // const n = code.toString(16); // hex += n.length < 2 ? `0${n}` : n; // } // } // return `0x${hex}`; // };