UNPKG

ox

Version:

Ethereum Standard Library

716 lines 20.3 kB
import { equalBytes } from '@noble/curves/abstract/utils'; import * as Bytes from './Bytes.js'; import * as Errors from './Errors.js'; import * as Json from './Json.js'; import * as internal_bytes from './internal/bytes.js'; import * as internal from './internal/hex.js'; const encoder = /*#__PURE__*/ new TextEncoder(); const hexes = /*#__PURE__*/ Array.from({ length: 256 }, (_v, i) => i.toString(16).padStart(2, '0')); /** * Asserts if the given value is {@link ox#Hex.Hex}. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.assert('abc') * // @error: InvalidHexValueTypeError: * // @error: Value `"abc"` of type `string` is an invalid hex type. * // @error: Hex types must be represented as `"0x\${string}"`. * ``` * * @param value - The value to assert. * @param options - Options. */ export function assert(value, options = {}) { const { strict = false } = options; if (!value) throw new InvalidHexTypeError(value); if (typeof value !== 'string') throw new InvalidHexTypeError(value); if (strict) { if (!/^0x[0-9a-fA-F]*$/.test(value)) throw new InvalidHexValueError(value); } if (!value.startsWith('0x')) throw new InvalidHexValueError(value); } /** * Concatenates two or more {@link ox#Hex.Hex}. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.concat('0x123', '0x456') * // @log: '0x123456' * ``` * * @param values - The {@link ox#Hex.Hex} values to concatenate. * @returns The concatenated {@link ox#Hex.Hex} value. */ export function concat(...values) { return `0x${values.reduce((acc, x) => acc + x.replace('0x', ''), '')}`; } /** * Instantiates a {@link ox#Hex.Hex} value from a hex string or {@link ox#Bytes.Bytes} value. * * :::tip * * To instantiate from a **Boolean**, **String**, or **Number**, use one of the following: * * - `Hex.fromBoolean` * * - `Hex.fromString` * * - `Hex.fromNumber` * * ::: * * @example * ```ts twoslash * import { Bytes, Hex } from 'ox' * * Hex.from('0x48656c6c6f20576f726c6421') * // @log: '0x48656c6c6f20576f726c6421' * * Hex.from(Bytes.from([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33])) * // @log: '0x48656c6c6f20576f726c6421' * ``` * * @param value - The {@link ox#Bytes.Bytes} value to encode. * @returns The encoded {@link ox#Hex.Hex} value. */ export function from(value) { if (value instanceof Uint8Array) return fromBytes(value); if (Array.isArray(value)) return fromBytes(new Uint8Array(value)); return value; } /** * Encodes a boolean into a {@link ox#Hex.Hex} value. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.fromBoolean(true) * // @log: '0x1' * * Hex.fromBoolean(false) * // @log: '0x0' * * Hex.fromBoolean(true, { size: 32 }) * // @log: '0x0000000000000000000000000000000000000000000000000000000000000001' * ``` * * @param value - The boolean value to encode. * @param options - Options. * @returns The encoded {@link ox#Hex.Hex} value. */ export function fromBoolean(value, options = {}) { const hex = `0x${Number(value)}`; if (typeof options.size === 'number') { internal.assertSize(hex, options.size); return padLeft(hex, options.size); } return hex; } /** * Encodes a {@link ox#Bytes.Bytes} value into a {@link ox#Hex.Hex} value. * * @example * ```ts twoslash * import { Bytes, Hex } from 'ox' * * Hex.fromBytes(Bytes.from([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33])) * // @log: '0x48656c6c6f20576f726c6421' * ``` * * @param value - The {@link ox#Bytes.Bytes} value to encode. * @param options - Options. * @returns The encoded {@link ox#Hex.Hex} value. */ export function fromBytes(value, options = {}) { let string = ''; for (let i = 0; i < value.length; i++) string += hexes[value[i]]; const hex = `0x${string}`; if (typeof options.size === 'number') { internal.assertSize(hex, options.size); return padRight(hex, options.size); } return hex; } /** * Encodes a number or bigint into a {@link ox#Hex.Hex} value. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.fromNumber(420) * // @log: '0x1a4' * * Hex.fromNumber(420, { size: 32 }) * // @log: '0x00000000000000000000000000000000000000000000000000000000000001a4' * ``` * * @param value - The number or bigint value to encode. * @param options - Options. * @returns The encoded {@link ox#Hex.Hex} value. */ export function fromNumber(value, options = {}) { const { signed, size } = options; const value_ = BigInt(value); let maxValue; if (size) { if (signed) maxValue = (1n << (BigInt(size) * 8n - 1n)) - 1n; else maxValue = 2n ** (BigInt(size) * 8n) - 1n; } else if (typeof value === 'number') { maxValue = BigInt(Number.MAX_SAFE_INTEGER); } const minValue = typeof maxValue === 'bigint' && signed ? -maxValue - 1n : 0; if ((maxValue && value_ > maxValue) || value_ < minValue) { const suffix = typeof value === 'bigint' ? 'n' : ''; throw new IntegerOutOfRangeError({ max: maxValue ? `${maxValue}${suffix}` : undefined, min: `${minValue}${suffix}`, signed, size, value: `${value}${suffix}`, }); } const stringValue = (signed && value_ < 0 ? (1n << BigInt(size * 8)) + BigInt(value_) : value_).toString(16); const hex = `0x${stringValue}`; if (size) return padLeft(hex, size); return hex; } /** * Encodes a string into a {@link ox#Hex.Hex} value. * * @example * ```ts twoslash * import { Hex } from 'ox' * Hex.fromString('Hello World!') * // '0x48656c6c6f20576f726c6421' * * Hex.fromString('Hello World!', { size: 32 }) * // '0x48656c6c6f20576f726c64210000000000000000000000000000000000000000' * ``` * * @param value - The string value to encode. * @param options - Options. * @returns The encoded {@link ox#Hex.Hex} value. */ export function fromString(value, options = {}) { return fromBytes(encoder.encode(value), options); } /** * Checks if two {@link ox#Hex.Hex} values are equal. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.isEqual('0xdeadbeef', '0xdeadbeef') * // @log: true * * Hex.isEqual('0xda', '0xba') * // @log: false * ``` * * @param hexA - The first {@link ox#Hex.Hex} value. * @param hexB - The second {@link ox#Hex.Hex} value. * @returns `true` if the two {@link ox#Hex.Hex} values are equal, `false` otherwise. */ export function isEqual(hexA, hexB) { return equalBytes(Bytes.fromHex(hexA), Bytes.fromHex(hexB)); } /** * Pads a {@link ox#Hex.Hex} value to the left with zero bytes until it reaches the given `size` (default: 32 bytes). * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.padLeft('0x1234', 4) * // @log: '0x00001234' * ``` * * @param value - The {@link ox#Hex.Hex} value to pad. * @param size - The size (in bytes) of the output hex value. * @returns The padded {@link ox#Hex.Hex} value. */ export function padLeft(value, size) { return internal.pad(value, { dir: 'left', size }); } /** * Pads a {@link ox#Hex.Hex} value to the right with zero bytes until it reaches the given `size` (default: 32 bytes). * * @example * ```ts * import { Hex } from 'ox' * * Hex.padRight('0x1234', 4) * // @log: '0x12340000' * ``` * * @param value - The {@link ox#Hex.Hex} value to pad. * @param size - The size (in bytes) of the output hex value. * @returns The padded {@link ox#Hex.Hex} value. */ export function padRight(value, size) { return internal.pad(value, { dir: 'right', size }); } /** * Generates a random {@link ox#Hex.Hex} value of the specified length. * * @example * ```ts twoslash * import { Hex } from 'ox' * * const hex = Hex.random(32) * // @log: '0x...' * ``` * * @returns Random {@link ox#Hex.Hex} value. */ export function random(length) { return fromBytes(Bytes.random(length)); } /** * Returns a section of a {@link ox#Bytes.Bytes} value given a start/end bytes offset. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.slice('0x0123456789', 1, 4) * // @log: '0x234567' * ``` * * @param value - The {@link ox#Hex.Hex} value to slice. * @param start - The start offset (in bytes). * @param end - The end offset (in bytes). * @param options - Options. * @returns The sliced {@link ox#Hex.Hex} value. */ export function slice(value, start, end, options = {}) { const { strict } = options; internal.assertStartOffset(value, start); const value_ = `0x${value .replace('0x', '') .slice((start ?? 0) * 2, (end ?? value.length) * 2)}`; if (strict) internal.assertEndOffset(value_, start, end); return value_; } /** * Retrieves the size of a {@link ox#Hex.Hex} value (in bytes). * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.size('0xdeadbeef') * // @log: 4 * ``` * * @param value - The {@link ox#Hex.Hex} value to get the size of. * @returns The size of the {@link ox#Hex.Hex} value (in bytes). */ export function size(value) { return Math.ceil((value.length - 2) / 2); } /** * Trims leading zeros from a {@link ox#Hex.Hex} value. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.trimLeft('0x00000000deadbeef') * // @log: '0xdeadbeef' * ``` * * @param value - The {@link ox#Hex.Hex} value to trim. * @returns The trimmed {@link ox#Hex.Hex} value. */ export function trimLeft(value) { return internal.trim(value, { dir: 'left' }); } /** * Trims trailing zeros from a {@link ox#Hex.Hex} value. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.trimRight('0xdeadbeef00000000') * // @log: '0xdeadbeef' * ``` * * @param value - The {@link ox#Hex.Hex} value to trim. * @returns The trimmed {@link ox#Hex.Hex} value. */ export function trimRight(value) { return internal.trim(value, { dir: 'right' }); } /** * Decodes a {@link ox#Hex.Hex} value into a BigInt. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.toBigInt('0x1a4') * // @log: 420n * * Hex.toBigInt('0x00000000000000000000000000000000000000000000000000000000000001a4', { size: 32 }) * // @log: 420n * ``` * * @param hex - The {@link ox#Hex.Hex} value to decode. * @param options - Options. * @returns The decoded BigInt. */ export function toBigInt(hex, options = {}) { const { signed } = options; if (options.size) internal.assertSize(hex, options.size); const value = BigInt(hex); if (!signed) return value; const size = (hex.length - 2) / 2; const max_unsigned = (1n << (BigInt(size) * 8n)) - 1n; const max_signed = max_unsigned >> 1n; if (value <= max_signed) return value; return value - max_unsigned - 1n; } /** * Decodes a {@link ox#Hex.Hex} value into a boolean. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.toBoolean('0x01') * // @log: true * * Hex.toBoolean('0x0000000000000000000000000000000000000000000000000000000000000001', { size: 32 }) * // @log: true * ``` * * @param hex - The {@link ox#Hex.Hex} value to decode. * @param options - Options. * @returns The decoded boolean. */ export function toBoolean(hex, options = {}) { if (options.size) internal.assertSize(hex, options.size); const hex_ = trimLeft(hex); if (hex_ === '0x') return false; if (hex_ === '0x1') return true; throw new InvalidHexBooleanError(hex); } /** * Decodes a {@link ox#Hex.Hex} value into a {@link ox#Bytes.Bytes}. * * @example * ```ts twoslash * import { Hex } from 'ox' * * const data = Hex.toBytes('0x48656c6c6f20776f726c6421') * // @log: Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]) * ``` * * @param hex - The {@link ox#Hex.Hex} value to decode. * @param options - Options. * @returns The decoded {@link ox#Bytes.Bytes}. */ export function toBytes(hex, options = {}) { return Bytes.fromHex(hex, options); } /** * Decodes a {@link ox#Hex.Hex} value into a number. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.toNumber('0x1a4') * // @log: 420 * * Hex.toNumber('0x00000000000000000000000000000000000000000000000000000000000001a4', { size: 32 }) * // @log: 420 * ``` * * @param hex - The {@link ox#Hex.Hex} value to decode. * @param options - Options. * @returns The decoded number. */ export function toNumber(hex, options = {}) { const { signed, size } = options; if (!signed && !size) return Number(hex); return Number(toBigInt(hex, options)); } /** * Decodes a {@link ox#Hex.Hex} value into a string. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.toString('0x48656c6c6f20576f726c6421') * // @log: 'Hello world!' * * Hex.toString('0x48656c6c6f20576f726c64210000000000000000000000000000000000000000', { * size: 32, * }) * // @log: 'Hello world' * ``` * * @param hex - The {@link ox#Hex.Hex} value to decode. * @param options - Options. * @returns The decoded string. */ export function toString(hex, options = {}) { const { size } = options; let bytes = Bytes.fromHex(hex); if (size) { internal_bytes.assertSize(bytes, size); bytes = Bytes.trimRight(bytes); } return new TextDecoder().decode(bytes); } /** * Checks if the given value is {@link ox#Hex.Hex}. * * @example * ```ts twoslash * import { Bytes, Hex } from 'ox' * * Hex.validate('0xdeadbeef') * // @log: true * * Hex.validate(Bytes.from([1, 2, 3])) * // @log: false * ``` * * @param value - The value to check. * @param options - Options. * @returns `true` if the value is a {@link ox#Hex.Hex}, `false` otherwise. */ export function validate(value, options = {}) { const { strict = false } = options; try { assert(value, { strict }); return true; } catch { return false; } } /** * Thrown when the provided integer is out of range, and cannot be represented as a hex value. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.fromNumber(420182738912731283712937129) * // @error: Hex.IntegerOutOfRangeError: Number \`4.2018273891273126e+26\` is not in safe unsigned integer range (`0` to `9007199254740991`) * ``` */ export class IntegerOutOfRangeError extends Errors.BaseError { constructor({ max, min, signed, size, value, }) { super(`Number \`${value}\` is not in safe${size ? ` ${size * 8}-bit` : ''}${signed ? ' signed' : ' unsigned'} integer range ${max ? `(\`${min}\` to \`${max}\`)` : `(above \`${min}\`)`}`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Hex.IntegerOutOfRangeError' }); } } /** * Thrown when the provided hex value cannot be represented as a boolean. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.toBoolean('0xa') * // @error: Hex.InvalidHexBooleanError: Hex value `"0xa"` is not a valid boolean. * // @error: The hex value must be `"0x0"` (false) or `"0x1"` (true). * ``` */ export class InvalidHexBooleanError extends Errors.BaseError { constructor(hex) { super(`Hex value \`"${hex}"\` is not a valid boolean.`, { metaMessages: [ 'The hex value must be `"0x0"` (false) or `"0x1"` (true).', ], }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Hex.InvalidHexBooleanError' }); } } /** * Thrown when the provided value is not a valid hex type. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.assert(1) * // @error: Hex.InvalidHexTypeError: Value `1` of type `number` is an invalid hex type. * ``` */ export class InvalidHexTypeError extends Errors.BaseError { constructor(value) { super(`Value \`${typeof value === 'object' ? Json.stringify(value) : value}\` of type \`${typeof value}\` is an invalid hex type.`, { metaMessages: ['Hex types must be represented as `"0x${string}"`.'], }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Hex.InvalidHexTypeError' }); } } /** * Thrown when the provided hex value is invalid. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.assert('0x0123456789abcdefg') * // @error: Hex.InvalidHexValueError: Value `0x0123456789abcdefg` is an invalid hex value. * // @error: Hex values must start with `"0x"` and contain only hexadecimal characters (0-9, a-f, A-F). * ``` */ export class InvalidHexValueError extends Errors.BaseError { constructor(value) { super(`Value \`${value}\` is an invalid hex value.`, { metaMessages: [ 'Hex values must start with `"0x"` and contain only hexadecimal characters (0-9, a-f, A-F).', ], }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Hex.InvalidHexValueError' }); } } /** * Thrown when the provided hex value is an odd length. * * @example * ```ts twoslash * import { Bytes } from 'ox' * * Bytes.fromHex('0xabcde') * // @error: Hex.InvalidLengthError: Hex value `"0xabcde"` is an odd length (5 nibbles). * ``` */ export class InvalidLengthError extends Errors.BaseError { constructor(value) { super(`Hex value \`"${value}"\` is an odd length (${value.length - 2} nibbles).`, { metaMessages: ['It must be an even length.'], }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Hex.InvalidLengthError' }); } } /** * Thrown when the size of the value exceeds the expected max size. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.fromString('Hello World!', { size: 8 }) * // @error: Hex.SizeOverflowError: Size cannot exceed `8` bytes. Given size: `12` bytes. * ``` */ export class SizeOverflowError extends Errors.BaseError { constructor({ givenSize, maxSize }) { super(`Size cannot exceed \`${maxSize}\` bytes. Given size: \`${givenSize}\` bytes.`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Hex.SizeOverflowError' }); } } /** * Thrown when the slice offset exceeds the bounds of the value. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.slice('0x0123456789', 6) * // @error: Hex.SliceOffsetOutOfBoundsError: Slice starting at offset `6` is out-of-bounds (size: `5`). * ``` */ export class SliceOffsetOutOfBoundsError extends Errors.BaseError { constructor({ offset, position, size, }) { super(`Slice ${position === 'start' ? 'starting' : 'ending'} at offset \`${offset}\` is out-of-bounds (size: \`${size}\`).`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Hex.SliceOffsetOutOfBoundsError' }); } } /** * Thrown when the size of the value exceeds the pad size. * * @example * ```ts twoslash * import { Hex } from 'ox' * * Hex.padLeft('0x1a4e12a45a21323123aaa87a897a897a898a6567a578a867a98778a667a85a875a87a6a787a65a675a6a9', 32) * // @error: Hex.SizeExceedsPaddingSizeError: Hex size (`43`) exceeds padding size (`32`). * ``` */ export class SizeExceedsPaddingSizeError extends Errors.BaseError { constructor({ size, targetSize, type, }) { super(`${type.charAt(0).toUpperCase()}${type .slice(1) .toLowerCase()} size (\`${size}\`) exceeds padding size (\`${targetSize}\`).`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'Hex.SizeExceedsPaddingSizeError' }); } } //# sourceMappingURL=Hex.js.map