UNPKG

viem

Version:

TypeScript Interface for Ethereum

249 lines (228 loc) • 6.88 kB
import { BaseError } from '../../errors/base.js' import type { ErrorType } from '../../errors/utils.js' import type { ByteArray, Hex } from '../../types/misc.js' import { type IsHexErrorType, isHex } from '../data/isHex.js' import { type PadErrorType, pad } from '../data/pad.js' import { type AssertSizeErrorType, assertSize } from './fromHex.js' import { type NumberToHexErrorType, type NumberToHexOpts, numberToHex, } from './toHex.js' const encoder = /*#__PURE__*/ new TextEncoder() export type ToBytesParameters = { /** Size of the output bytes. */ size?: number | undefined } export type ToBytesErrorType = | NumberToBytesErrorType | BoolToBytesErrorType | HexToBytesErrorType | StringToBytesErrorType | IsHexErrorType | ErrorType /** * Encodes a UTF-8 string, hex value, bigint, number or boolean to a byte array. * * - Docs: https://viem.sh/docs/utilities/toBytes * - Example: https://viem.sh/docs/utilities/toBytes#usage * * @param value Value to encode. * @param opts Options. * @returns Byte array value. * * @example * import { toBytes } from 'viem' * const data = toBytes('Hello world') * // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]) * * @example * import { toBytes } from 'viem' * const data = toBytes(420) * // Uint8Array([1, 164]) * * @example * import { toBytes } from 'viem' * const data = toBytes(420, { size: 4 }) * // Uint8Array([0, 0, 1, 164]) */ export function toBytes( value: string | bigint | number | boolean | Hex, opts: ToBytesParameters = {}, ): ByteArray { if (typeof value === 'number' || typeof value === 'bigint') return numberToBytes(value, opts) if (typeof value === 'boolean') return boolToBytes(value, opts) if (isHex(value)) return hexToBytes(value, opts) return stringToBytes(value, opts) } export type BoolToBytesOpts = { /** Size of the output bytes. */ size?: number | undefined } export type BoolToBytesErrorType = | AssertSizeErrorType | PadErrorType | ErrorType /** * Encodes a boolean into a byte array. * * - Docs: https://viem.sh/docs/utilities/toBytes#booltobytes * * @param value Boolean value to encode. * @param opts Options. * @returns Byte array value. * * @example * import { boolToBytes } from 'viem' * const data = boolToBytes(true) * // Uint8Array([1]) * * @example * import { boolToBytes } from 'viem' * const data = boolToBytes(true, { size: 32 }) * // Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) */ export function boolToBytes(value: boolean, opts: BoolToBytesOpts = {}) { const bytes = new Uint8Array(1) bytes[0] = Number(value) if (typeof opts.size === 'number') { assertSize(bytes, { size: opts.size }) return pad(bytes, { size: opts.size }) } return bytes } // We use very optimized technique to convert hex string to byte array const charCodeMap = { zero: 48, nine: 57, A: 65, F: 70, a: 97, f: 102, } as const function charCodeToBase16(char: number) { if (char >= charCodeMap.zero && char <= charCodeMap.nine) return char - charCodeMap.zero if (char >= charCodeMap.A && char <= charCodeMap.F) return char - (charCodeMap.A - 10) if (char >= charCodeMap.a && char <= charCodeMap.f) return char - (charCodeMap.a - 10) return undefined } export type HexToBytesOpts = { /** Size of the output bytes. */ size?: number | undefined } export type HexToBytesErrorType = AssertSizeErrorType | PadErrorType | ErrorType /** * Encodes a hex string into a byte array. * * - Docs: https://viem.sh/docs/utilities/toBytes#hextobytes * * @param hex Hex string to encode. * @param opts Options. * @returns Byte array value. * * @example * import { hexToBytes } from 'viem' * const data = hexToBytes('0x48656c6c6f20776f726c6421') * // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]) * * @example * import { hexToBytes } from 'viem' * const data = hexToBytes('0x48656c6c6f20776f726c6421', { size: 32 }) * // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) */ export function hexToBytes(hex_: Hex, opts: HexToBytesOpts = {}): ByteArray { let hex = hex_ if (opts.size) { assertSize(hex, { size: opts.size }) hex = pad(hex, { dir: 'right', size: opts.size }) } let hexString = hex.slice(2) as string if (hexString.length % 2) hexString = `0${hexString}` const length = hexString.length / 2 const bytes = new Uint8Array(length) for (let index = 0, j = 0; index < length; index++) { const nibbleLeft = charCodeToBase16(hexString.charCodeAt(j++)) const nibbleRight = charCodeToBase16(hexString.charCodeAt(j++)) if (nibbleLeft === undefined || nibbleRight === undefined) { throw new BaseError( `Invalid byte sequence ("${hexString[j - 2]}${ hexString[j - 1] }" in "${hexString}").`, ) } bytes[index] = nibbleLeft * 16 + nibbleRight } return bytes } export type NumberToBytesErrorType = | NumberToHexErrorType | HexToBytesErrorType | ErrorType /** * Encodes a number into a byte array. * * - Docs: https://viem.sh/docs/utilities/toBytes#numbertobytes * * @param value Number to encode. * @param opts Options. * @returns Byte array value. * * @example * import { numberToBytes } from 'viem' * const data = numberToBytes(420) * // Uint8Array([1, 164]) * * @example * import { numberToBytes } from 'viem' * const data = numberToBytes(420, { size: 4 }) * // Uint8Array([0, 0, 1, 164]) */ export function numberToBytes( value: bigint | number, opts?: NumberToHexOpts | undefined, ) { const hex = numberToHex(value, opts) return hexToBytes(hex) } export type StringToBytesOpts = { /** Size of the output bytes. */ size?: number | undefined } export type StringToBytesErrorType = | AssertSizeErrorType | PadErrorType | ErrorType /** * Encodes a UTF-8 string into a byte array. * * - Docs: https://viem.sh/docs/utilities/toBytes#stringtobytes * * @param value String to encode. * @param opts Options. * @returns Byte array value. * * @example * import { stringToBytes } from 'viem' * const data = stringToBytes('Hello world!') * // Uint8Array([72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]) * * @example * import { stringToBytes } from 'viem' * const data = stringToBytes('Hello world!', { size: 32 }) * // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) */ export function stringToBytes( value: string, opts: StringToBytesOpts = {}, ): ByteArray { const bytes = encoder.encode(value) if (typeof opts.size === 'number') { assertSize(bytes, { size: opts.size }) return pad(bytes, { dir: 'right', size: opts.size }) } return bytes }