@metamask/utils
Version:
Various JavaScript/TypeScript utilities of wide relevance to the MetaMask codebase
188 lines • 7.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.remove0x = exports.add0x = exports.isValidHexAddress = exports.isValidHexAddressUnmemoized = exports.isValidChecksumAddress = exports.isValidChecksumAddressUnmemoized = exports.getChecksumAddress = exports.getChecksumAddressUnmemoized = exports.assertIsStrictHexString = exports.assertIsHexString = exports.isHexChecksumAddress = exports.isHexAddress = exports.isStrictHexString = exports.isHexString = exports.HexChecksumAddressStruct = exports.HexAddressStruct = exports.StrictHexStruct = exports.HexStruct = void 0;
const superstruct_1 = require("@metamask/superstruct");
const sha3_1 = require("@noble/hashes/sha3");
const lodash_1 = require("lodash");
const assert_1 = require("./assert.cjs");
// Use native regexes instead of superstruct for maximum performance.
// Pre-compiled regex for maximum performance - avoids recompilation on each call
const HEX_REGEX = /^(?:0x)?[0-9a-f]+$/iu;
const STRICT_HEX_REGEX = /^0x[0-9a-f]+$/iu;
const HEX_ADDRESS_REGEX = /^0x[0-9a-f]{40}$/u;
const HEX_CHECKSUM_ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/u;
exports.HexStruct = (0, superstruct_1.pattern)((0, superstruct_1.string)(), HEX_REGEX);
exports.StrictHexStruct = (0, superstruct_1.pattern)((0, superstruct_1.string)(), STRICT_HEX_REGEX);
exports.HexAddressStruct = (0, superstruct_1.pattern)((0, superstruct_1.string)(), HEX_ADDRESS_REGEX);
exports.HexChecksumAddressStruct = (0, superstruct_1.pattern)((0, superstruct_1.string)(), HEX_CHECKSUM_ADDRESS_REGEX);
const isString = (value) => typeof value === 'string';
/**
* Check if a string is a valid hex string.
*
* @param value - The value to check.
* @returns Whether the value is a valid hex string.
*/
function isHexString(value) {
return isString(value) && HEX_REGEX.test(value);
}
exports.isHexString = isHexString;
/**
* Strictly check if a string is a valid hex string. A valid hex string must
* start with the "0x"-prefix.
*
* @param value - The value to check.
* @returns Whether the value is a valid hex string.
*/
function isStrictHexString(value) {
return isString(value) && STRICT_HEX_REGEX.test(value);
}
exports.isStrictHexString = isStrictHexString;
/**
* Check if a string is a valid hex address.
*
* @param value - The value to check.
* @returns Whether the value is a valid hex address.
*/
function isHexAddress(value) {
return isString(value) && HEX_ADDRESS_REGEX.test(value);
}
exports.isHexAddress = isHexAddress;
/**
* Check if a string is a valid hex checksum address.
*
* @param value - The value to check.
* @returns Whether the value is a valid hex checksum address.
*/
function isHexChecksumAddress(value) {
return isString(value) && HEX_CHECKSUM_ADDRESS_REGEX.test(value);
}
exports.isHexChecksumAddress = isHexChecksumAddress;
/**
* Assert that a value is a valid hex string.
*
* @param value - The value to check.
* @throws If the value is not a valid hex string.
*/
function assertIsHexString(value) {
(0, assert_1.assert)(isHexString(value), 'Value must be a hexadecimal string.');
}
exports.assertIsHexString = assertIsHexString;
/**
* Assert that a value is a valid hex string. A valid hex string must start with
* the "0x"-prefix.
*
* @param value - The value to check.
* @throws If the value is not a valid hex string.
*/
function assertIsStrictHexString(value) {
(0, assert_1.assert)(isStrictHexString(value), 'Value must be a hexadecimal string, starting with "0x".');
}
exports.assertIsStrictHexString = assertIsStrictHexString;
/**
* Encode a passed hex string as an ERC-55 mixed-case checksum address.
* This is the unmemoized version, primarily used for testing.
*
* @param hexAddress - The hex address to encode.
* @returns The address encoded according to ERC-55.
* @see https://eips.ethereum.org/EIPS/eip-55
*/
function getChecksumAddressUnmemoized(hexAddress) {
(0, assert_1.assert)(isHexChecksumAddress(hexAddress), 'Invalid hex address.');
const address = remove0x(hexAddress).toLowerCase();
const hashBytes = (0, sha3_1.keccak_256)(address);
const { length } = address;
const result = new Array(length); // Pre-allocate array
for (let i = 0; i < length; i++) {
/* eslint-disable no-bitwise */
const byteIndex = i >> 1; // Faster than Math.floor(i / 2)
const nibbleIndex = i & 1; // Faster than i % 2
const byte = hashBytes[byteIndex];
const nibble = nibbleIndex === 0 ? byte >> 4 : byte & 0x0f;
/* eslint-enable no-bitwise */
result[i] = nibble >= 8 ? address[i].toUpperCase() : address[i];
}
return `0x${result.join('')}`;
}
exports.getChecksumAddressUnmemoized = getChecksumAddressUnmemoized;
/**
* Encode a passed hex string as an ERC-55 mixed-case checksum address.
* This function is memoized for performance.
*
* @param hexAddress - The hex address to encode.
* @returns The address encoded according to ERC-55.
* @see https://eips.ethereum.org/EIPS/eip-55
*/
exports.getChecksumAddress = (0, lodash_1.memoize)(getChecksumAddressUnmemoized);
/**
* Validate that the passed hex string is a valid ERC-55 mixed-case
* checksum address.
*
* @param possibleChecksum - The hex address to check.
* @returns True if the address is a checksum address.
*/
function isValidChecksumAddressUnmemoized(possibleChecksum) {
if (!isHexChecksumAddress(possibleChecksum)) {
return false;
}
return (0, exports.getChecksumAddress)(possibleChecksum) === possibleChecksum;
}
exports.isValidChecksumAddressUnmemoized = isValidChecksumAddressUnmemoized;
/**
* Validate that the passed hex string is a valid ERC-55 mixed-case
* checksum address.
*
* @param possibleChecksum - The hex address to check.
* @returns True if the address is a checksum address.
*/
exports.isValidChecksumAddress = (0, lodash_1.memoize)(isValidChecksumAddressUnmemoized);
/**
* Validate that the passed prefixed hex string is an all-lowercase
* hex address, or a valid mixed-case checksum address.
*
* @param possibleAddress - Input parameter to check against.
* @returns Whether or not the input is a valid hex address.
*/
function isValidHexAddressUnmemoized(possibleAddress) {
return (isHexAddress(possibleAddress) || (0, exports.isValidChecksumAddress)(possibleAddress));
}
exports.isValidHexAddressUnmemoized = isValidHexAddressUnmemoized;
/**
* Validate that the passed prefixed hex string is an all-lowercase
* hex address, or a valid mixed-case checksum address.
*
* @param possibleAddress - Input parameter to check against.
* @returns Whether or not the input is a valid hex address.
*/
exports.isValidHexAddress = (0, lodash_1.memoize)(isValidHexAddressUnmemoized);
/**
* Add the `0x`-prefix to a hexadecimal string. If the string already has the
* prefix, it is returned as-is.
*
* @param hexadecimal - The hexadecimal string to add the prefix to.
* @returns The prefixed hexadecimal string.
*/
function add0x(hexadecimal) {
if (hexadecimal.startsWith('0x')) {
return hexadecimal;
}
if (hexadecimal.startsWith('0X')) {
return `0x${hexadecimal.substring(2)}`;
}
return `0x${hexadecimal}`;
}
exports.add0x = add0x;
/**
* Remove the `0x`-prefix from a hexadecimal string. If the string doesn't have
* the prefix, it is returned as-is.
*
* @param hexadecimal - The hexadecimal string to remove the prefix from.
* @returns The un-prefixed hexadecimal string.
*/
function remove0x(hexadecimal) {
if (hexadecimal.startsWith('0x') || hexadecimal.startsWith('0X')) {
return hexadecimal.substring(2);
}
return hexadecimal;
}
exports.remove0x = remove0x;
//# sourceMappingURL=hex.cjs.map